[fix]Config创建默认配置文件的开关Runtime.CreateConfigOnMissing,仅需对自动创建生效,而不应该阻止用户主动Save
智能大石头 authored at 2024-08-09 00:30:41 石头 committed at 2024-08-10 14:22:24
21.70 KiB
X
#region Modbus协议
/*
 * GB/T 19582.1-2008 基于Modbus协议的工业自动化网络规范
 * 请求响应:1字节功能码|n字节数据|2字节CRC校验
 * 异常响应:1字节功能码+0x80|1字节异常码
 * 
 * Modbus数据模型基本表
 * 基本表        对象类型   访问类型    注释
 * 离散量输入    单个位     只读        I/O系统可提供这种类型的数据
 * 线圈          单个位     读写        通过应用程序可改变这种类型的数据
 * 输入寄存器    16位字     只读        I/O系统可提供这种类型的数据
 * 保持寄存器    16位字     读写        通过应用程序可改变这种类型的数据
 * 
 */
#endregion

using System;

namespace NewLife.Net.Modbus
{
    /// <summary>Modbus从站</summary>
    /// <example>
    /// <code>
    /// var slave = new ModbusSlave();
    /// slave.Transport = new UdpTransport(502);
    /// slave.Listen();
    /// </code>
    /// </example>
    public class ModbusSlave : IDisposable
    {
        #region 属性
        private Byte _Host;
        /// <summary>主站ID</summary>
        public Byte Host { get { return _Host; } set { _Host = value; } }

        private IDataStore _DataStore;
        /// <summary>数据存储</summary>
        public IDataStore DataStore { get { return _DataStore ?? (_DataStore = new DataStore()); } set { _DataStore = value; } }

        private ITransport[] _Transports = new ITransport[4];
        /// <summary>传输口</summary>
        public ITransport[] Transports { get { return _Transports; } /*set { _Transport = value; }*/ }

        private Boolean _EnableDebug;
        /// <summary>启用调试</summary>
        public Boolean EnableDebug { get { return _EnableDebug; } set { _EnableDebug = value; } }

        //private Boolean inited;
        #endregion

        #region 构造
        /// <summary>析构</summary>
        ~ModbusSlave() { Dispose(false); }

        /// <summary>销毁</summary>
        public void Dispose() { Dispose(true); }

        /// <summary>销毁</summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(Boolean disposing)
        {
            if (disposing) GC.SuppressFinalize(this);

            //if (Transport != null) Transport.Dispose();
            for (int i = 0; i < Transports.Length; i++)
            {
                if (Transports[i] != null) Transports[i].Dispose();
            }
        }
        #endregion

        #region Modbus功能
        /// <summary>开始监听</summary>
        /// <returns></returns>
        public virtual ModbusSlave Listen(ITransport transport)
        {
            if (transport == null) throw new ArgumentNullException("transport");

            // 找到一个空位,放入数组
            for (int i = 0; i < Transports.Length; i++)
            {
                if (Transports[i] == null)
                {
                    Transports[i] = transport;
                    break;
                }
            }

            var name = transport.ToString();

#if !MF
            transport.Received += (s, e) => { e.Data = Process(e.Data); e.Feedback = true; };
#else
            transport.Received += (ts, data) => { return Process(data); };
#endif
            transport.ReceiveAsync();

            WriteLine(this.GetType().Name + "在" + name + "上监听Host=" + Host);

            return this;
        }

        /// <summary>处理Modbus消息</summary>
        /// <param name="buf"></param>
        /// <returns></returns>
        public virtual Byte[] Process(Byte[] buf)
        {
#if DEBUG
            var str = "Request :";
            for (int i = 0; i < buf.Length; i++)
            {
                str += " " + buf[i].ToString("X2");
            }
            WriteLine(str);
#endif

            // 处理
            var entity = new ModbusEntity().Parse(buf);
            // 检查主机
            if (entity.Host != 0 && entity.Host != Host) return null;
            // 检查Crc校验
            var crc = buf.Crc(0, buf.Length - 2);
            if (crc != entity.Crc)
                entity.SetError(Errors.CrcError);
            else
                entity = Process(entity);
            buf = entity.ToArray();

#if DEBUG
            str = "Response:";
            for (int i = 0; i < buf.Length; i++)
            {
                str += " " + buf[i].ToString("X2");
            }
            WriteLine(str);
            WriteLine("");
#endif
            return buf;
        }

