必须填写至少10个字的日志
nnhy 编写于 2012-07-27 18:48:21
X
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Ports;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Xml.Serialization;
using NewLife.Exceptions;
using NewLife.Log;
using NewLife.Model;
using NewLife.Reflection;
using NewLife.Security;
using NewLife.Serialization;
#if NET4
using System.Linq;
#else
using NewLife.Linq;
#endif

namespace NewLife.Net.ModBus
{
    /// <summary>ModBus实体</summary>
    public class MBEntity
    {
        #region 属性
        [NonSerialized]
        private Boolean _IsAscii;
        /// <summary>使用ASCII通讯,默认RTU</summary>
        [XmlIgnore]
        public Boolean IsAscii { get { return _IsAscii; } set { _IsAscii = value; } }

        [NonSerialized]
        private Boolean _UseAddress;
        /// <summary>是否使用地址。实际使用中,没有总线结构时,地址是没有意义的</summary>
        [XmlIgnore]
        public Boolean UseAddress { get { return _UseAddress; } set { _UseAddress = value; } }

        [NonSerialized]
        private Byte _Address;
        /// <summary>地址码</summary>
        [XmlIgnore]
        public Byte Address { get { return _Address; } set { _Address = value; UseAddress = value > 0; } }

        [NonSerialized]
        private MBFunction _Function;
        /// <summary>功能码</summary>
        [XmlIgnore]
        public MBFunction Function { get { return _Function; } set { _Function = value; } }

        [NonSerialized]
        private Byte[] _ExtendData;
        /// <summary>扩展业务数据,对于某些没有实现或者未完全实现的功能码有用</summary>
        [XmlIgnore]
        public Byte[] ExtendData { get { return _ExtendData; } set { _ExtendData = value; } }
        #endregion

        #region 构造、注册
        static MBEntity()
        {
            Init();
        }

        /// <summary>初始化</summary>
        static void Init()
        {
            var container = ObjectContainer.Current;
            var asm = Assembly.GetExecutingAssembly();
            // 搜索已加载程序集里面的消息类型
            foreach (var item in AssemblyX.FindAllPlugins(typeof(IModBusRequest), true))
            {
                var msg = TypeX.CreateInstance(item) as IModBusRequest;
                if (msg != null) container.Register(typeof(IModBusRequest), item, null, msg.Function);
            }
            foreach (var item in AssemblyX.FindAllPlugins(typeof(IModBusResponse), true))
            {
                var msg = TypeX.CreateInstance(item) as IModBusResponse;
                if (msg != null) container.Register(typeof(IModBusResponse), item, null, msg.Function);
            }
        }
        #endregion

        #region 读写
        /// <summary>把当前对象写入数据,包括可能的起始符和结束符</summary>
        /// <param name="stream"></param>
        public void Write(Stream stream)
        {
            // ASCII模式,先写入内存流
            var ms = new MemoryStream();
            var writer = new BinaryWriterX(ms);
            Set(writer);

            if (UseAddress) writer.Write(Address);
            writer.Write((Byte)Function);

            writer.WriteObject(this);

            var dt = ExtendData;
            if (dt != null && dt.Length > 0) writer.Write(dt, 0, dt.Length);

            // 计算CRC
            ms.Position = 0;
            if (IsAscii)
            {
                // 累加后取补码
                var crc = (Byte)ms.ReadBytes().Select(e => (Int32)e).Sum();
                //crc = (Byte)(~crc + 1);
                crc = (Byte)(0x100 - crc);
                writer.Write(crc);
            }
            else
            {
                var crc = new Crc16().Update(ms).Value;
                writer.Settings.IsLittleEndian = true;
                writer.Write(crc);
            }

            // ASCII模式,需要转为HEX字符编码
            if (IsAscii)
            {
                ms.Position = 0;
                var data = ms.ReadBytes();
                data = Encoding.ASCII.GetBytes(data.ToHex());

                writer.Stream = stream;
                writer.Write(':');
                writer.Write(data, 0, data.Length);
                writer.Write('\r');
                writer.Write('\n');
            }
            else
            {
                ms.Position = 0;
                ms.CopyTo(stream);
            }
        }

        /// <summary>序列化为数据流</summary>
        /// <returns></returns>
        public Stream GetStream()
        {
            var ms = new MemoryStream();
            Write(ms);
            ms.Position = 0;
            return ms;
        }

