C#使用欧姆龙PLC的Fins协议读写PLC地址(基本封装)

2023-11-12

FINS通讯概述

FINS(factory interface network service)通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令/响应系统。运用 FINS指令可实现各种网络间的无缝通信,通过编程发送FINS指令,上位机或PLC就能够读写另一个PLC数据区的内容,甚至控制其运行状态,从而简化了用户程序。FINS协议支持工业以太网,这就为OMRON PLC与上位机以太网通信的实现提供了途径。

FINS-TCP需要握手命令成功后方可真正连接上PLC。

以下命令均为十六进制 字节数据流:

1. 握手指令

1.1. 发送

46494E53 0000000C 00000000 00000000 00000018

46494E53:ASCII编码:FINS;

0000000C:指后面跟的字节长度;12个字节

00000000:固定命令;

00000000:错误代码;

00000018:PC节点IP,当设置为0时,会自动获取节点IP。

1.2. 反馈

46494E53 00000010 00000001 00000000 00000018 00000017

46494E53:ASCII编码:FINS;

00000010:指后面跟的字节长度;16个字节

00000001:固定命令;

00000000:错误代码;

00000018:本机电脑节点IP;

00000017:PLC节点IP。

2. 读取指令

读D100开始的2个地址,注:一次最多读1000个地址【以字为单位,也就是2000个字节】。

2.1. 发送

46494E53 0000001A 00000002 00000000 80 00 02 001700 001800 FF 0101 82 006400 0002

46494E53:ASCII编码:FINS;

0000001A:指后面跟的字节长度;26个字节

00000002:固定命令;

00000000:错误代码;

80:ICF;

00:RSV;

02:GCT;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

FF:SID;

0101:读指令;

82:读地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0);

006400:起始地址;

0002:读个数。读取字【WORD】个数,也就是【WORD*2】个字节

2.2. 反馈

46494E53 0000001A 00000002 00000000 C0 00 02 001800 001700 FF 0101 0000 AABB CCDD

46494E53:ASCII编码:FINS;

0000001A:指后面跟的字节长度;

00000002:固定命令;

00000000:错误代码;

C0:ICF;

00:RSV;

02:GCT;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

FF:SID;

0101:读指令;

0000:读取成功标识;

AABB CCDD:读到的数据。

3. 写入指令

往W10,W11写入AABB,CCDD

3.1. 发送

46494E53 0000001E 00000002 00000000 80 00 02 001700 001800 FF 0102 B1 000A00 0002 AABBCCDD

46494E53:ASCII编码:FINS;

0000001E:指后面跟的字节长度;

00000002:固定命令;

00000000:错误代码;

80:ICF;

00:RSV;

02:GCT;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

FF:SID;

0102:写指令;

B1:写地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0);

000A00:起始地址;【2的24次方-1】,十进制范围【0~16777215】

0002:写个数;写入的字【WORD】个数,也就是【WORD*2】个字节

AABBCCDD:写入数据。 

3.2. 反馈

46494E53 00000016 00000002 00000000 C0 00 02 001800 001700 FF 0102 0000

46494E53:ASCII编码:FINS;

00000016:指后面跟的字节长度;

00000002:固定命令;

00000000:错误代码;

C0:ICF;

00:RSV;

02:GCT;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

FF:SID;

0102:写指令;

0000:写入成功标识。

【原封装代码有误,这次重新更新 斯内科 20211116】

下面C#封装程序【部分类FinsTcpUtil.cs】,代码如下:

PLC寄存器区域类型枚举类【OmronAddressType.cs】,源程序如下:

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

namespace OmronFinsDemo
{
    /// <summary>
    /// Omron PLC地址类型【存储区域类别】
    /// </summary>
    public enum OmronAddressType
    {
        /// <summary>
        /// C区:I/O继电器区 【Input Output Area】 
        /// 输入区Input 1600 bits=100 word 范围【CIO0~CIO99】
        /// 输出区Output 1600 bits=100 word 范围【CIO100~CIO199】
        /// CPU Bus Unit Area 6400 bits=400Word 范围【CIO1500~CIO1899】
        /// </summary>
        CIO = 0,
        /// <summary>
        /// W区:工作继电器区 【Work Relay Area】
        /// 8192 bits = 512 words 范围【W0~W511】
        /// </summary>
        WR = 1,
        /// <summary>
        /// D区:动态数据存储区,仅可由字(16位Word)进行存取【Data Memory Area】
        /// 32768Words 范围【D0~D32767】
        /// </summary>
        DM = 2,
        /// <summary>
        /// 保持继电器区 【Hold Relay】
        /// 8192 bits = 512 words 范围【H0~H511】
        /// </summary>
        HR = 3,
        /// <summary>
        /// 定时器区 【Timer】
        /// PVs 4096Words  范围【T0~T4095】
        /// CompletionFlag 4096bits 范围【T0~T4095】
        /// </summary>
        TIM = 4,
        /// <summary>
        /// 特殊辅助继电器区 【Auxiliary Relay Area】
        /// ReadOnly 7168 bits=448Words 范围【A0~A447】
        /// Read-Write 8192 bits=512Words 范围【A448~A959】
        /// </summary>
        AR = 5,
        /// <summary>
        /// 计数器区 【Counter】
        /// PVs 4096Words  范围【C0~C4095】
        /// CompletionFlag 4096bits 范围【C0~C4095】
        /// </summary>
        CNT = 6
    }
}