        /// <summary>处理Modbus消息</summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        protected virtual ModbusEntity Process(ModbusEntity entity)
        {
            // 如果是广播消息,则设置主站ID,便于其他人知道我的主站ID
            if (entity.Host == 0) entity.Host = Host;
            try
            {
                switch (entity.Function)
                {
                    case MBFunction.ReadCoils:
                    case MBFunction.ReadInputs:
                        entity = ReadCoils(entity);
                        break;
                    case MBFunction.ReadHoldingRegisters:
                    case MBFunction.ReadInputRegisters:
                        entity = ReadRegisters(entity);
                        break;
                    case MBFunction.WriteSingleCoil:
                        entity = WriteSingleCoil(entity);
                        break;
                    case MBFunction.WriteSingleRegister:
                        entity = WriteSingleRegister(entity);
                        break;
                    case MBFunction.WriteMultipleCoils:
                        entity = WriteMultipleCoils(entity);
                        break;
                    case MBFunction.WriteMultipleRegisters:
                        entity = WriteMultipleRegisters(entity);
                        break;
                    case MBFunction.Diagnostics:
                        entity = Diagnostics(entity);
                        break;
                    case MBFunction.ReportIdentity:
                        entity = ReportIdentity(entity);
                        break;
                    default:
                        // 不支持的功能码
                        return entity.SetError(Errors.FunctionCode);
                }

                return entity;
            }
            catch (Exception ex)
            {
                //WriteLine(ex.Message);
#if MF
                Microsoft.SPOT.Debug.Print(ex.Message);
#else
                NewLife.Log.XTrace.WriteLine(ex.ToString());
#endif

                // 执行错误
                return entity.SetError(Errors.ProcessError);
            }
        }
        #endregion

        #region 线圈
        /// <summary>读状态 离散量输入/线圈</summary>
        /// <remarks>
        /// 线圈
        /// 请求:0x01|2字节起始地址|2字节线圈数量(1~2000)
        /// 响应:0x01|1字节字节计数|n字节线圈状态(n=输出数量/8,如果余数不为0,n=n+1)
        /// 
        /// 离散量输入
        /// 请求:0x02|2字节起始地址|2字节输入数量(1~2000)
        /// 响应:0x02|1字节字节计数|n字节输入状态(n=输入数量/8,如果余数不为0,n=n+1)
        /// </remarks>
        /// <param name="entity"></param>
        /// <returns></returns>
        ModbusEntity ReadCoils(ModbusEntity entity)
        {
            var data = entity.Data;
            // 无效功能指令
            if (data == null || data.Length != 4) return entity.SetError(Errors.MessageLength);

            var addr = data.ReadUInt16(0);
            var count = data.ReadUInt16(2);
            // 输出数量不正确 count <= 0x07D0=2000
            if (count == 0 || count > 0x07D0) return entity.SetError(Errors.Count);

            IBitStore store = null;
#if DEBUG
            var func = "";
#endif
            switch (entity.Function)
            {
                case MBFunction.ReadCoils:
                    store = DataStore.Coils;
#if DEBUG
                    func = "ReadCoils";
#endif
                    break;
                case MBFunction.ReadInputs:
                    store = DataStore.Inputs;
#if DEBUG
                    func = "ReadInputs";
#endif
                    break;
                default:
                    break;
            }

            // 起始地址+数量 不正确
            if (addr + count >= store.Count) return entity.SetError(Errors.Address);
#if DEBUG
            WriteLine(func + "(0x" + addr.ToString("X2") + ", 0x" + count.ToString("X2") + ")");
#endif
            if (OnReadCoil != null) OnReadCoil(entity, addr, count);

            // 返回的时候,用字节存储每一个线圈的状态
            var n = count >> 3;
            if ((count & 0x07) != 0) n++;
            var buf = new Byte[1 + n];
            // 字节数
            buf[0] = (Byte)n;
            // 元素存放于m字节n位
            var m = n = 0;
            for (var i = 0; i < count; i++)
            {
                var p = store.Read(addr + i);

                // 存放在m个字节的n位,注意前面预留一个字节
                if (p) buf[1 + m] |= (Byte)(1 << n);
                if (++n >= 8)
                {
                    m++;
                    n = 0;
                }
            }

            entity.Data = buf;

            return entity;
        }

