C#上位机与欧姆龙PLC的通信08----开发自己的通讯库读写数据

1、介绍

前面已经完成了7项工作:

C#上位机与欧姆龙PLC的通信01----项目背景-CSDN博客

C#上位机与欧姆龙PLC的通信02----搭建仿真环境-CSDN博客

C#上位机与欧姆龙PLC的通信03----创建项目工程-CSDN博客

C#上位机与欧姆龙PLC的通信04---- 欧姆龙plc的存储区

C#上位机与欧姆龙PLC的通信05---- HostLink协议(C-Mode版)

C#上位机与欧姆龙PLC的通信06---- HostLink协议(FINS版)

C#上位机与欧姆龙PLC的通信07----使用第3方通讯库读写数据

 这当中,06是重点的重点,需要非常熟悉才能自己写通讯库,封装自己的库需要掌握socket通讯,串口通讯,同步异步,集合数组,字节序列等技能点,这是走向武林高手的必经之路,这样才能强大自己,丰满的肌肉需要一步步啃。

2、开搞

1、创建VS解决方案项目

2、添加类库项目

类库项目创建目录Omron及Base及5个基础类

添加对类库的引用

3、编写基础类库文件

 1)AreaType.cs

存储区对应厂家手册规定的值,不能自己改。

(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0,H字:B2)

    /// <summary>
    /// 存储区枚举
    /// </summary>
    public enum AreaType
    {
        CIOBIT = 0x30,
        WBIT = 0x31,
        DMBIT = 0x02,
        ABIT = 0x33,
        HBIT = 0x32,

        CIOWORD = 0xB0,
        WWORD = 0xB1,
        DMWORD = 0x82,
        AWORD = 0xB3,
        HWORD = 0xB2 
    }

 2)DataAddress.cs

/// <summary>
/// 地址模型类
/// </summary>
public class DataAddress
{
    /// <summary>
    /// 区域类型
    /// </summary>
    public AreaType AreaType { get; set; }
    /// <summary>
    /// Word起始地址
    /// </summary>
    public ushort WordAddress { get; set; }
    /// <summary>
    /// Bit起始地址
    /// </summary>
    public byte BitAddress { get; set; }

}

3)EndianType.cs

 这就是大小端的列举,涉及字节序列中的高位低位交换等处理

namespace Zhaoxi.Communication.Omron.Base
{
    /// <summary>
    /// 字节序列
    /// </summary>
    public enum EndianType
    {
        AB, BA,
        ABCD, CDAB, BADC, DCBA,
        ABCDEFGH, GHEFCDAB, BADCFEHG, HGFEDCBA
    }
}

4)Result.cs

通信结果类

namespace Omron.Communimcation.Fins.Omron
{
    /// <summary>
    /// 通讯结果类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Result<T>
    {
        /// <summary>
        /// 状态
        /// </summary>
        public bool IsSuccessed { get; set; }
        /// <summary>
        /// 对应的消息
        /// </summary>
        public string Message { get; set; }
        /// <summary>
        /// 数据列表
        /// </summary>
        public List<T> Datas { get; set; }

        public Result() : this(true, "OK") { }
        public Result(bool state, string msg) : this(state, msg, new List<T>()) { }
        public Result(bool state, string msg, List<T> datas)
        {
            this.IsSuccessed = state; Message = msg; Datas = datas;
        }
    }

    public class Result : Result<bool> { }
}

4、编写核心的通信类Fins

这个类就是实现读取和写入数据的方法,封装了报文的每个组成部分,象请求的报文头,命令码,数据处理,发送和接收处理等,字节处理,寄存器的数据类型处理,辅助方法等很多。该类很强大,能很好的处理short,ushort,bool,float,OOP的思想在这个类中得到了很好的发挥,注释很详细,代码很规范,需要很深的功底才可以写得出来的,需要对fins的报文结构指令内容非常精通才能写好,各位高僧能人自行阅读,欢迎留言。