【部分类FinsTcpUtil.cs】

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace OmronFinsDemo
{
    /// <summary>
    /// FINS(factory interface network service)通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令-响应系统。
    /// 我们只使用欧姆龙PLC的FINS-TCP协议,FINS协议可以读写位(Bit)、字(WORD)操作
    /// 斯内科
    /// </summary>
    public partial class FinsTcpUtil
    {
        /// <summary>
        /// 是否已连接
        /// </summary>
        private bool connected;
        /// <summary>
        /// PLC的IP地址
        /// </summary>
        private string serverIP;
        /// <summary>
        /// PLC的端口,默认9600
        /// </summary>
        private int serverPort;

        /// <summary>
        /// 连接PLC的客户端对象
        /// </summary>
        TcpClient msender;
        /// <summary>
        /// 发送命令 和 接收反馈的套接字对象
        /// </summary>
        Socket msock;
        /// <summary>
        /// 工控机【上位机PC】的IP地址的尾节点
        /// </summary>
        private byte clientIpTail;
        /// <summary>
        /// 服务端【欧姆龙PLC】的IP地址的尾节点
        /// </summary>
        private byte serverIpTail;

        /// <summary>
        /// 记录FINS的发送和接收数据包事件
        /// 第一个参数是发送的数据包,第二个参数是响应的数据包,第三个参数的获取到响应数据包所花费的时间(ms)
        /// </summary>
        public event Action<byte[], byte[], double> RecordDataEvent;
        /// <summary>
        /// 是否已连接上欧姆龙PLC【握手成功后才连接成功】
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return connected;
            }
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void Disconnect()
        {
            if (connected)
            {
                msender.Close();
                connected = false;
            }
        }

        /// <summary>
        /// 初始化TCP,并发送握手协议命令,尝试连接欧姆龙PLC
        /// </summary>
        /// <param name="ip">PLC的IP地址</param>
        /// <param name="port">端口号,默认为9600</param>
        /// <param name="frame"></param>
        /// <returns></returns>
        public bool ConnectPlc(string ip, int port = 9600, int frame = 0)
        {
            serverIP = ip;
            serverPort = port;
            if (!connected)
            {
                msender = new TcpClient(ip, port);
                msender.ReceiveTimeout = 3000;
                msock = msender.Client;
                //握手协议 共20个字节【标识头4个字节,长度4个字节,命令码4个字节,错误代码4个字节,客户端节点地址4个字节】
                byte[] handshakeProtocol = new byte[20];
                //标识头命令:固定FINS
                handshakeProtocol[0] = 0x46;//F
                handshakeProtocol[1] = 0x49;//I
                handshakeProtocol[2] = 0x4E;//N
                handshakeProtocol[3] = 0x53;//S
                //数据长度:4个字节
                handshakeProtocol[4] = 0;
                handshakeProtocol[5] = 0;
                handshakeProtocol[6] = 0;
                handshakeProtocol[7] = 0x0C;//Length长度:后面跟的字节长度:12个字节
                //00000000:固定命令;【索引 8~11】
                //00000000:错误代码;【索引 12~15】
                handshakeProtocol[16] = 0;
                handshakeProtocol[17] = 0;
                handshakeProtocol[18] = 0;
                handshakeProtocol[19] = (byte)frame;//FINS Frame (工控机IP节点的最后一个字节,如 192.168.1.139 就填入139),frame为0时服务端为客户端自动分配IP尾号

                msock.Send(handshakeProtocol);
                //【握手的】反馈结果 共24个字节【标识头4个字节,长度4个字节,命令码4个字节,错误代码4个字节,客户端节点地址4个字节,服务端节点地址4个字节】
                /*
                 * 46494E53:ASCII编码:FINS;
                 * 00000010:指后面跟的字节长度;16个字节
                 * 00000001:固定命令;
                 * 00000000:错误代码;
                 * 00000018:本机电脑节点IP;
                 * 00000017:PLC节点IP。
                */
                byte[] feedbackBuffer = new byte[24];//反馈结果
                msock.Receive(feedbackBuffer, SocketFlags.None);
                if (handshakeProtocol[0] == feedbackBuffer[0] && handshakeProtocol[1] == feedbackBuffer[1] && handshakeProtocol[2] == feedbackBuffer[2] && handshakeProtocol[3] == feedbackBuffer[3]
                    && feedbackBuffer[7] == 0x10  && feedbackBuffer[15] == 0x00 && (handshakeProtocol[19] == 0 || feedbackBuffer[19] == handshakeProtocol[19]))
                {
                    //&& feedbackBuffer[11] == 0x01
                    //反馈结果 需满足 按照FINS开头,字节长度一定是16,错误代码为0
                    //客户端IP尾号frame为0时服务端将为客户端自动分配IP尾号
                    clientIpTail = feedbackBuffer[19];
                    serverIpTail = feedbackBuffer[23];
                    connected = true;
                }
            }
            return connected;
        }

        /// <summary>
        /// 获取欧姆龙地址类型对应的 FINS通信标识
        /// </summary>
        /// <param name="omronAddressType">欧姆龙地址类型枚举</param>
        /// <param name="isBitProcess">是否位处理。true:位处理,false:字处理</param>
        /// <returns></returns>
        private byte GetAreaTypeFlag(OmronAddressType omronAddressType, bool isBitProcess)
        {
            if (isBitProcess)
            {
                //位处理
                switch (omronAddressType)
                {
                    case OmronAddressType.CIO:
                        return 0x30;
                    case OmronAddressType.WR:
                        return 0x31;
                    case OmronAddressType.DM:
                        return 0x02;
                    case OmronAddressType.HR:
                        return 0x32;
                    case OmronAddressType.TIM:
                        return 0x09;
                    case OmronAddressType.AR:
                        return 0x33;
                    case OmronAddressType.CNT:
                        return 0x09;
                    default:
                        return 0x00;
                }
            }
            else
            {
                //字处理
                switch (omronAddressType)
                {
                    case OmronAddressType.CIO:
                        return 0xB0;
                    case OmronAddressType.WR:
                        return 0xB1;
                    case OmronAddressType.DM:
                        return 0x82;
                    case OmronAddressType.HR:
                        return 0xB2;
                    case OmronAddressType.TIM:
                        return 0x89;
                    case OmronAddressType.AR:
                        return 0xB3;
                    case OmronAddressType.CNT:
                        return 0x89;
                    default:
                        return 0x00;
                }
            }
        }

        /// <summary>
        /// 用于排他锁,确保在多线程调用该接口时,不会同时调用。确保在处理当前命令时,其他命令请等待
        /// </summary>
        static int lockedValue = 0;

        /// <summary>
        /// 发送命令并解析反馈【关键方法】,需要进行加锁操作,防止同时操作一个地址时出现脏读等异常情况
        /// 当位操作时,只读取或写入一位
        /// </summary>
        /// <param name="omronAddressType">欧姆龙PLC存储区域类别枚举</param>
        /// <param name="startAddress">起始地址</param>
        /// <param name="bitIndexOrWordLength">要读写的位索引 或者 字长度。读字时,一次最多读1000个字【也就是2000个字节】.如果是写入Word时,该参数无意义,直接按(writeData.Length+1)/2处理</param>
        /// <param name="isBitProcess">是否位处理。true:位处理,false:字【WORD】处理</param>
        /// <param name="isRead">读内存区域还是写内存区域 true:读,false:写</param>
        /// <param name="receiveData">读取时反馈的数据流,写入操作时该参数无意义</param>
        /// <param name="errMsg">处理时的异常错误信息,默认为空</param>
        /// <param name="writeData">需要写入的连续字节流,读取操作时该参数无意义,写入操作【isRead为false】时该参数不能为空</param>
        /// <returns>返回的错误号,0代表操作成功</returns>
        private int SendCommandAndParseFeedback(OmronAddressType omronAddressType, int startAddress, int bitIndexOrWordLength, bool isBitProcess, bool isRead, ref byte[] receiveData, out string errMsg, byte[] writeData = null)
        {
            errMsg = string.Empty;
            if (!IsConnected)
            {
                errMsg = $"【未建立连接】尚未连接PLC成功或者握手协议失败【{serverIP}:{serverPort}】,请检查配置和网络,当前起始地址【{startAddress}】";
                return 1000;
            }
            if (!isRead && (writeData == null || writeData.Length == 0 || writeData.Length > 2000))
            {
                errMsg = $"【参数非法】写入操作时,需要写入的数据为空 或者 写入数据流的字节长度不在【1~2000】之间,当前起始地址【{startAddress}】";
                return 1001;
            }
            if (startAddress < 0 || startAddress > 65535)
            {
                errMsg = $"【参数非法】欧姆龙PLC的FINS协议的起始地址必须在0~65535之间,当前起始地址【{startAddress}】";
                return 1002;
            }
            //读保持寄存器0x03读取的寄存器数量的范围为 1~1000。因一个寄存器【一个Word】存放两个字节,因此 字节数组的长度范围 为 1~2000
            if (isRead && bitIndexOrWordLength > 1000)
            {
                errMsg = $"【参数非法】读取的位索引 或者 字长度不能超过1000,当前起始地址【{startAddress}】,读取长度【{bitIndexOrWordLength}】";
                return 1003;
            }

            //写入WORD时,直接使用 写入的字节长度除以2【因 一个字相当于两个字节 1WORD=2Byte】
            if (!isRead && !isBitProcess)
            {
                bitIndexOrWordLength = (writeData.Length + 1) / 2;
            }
            
            byte serviceId = 0x15;//SID:SID用于标识数据发送的过程。【服务标识】,可以任意指定,返回数据包的SID和发送包的SID一致 
            int commandLength = 34;//发送命令的长度【读取命令 固定34位】
            if (!isRead)
            {
                commandLength = 34 + writeData.Length;
            }
            byte[] sendBytes = new byte[commandLength];
            //标识头命令:固定FINS
            sendBytes[0] = 0x46;//F
            sendBytes[1] = 0x49;//I
            sendBytes[2] = 0x4E;//N
            sendBytes[3] = 0x53;//S
                                //数据长度:4个字节
            sendBytes[4] = 0;
            sendBytes[5] = 0;
            sendBytes[6] = 0;
            sendBytes[7] = 0x1A;//Length长度:后面跟的字节长度:26个字节
            if (!isRead)
            {
                sendBytes[7] = (byte)(26 + writeData.Length);
            }
            //命令码 固定 00 00 00 02
            sendBytes[8] = 0;
            sendBytes[9] = 0;
            sendBytes[10] = 0;
            sendBytes[11] = 0x02;//frame command 
            //【索引12~15】错误码 00 00 00 00
            sendBytes[16] = 0x80;//ICF
            sendBytes[17] = 0x00;//RSV
            sendBytes[18] = 0x02;//GCT, less than 8 network layers
            sendBytes[19] = 0x00;//DNA, local network
            sendBytes[20] = serverIpTail;//DA1 PLC的IP节点尾号
            sendBytes[21] = 0x00;//DA2, CPU unit
            sendBytes[22] = 0x00;//SNA, local network
            sendBytes[23] = clientIpTail;//SA1 工控机IP节点尾号
            sendBytes[24] = 0x00;//SA2, CPU unit
            sendBytes[25] = serviceId;//SID:SID用于标识数据发送的过程。 【服务标识】
            //SID能够设置为00到FF十六进制的任何数字。SID用于检测响应请求是否正确,当发送节点与响应节点的SID值相同,表明响应的数据是请求的数据,不相同,表明响应的数据非请求数据。

            if (isRead)
            {
                //读命令 01 01
                sendBytes[26] = 0x01;
                sendBytes[27] = 0x01;
            }
            else
            {
                //写命令 01 02
                sendBytes[26] = 0x01;
                sendBytes[27] = 0x02;
            }
            //地址区域标识
            sendBytes[28] = GetAreaTypeFlag(omronAddressType, isBitProcess);
            byte[] addressParts = BitConverter.GetBytes((ushort)startAddress);
            //起始地址【索引 29~30】
            sendBytes[29] = addressParts[1];
            sendBytes[30] = addressParts[0];

            //读取的字【Word】的长度
            byte[] lengthParts = BitConverter.GetBytes((ushort)bitIndexOrWordLength);
            if (isBitProcess)
            {
                //位处理时,表示读取的位的索引,只读取 或者 写入 一位
                sendBytes[31] = lengthParts[0];
                sendBytes[32] = 0x00;
                sendBytes[33] = 0x01;//每次只读取一位
                /*
                 * 读取位操作【从索引26开始】0101+1字节存储区代码+3字节开始地址+2字节数量                
                 * 示例 0101 31  010A 01  000A 
                 * 0101 代表读操作  0x31代表WR区的位操作  010A代表起始地址266 01代表起始位  000A代表读取位的个数:10个
                
                 * 写入位操作【从索引26开始】 0102+1字节存储区代码+3字节开始地址+2字节数量+1字节第1位值+1字节第2位值+…
                 * 示例 0102 31 00D4 01 0002 01 00
                 * 31是W位代码,0x00D401=212.01, 00D4代表起始地址,01代表位索引 0002写入2个位,0100 第一个位W212.01写入1,第二个位W212.02写入0
                 *                 
                */
            }
            else
            {
                //字处理时,按零处理
                sendBytes[31] = 0x00;//字处理时,起始地址的位索引,按零处理如 D1234.0
                sendBytes[32] = lengthParts[1];
                sendBytes[33] = lengthParts[0];
            }
            //要写入的字节流数据,写入位时writeData只考虑第一个
            if (!isRead)
            {
                Array.Copy(writeData, 0, sendBytes, 34, writeData.Length);
            }

            //添加锁
            while (Interlocked.Exchange(ref lockedValue, 1) != 0) { }
            System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
            try
            {
                msock.Send(sendBytes, SocketFlags.None);
            }
            catch (SocketException ex)
            {
                connected = false;
                errMsg = $"发送数据时出现套接字异常,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】【{ex.SocketErrorCode}】,{ex.Message}";
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1004;
            }
            catch (Exception ex)
            {
                connected = false;
                errMsg = $"发送数据时出现异常,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】【{ex.Message}】";
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1005;
            }

            byte[] buffer = new byte[2048];
            byte[] receiveByts = null;//接收的有效字节流
            int rcvCount = 0;//收到的字节数
            try
            {
                rcvCount = msock.Receive(buffer);
                receiveByts = new ArraySegment<byte>(buffer, 0, rcvCount).ToArray();
                stopwatch.Stop();
                RecordDataEvent?.Invoke(sendBytes, receiveByts, stopwatch.Elapsed.TotalMilliseconds);
                /*
                 * 反馈数据流
                 * 46494E53 0000001A       00000002 00000000 C0   00   02   001800   001700   FF   0101    0000       AABB CCDD XXXX XXXX 
                 * 标识头 后面跟的字节长度  固定    错误代码 ICF  RSV  GCT  工控机IP PLC的IP  SID  读命令  成功标识   实际读取到的数据,索引30开始
                */
            }
            catch (SocketException ex)
            {
                connected = false;
                errMsg = $"【网络异常】接收数据失败,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】,套接字错误【{ex.SocketErrorCode}】,【{ex.Message}】";
                //释放锁
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1006;
            }
            catch (Exception ex)
            {
                connected = false;
                errMsg = $"【处理异常】接收数据失败,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】【{ex.Message}】";
                //释放锁
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1007;
            }
            if (receiveByts == null || receiveByts.Length < 30)
            {
                errMsg = $"接收数据为空,或者低于30个字节,接收数据为【{(receiveByts == null ? "" : string.Join(",", receiveByts))}】";
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1008;
            }
            if (receiveByts[0] != sendBytes[0] || receiveByts[1] != sendBytes[1] || receiveByts[2] != sendBytes[2] || receiveByts[3] != sendBytes[3])
            {
                errMsg = $"接收数据头非法,不是从【FINS】开始,接收数据为【{string.Join(",", receiveByts)}】";
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1009;
            }
            if (receiveByts[15] != 0)//if (receiveByts[11] != 2 || receiveByts[15] != 0)
            {
                errMsg = $"数据非法,解析时出现错误,错误号【{receiveByts[15]}】,接收数据为【{string.Join(",", receiveByts)}】";
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1010;
            }
            //if (receiveByts[25] != serviceId)
            //{
            //    errMsg = $"服务标识非法,发送的SID【{serviceId}】与接收的SID【{receiveByts[25]}】不一致,接收数据为【{string.Join(",", receiveByts)}】";
            //    Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
            //    return 1011;
            //}
            if (receiveByts[26] != sendBytes[26] || receiveByts[27] != sendBytes[27])
            {
                errMsg = $"读写命令不匹配,发送命令【{sendBytes[26].ToString("X2")}{sendBytes[27].ToString("X2")}】,反馈命令【{receiveByts[26].ToString("X2")}{receiveByts[27].ToString("X2")}】";
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1012;
            }
            if (receiveByts[28] != 0 || receiveByts[29] != 0)
            {
                errMsg = GetErrorMessage(receiveByts[28], receiveByts[29]);
                Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
                return 1013;
            }

            //抓取读取到的实际字节流 【一个字WORD 相当于 两个字节】
            if (isRead)
            {
                //读取位时,只读取一位
                receiveData = new byte[isBitProcess ? 1 : (bitIndexOrWordLength * 2)];                
                Array.Copy(receiveByts, 30, receiveData, 0, receiveData.Length);
            }

            //释放锁
            Interlocked.Exchange(ref lockedValue, 0);//将current重置为0
            return 0;
        }

        /// <summary>
        /// 高低字节转化,也就是相邻的两个字节交换位置。【索引0与索引1的值交换位置,索引2与索引3的值交换位置,索引4与索引5的值交换位置...】
        /// 注意奇数个数组长度时,强行补零按偶数处理【一个字 相当于 两个字节】
        /// </summary>
        /// <param name="sourceData"></param>
        /// <returns></returns>
        private byte[] HightLowChange(byte[] sourceData)
        {
            if (sourceData.Length % 2 == 1)
            {
                byte[] dres = new byte[sourceData.Length];
                for (int i = 0; i < sourceData.Length - 1; i += 2)
                {
                    dres[i] = sourceData[i + 1];
                    dres[i + 1] = sourceData[i];
                }
                dres[sourceData.Length - 1] = sourceData[sourceData.Length - 1];
                return dres;
            }

            int len = (sourceData.Length % 2 == 1 ? sourceData.Length + 1 : sourceData.Length);
            byte[] res = new byte[len];
            for (int i = 0; i < sourceData.Length; i += 2)
            {
                res[i] = sourceData[i + 1];
                res[i + 1] = sourceData[i];
            }

            return res;
        }

        /// <summary>
        /// 获得反馈指定错误消息描述
        /// </summary>
        /// <param name="errCode1"></param>
        /// <param name="errCode2"></param>
        /// <returns></returns>
        private string GetErrorMessage(byte errCode1, byte errCode2)
        {
            string msg = $"错误代码【{errCode1.ToString("X2")} {errCode2.ToString("X2")}】";
            switch (errCode1)
            {
                case 0x00:
                    if (errCode2 == 0x01)
                        msg += "service canceled";
                    break;
                case 0x01:
                    switch (errCode2)
                    {
                        case 0x01: msg += "Ip配置错误:local node not in network"; break; 
                        case 0x02: msg += "权限出错:token timeout"; break;
                        case 0x03: msg += "retries failed"; break;
                        case 0x04: msg += "too many send frames"; break; 
                        case 0x05: msg += "node address range error"; break;
                        case 0x06: msg += "node address duplication"; break; 
                    }
                    break;
                case 0x02:
                    switch (errCode2)
                    {
                        case 0x01: msg += "destination node not in network"; break; 
                        case 0x02: msg += "unit missing"; break; 
                        case 0x03: msg += "third node missing"; break;
                        case 0x04: msg += "destination node busy"; break; 
                        case 0x05: msg += "response timeout"; break; 
                    }
                    break;
                case 0x03:
                    switch (errCode2)
                    {
                        case 0x01: msg += "communications controller error"; break; 
                        case 0x02: msg += "CPU unit error"; break; 
                        case 0x03: msg += "控制器错误:controller error"; break; 
                        case 0x04: msg += "unit number error"; break; 
                    }
                    break;
                case 0x04:
                    switch (errCode2)
                    {
                        case 0x01: msg += "未定义的指令:undefined command"; break;
                        case 0x02: msg += "不支持的模式:not supported by model/version"; break; 
                    }
                    break;
                case 0x05:
                    switch (errCode2)
                    {
                        case 0x01: msg += "destination address setting error"; break; 
                        case 0x02: msg += "no routing tables"; break; 
                        case 0x03: msg += "routing table error"; break; 
                        case 0x04: msg += "too many relays"; break; 
                    }
                    break;
                case 0x10:
                    switch (errCode2)
                    {
                        case 0x01: msg += "指令太长:command too long"; break;
                        case 0x02: msg += "command too short"; break; 
                        case 0x03: msg += "数据与长度不匹配:elements/data don't match"; break; 
                        case 0x04: msg += "command format error"; break; 
                        case 0x05: msg += "header error"; break; 
                    }
                    break;
                case 0x11:
                    switch (errCode2)
                    {
                        case 0x01: msg += "area classification missing"; break; 
                        case 0x02: msg += "access size error"; break; 
                        case 0x03: msg += "address range error"; break; 
                        case 0x04: msg += "address range exceeded"; break; 
                        case 0x06: msg += "program missing"; break; 
                        case 0x09: msg += "relational error"; break; 
                        case 0x0a: msg += "duplicate data access"; break; 
                        case 0x0b: msg += "response too long"; break; 
                        case 0x0c: msg += "parameter error"; break; 
                    }
                    break;
                case 0x20:
                    switch (errCode2)
                    {
                        case 0x02: msg += "protected"; break; 
                        case 0x03: msg += "table missing"; break; 
                        case 0x04: msg += "data missing"; break; 
                        case 0x05: msg += "program missing"; break; 
                        case 0x06: msg += "file missing"; break; 
                        case 0x07: msg += "data mismatch"; break; 
                    }
                    break;
                case 0x21:
                    switch (errCode2)
                    {
                        case 0x01: msg += "read-only"; break; 
                        case 0x02: msg += "protected,cannot write data link table"; break; 
                        case 0x03: msg += "cannot register"; break; 
                        case 0x05: msg += "program missing"; break; 
                        case 0x06: msg += "file missing"; break; 
                        case 0x07: msg += "file name already exists"; break; 
                        case 0x08: msg += "cannot change"; break; 
                    }
                    break;
                case 0x22:
                    switch (errCode2)
                    {
                        case 0x01: msg += "not possible during execution"; break; 
                        case 0x02: msg += "not possible while running"; break; 
                        case 0x03: msg += "wrong PLC mode"; break; 
                        case 0x04: msg += "wrong PLC mode"; break; 
                        case 0x05: msg += "wrong PLC mode"; break; 
                        case 0x06: msg += "wrong PLC mode"; break; 
                        case 0x07: msg += "specified node not polling node"; break; 
                        case 0x08: msg += "step cannot be executed"; break; 
                    }
                    break;
                case 0x23:
                    switch (errCode2)
                    {
                        case 0x01: msg += "file device missing"; break; 
                        case 0x02: msg += "memory missing"; break; 
                        case 0x03: msg += "clock missing"; break; 
                    }
                    break;
                case 0x24:
                    if (errCode2 == 0x01) msg += "table missing"; 
                    break;
                case 0x25:
                    switch (errCode2)
                    {
                        case 0x02: msg += "memory error"; break; 
                        case 0x03: msg += "I/O setting error"; break; 
                        case 0x04: msg += "too many I/O points"; break; 
                        case 0x05: msg += "CPU bus error"; break; 
                        case 0x06: msg += "I/O duplication"; break; 
                        case 0x07: msg += "CPU bus error"; break; 
                        case 0x09: msg += "SYSMAC BUS/2 error"; break; 
                        case 0x0a: msg += "CPU bus unit error"; break; 
                        case 0x0d: msg += "SYSMAC BUS No. duplication"; break; 
                        case 0x0f: msg += "memory error"; break; 
                        case 0x10: msg += "SYSMAC BUS terminator missing"; break; 
                    }
                    break;
                case 0x26:
                    switch (errCode2)
                    {
                        case 0x01: msg += "no protection"; break; 
                        case 0x02: msg += "incorrect password"; break; 
                        case 0x04: msg += "protected"; break; 
                        case 0x05: msg += "service already executing"; break; 
                        case 0x06: msg += "service stopped"; break; 
                        case 0x07: msg += "no execution right"; break; 
                        case 0x08: msg += "settings required before execution"; break; 
                        case 0x09: msg += "necessary items not set"; break; 
                        case 0x0a: msg += "number already defined"; break; 
                        case 0x0b: msg += "error will not clear"; break; 
                    }
                    break;
                case 0x30:
                    if (errCode2 == 0x01)  
                        msg += "无访问权限:no access right";
                    break;
                case 0x40:
                    if (errCode2 == 0x01) 
                        msg += "服务未开启:service aborted";
                    break;
            }
            return msg;
        }
    }
}