        /// <summary>写单个线圈</summary>
        /// <remarks>
        /// 请求:0x05|2字节输出地址|2字节输出值(0x0000/0xFF00)
        /// 响应:0x05|2字节输出地址|2字节输出值(0x0000/0xFF00)
        /// </remarks>
        /// <param name="entity"></param>
        /// <returns></returns>
        ModbusEntity WriteSingleCoil(ModbusEntity entity)
        {
            var data = entity.Data;
            // 无效功能指令
            if (data == null || data.Length < 4) return entity.SetError(Errors.MessageLength);

            var addr = data.ReadUInt16(0);
            var val = data.ReadUInt16(2);
            // 输出值 False=0 True=0xFF00
            if (val != 0 && val != 0xFF00) return entity.SetError(Errors.Value);

            var store = DataStore.Coils;
            // 输出地址
            if (addr >= store.Count) return entity.SetError(Errors.Address);

            var flag = val != 0;

#if DEBUG
            WriteLine("WriteSingleCoil(0x" + addr.ToString("X2") + ", " + flag + ")");
#endif

            //store.Write(addr, flag);

            var count = 0;
            // 支持一下连续写入
            for (var i = 2; i + 1 < data.Length; i += 2, count++)
            {
                store.Write(addr + count, data.ReadUInt16(i) != 0);

            }

            if (OnWriteCoil != null) OnWriteCoil(entity, addr, count);

            // 读出来
            for (var i = 2; i + 1 < data.Length; i += 2)
            {
                data.WriteUInt16(i, (UInt16)(store.Read(addr + i - 2) ? 0xFF00 : 0));
            }

            //// 读出来
            //data.WriteUInt16(2, (UInt16)(store.Read(addr) ? 0xFF00 : 0));

            return entity;
        }

        /// <summary>写多个线圈</summary>
        /// <remarks>
        /// 请求:0x0F|2字节起始地址|2字节输出数量(1~1698)|1字节字节计数|n字节输出值(n=输出数量/8,如果余数不为0,n=n+1)
        /// 响应:0x0F|2字节起始地址|2字节输出数量
        /// </remarks>
        /// <param name="entity"></param>
        /// <returns></returns>
        ModbusEntity WriteMultipleCoils(ModbusEntity entity)
        {
            var data = entity.Data;
            // 2字节地址,2字节数量,1字节计数,至少1字节的数据字节
            if (data == null || data.Length < 2 + 2 + 1 + 1) return entity.SetError(Errors.MessageLength);

            var addr = data.ReadUInt16(0);
            var size = data.ReadUInt16(2);
            var count = data[4];

            // 输出数量
            if (size > 0x07B0 || count + 5 != data.Length) return entity.SetError(Errors.Count);

            var store = DataStore.Coils;
            // 起始地址+输出数量
            if (addr + size >= store.Count) return entity.SetError(Errors.Address);

#if DEBUG
            WriteLine("WriteMultipleCoils(0x" + addr.ToString("X2") + ", 0x" + size.ToString("X2") + ")");
#endif

            // 元素存放于m字节n位
            Int32 m = 0, n = 0;
            for (int i = 0; i < size; i++)
            {
                // 数据位于5+m字节的n位
                var flag = ((data[5 + m] >> n) & 0x01) == 0x01;

                store.Write(addr + i, flag);

                if (++n >= 8)
                {
                    m++;
                    n = 0;
                }
            }

            if (OnWriteCoil != null) OnWriteCoil(entity, addr, size);

            // 响应只要这么一点点
            entity.Data = data.ReadBytes(0, 4);

            return entity;
        }