using Omron.Communimcation.Fins.Omron.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Omron.Communimcation.Fins.Omron
{
    /// <summary>
    /// 欧姆龙FINS协议通讯库
    /// </summary>
    public class FinsTcp
    {
        /// <summary>
        /// tcp服务器地址
        /// </summary>
        string _ip;
        /// <summary>
        /// tcp端口号
        /// </summary>
        int _port;
        /// <summary>
        /// 目标节点
        /// </summary>
        byte _da;
        /// <summary>
        /// 源节点
        /// </summary>
        byte _sa;
        /// <summary>
        /// socket连接对象(全局变量)
        /// </summary>
        Socket socket = null;
        /// <summary>
        /// 超时对象
        /// </summary>
        ManualResetEvent TimeoutObject = new ManualResetEvent(false);
        /// <summary>
        /// 连接状态
        /// </summary>
        bool connectState = false;
        /// <summary>
        /// 头部字节
        /// </summary>
        byte[] finsTcpHeader = new byte[] { 0x46, 0x49, 0x4E, 0x53 };

        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="ip">TCP服务器IP</param>
        /// <param name="port">TCP服务器端口</param>
        /// <param name="da">PLC节点</param>
        /// <param name="sa">PC端节点</param>
        public FinsTcp(string ip, int port, byte da = 0x00, byte sa = 0x00)
        {
            _ip = ip;
            _port = port;
            _da = da;
            _sa = sa;
            // 初始化一个通信对象
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        #region 操作PLC
        /// <summary>
        /// 连接PLC
        /// </summary>
        /// <param name="timeout">超时时间</param>
        /// <returns></returns>
        public Result Connect(int timeout = 50)
        {
            TimeoutObject.Reset();
            Result result = new Result();
            try
            {
                if (socket == null)
                {
                    throw new Exception("通信对象未初始化");
                }
                int count = 0;
                while (count < timeout)
                {
                    // 断线重连:
                    // 1、被服务端主动踢掉(服务连接列表里已经没有当前客户端信息了)
                    // 2、断网(拔网线)   客户端(不知道-》新的端口进行连接)、服务端(可以知道、检查客户端的心跳)
                    if (!(!socket.Connected || (socket.Poll(200, SelectMode.SelectRead) && (socket.Available == 0))))
                    {
                        return result;
                    }
                    try
                    {
                        socket?.Close();
                        socket.Dispose();
                        socket = null;
                        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                        //异步连接PLC
                        socket.BeginConnect(_ip, _port, callback =>
                        {
                            connectState = false;
                            var cbSocket = callback.AsyncState as Socket;
                            if (cbSocket != null)
                            {
                                connectState = cbSocket.Connected;
                                if (cbSocket.Connected)
                                {
                                    cbSocket.EndConnect(callback);
                                }
                            }
                            TimeoutObject.Set();
                        }, socket);
                        TimeoutObject.WaitOne(2000, false);
                        if (!connectState)
                        {
                            throw new Exception("网络连接异常");
                        }
                        else
                        {
                            break;
                        }
                    }
                    catch (SocketException ex)
                    {
                        if (ex.ErrorCode == 10060)
                        {
                            throw new Exception(ex.Message);
                        }
                    }
                    catch (Exception)
                    {
                        throw new Exception("网络连接异常");
                    }
                    finally
                    {
                        count++;
                    }
                }
                if (socket == null || !socket.Connected || ((socket.Poll(200, SelectMode.SelectRead) && (socket.Available == 0))))
                {
                    throw new Exception("网络连接失败");
                }
                else
                {
                    // 2、建立连接
                    result = this.SetupConnect();
                    if (!result.IsSuccessed)
                    {
                        return result;
                    }
                }
            }
            catch (Exception ex)
            {
                result.IsSuccessed = false;
                result.Message = ex.Message;
            }
            return result;
        }

        /// <summary>
        /// 启动PLC
        /// </summary>
        /// <returns></returns>
        public Result Run()
        {
            return PlcSate(0x01);
        }

        /// <summary>
        /// 停止PLC
        /// </summary>
        /// <returns></returns>
        public Result Stop()
        {
            return PlcSate(0x02);
        }

        /// <summary>
        /// PLC状态
        /// </summary>
        /// <param name="state">0401启动,0402停止</param>
        /// <returns></returns>
        public Result PlcSate(byte state)
        {
            Result result = new Result();
            try
            {
                var connectState = this.Connect();
                if (!connectState.IsSuccessed)
                {
                    throw new Exception(connectState.Message);
                }
                List<byte> baseBytes = this.GetBaseCommand(0x04, state); //构建命令
                this.Send(baseBytes);//发送命令
            }
            catch (Exception ex)
            {
                result.IsSuccessed = false;
                result.Message = ex.Message;
            }
            return result;
        }

        #endregion

        #region 读取数据
        / <summary>
        /  读取命令,原有方法
        / </summary>
        / <typeparam name="T">返回的数据类型,如ushort,short,int32,float等</typeparam>
        / <param name="areaCode">存储区代码</param>
        / <param name="wStartAddr">开始地址(16进制格式,如ox64)</param>
        / <param name="bStartAddr">Bit地址(16进制格式,如ox00)</param>
        / <param name="count">读取个数(16进制格式,如读取12个,就是ox0b)</param>
        / <returns></returns>
        //public Result<T> Read<T>(AreaType areaCode, ushort wStartAddr, byte bStartAddr, ushort count)
        //{
        //     //发送: 46 49 4E 53 00 00 00 1A 00 00 00 02 00 00 00 00 80 00 02 00 02 00 00 0A 00 00 01 01 82 00 C8 00 00 01
        //     //接收: 46 49 4E 53 00 00 00 18 00 00 00 02 00 00 00 00 C0 00 02 00 02 00 00 0A 00 00 01 01 00 00 02 2E
        //    Result<T> result = new Result<T>();
        //    int typeLen = 1;//数据类型所占的字节宽度
        //    if (!typeof(T).Equals(typeof(bool)))
        //    {
        //        typeLen = Marshal.SizeOf<T>() / 2;   // 每一个数据需要多少个寄存器
        //    }
        //    try
        //    {
        //        byte[] bytes = new byte[] {
        //        0x00,0x00,0x00,0x02,   // 读写的时候固定传这个值 
        //        0x00,0x00,0x00,0x00, // 错误代码;
        //        0x80,// ICF 
        //        0x00, // Rev  
        //        0x02, // GCT 
        //        0x00,_da,0x00, // DNA DA1 DA2 ,即目标网络号,目标节点号,目标单元号
        //        0x00,_sa,0x00,// SNA SA1 SA2,即源网络号,源节点号,源单元号
        //        0x00,   // SID,固定值  
        //        0x01,0x01,// 命令码,读操作固定值为0101 
        //        (byte)areaCode,  // 存储区
        //        (byte)(wStartAddr/256%256),//Word起始地址,占2个字节
        //        (byte)( wStartAddr%256),
        //        bStartAddr,//起始位地址,占1个字节
        //        (byte)(count*typeLen/256%256),//读取个数,占2个字节
        //        (byte)(count*typeLen%256)
        //    };
        //        //命令字节
        //        List<byte> commandBytes = new List<byte>();

        //        // 计算字节长度,占4个字节
        //        List<byte> lengthbyte = new List<byte>();
        //        lengthbyte.Add((byte)(bytes.Length / 256 / 256 / 256 % 256));
        //        lengthbyte.Add((byte)(bytes.Length / 256 / 256 % 256));
        //        lengthbyte.Add((byte)(bytes.Length / 256 % 256));
        //        lengthbyte.Add((byte)(bytes.Length % 256));

        //        commandBytes.AddRange(finsTcpHeader);//加入头部
        //        commandBytes.AddRange(lengthbyte.ToArray());//加入长度 
        //        commandBytes.AddRange(bytes);//加入协议内容

        //        socket.Send(commandBytes.ToArray());//发送报文

        //        //1.1接收数据中的前8个字节,即fins头部 
        //        byte[] respHeader = new byte[8];
        //        socket.Receive(respHeader, 0, 8, SocketFlags.None);

        //        //1.2 判断前面4个字节是否是FINS
        //        for (int i = 0; i < 4; i++)
        //        {
        //            if (respHeader[i] != finsTcpHeader[i])
        //            {
        //                throw new Exception("响应报文无效");
        //            }
        //        } 

        //        //1.3、获取剩余报文长度:  00 00 00 18
        //        byte[] lenBytes = new byte[4];
        //        lenBytes[0] = respHeader[7];
        //        lenBytes[1] = respHeader[6];
        //        lenBytes[2] = respHeader[5];
        //        lenBytes[3] = respHeader[4];
        //        int len = BitConverter.ToInt32(lenBytes);
        //        Console.WriteLine("剩余报文长度:" + len); 

        //        //1.4 接收剩余字节
        //        byte[] dataBytes = new byte[len];
        //        socket.Receive(dataBytes, 0, len, SocketFlags.None);

        //        // 1.5 错误信息判断 ,确定没有ErrorCode 
        //        var code = dataBytes[4] | dataBytes[5] | dataBytes[6] | dataBytes[7];
        //        if (code > 0)
        //        {
        //            Console.WriteLine($"有错误[{code}]");
        //        }   
        //        code = (dataBytes[20] << 8) | dataBytes[21];  // 01 01   0000 0001 0000 0001
        //        if (code > 0)
        //        {
        //            Console.WriteLine($"有错误[{code}]");  // 字典处理:{257:"当前网络环境中无法匹配此节点"}
        //        } 

        //        // 1.6 开始解析数据部分
        //        List<byte> dataList = new List<byte>(dataBytes);
        //        dataList.RemoveRange(0, 22);//移除指定部分,保留数据内容
        //        //循环处理数据
        //        for (int i = 0; i < dataList.Count;)
        //        {
        //            if (typeof(T) == typeof(bool))//布尔类型
        //            { 
        //                Type tConvert = typeof(Convert); 
        //                //查找 convert这个类中的toboolean方法
        //                MethodInfo method = tConvert.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(mi => mi.Name == "ToBoolean") as MethodInfo;
        //                result.Datas.Add((T)method.Invoke(tConvert, new object[] { int.Parse(dataList[i++].ToString()) }));
        //            }
        //            else //short,ushort,float,double类型
        //            {
        //                List<byte> datas = new List<byte>();
        //                // Word -> Short,DWrod-> Float,DD-> Double
        //                for (int j = 0; j < typeLen * 2; j++)
        //                { 
        //                    datas.Add(dataList[i++]); // 只能处理2个字节的情况,其他的处理不了,需要进行字节序的调整
        //                } 

        //                if (typeLen == 1)//2个字节,适合ushort,short,int16
        //                {
        //                    datas = new List<byte>(this.SwitchEndian(datas.ToArray(), EndianType.AB));
        //                } 
        //                else if (typeLen == 2)//4个字节,适合int32,float
        //                {
        //                    datas = new List<byte>(this.SwitchEndian(datas.ToArray(), EndianType.CDAB));
        //                } 

        //                Type tBitConverter = typeof(BitConverter);
        //                MethodInfo method = tBitConverter.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(mi => mi.ReturnType == typeof(T)) as MethodInfo;
        //                if (method == null)
        //                {
        //                    Console.WriteLine("未找到匹配的数据类型转换方法");
        //                } 
        //                result.Datas.Add((T)method?.Invoke(tBitConverter, new object[] { datas.ToArray(), 0 }));
        //            } 
        //        }
        //    }
        //    catch (Exception ex)
        //    {
        //        return new Result<T>(false, ex.Message);
        //    }
        //    return result;
        //}

        /// <summary>
        /// 读取操作
        /// </summary>
        /// <typeparam name="T">返回的数据类型</typeparam>
        /// <param name="address">存储区地址</param>
        /// <param name="count">读取数量</param>
        /// <returns></returns>
        public Result<T> Read<T>(string address, ushort count)
        {
            Result<T> result = new Result<T>();
            try
            {
                DataAddress dataAddress = this.AnalysisAddress(address);
                result = this.Read<T>(dataAddress, count);
            }
            catch (Exception ex)
            {
                result.IsSuccessed = false;
                result.Message = ex.Message;
            }
            return result;
        }

        /// <summary>
        /// 读取操作
        /// </summary>
        /// <typeparam name="T">返回的数据类型</typeparam>
        /// <param name="dataAddress">地址模型</param>
        /// <param name="count">读取数量</param>
        /// <returns></returns>
        public Result<T> Read<T>(DataAddress dataAddress, ushort count)
        {
            Result<T> result = new Result<T>();
            int typeLen = 1;
            if (!typeof(T).Equals(typeof(bool)))
            {
                typeLen = Marshal.SizeOf<T>() / 2;// 每一个数据需要多少个寄存器
            }

            try
            {
                //1、判断连接状态
                var connectState = this.Connect();
                //没有连接成功时则返回
                if (!connectState.IsSuccessed)
                {
                    throw new Exception(connectState.Message);
                }

                //2、构建基本命令:0101表示读取,0102表示写入
                //命令头部
                List<byte> baseBytes = this.GetBaseCommand(0x01, 0x01);
                //存储区类型
                baseBytes.Add((byte)dataAddress.AreaType);
                //开始地址,占3个字节
                baseBytes.Add((byte)(dataAddress.WordAddress / 256 % 256));
                baseBytes.Add((byte)(dataAddress.WordAddress % 256));
                baseBytes.Add(dataAddress.BitAddress);
                //读取数量,占2个字节
                baseBytes.Add((byte)(count * typeLen / 256 % 256));
                baseBytes.Add((byte)(count * typeLen % 256));

                //3、发送命令
                this.Send(baseBytes);

                //4、获取响应的数据报文
                List<byte> dataList = this.CheckResponseBytes();

                //5、解析数据报文
                for (int i = 0; i < dataList.Count;)
                {
                    if (typeof(T) == typeof(bool))//bool类型的处理
                    {
                        dynamic boolValue = dataList[i++] == 0x01;
                        result.Datas.Add(boolValue);
                    }
                    else  //short,ushort,float,double类型的处理
                    {
                        List<byte> datas = new List<byte>();
                        for (int j = 0; j < typeLen * 2; j++)
                        {
                            datas.Add(dataList[i++]); // 只能处理2个字节的情况,其他的处理不了,需要进行字节序的调整
                        }
                        datas = this.SwitchBytes(datas); //交换字节顺序
                        Type tBitConverter = typeof(BitConverter);
                        MethodInfo method = tBitConverter.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(mi => mi.ReturnType == typeof(T)) as MethodInfo;
                        if (method == null)
                        {
                            throw new Exception("未找到匹配的数据类型转换方法");
                        }
                        result.Datas.Add((T)method?.Invoke(tBitConverter, new object[] { datas.ToArray(), 0 }));
                    }
                }
            }
            catch (Exception ex)
            {
                return new Result<T>(false, ex.Message);
            }
            return result;
        }
        #endregion

        #region 写入数据
        /// <summary>
        /// 写入操作
        /// </summary>
        /// <typeparam name="T">写入的数据类型</typeparam>
        /// <param name="values">写入的具体数据</param>
        /// <param name="address">写入的具体地址</param>
        /// <returns></returns>
        public Result Write<T>(List<T> values, string address)
        {
            Result result = new Result();
            try
            {
                DataAddress dataAddress = this.AnalysisAddress(address);
                result = this.Write<T>(values, dataAddress);
            }
            catch (Exception ex)
            {
                result.IsSuccessed = false;
                result.Message = ex.Message;
            }
            return result;
        }


        /// <summary>
        /// 写入操作
        /// </summary>
        /// <typeparam name="T">写入的数据类型</typeparam>
        /// <param name="values">写入的具体数据</param>
        /// <param name="dataAddress">地址模型</param>
        /// <returns></returns>
        public Result Write<T>(List<T> values, DataAddress dataAddress)
        {
            Result result = new Result();
            int typeLen = 1;
            if (!typeof(T).Equals(typeof(bool)))
            {
                typeLen = Marshal.SizeOf<T>() / 2; // 每一个数据需要多少个寄存器
            }
            try
            {
                //1、建立连接,如果连接失败则抛出异常
                var connectState = this.Connect();
                if (!connectState.IsSuccessed)
                {
                    throw new Exception(connectState.Message);
                }

                //2、加入基本命令,0102表示写,0101表示读
                List<byte> baseBytes = this.GetBaseCommand(0x01, 0x02);
                //2.1加入存储区
                baseBytes.Add((byte)dataAddress.AreaType);
                //2.2加入地址,占3个字节
                baseBytes.Add((byte)(dataAddress.WordAddress / 256 % 256));
                baseBytes.Add((byte)(dataAddress.WordAddress % 256));
                baseBytes.Add(dataAddress.BitAddress);
                //2.3 加入长度,  short 是1字占1个长度 , float是2字占2个长度
                baseBytes.Add((byte)(values.Count * typeLen / 256 % 256));
                baseBytes.Add((byte)(values.Count * typeLen % 256));
                //处理数值
                foreach (dynamic item in values)
                {
                    if (typeof(T) == typeof(bool))//bool类型处理
                    {
                        baseBytes.Add((byte)(bool.Parse(item.ToString()) ? 0x01 : 0x00));
                    }
                    else // short,ushort,int32,float类型处理
                    {
                        List<byte> vBytes = new List<byte>(BitConverter.GetBytes(item));
                        vBytes = this.SwitchBytes(vBytes);
                        baseBytes.AddRange(vBytes);
                    }
                }

                //3、发送命令
                this.Send(baseBytes);

                //4、检查报文,写入时不需要处理返回数据
                this.CheckResponseBytes();
            }
            catch (Exception ex)
            {
                result.IsSuccessed = false;
                result.Message = ex.Message;
            }
            return result;
        }
        #endregion

        #region 基本方法

        /// <summary>
        /// 初始化内容
        /// </summary>
        /// <returns></returns>
        private Result SetupConnect()
        {
            byte[] connectBytes = new byte[] {
                // Header     FINS对应的Ascii编码(16进制)
                0x46,0x49,0x4E,0x53,
                // Length
                0x00,0x00,0x00,0x0C,
                // Command
                0x00,0x00,0x00,0x00,
                // Error code
                0x00,0x00,0x00,0x00,
                // Client node addr
                0x00,0x00,0x00,0x04
            };
            try
            {
                socket.Send(connectBytes);//发送握手报文

                byte[] respBytes = new byte[24];
                int count = socket.Receive(respBytes, 0, 24, SocketFlags.None);

                // 判断是否FINS开头的报文
                for (int i = 0; i < 4; i++)
                {
                    if (respBytes[i] != finsTcpHeader[i])
                        throw new Exception("连接请求响应报文无效");
                }
                // 确定没有ErrorCode
                var state = respBytes[12] | respBytes[13] | respBytes[14] | respBytes[15];
                if (state > 0)
                    throw new Exception($"有错误[{state}]");
            }
            catch (Exception ex)
            {
                return new Result() { IsSuccessed = false, Message = ex.Message };
            }
            return new Result();
        }

        /// <summary>
        /// 调整字节序
        /// </summary>
        /// <param name="value"></param>
        /// <param name="endianType"></param>
        /// <returns></returns>
        byte[] SwitchEndian(byte[] value, EndianType endianType)
        {
            List<byte> result = new List<byte>(value);
            switch (endianType)
            {
                case EndianType.AB:
                case EndianType.ABCD:
                case EndianType.ABCDEFGH:
                    result.Reverse();
                    return result.ToArray();
                case EndianType.CDAB: // 4字节处理
                    if (value.Length == 4)
                    {
                        result[3] = value[2];
                        result[2] = value[3];
                        result[1] = value[0];
                        result[0] = value[1];
                    }
                    return result.ToArray();
                case EndianType.BADC: // 4字节处理
                    if (value.Length == 4)
                    {
                        result[3] = value[1];
                        result[2] = value[0];
                        result[1] = value[3];
                        result[0] = value[2];
                    }
                    return result.ToArray();
                case EndianType.GHEFCDAB:  // 8字节处理
                    if (value.Length == 8)
                    {
                        result[7] = value[6];
                        result[6] = value[7];
                        result[5] = value[4];
                        result[4] = value[5];
                        result[3] = value[2];
                        result[2] = value[3];
                        result[1] = value[0];
                        result[0] = value[1];
                    }
                    return result.ToArray();
                case EndianType.BADCFEHG: // 8字节处理
                    if (value.Length == 8)
                    {
                        result[7] = value[1];
                        result[6] = value[0];
                        result[5] = value[3];
                        result[4] = value[2];
                        result[3] = value[5];
                        result[2] = value[4];
                        result[1] = value[7];
                        result[0] = value[6];
                    }
                    return result.ToArray();
                case EndianType.BA:
                case EndianType.DCBA:
                case EndianType.HGFEDCBA:
                    return value;
                default:
                    break;
            }
            return null;
        }


        /// <summary>
        /// 字节交换
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private List<byte> SwitchBytes(List<byte> bytes)
        {
            byte temp;
            for (int i = 0; i < bytes.Count; i += 2)
            {
                temp = bytes[i];
                bytes[i] = bytes[i + 1];
                bytes[i + 1] = temp;
            }
            return bytes;
        }

        /// <summary>
        /// 地址解析
        /// </summary>
        /// <param name="addr">地址</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private DataAddress AnalysisAddress(string addr)
        {
            DataAddress dataAddress = new DataAddress();
            addr = addr.ToUpper();
            AreaType wordCode = 0x00, bitCode = 0x00;
            if (addr.Substring(0, 3) == "CIO")
            {
                wordCode = AreaType.CIOWORD;
                bitCode = AreaType.CIOBIT;
                addr = addr.Substring(3);
            }
            else if (addr.Substring(0, 2) == "DM")
            {
                wordCode = AreaType.DMWORD;
                bitCode = AreaType.DMBIT;
                addr = addr.Substring(2);
            }
            else
            {
                switch (addr[0])
                {
                    case 'W':
                        bitCode = AreaType.WBIT;
                        wordCode = AreaType.WWORD;
                        break;
                    case 'A':
                        bitCode = AreaType.ABIT;
                        wordCode = AreaType.AWORD;
                        break;
                    case 'H':
                        bitCode = AreaType.HBIT;
                        wordCode = AreaType.HWORD;
                        break;
                }
                addr = addr.Substring(1);
            }
            if (bitCode == 0x00)
            {
                throw new Exception("地址类型暂不支持!");
            }

            string[] tempAddr = addr.Split('.');
            dataAddress.AreaType = wordCode;
            if (string.IsNullOrEmpty(tempAddr[0]))
            {
                throw new Exception("地址标记错误");
            }
            ushort ws = 0;
            if (ushort.TryParse(tempAddr[0], out ws))
            {
                dataAddress.WordAddress = ws;
            }
            // 如果有小数点 
            if (tempAddr.Length > 1)
            {
                dataAddress.AreaType = bitCode;
                if (string.IsNullOrEmpty(tempAddr[1]))
                {
                    throw new Exception("地址标记错误");
                }
                byte bs = 0;
                if (byte.TryParse(tempAddr[1], out bs))
                {
                    dataAddress.BitAddress = bs;
                }
            }

            return dataAddress;
        }

        /// <summary>
        /// 创建基本命令
        /// </summary>
        /// <param name="mCommand">主命令</param>
        /// <param name="sCommand">次命令</param>
        /// <returns></returns>
        private List<byte> GetBaseCommand(byte mCommand, byte sCommand)
        {
            return new List<byte> {
                    // Command   读写的时候固定传这个值
                    0x00,0x00,0x00,0x02,
                    // Error code 错误代码;
                    0x00,0x00,0x00,0x00,  
                    // ICF
                    0x80,
                    // Rev 
                    0x00,
                    // GCT
                    0x02,
                    // DNA DA1 DA2,即目标网络号,目标节点号,目标单元号,也就是指PLC网络
                    0x00,_da,0x00,
                    // SNA SA1 SA2,即源网络号,源节点号,源单元号,也就是指PC网络
                    0x00,_sa,0x00,
                    // SID  ,固定值  
                    0x00,
                    //读写命令
                    mCommand,
                    sCommand
            };
        }

        /// <summary>
        /// 检查响应报文,返回数据内容
        /// </summary>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private List<byte> CheckResponseBytes()
        {
            byte[] respHeader = new byte[8];//头部数据,占8个字节
            socket.Receive(respHeader, 0, 8, SocketFlags.None); //接收fins头部

            //1、判断前面4个字节是否是FINS
            for (int i = 0; i < 4; i++)
            {
                if (respHeader[i] != finsTcpHeader[i])
                {
                    throw new Exception("响应报文无效");
                }
            }

            //2、获取剩余报文长度 00 00 00 18
            byte[] lenBytes = new byte[4];
            lenBytes[0] = respHeader[7];
            lenBytes[1] = respHeader[6];
            lenBytes[2] = respHeader[5];
            lenBytes[3] = respHeader[4];
            int len = BitConverter.ToInt32(lenBytes, 0);//字节数组转换成int32
            //Console.WriteLine("剩余报文长度:" + len); 

            //3、接收剩余字节
            byte[] dataBytes = new byte[len];
            socket.Receive(dataBytes, 0, len, SocketFlags.None);

            //4、错误信息解读 确定没有ErrorCode ResponseCode
            var code = dataBytes[4] | dataBytes[5] | dataBytes[6] | dataBytes[7];
            if (code > 0)
            {
                throw new Exception($"有错误[{code}]");
            }
            code = (dataBytes[20] << 8) | dataBytes[21];  /// 判断End Code 01 01   0000 0001 0000 0001   0x10  0x03 "1003"
            if (code > 0)
            {
                throw new Exception($"有错误[{code}]");  // 字典处理:{257:"当前网络环境中无法匹配此节点"}
            }

            //5、获取数据部分
            List<byte> dataList = new List<byte>(dataBytes);
            dataList.RemoveRange(0, 22);//移除前面22个字节,剩下的就是数据部分

            //6、返回数据部分
            return dataList;
        }

        /// <summary>
        /// 发送TCP数据
        /// </summary>
        /// <param name="baseBytes">命令字节集合</param>
        private void Send(List<byte> baseBytes)
        {
            //命令字节
            List<byte> commandBytes = new List<byte>();
            //  计算字节长度,占4个字节
            List<byte> lengthbyte = new List<byte>();
            lengthbyte.Add((byte)(baseBytes.Count / 256 / 256 / 256 % 256));
            lengthbyte.Add((byte)(baseBytes.Count / 256 / 256 % 256));
            lengthbyte.Add((byte)(baseBytes.Count / 256 % 256));
            lengthbyte.Add((byte)(baseBytes.Count % 256));
            commandBytes.AddRange(finsTcpHeader);//加入头部
            commandBytes.AddRange(lengthbyte.ToArray());//加入长度 
            commandBytes.AddRange(baseBytes);//加入协议内容 
            socket.Send(commandBytes.ToArray());//发送命令
        }
        #endregion
    }
}

5、测试通讯库-读取数据

这里还是以前面的存储区数据进行测试:

读取CIO区0.0开始的6个bool数据

读取D区100开始的4个数据,ushort类型

读取H区100开始的4个short类型的数据

读取W区100开始的5个float浮点字

读取W区104开始的2个float数据

1)连接PLC