部分类FinsTcpUtil.Partial.cs,源程序如下:

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

namespace OmronFinsDemo
{
    public partial class FinsTcpUtil
    {
        /// <summary>
        /// 写基本数据类型到PLC,如bool,short,int,uint,float等
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="startAddress">起始寄存器地址,以字(WORD)为单位,形如D32000</param>
        /// <param name="value"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int WriteValue<T>(OmronAddressType omronAddressType, int startAddress, T value, out string msg) where T : struct
        {
            bool isBitProcess = false;//是否位操作,true:位操作,false:字操作【一个字 相当于 2个字节】
            byte[] datas = new byte[0];
            if (typeof(T) == typeof(bool))
            {
                //位操作 只考虑0位
                datas = BitConverter.GetBytes(Convert.ToBoolean(value));
                isBitProcess = true;
            }
            else if (typeof(T) == typeof(sbyte))
            {
                datas = new byte[1] { (byte)Convert.ToSByte(value) };
            }
            else if (typeof(T) == typeof(byte))
            {
                datas = new byte[1] { Convert.ToByte(value) };
            }
            else if (typeof(T) == typeof(short))
            {
                datas = BitConverter.GetBytes(Convert.ToInt16(value));
            }
            else if (typeof(T) == typeof(ushort))
            {
                datas = BitConverter.GetBytes(Convert.ToUInt16(value));
            }
            else if (typeof(T) == typeof(int))
            {
                datas = BitConverter.GetBytes(Convert.ToInt32(value));
            }
            else if (typeof(T) == typeof(uint))
            {
                datas = BitConverter.GetBytes(Convert.ToUInt32(value));
            }
            else if (typeof(T) == typeof(long))
            {
                datas = BitConverter.GetBytes(Convert.ToInt64(value));
            }
            else if (typeof(T) == typeof(ulong))
            {
                datas = BitConverter.GetBytes(Convert.ToUInt64(value));
            }
            else if (typeof(T) == typeof(float))
            {
                datas = BitConverter.GetBytes(Convert.ToSingle(value));
            }
            else if (typeof(T) == typeof(double))
            {
                datas = BitConverter.GetBytes(Convert.ToDouble(value));
            }
            else
            {
                //暂不考虑 char(就是ushort,两个字节),decimal(十六个字节) 等类型
                msg = $"写Fins数据暂不支持其他类型:{value.GetType()}";
                return -1;
            }
            byte[] rcvBuffer = new byte[0];
            int bitIndexOrWordLength = 0;//位操作 默认处理0位
            if (!isBitProcess)
            {
                bitIndexOrWordLength = (datas.Length + 1) / 2;
            }
            datas = HightLowChange(datas);
            return SendCommandAndParseFeedback(omronAddressType, startAddress, bitIndexOrWordLength, isBitProcess, false, ref rcvBuffer, out msg, datas);
        }