        /// <summary>读取线圈前触发</summary>
        public event ModbusHandler OnReadCoil;

        /// <summary>写入线圈后触发</summary>
        public event ModbusHandler OnWriteCoil;
        #endregion

        #region 寄存器
        /// <summary>读取寄存器 输入寄存器/保持寄存器</summary>
        /// <remarks>
        /// 保持寄存器
        /// 请求:0x03|2字节起始地址|2字节寄存器数量(1~2000)
        /// 响应:0x03|1字节字节数|n*2字节寄存器值
        /// 
        /// 输入寄存器
        /// 请求:0x04|2字节起始地址|2字节输入寄存器数量(1~2000)
        /// 响应:0x04|1字节字节数|n*2字节输入寄存器
        /// </remarks>
        /// <param name="entity"></param>
        /// <returns></returns>
        ModbusEntity ReadRegisters(ModbusEntity entity)
        {
            var data = entity.Data;
            // 无效功能指令
            if (data == null || data.Length != 4) return entity.SetError(Errors.MessageLength);

            var addr = data.ReadUInt16(0);
            var count = data.ReadUInt16(2);
            // 输出数量不正确 count <= 0x07D0=2000
            //if (count == 0 || count > 0x07D0) return entity.SetError(3);
            if (count == 0) return entity.SetError(Errors.Count);

            IWordStore store = null;
#if DEBUG
            var func = "";
#endif
            switch (entity.Function)
            {
                case MBFunction.ReadHoldingRegisters:
                    store = DataStore.HoldingRegisters;
#if DEBUG
                    func = "ReadHoldingRegisters";
#endif
                    break;
                case MBFunction.ReadInputRegisters:
                    store = DataStore.InputRegisters;
#if DEBUG
                    func = "ReadInputRegisters";
#endif
                    break;
                default:
                    break;
            }
            if (count > store.Count) return entity.SetError(Errors.Count);
            // 起始地址+数量 不正确
            if (addr + count > 0xFFFF) return entity.SetError(Errors.Address);

#if DEBUG
            WriteLine(func + "(0x" + addr.ToString("X2") + ", 0x" + count.ToString("X2") + ")");
#endif
            if (OnReadRegister != null) OnReadRegister(entity, addr, count);

            var buf = new Byte[1 + count * 2];
            buf[0] = (Byte)(count * 2);

            for (var i = 0; i < count; i++)
            {
                buf.WriteUInt16(1 + i * 2, store.Read(addr + i));
            }

            // 读出来
            entity.Data = buf;

            return entity;
        }

        /// <summary>写单个寄存器</summary>
        /// <remarks>
        /// 请求:0x06|2字节寄存器地址|2字节寄存器值
        /// 响应:0x06|2字节寄存器地址|2字节寄存器值
        /// </remarks>
        /// <param name="entity"></param>
        /// <returns></returns>
        ModbusEntity WriteSingleRegister(ModbusEntity entity)
        {
            var data = entity.Data;
            // 无效功能指令
            if (data == null || data.Length < 4) return entity.SetError(Errors.MessageLength);

            var addr = data.ReadUInt16(0);
            var val = data.ReadUInt16(2);
            // 寄存器值 0<<val<<0xFFFF
            //if (val != 0 && val != 0xFF00) return entity.SetError(3);

            var store = DataStore.HoldingRegisters;
            // 寄存器地址
            if (addr >= store.Count) return entity.SetError(Errors.Address);

#if DEBUG
            WriteLine("WriteSingleRegister(0x" + addr.ToString("X2") + ", 0x" + val.ToString("X2") + ")");
#endif

            //store.Write(addr, val);
            var count = 0;
            // 支持多字连续写入
            for (int i = 2; i + 1 < data.Length; i += 2, count++)
            {
                store.Write(addr + count, data.ReadUInt16(i));
            }

            if (OnWriteRegister != null) OnWriteRegister(entity, addr, count);

            return entity;
        }