namespace Omron.Communimcation.Test
{
    internal class Program
    {
        static void Main(string[] args)
        {
            #region 通讯库 
            FinsTcpLibTest();
            Console.WriteLine("执行完成!"); 
            #endregion  
            Console.ReadKey();
        }

        private static void FinsTcpLibTest()
        {
            FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 
            var result = finsTcp.Connect();// 开始连接PLC
            if (!result.IsSuccessed)
            {
                Console.WriteLine(result.Message);
                return;
            }
        }
    }
}

 

 通讯的TCP报文

2)读取CIO区0.0开始的6个bool数据

设置PLC内存区数据

  private static void FinsTcpLibTest()
  {
      FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 
      var result = finsTcp.Connect();// 开始连接PLC
      if (!result.IsSuccessed)
      {
          Console.WriteLine(result.Message);
          return;
      }

      //1,读取CIO区0.0开始的6个bool数据, 
       var datas5 = finsTcp.Read<bool>("CIO0.0", 6);
      if (!datas5.IsSuccessed)
      {
          Console.WriteLine(datas5.Message);
          return;
      }
      Console.WriteLine("读取CIO区0.0开始的6个bool数据");
      datas5.Datas.ForEach(dd => Console.WriteLine(dd)); 

  }

通信报文结果 

3)读取D区100开始的4个数据,ushort类型

 private static void FinsTcpLibTest()
 {
     FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 
     var result = finsTcp.Connect();// 开始连接PLC
     if (!result.IsSuccessed)
     {
         Console.WriteLine(result.Message);
         return;
     } 
     1,读取CIO区0.0开始的6个bool数据, 
     // var datas5 = finsTcp.Read<bool>("CIO0.0", 6);
     //if (!datas5.IsSuccessed)
     //{
     //    Console.WriteLine(datas5.Message);
     //    return;
     //}
     //Console.WriteLine("读取CIO区0.0开始的6个bool数据");
     //datas5.Datas.ForEach(dd => Console.WriteLine(dd));


     //2、读取D区100开始的4个数据,ushort类型, 
      var datas1 = finsTcp.Read<ushort>("DM100", 4);
     if (!datas1.IsSuccessed)
     {
         Console.WriteLine(datas1.Message);
         return;
     }
     Console.WriteLine("读取D区100开始的4个ushort类型数据");
     datas1.Datas.ForEach(dd => Console.WriteLine(dd));

 }