        /// <summary>
        /// 写入连续的字节数组
        /// </summary>
        /// <param name="startAddress">起始寄存器地址,以字(WORD)为单位,形如MW32000</param>
        /// <param name="buffer"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int WriteValue(OmronAddressType omronAddressType, int startAddress, byte[] buffer, out string msg)
        {
            byte[] rcvBuffer = new byte[0];
            buffer = HightLowChange(buffer);
            return SendCommandAndParseFeedback(omronAddressType, startAddress, (buffer.Length + 1) / 2, false, false, ref rcvBuffer, out msg, buffer);
        }
        /// <summary>
        /// 写指定长度的字符串(比如条码)到汇川PLC,最大240个字符
        /// 将字符串填充满length个,不足时用 空字符'\0'填充,相当于清空所有字符后重新填充
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="length">字符串的最大长度【范围1~240】,超过240个字符将直接返回错误</param>
        /// <param name="barcode">实际字符串,长度可能小于最大长度length</param>
        /// <returns></returns>
        public int WriteString(OmronAddressType omronAddressType, int startAddress, int length, string barcode, out string msg)
        {
            if (barcode == null)
            {
                barcode = string.Empty;
            }
            //将字符串填充满length个,不足时用 空字符'\0'填充,相当于清空所有字符后填充
            //防止出现 上一次写 【ABCD】,本次写 【12】, 读取字符串 结果是【12CD】 的问题
            barcode = barcode.PadRight(length, '\0');
            return WriteValue(omronAddressType, startAddress, Encoding.ASCII.GetBytes(barcode), out msg);
        }