        /// <summary>写多个寄存器</summary>
        /// <remarks>
        /// 请求:0x10|2字节起始地址|2字节寄存器数量(1~123)|1字节字节计数|n*2寄存器值
        /// 响应:0x10|2字节起始地址|2字节寄存器数量
        /// </remarks>
        /// <param name="entity"></param>
        /// <returns></returns>
        ModbusEntity WriteMultipleRegisters(ModbusEntity entity)
        {
            var data = entity.Data;
            // 2字节地址,2字节数量,1字节计数,至少1字节的数据字节
            if (data == null || data.Length < 2 + 2 + 1 + 2) return entity.SetError(Errors.MessageLength);

            var addr = data.ReadUInt16(0);
            var size = data.ReadUInt16(2);
            var count = data[4];

            // 输出数量
            if (size > 0x07B0 || data.Length - 5 != count) return entity.SetError(Errors.Count);

            var store = DataStore.HoldingRegisters;
            // 起始地址+输出数量
            if (addr + size >= store.Count) return entity.SetError(Errors.Address);

#if DEBUG
            WriteLine("WriteMultipleRegisters(0x" + addr.ToString("X2") + ", 0x" + size.ToString("X2") + ")");
#endif

            for (int i = 0; i < size; i++)
            {
                store.Write(addr + i, data.ReadUInt16(5 + i * 2));
            }

            if (OnWriteRegister != null) OnWriteRegister(entity, addr, size);

            // 响应只要这么一点点
            entity.Data = data.ReadBytes(0, 4);

            return entity;
        }

        /// <summary>读取寄存器前触发</summary>
        public event ModbusHandler OnReadRegister;

        /// <summary>写入寄存器后触发</summary>
        public event ModbusHandler OnWriteRegister;
        #endregion

        #region 诊断标识
        /// <summary>诊断</summary>
        /// <remarks>
        /// 请求:0x08|2字节子功能|n*2字节数据
        /// 响应:0x08|2字节子功能|n*2字节数据
        /// </remarks>
        /// <param name="entity"></param>
        /// <returns></returns>
        ModbusEntity Diagnostics(ModbusEntity entity)
        {
            var data = entity.Data;
            // 无效功能指令。2字节子功能码,多字节的数据
            if (data == null || data.Length < 2) return entity.SetError(Errors.MessageLength);

            var sub = data.ReadUInt16(0);
#if DEBUG
            WriteLine("Diagnostics(0x" + sub.ToString("X2") + ")");
#endif

            // 默认原样返回,暂时没有什么有用的子功能码需要处理
            return entity;
        }

        /// <summary>报告从站ID</summary>
        /// <remarks>
        /// 请求:0x11
        /// 响应:0x11|1字节字节计数|从站ID|运行指示状态(0x00=OFF,0xFF=ON)|附加数据
        /// </remarks>
        /// <param name="entity"></param>
        /// <returns></returns>
        ModbusEntity ReportIdentity(ModbusEntity entity)
        {
            var data = entity.Data;
            // 无效功能指令。
            if (data != null && data.Length > 0) return entity.SetError(Errors.MessageLength);

#if DEBUG
            WriteLine("ReportIdentity()");
#endif

            var hid = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, };

            var buf = new Byte[1 + hid.Length];
            buf[0] = (Byte)hid.Length;
            Array.Copy(hid, 0, buf, 1, hid.Length);
            entity.Data = buf;

            return entity;
        }
        #endregion

        #region 日志
        void WriteLine(String msg)
        {
#if MF
            if (EnableDebug) Microsoft.SPOT.Debug.Print(msg);
#else
            if (EnableDebug) NewLife.Log.XTrace.WriteLine(msg);
#endif
        }
        #endregion
    }

    /// <summary>事件委托</summary>
    /// <param name="entity"></param>
    /// <param name="index"></param>
    /// <param name="count"></param>
    public delegate void ModbusHandler(ModbusEntity entity, Int32 index, Int32 count);

    ///// <summary>写线圈委托</summary>
    ///// <param name="i"></param>
    ///// <param name="value"></param>
    //public delegate void WriteCoilHandler(Int32 i, Int32 count);

    ///// <summary>写寄存器委托</summary>
    ///// <param name="i"></param>
    ///// <param name="value"></param>
    //public delegate void WriteRegisterHandler(Int32 i, Int32 count);
}