通讯报文结构

4)读取H区100开始的4个short类型的数据

  private static void FinsTcpLibTest()
  {
      FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 
      var result = finsTcp.Connect();// 开始连接PLC
      if (!result.IsSuccessed)
      {
          Console.WriteLine(result.Message);
          return;
      } 
      1,读取CIO区0.0开始的6个bool数据, 
      // var datas5 = finsTcp.Read<bool>("CIO0.0", 6);
      //if (!datas5.IsSuccessed)
      //{
      //    Console.WriteLine(datas5.Message);
      //    return;
      //}
      //Console.WriteLine("读取CIO区0.0开始的6个bool数据");
      //datas5.Datas.ForEach(dd => Console.WriteLine(dd));


      2、读取D区100开始的4个数据,ushort类型, 
      // var datas1 = finsTcp.Read<ushort>("DM100", 4);
      //if (!datas1.IsSuccessed)
      //{
      //    Console.WriteLine(datas1.Message);
      //    return;
      //}
      //Console.WriteLine("读取D区100开始的4个ushort类型数据");
      //datas1.Datas.ForEach(dd => Console.WriteLine(dd));

      //3、 读取H区100开始的4个short类型的数据 
       var datas2 = finsTcp.Read<short>("H100", 4);
      if (!datas2.IsSuccessed)
      {
          Console.WriteLine(datas2.Message);
          return;
      }
      Console.WriteLine("读取H区100开始的4个short类型数据");
      datas2.Datas.ForEach(dd => Console.WriteLine(dd));


  }