        /// <summary>
        /// 写长字符串
        /// 一个寄存器地址可以存放两个字节【两个字符】,设定每次最多写入200个字符,
        /// 分多次写入,每一次写入的起始寄存器地址偏移100
        /// </summary>
        /// <param name="startAddress">起始地址</param>
        /// <param name="longString">长字符串</param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int WriteLongString(OmronAddressType omronAddressType, int startAddress, string longString, out string msg)
        {
            msg = string.Empty;
            if (longString == null)
            {
                longString = string.Empty;
            }
            int cycleCount = 200;//每20个字符就进行分段
            int maxLength = longString.Length;
            int pageSize = (maxLength + cycleCount - 1) / cycleCount;
            int errorCode = -1;
            for (int i = 0; i < pageSize; i++)
            {
                int writeLength = cycleCount;
                if (i == pageSize - 1)
                {
                    //最后一次
                    writeLength = maxLength - i * cycleCount;
                }
                //分段字符串,每次最多200个
                string segment = longString.Substring(i * cycleCount, writeLength);
                //寄存器地址是字,所以需要 字节个数 除以2
                errorCode = WriteString(omronAddressType, startAddress + (i * cycleCount / 2), writeLength, segment, out msg);
                if (errorCode != 0)
                {
                    return errorCode;
                }
            }
            return errorCode;
        }