        /// <summary>从流中读取消息</summary>
        /// <param name="stream"></param>
        /// <param name="isResponse">是否响应</param>
        /// <param name="useAddress">是否使用地址</param>
        /// <param name="isAscii">是否ASCII方式</param>
        /// <returns></returns>
        public static MBEntity Read(Stream stream, Boolean isResponse = false, Boolean useAddress = false, Boolean isAscii = false)
        {
            // ASCII模式,需要先从HEX字符转回来
            var ms = stream;
            if (isAscii)
            {
                var data = stream.ReadBytes();
                data = DataHelper.FromHex(Encoding.ASCII.GetString(data));
                ms = new MemoryStream(data);
            }

            var start = ms.Position;
            var reader = new BinaryReaderX(ms);
            Set(reader);

            if (isAscii && reader.ReadChar() != ':') return null;

            // 读取地址和功能码
            var addr = useAddress ? reader.ReadByte() : (Byte)0;
            var func = (MBFunction)reader.ReadByte();

            var type = !isResponse ? ObjectContainer.Current.ResolveType<IModBusRequest>(func) : ObjectContainer.Current.ResolveType<IModBusResponse>(func);
            //if (type == null) throw new XException("无法识别的消息类型(Function={0})!", func);
            if (type == null) type = typeof(MBEntity);

            if (stream.Position == stream.Length) return TypeX.CreateInstance(type, null) as MBEntity;

            MBEntity entity = null;
            try
            {
                entity = reader.ReadObject(type) as MBEntity;
                if (entity == null) entity = TypeX.CreateInstance(type, null) as MBEntity;

                // 读取剩余的数据,注意扣除校验部分
                var len = ms.Length - ms.Position - (isAscii ? 1 : 2);
                if (len > 0) entity.ExtendData = reader.ReadBytes((Int32)len);
            }
            catch (Exception ex) { throw new XException(String.Format("无法从数据流中读取{0}(Function={1})消息!", type.Name, func), ex); }

            entity.UseAddress = useAddress;
            entity.IsAscii = isAscii;
            entity.Address = addr;
            entity.Function = func;

            // 计算Crc
            var ori = ms.Position;
            ms.Position = start;
            if (isAscii)
            {
                var crc = (Byte)ms.ReadBytes(ori - start).Select<Byte, Int32>(e => (Int32)e).Sum();
                if (~crc != reader.ReadByte()) throw new Exception("Crc校验失败!");
            }
            else
            {
                var crc = new Crc16().Update(ms, ori - start).Value;
                reader.Settings.IsLittleEndian = true;
                if (crc != reader.ReadUInt16()) throw new Exception("Crc校验失败!");
            }

            if (isAscii && (reader.ReadChar() != '\r' || reader.ReadChar() != '\n')) return null;

            return entity;
        }

        /// <summary>从流中读取消息</summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="stream"></param>
        /// <param name="isResponse">是否响应</param>
        /// <param name="useAddress">是否使用地址</param>
        /// <param name="isAscii">是否ASCII方式</param>
        /// <returns></returns>
        public static TEntity Read<TEntity>(Stream stream, Boolean isResponse = false, Boolean useAddress = false, Boolean isAscii = false) where TEntity : MBEntity
        {
            return Read(stream, isResponse, useAddress, isAscii) as TEntity;
        }

        static void Set(IReaderWriter rw)
        {
            var setting = rw.Settings as BinarySettings;
            setting.IsLittleEndian = false;
            //setting.IsBaseFirst = true;
            //setting.EncodeInt = true;
            setting.UseObjRef = false;
            //setting.UseTypeFullName = false;

            //SetDebug(rw);
        }

        [Conditional("DEBUG")]
        static void SetDebug(IReaderWriter rw)
        {
            rw.Debug = true;
            rw.EnableTraceStream();
        }
        #endregion

        #region 业务处理
        /// <summary>处理消息,通过数据回调来发出或收回数据</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="callback">用于处理数据的回调方法,其中第二个参数是用户变量。
        /// 回调方法为空时,默认采用串口通信,用户变量指定串口对象或串口名,不指定时采用COM1</param>
        /// <param name="state">需要传递给回调方法的用户变量</param>
        /// <returns></returns>
        public T Process<T>(Func<Byte[], Object, Byte[]> callback = null, Object state = null) where T : MBEntity
        {
            return (T)Process(this, callback, state);
        }

        /// <summary>处理消息,通过数据回调来发出或收回数据</summary>
        /// <param name="request">需要发出的请求消息</param>
        /// <param name="callback">用于处理数据的回调方法,其中第二个参数是用户变量。
        /// 回调方法为空时,默认采用串口通信,用户变量指定串口对象或串口名,不指定时采用COM1</param>
        /// <param name="state">需要传递给回调方法的用户变量</param>
        /// <returns></returns>
        public static MBEntity Process(MBEntity request, Func<Byte[], Object, Byte[]> callback = null, Object state = null)
        {
            var dt = request.GetStream().ReadBytes();
            if (request.IsAscii)
                WriteLog("发送:{0}", Encoding.ASCII.GetString(dt));
            else
                WriteLog("发送:{0}", BitConverter.ToString(dt));

            if (callback == null) callback = ReadSerialPort;
            var data = callback(dt, state);
            if (data == null || data.Length < 1) return null;

            WriteLog("接收:{0}", BitConverter.ToString(data));

            var ms = new MemoryStream(data);
            return Read(ms, true, request.UseAddress, request.IsAscii);
        }

        static Byte[] ReadSerialPort(Byte[] dt, Object state)
        {
            var sp = state as SerialPort;
            if (sp == null)
            {
                sp = new SerialPort(state == null ? "COM1" : "" + state);
                sp.ReadTimeout = sp.WriteTimeout = 500;
                sp.Open();
            }

            try
            {
                sp.Write(dt, 0, dt.Length);

                dt = new Byte[256];
                Int32 i = 0;
                do
                {
                    try
                    {
                        var count = sp.Read(dt, i, dt.Length - i);
                        i += count;
                    }
                    catch { }
                    //if (i >= dt.Length) break;
                    Thread.Sleep(500);
                } while (i < dt.Length && sp.BytesToRead > 0);
                if (i <= 0) return null;

                //var data = new Byte[i];
                //Buffer.BlockCopy(dt, 0, data, 0, data.Length);

                return dt.ReadBytes(0, i);
            }
            finally
            {
                // 如果不是外部传入,则销毁
                if (!(state is SerialPort)) sp.Dispose();
            }
        }
        #endregion

        #region 辅助
        [Conditional("DEBUG")]
        static void WriteLog(String format, params Object[] args)
        {
            if (XTrace.Debug) XTrace.WriteLine(format, args);
        }
        #endregion
    }
}