可以看到,通讯库对负数的处理,报文返回的数据需要对short类型的负数进行处理,因为short是有符号的10进制数,包括正负整数,如908,-85

5)读取W区100开始的5个float浮点字

 private static void FinsTcpLibTest()
 {
     FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 
     var result = finsTcp.Connect();// 开始连接PLC
     if (!result.IsSuccessed)
     {
         Console.WriteLine(result.Message);
         return;
     }
     1,读取CIO区0.0开始的6个bool数据, 
     // var datas5 = finsTcp.Read<bool>("CIO0.0", 6);
     //if (!datas5.IsSuccessed)
     //{
     //    Console.WriteLine(datas5.Message);
     //    return;
     //}
     //Console.WriteLine("读取CIO区0.0开始的6个bool数据");
     //datas5.Datas.ForEach(dd => Console.WriteLine(dd));


     2、读取D区100开始的4个数据,ushort类型, 
     // var datas1 = finsTcp.Read<ushort>("DM100", 4);
     //if (!datas1.IsSuccessed)
     //{
     //    Console.WriteLine(datas1.Message);
     //    return;
     //}
     //Console.WriteLine("读取D区100开始的4个ushort类型数据");
     //datas1.Datas.ForEach(dd => Console.WriteLine(dd));

     3、 读取H区100开始的4个short类型的数据 
     // var datas2 = finsTcp.Read<short>("H100", 4);
     //if (!datas2.IsSuccessed)
     //{
     //    Console.WriteLine(datas2.Message);
     //    return;
     //}
     //Console.WriteLine("读取H区100开始的4个short类型数据");
     //datas2.Datas.ForEach(dd => Console.WriteLine(dd));

     //4、读取W区100开始的5个float浮点字,包括正负整数,如223,-987和正负小数,如2.34,-87.65
      var datas3 = finsTcp.Read<float>("W100", 5);
     if (!datas3.IsSuccessed)
     {
         Console.WriteLine(datas3.Message);
         return;
     }
     Console.WriteLine("读取W区100开始的5个float类型数据");
     datas3.Datas.ForEach(dd => Console.WriteLine(dd));


 }