        /// <summary>
        /// 读取基本数据类型
        /// </summary>
        /// <typeparam name="T">基本的数据类型,如short,int,double等</typeparam>
        /// <param name="startAddress"></param>
        /// <param name="value"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int ReadValue<T>(OmronAddressType omronAddressType, int startAddress, out T value, out string msg) where T : struct
        {
            bool isBitProcess = false;//是否位操作,true:位操作,false:字操作【一个字 相当于 2个字节】
            value = default(T);
            int length = 0;//读取的连续字节个数
            if (typeof(T) == typeof(bool))
            {
                isBitProcess = true;//位操作时,只考虑0位
            }
            else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(byte))
            {
                length = 1;
            }
            else if (typeof(T) == typeof(short) || typeof(T) == typeof(ushort))
            {
                length = 2;
            }
            else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint) || typeof(T) == typeof(float))
            {
                length = 4;
            }
            else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong) || typeof(T) == typeof(double))
            {
                length = 8;
            }
            else
            {
                //暂不考虑 char(就是ushort,两个字节),decimal(十六个字节) 等类型
                msg = $"读Fins数据暂不支持其他类型:{value.GetType()}";
                return -1;
            }
            byte[] rcvBuffer = new byte[0];
            int errorCode = SendCommandAndParseFeedback(omronAddressType, startAddress, (length + 1) / 2, isBitProcess, true, ref rcvBuffer, out msg);
            rcvBuffer = HightLowChange(rcvBuffer);
            if (errorCode != 0)
            {
                return errorCode;
            }
            if (typeof(T) == typeof(bool))
            {
                value = (T)(object)Convert.ToBoolean(rcvBuffer[0]);
            }
            else if (typeof(T) == typeof(sbyte))
            {
                value = (T)(object)(sbyte)rcvBuffer[0];
            }
            else if (typeof(T) == typeof(byte))
            {
                value = (T)(object)rcvBuffer[0];
            }
            else if (typeof(T) == typeof(short))
            {
                value = (T)(object)BitConverter.ToInt16(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(ushort))
            {
                value = (T)(object)BitConverter.ToUInt16(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(int))
            {
                value = (T)(object)BitConverter.ToInt32(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(uint))
            {
                value = (T)(object)BitConverter.ToUInt32(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(long))
            {
                value = (T)(object)BitConverter.ToInt64(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(ulong))
            {
                value = (T)(object)BitConverter.ToUInt64(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(float))
            {
                value = (T)(object)BitConverter.ToSingle(rcvBuffer, 0);
            }
            else if (typeof(T) == typeof(double))
            {
                value = (T)(object)BitConverter.ToDouble(rcvBuffer, 0);
            }
            return 0;
        }

        /// <summary>
        /// 从起始地址开始,读取一段字节流
        /// </summary>
        /// <param name="startAddress">起始寄存器地址</param>
        /// <param name="length">读取的字节个数</param>
        /// <param name="values">返回的字节流数据</param>
        /// <returns>true:读取成功 false:读取失败</returns>
        public int ReadValue(OmronAddressType omronAddressType, int startAddress, int length, out byte[] values, out string msg)
        {
            values = new byte[length];
            int errorCode = SendCommandAndParseFeedback(omronAddressType, startAddress, (length + 1) / 2, false, true, ref values, out msg);           
            if (errorCode != 0)
            {
                return errorCode;
            }
            values = HightLowChange(values);
            return errorCode;
        }

        /// <summary>
        /// 读取(长)字符串,设定每次最多读取200个字符,
        /// 分多次读取,每一次读取的起始寄存器地址偏移100
        /// </summary>
        /// <param name="startAddress"></param>
        /// <param name="length"></param>
        /// <param name="longString"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int ReadLongString(OmronAddressType omronAddressType, int startAddress, int length, out string longString, out string msg)
        {
            msg = string.Empty;
            longString = string.Empty;
            int cycleCount = 200;
            int pageSize = (length + cycleCount - 1) / cycleCount;
            int errorCode = -1;
            for (int i = 0; i < pageSize; i++)
            {
                int readLength = cycleCount;
                if (i == pageSize - 1)
                {
                    //最后一次
                    readLength = length - i * cycleCount;
                }
                byte[] values;
                errorCode = ReadValue(omronAddressType, startAddress + (i * cycleCount / 2), readLength, out values, out msg);
                if (errorCode != 0)
                {
                    return errorCode;
                }
                longString += Encoding.ASCII.GetString(values);
            }
            return errorCode;
        }
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#使用欧姆龙PLC的Fins协议读写PLC地址(基本封装) 的相关文章

  • 将复选框添加到 UniformGrid

    我正在尝试将复选框动态添加到 wpf 中的统一网格中 但看起来网格没有为它们分配足够的空间 所以它们都有点互相重叠 这就是我将它们添加到后面的代码中的方法 foreach string folder in subfolders PathCh
  • 如何检查图像对象与资源中的图像对象是否相同?

    所以我试图创建一个简单的程序 只需在单击图片框中更改图片即可 我目前只使用两张图片 所以我的图片框单击事件函数的代码 看起来像这样 private void pictureBox1 Click object sender EventArgs
  • 访问私人成员[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过将类的私有成员转换为 void 指针 然后转换为结构来访问类的私有成员是否合适 我认为我无权修改包含我需要访问的数据成员的类 如果不道德 我
  • Qt-Qlist 检查包含自定义类

    有没有办法覆盖加载自定义类的 Qt QList 的比较机制 即在 java 中你只需要重写一个比较方法 我有一个带有我的自定义类模型的 QList QList
  • 从父类调用子类方法

    a doStuff 方法是否可以在不编辑 A 类的情况下打印 B did stuff 如果是这样 我该怎么做 class Program static void Main string args A a new A B b new B a
  • C#:如何防止主窗体过早显示

    在我的 main 方法中 我像往常一样启动主窗体 Application EnableVisualStyles Application SetCompatibleTextRenderingDefault false Application
  • 将目录压缩为单个文件的方法有哪些

    不知道怎么问 所以我会解释一下情况 我需要存储一些压缩文件 最初的想法是创建一个文件夹并存储所需数量的压缩文件 并创建一个文件来保存有关每个压缩文件的数据 但是 我不被允许创建许多文件 只能有一个 我决定创建一个压缩文件 其中包含有关进一步
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • 使用 System.Text.Json 即时格式化 JSON 流

    我有一个未缩进的 Json 字符串 例如 hash 123 id 456 我想缩进字符串并将其序列化为 JSON 文件 天真地 我可以使用缩进字符串Newtonsoft如下 using Newtonsoft Json Linq JToken
  • 如何返回 json 结果并将 unicode 字符转义为 \u1234

    我正在实现一个返回 json 结果的方法 例如 public JsonResult MethodName Guid key var result ApiHelper GetData key Data is stored in db as v
  • 如何将图像路径保存到Live Tile的WP8本地文件夹

    我正在更新我的 Windows Phone 应用程序以使用新的 WP8 文件存储 API 本地文件夹 而不是 WP7 API 隔离存储文件 旧的工作方法 这是我如何成功地将图像保存到 共享 ShellContent文件夹使用隔离存储文件方法
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • 将自定义元数据添加到 jpeg 文件

    我正在开发一个图像处理项目 C 我需要在处理完成后将自定义元数据写入 jpeg 文件 我怎样才能做到这一点 有没有可用的图书馆可以做到这一点 如果您正在谈论 EXIF 元数据 您可能需要查看exiv2 http www exiv2 org
  • 如何衡量两个字符串之间的相似度? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 给定两个字符串text1 and text2 public SOMEUSABLERETURNTYPE Compare string t
  • 如何将单个 char 转换为 int [重复]

    这个问题在这里已经有答案了 我有一串数字 例如 123456789 我需要提取它们中的每一个以在计算中使用它们 我当然可以通过索引访问每个字符 但是如何将其转换为 int 我研究过 atoi 但它需要一个字符串作为参数 因此 我必须将每个字
  • clang 实例化后静态成员初始化

    这样的代码可以用 GCC 编译 但 clang 3 5 失败 include
  • 将 unsigned char * (uint8_t *) 转换为 const char *

    我有一个带有 uint8 t 参数的函数 uint8 t ihex decode uint8 t in size t len uint8 t out uint8 t i hn ln for i 0 i lt len i 2 hn in i
  • 控制到达非 void 函数末尾 -wreturn-type

    这是查找四个数字中的最大值的代码 include
  • WCF:将随机数添加到 UsernameToken

    我正在尝试连接到用 Java 编写的 Web 服务 但有些东西我无法弄清楚 使用 WCF 和 customBinding 几乎一切似乎都很好 除了 SOAP 消息的一部分 因为它缺少 Nonce 和 Created 部分节点 显然我错过了一
  • 使用 libcurl 检查 SFTP 站点上是否存在文件

    我使用 C 和 libcurl 进行 SFTP FTPS 传输 在上传文件之前 我需要检查文件是否存在而不实际下载它 如果该文件不存在 我会遇到以下问题 set up curlhandle for the public private ke

随机推荐

  • 程序员都要学学任正非的坚强!这个老头,在逆境中崛起!

    点赞再看 养成习惯 微信搜一搜 findyi 关注这个喜欢写情怀的程序员 回复 1 获得程序员职场晋升PPT一份 2019年的最后一天 罗胖在跨年演讲中引用了何帆老师的一句话 用一个人的长期主义 对冲世界的不确定性 那时候 谁也无法想到 世
  • 【STM32】为什么STM32的Flash地址要设置到0x08000000

    参考 不是问题的问题 为什么STM32的Flash地址要设置到0x08000000 这边涉及到分散加载文件 启动文件等等 先挖坑 搞定IAP升级以后再填 为什么STM32的Flash地址要设置到0x08000000 主flash启动时 是从
  • tomcat多系统部署方案

    多系统部署到一个 Tomcat 中 如果某一个系统崩溃可能会同时使其他系统不能正常工作 因为它们运行在同一个 JVM 上 就需要在同一个服务器中安装多个 Tomcat 来运行不同的 WEB 系统 一 Tomcat 版本选择 安装 JDK 版
  • 事件循环(Event Loop)

    目录 一 浏览器的进程模型 1 1 进程 1 2 线程 1 3 浏览器的进程和线程 二 渲染主线程 2 1 渲染主线程中为什么使用异步 2 2 js的异步 2 3 队列的优先级 添加任务到微队列的主要 式主要是使 Promise NextT
  • 板材眼镜大小调整方法

    http v youku com v show id XNTY0NjUwOTU2 html
  • ROS 2正式版来了,到底有哪些新变化?

    锋影 email 174176320 qq com 如果你认为本系列文章对你有所帮助 请大家有钱的捧个钱场 点击此处赞助 赞助额0 1元起步 多少随意 2017 12 09 机器人开源操作系统软件ROS 2终于推出首个正式版 新版本命名为
  • openGL之API学习(一零三)glGetActiveUniform

    获取活跃一致变量的信息 变量可以在程序执行期间被访问 则该变量被认为是活动的 void glGetActiveUniform GLuint program GLuint index GLsizei bufSize GLsizei lengt
  • 微信小程序唤起键盘页面溢出的解决方案

    文章目录 背景 解决方案 效果 背景 微信小程序的 input 组件 如果使用自带的adjust position会引起除了 input 元素的其他元素一并上移 解决方案 首先获取到屏幕的高度screenHeight 确定好每个元素所占的大
  • 静态代码分析工具清单:开源篇(多语言)

    http hao jobbole com static code analysis tool list opensource utm source hao jobbole com utm medium relatedResources 静态
  • 百度地图入门

    百度地图官网百度api 进入官网选择javascript API 里面有详细的教程 跟着教程先登录注册一个个人开发账号 并创建一个应用获取ak 创建时js需要填白名单 如果是在本地运行填写localhost就好了 当你在控制台看到这个界面是
  • eclipse双击变量高亮显示开关

    eclipse双击变量高亮显示开关 在eclipse myeclipse中如果不小心把变量的高亮显示弄丢了 可真是件愁人的事 不过看到这你就不用愁了 windows gt preferences gt java gt Editor gt M
  • Springboot读取jar下的文件(在springboot打包成jar后)

    关于取web jar中的配置数据 以及存储下载的数据临时目录 按如下方法处理 均已测试验证过 1 更新了文件路径问题 所有的初始化数据Jason直接从reasource目录的mockdata里读 2 所有下载的数据 放到web jar同一级
  • C语言学习———函数

    目录 编辑 1 函数的概念 2 函数的分类 3 库函数是什么 3 1库函数的查找学习方法 3 2库函数的分类 4 自定义函数 4 1形参与实参 4 2传值与传址调用 4 3总结一句 5 函数的嵌套定义与链式访问 5 1嵌套定义 5 2链式访
  • 什么是智能合约? 智能合约到底做什么的?

    Solidity Solidity是一种用于编写智能合约的高级语言 语法类似于JavaScript 在以太坊平台上 Solidity编写的智能合约可以被编译成字节码在以太坊虚拟机上运行 使用Solidity语言编写智能合约避免了直接编写底层
  • thinkphp5学习路程 二 URL访问路径

    URL访问路径 localhost studytp1 public index php 模块 控制器 操作名 参数名 参数值 默认情况下URL是不区分大小写的 自动转换成小写 如果要区分 就要打开配置文件中的 关闭URL中控制器和操作名的自
  • 计算机网络一到六章知识点

    一 概述 1 1互联网的基本特性 连通性 互联网上用户不管距离多远 都能通信 就像这些用户终端都彼此连通 共享性 所谓共享就是指资源共享 资源共享 包含信息 软件 硬件等共享 就像资源在用户身边 1 2 因特网 互联网 概述 1 网络的网络
  • 在小程序开发中使用 npm

    微信小程序在 2 2 1 版本后增加了对 npm 包加载的支持 使得小程序支持使用 npm 安装第三方包 1 在小程序中加载 npm 包 npm install miniprogram datepicker production node
  • 数组查找操作:寻找第二大

    一 找出数组中第二大的数字 public class Main public static void main String args int max 0 int smax 0 int arr 1 2 3 4 6 8 7 if arr 0
  • 解决bash: mysql: command not found 的方法【linux mysql命令 】

    linux下 在mysql正常运行的情况下 输入mysql提示 mysql command not found 遇上 bash mysql command not found的情况别着急 这个是因为 usr local bin目录下缺失my
  • C#使用欧姆龙PLC的Fins协议读写PLC地址(基本封装)

    FINS通讯概述 FINS factory interface network service 通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令 响应系统 运用 FINS指令可实现各种网络间的无缝通信 通过编程发送FINS指令 上位机