cugzhaolei / KeyencePLC

a plc remote monitor for keyence,it contains the socket communication /基于Socket的PLC通信程序
https://blog.csdn.net/qq_25482087/article/details/100849772
22 stars 14 forks source link

交流一下 #1

Open chzhm159 opened 4 years ago

chzhm159 commented 4 years ago

首先提供一个实现品参考 NuGet 搜索 "Infrastructure.Plc.Keyence" 其次说一下我目前的实现思路 我是基于 DotNetty ,这样我就只专注于 协议的 解析上.
设计思路 变量为核心对象,从哪里采集,采集到的如何处理,处理完之后的结果输送到哪里去 从而达到将协议细节封装,而只关注业务逻辑上. 已经支持了 OMRON Fins/TCP 协议 现在打算支持 基恩士的上位链路 其次 设计一个 Tag 类. 主要包含 三部分 信息

从哪里读取

顺带贴上我目前正在测试的代码,如果需要我可以将全部源码发给你.

 public enum TagOperator
    {   
        /// <summary>
        /// 只读
        /// </summary>
        R,
        /// <summary>
        /// 只写
        /// </summary>
        W,
        /// <summary>
        /// 读+写
        /// </summary>
        RW
    }
    public enum TagOperatorUnit
    {
        /// <summary>
        /// 位 ,一位只有两种状态: 0 , 1
        /// </summary>
        BIT,
        /// <summary>
        /// 字,1字=2字节. 1 word=2 byte
        /// </summary>
        WORD
    }
    public enum ValueType
    {
        /// <summary>
        /// 8 位 无符号整数,取值范围 16# 00~FF
        /// </summary>
        IByte,
        /// <summary>
        /// 16 位,整型, 取值范围: -32768~32767
        /// </summary>        
        IInt16,
        /// <summary>
        /// 16位,无符号整数,取值范围 0~65535
        /// </summary>
        UInt16,
        /// <summary>
        /// 32位,无符号整数,
        /// </summary>
        ULong32,
        /// <summary>
        /// 32位,有符号整数,
        /// </summary>
        ILong32,
        /// <summary>
        /// 64 位,有符号整数类型
        /// </summary>
        ILong64,

        /// <summary>
        /// 自定义的处理
        /// </summary>
        ICustome

    }
    class Tag : EventArgs
    {

        /// <summary>
        /// tag的业务友好的名称
        /// </summary>
        public String TagName { get; set; }

        /// <summary>
        ///  全局唯一性的变量名称.其组成结果默认为 [DeviceName]:[TagName]
        ///  目前是已此名称作为 redis 中的 key. 
        /// </summary>
        public String UniqueName { get; set; }

        /// <summary>
        /// 寄存器类型,例如Omron的 DM 区
        /// </summary>
        public String registerType { get; set; }
        /// <summary>
        /// 寄存器地址编号
        /// </summary>
        public int begin { get; set; }
        /// <summary>
        /// 自起始位置往后的偏移量
        /// </summary>
        public int offset { get; set; }
        /// <summary>
        /// 读取单位
        /// </summary>
        public TagOperatorUnit unit { get; set; }
        /// <summary>
        /// 一次读取多少个单位
        /// </summary>
        public int count { get; set; }

        /// <summary>
        /// tag 的读写控制,r=read-only,w=write-only,rw=read-write
        /// </summary>
        public TagOperator opt { get; set; }

        /// <summary>
        /// 读取变量的处理函数委托声明,
        /// </summary>
        /// <param name="tag"></param>
        /// <param name="buf"></param>
        /// <returns></returns>
        public delegate object valueProcess(Tag tag, IByteBuffer buf);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>        
        public event EventHandler<Tag> OnValueReadyEvent;

        private bool _loopRead = true;
        /// <summary>
        /// 是否为循环读取模式,默认为 true
        /// </summary>
        public bool loopRead {
            get {
                return this._loopRead;
            }
            set {
                this._loopRead = value;
            } 
        }
        private ValueType _valueType ;
        public void setValueType(ValueType vt) {
            // TODO 这里可以根据最终数值类型,自动计算 读取长度.            
            this._valueType = vt;
        }
        private ValueType getValueType() {
            return this._valueType;
        }
        /*
         * 暂时不支持变量级别的采集周期设置
        private int _rInterval = 1000;
        /// <summary>
        /// 周期性读取间隔,单位为毫秒
        /// </summary>
        public void setReadInterval (int interval){
            this._rInterval = interval;
        }
        /// <summary>
        /// 获取读取周期,单位为毫秒
        /// </summary>
        /// <returns></returns>
        public int getReadInterval()
        {
            return this._rInterval ;
        }
        */

        /// <summary>
        /// 保留原始数据,便于更灵活的处理,但是需要注意手动释放调用 .Release();
        /// </summary>
        private IByteBuffer ValueBuf { get; set; }

        /// <summary>
        /// 默认的返回值解析. 根据最初设定的 ValueType 自动做数值转换
        /// </summary>
        /// <param name="buf"></param>
        public virtual void setValueBuffer(IByteBuffer buf)
        {   
            // @Warning,后续会替换掉这个buf的缓存,根据这个buffer的byte数值和ValueType计算得到最终数值后,释放这个缓存
            this.ValueBuf = buf.Duplicate();            
            EventHandler<Tag> raiseEvent = OnValueReadyEvent;
            // Event will be null if there are no subscribers
            if (raiseEvent != null)
            {
                Delegate[] delegAry = raiseEvent.GetInvocationList();
                foreach (EventHandler<Tag> deleg in delegAry) {
                    deleg.BeginInvoke(this,this,null,null);
                }
            }
        }
        /// <summary>
        /// 默认的变量值处理函数. 根据ValueType来自动处理,目前支持的有 IInt16,
        /// </summary>
        /// <param name="tag"></param>
        /// <param name="buf"></param>
        /// <returns></returns>
        public virtual object defaultValueProcess(Tag tag,IByteBuffer buf) {

            object v;
            switch (tag.getValueType())
            {
                case ValueType.IInt16:
                    // Gets a 16-bit short integer at the specified absolute index in this buffer.
                    v = buf.GetShort(0);
                    break;
                case ValueType.ULong32:
                    //Gets an unsigned 32-bit integer at the specified absolute index in this buffer.
                    v = buf.GetUnsignedInt(0) ;
                    break;
                case ValueType.UInt16:
                    // Gets an unsigned 16-bit short integer at the specified absolute index in this buffer.
                    v = buf.GetUnsignedShort(0);
                    break;
                case ValueType.ICustome:
                    //这里支持自定义处理,将byte转换为字符串形式,以便自行拆分
                    v = ByteBufferUtil.HexDump(buf);
                    break;
                default:
                    v = ByteBufferUtil.HexDump(buf);
                    break;
            }
            return v;

        }        
        public object getValue(valueProcess proc) {
            return proc(this,this.ValueBuf);
        }

        /// <summary>
        /// 获取该变量的数值.为默认处理机制.
        /// </summary>
        public object value { 
            get {
                // TODO,这里可以做性能优化,否则每次获取数值的时候,都需要重新计算一遍
                // 而且还有个问题就是,读取到的是旧值....因为 IByteBuffer 是不断被更新的
                valueProcess proc = defaultValueProcess;
                return this.getValue(proc);
            }
        }

    }
cugzhaolei commented 3 years ago

你好,看到你的这个设计,觉得很好,不知道现在进行到什么程序了,在基恩士的上位链路上,他的协议封装和楼主的设计类似,也是将数据分为不同的操作类型,数据大小,通过特定的指令去读取数据,如果设计一个支持的tag类,我觉得可以参考楼主的这种设计思想,不好意思,过了这么长时间才看到。

chzhm159 commented 3 years ago

我的架构设计 做了几个项目,也参考了不少产品,例如 HslCommunication ,NI opc server,亚控ioserver,等还是决定自己打造一款产品. 希望有兴趣一起