fins报文 

6)读取W区104开始的2个float数据

 private static void FinsTcpLibTest()
 {
     FinsTcp finsTcp = new FinsTcp("192.168.1.4", 7788, (byte)10, (byte)04);// 创建连接 
     var result = finsTcp.Connect();// 开始连接PLC
     if (!result.IsSuccessed)
     {
         Console.WriteLine(result.Message);
         return;
     }
     1,读取CIO区0.0开始的6个bool数据, 
     // var datas5 = finsTcp.Read<bool>("CIO0.0", 6);
     //if (!datas5.IsSuccessed)
     //{
     //    Console.WriteLine(datas5.Message);
     //    return;
     //}
     //Console.WriteLine("读取CIO区0.0开始的6个bool数据");
     //datas5.Datas.ForEach(dd => Console.WriteLine(dd));


     2、读取D区100开始的4个数据,ushort类型, 
     // var datas1 = finsTcp.Read<ushort>("DM100", 4);
     //if (!datas1.IsSuccessed)
     //{
     //    Console.WriteLine(datas1.Message);
     //    return;
     //}
     //Console.WriteLine("读取D区100开始的4个ushort类型数据");
     //datas1.Datas.ForEach(dd => Console.WriteLine(dd));

     3、 读取H区100开始的4个short类型的数据 
     // var datas2 = finsTcp.Read<short>("H100", 4);
     //if (!datas2.IsSuccessed)
     //{
     //    Console.WriteLine(datas2.Message);
     //    return;
     //}
     //Console.WriteLine("读取H区100开始的4个short类型数据");
     //datas2.Datas.ForEach(dd => Console.WriteLine(dd));

     4、读取W区100开始的5个float浮点字,包括正负整数,如223,-987和正负小数,如2.34,-87.65
     // var datas3 = finsTcp.Read<float>("W100", 5);
     //if (!datas3.IsSuccessed)
     //{
     //    Console.WriteLine(datas3.Message);
     //    return;
     //}
     //Console.WriteLine("读取W区100开始的5个float类型数据");
     //datas3.Datas.ForEach(dd => Console.WriteLine(dd));

     //4,读取W区104开始的2个float数据
      var datas4 = finsTcp.Read<float>("W104", 2);
     if (!datas4.IsSuccessed)
     {
         Console.WriteLine(datas4.Message);
         return;
     }
     Console.WriteLine("读取W区104开始的2个float类型数据");
     datas4.Datas.ForEach(dd => Console.WriteLine(dd));


 }

 通讯报文

 可以对照前面讲的C#上位机与欧姆龙PLC的通信05---- HostLink协议(C-Mode版)

 C#上位机与欧姆龙PLC的通信06---- HostLink协议(FINS版)

串口报文和TCP报文,熟悉每个报文的组成部分,Hostlink通讯协议有两种模式:C-mode和FINS

C-mode报文在串口上通信的,FINS报文在网络上通信的。

6、测试通讯库-写入数据

这里测试4种数据的写入。

写入CIO区1.0开始的6个bool数据

写入D区30开始的4个数据,ushort类型

写入H区30开始的4个short类型的数据

写入W区30开始的5个float浮点字

 1)写入CIO区1.0开始的6个bool数据true, true, false, , false,true , true

写入成功,看PLC内存数据

 通讯报文 

2)写入D区30开始的4个数据,ushort类型

写入成功

3)写入H区30开始的4个short类型的数据

通讯报文

4)写入W区30开始的5个float浮点字

 

 

 可以对照前面讲的C#上位机与欧姆龙PLC的通信05---- HostLink协议(C-Mode版)

 C#上位机与欧姆龙PLC的通信06---- HostLink协议(FINS版)

串口报文和TCP报文,熟悉每个报文的组成部分,Hostlink通讯协议有两种模式:C-mode和FINS

C-mode报文在串口上通信的,FINS报文在网络上通信的。

7、小结

自己写的通讯库很强大,能读取写入单个或多个数据值,可以是多种数据类型,可以是多个存储区的操作,通讯库最后就是一个dll文件,项目中直接引用就可以啦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/285464.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

项目 杂碎 知识点 汇总!!!

Vue !!! setup生命周期 使用 nextTick &#xff01;&#xff01;获取节点 onMounted中可以使用JS&#xff0c;获取节点&#xff0c;setup生命周期无法获取节点 vue实现文本粘贴复制 Vue遍历对象 1、使用v-for指令&#xff1a;可以直接遍历对象的键和值 2、使用 Object.keys…

(已解决)word如何制作和引用参考文献

文章目录 正文其他 一般使用latex&#xff0c;但是有的时候会遇到使用word的情况&#xff0c;这里记录一下word如何弄参考文献。 正文 1.首先复制你的参考文献到word里面&#xff0c;然后要编号&#xff0c;记住&#xff0c;一定要编号&#xff0c;否则到时候无法引用。 那么…

深度学习|2.11 向量化vectorization

2.11 向量化的作用 向量化可以使得向量中的每一个维度的数据进行并行计算&#xff0c;从而加快了神经网络的计算速度。 验证 其他

Eureka注册及使用

一、Eureka的作用 Eureka是一个服务注册与发现的工具&#xff0c;主要用于微服务架构中的服务发现和负载均衡。其主要作用包括&#xff1a; 服务提供者将自己注册到Eureka Server上&#xff0c;包括服务的地址和端口等信息。服务消费者从Eureka Server上获取服务提供者的地址…

Jetson_Xavier_NX开发板重编译RT内核

一、准备源码和交叉编译工具 官方网址:Jetson Linux Archive | NVIDIA Developer 我的板子的jtop显示内核为35.4.1,因此以35.4.1为例: 点击进入: 新版本和老版本不一样,如果是老版本要注意自己的型号 不同版本的包名也不一样,但内部文件相差不大,注意仔细区分。 以3…

PiflowX组件-JDBCWrite

JDBCWrite组件 组件说明 使用JDBC驱动向任意类型的关系型数据库写入数据。 计算引擎 flink 有界性 Sink: Batch Sink: Streaming Append & Upsert Mode 组件分组 Jdbc 端口 Inport&#xff1a;默认端口 outport&#xff1a;默认端口 组件属性 名称展示名称默…

Java商城 免 费 搭 建:鸿鹄云商实现多种商业模式,VR全景到SAAS,应有尽有

鸿鹄云商 b2b2c产品概述 【b2b2c平台】&#xff0c;以传统电商行业为基石&#xff0c;鸿鹄云商支持“商家入驻平台自营”多运营模式&#xff0c;积极打造“全新市场&#xff0c;全新 模式”企业级b2b2c电商平台&#xff0c;致力干助力各行/互联网创业腾飞并获取更多的收益。从消…

Flume基础知识(一):Flume组成原理与架构

1. Flume定义 Flume是Cloudera提供的一个高可用的&#xff0c;高可靠的&#xff0c;分布式的海量日志采集、聚合和传输的系统。Flume基于流式架构&#xff0c;灵活简单。 Flume最主要的作用就是&#xff0c;实时读取服务器本地磁盘的数据&#xff0c;将数据写入到HDFS。 2. Fl…

win10连上了wifi热点但是无法上网

我的情况是能正常连接wifi热点&#xff08;手机连接这个热点能上网&#xff0c;说明这个wifi热点是正常的&#xff09; 但是没法上网 打开cmd窗口发现能ping通百度&#xff0c;掘金&#xff0c;csdn这些网址。这就更奇怪了&#xff01;于是根据上面的提示&#xff0c;检查了代…

PCBA电阻失效分析

一、案例背景 PCBA电阻使用一段时间后发生功能失效不良&#xff0c;据此情况&#xff0c;对失效电阻进行分析&#xff0c;明确失效原因。 二、分析过程 1、针对排阻的分析 数据通讯的主要连接点&#xff1a; 电阻测试结果&#xff1a; 测试结果&#xff1a;RP2、RP5 排阻第 3 …

Android Studio xml布局代码补全功能失效问题

这里写目录标题 前言&#xff1a;问题描述原因分析&#xff1a;解决方案&#xff1a;1.更新 Android Studio 版本2.原版本解决XML补全失效 小结 前言&#xff1a; 在开发过程中&#xff0c;你可能遇到很多奇奇怪怪的问题。Android Studio 编译器出现问题也是常有的事情&#x…

【计算机设计大赛作品】豆瓣电影数据挖掘可视化—信息可视化赛道获奖项目深入剖析【可视化项目案例-22】

文章目录 一.【计算机设计大赛作品】豆瓣电影数据挖掘可视化—信息可视化赛道获奖项目深入剖析【可视化项目案例-22】1.1 项目主题:豆瓣电影二.代码剖析2.1 项目效果展示2.2 服务端代码剖析2.3 数据分析2.4 数据评分三.寄语四.本案例完整源码下载一.【计算机设计大赛作品】豆瓣…

Qt高质量的开源项目合集

文章目录 1.Qt官网下载/文档2.第三方开源 1.Qt官网下载/文档 Qt Downloads Qt 清华大学开源软件镜像站 Qt 官方博客 2.第三方开源 记录了平常项目开发中用到的第三方库&#xff0c;以及一些值得参考的项目&#xff01; Qt AV 基于Qt和FFmpeg的跨平台高性能音视频播放框…

【C Primer Plus第六版 学习笔记】 第十六章 C预处理器和C库

有基础&#xff0c;进阶用&#xff0c;个人查漏补缺 第十五章的内容之前学过&#xff0c;跳过 预处理之前&#xff0c;编译器必须对该程序进行一些翻译处理 首先把源代码中出现的字符映射到原字符集 其次编译器定位每个反斜杠后面跟着换行符的实例&#xff0c;并删除它们&…

数据结构基本概念

数据结构 数据结构是相互之间存在一种或多种特定关系的数据元素的集合。它包括数据的逻辑结构、数据的存储结构和数据的基本运算。 数据的逻辑结构 数据的逻辑结构是指数据元素之间的逻辑关系。所谓逻辑关系是指数据元素之间的关联方式或“邻接关系”。 逻辑结构与数据元素本…

[④Meson]: Unit Tests

前言 Meson构建系统支持uni-tests&#xff0c;使用run()命令可以非常方便进行uni-test测试。 Syntax 基本语法&#xff1a; e executable(prog, source.c) test(name of test, e)下面是创建两个可执行程序&#xff0c;并且将它们在test中使用的例子&#xff1a; test0 e…

【Unity引擎技术整合】 Unity学习路线 | 知识汇总 | 持续更新 | 保持乐趣 | 共同成长

前言 本文对Unity引擎的知识进行了一个整理总结&#xff0c;基本包含了Unity中大部分的知识介绍。网上也有很多Unity相关的学习资料&#xff0c;但大多数都不成体系&#xff0c;学起来的时候难免会东奔西走的摸不着头脑。本文整理的多数文章都是有对应的系列性文章专栏&#x…

在 Oracle 数据库表中加载多个数据文件

在本文中&#xff0c;我将展示 SQL 加载器 Unix 脚本实用程序的强大功能&#xff0c;其中 SQL 加载器可以使用自动 shell 脚本加载多个数据文件。这在处理大量数据以及需要将数据从一个系统移动到另一个系统时非常有用。 它适合涉及大量历史数据的迁移项目。那么就不可能为每…

Laya3D常见报错信息汇总

1.Cannot read property isTrigger of undefined&#xff1a;貌似是Laya引擎的bug 解决方法&#xff1a; 在初次加载带有刚体的3D游戏对象组件的时候&#xff0c;使用代码获取刚体组件&#xff0c;设置刚体组件的isTrigger属性&#xff1a; let rigid this.obj.getComponent(L…

SELinux 安全模型——MLS

首发公号&#xff1a;Rand_cs BLP 模型&#xff1a;于1973年被提出&#xff0c;是一种模拟军事安全策略的计算机访问控制模型&#xff0c;它是最早也是最常用的一种多级访问控制模型&#xff0c;主要用于保证系统信息的机密性&#xff0c;是第一个严格形式化的安全模型 暂时无…