HslSiemensImplement.cs 15.3 KB
using Hh.Mes.POJO.BllModel;
using Hh.Mes.POJO.Entity;
using HHECS.Model.PLCHelper.Interfaces;
using HHECS.Model.PLCHelper.PLCComponent;
using HHECS.Model.PLCHelper.PLCComponent.HslComponent;
using HslCommunication;
using HslCommunication.Profinet.Siemens;
using System;
using System.Collections.Generic;
using System.Linq;

namespace HHECS.Model.PLCHelper.Implement
{
    /// <summary>
    /// 使用Hsl开源组件(7.0.1)实现的西门子访问类
    /// 提供对DB与M区读写的支持
    /// 默认string以20个为读写标准
    /// </summary>
    public class HslSiemensImplement : IPLC
    {
        #region 属性

        SiemensS7Net siemensTcpNet;

        /// <summary>
        /// 读取限制,超过此值使用批量读取;
        /// </summary>
        private const int READLIMIT = 10;

        /// <summary>
        /// 缓存已被解析的地址
        /// </summary>
        private List<HslSiemensDataEntity> _hslSiemensDataEntities { get; set; } = new List<HslSiemensDataEntity>();

        public object Lock { get; set; } = new object();
        public string IP { get; set; }
        public PLCType PLCType { get; set; } = PLCType.Siemens;
        public string Name { get; set; }

        #endregion


        /// <summary>
        /// 初始化连接client,传递PLC构建类型
        /// </summary>
        /// <param name="siemensPLCS"></param>
        /// <exception cref="ArgumentNullException">未传递参数</exception>
        /// <exception cref="Exception">其他可能异常</exception>
        public HslSiemensImplement(SiemensPLCBuildModel siemensPLCBuildModel)
        {
            if (siemensPLCBuildModel == null)
            {
                throw new ArgumentNullException("请传递参数");
            }
            try
            {
                siemensTcpNet = new SiemensS7Net((SiemensPLCS)siemensPLCBuildModel.SiemensPLCS);
                siemensTcpNet.IpAddress = siemensPLCBuildModel.IP;
                siemensTcpNet.Port = siemensPLCBuildModel.Port;
                siemensTcpNet.Rack = byte.Parse(siemensPLCBuildModel.Rack.ToString());
                siemensTcpNet.Slot = byte.Parse(siemensPLCBuildModel.Slot.ToString());
                IP = siemensPLCBuildModel.IP;
                Name = siemensPLCBuildModel.Name;

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 对内部关联的所有PLC进行连接,存在连接失败,则连接失败
        /// </summary>
        /// <returns></returns>
        public BllResult Connect()
        {
            var result = siemensTcpNet.ConnectServer();
            if (!result.IsSuccess)
            {
                //连接失败,则失败
                return BllResultFactory.Error($"连接PLC:{siemensTcpNet.IpAddress}失败:{result.Message}");
            }

            return BllResultFactory.Success("连接成功");
        }


        /// <summary>
        /// 断开所有PLC
        /// </summary>
        /// <returns></returns>
        public BllResult DisConnect()
        {

            //基本不需要查看关闭成功与否
            siemensTcpNet.ConnectClose();

            return BllResultFactory.Success("关闭成功");
        }

        /// <summary>
        /// 获取连接状态
        /// 由于HSL不支持状态访问,所以这里直接返回成功,需要通过读写函数的返回值判断连接是否断开
        /// </summary>
        /// <returns></returns>
        public BllResult GetConnectStatus()
        {
            return BllResultFactory.Success();
        }


        /// <summary>
        /// 读取单个属性
        /// </summary>
        /// <param name="equipmentProp"></param>
        /// <returns></returns>
        public BllResult Read(base_equipment_prop equipmentProp)
        {
            return Reads(new List<base_equipment_prop>() { equipmentProp });
        }

        /// <summary>
        /// 读取多个属性
        /// </summary>
        /// <param name="equipmentProps"></param>
        /// <returns></returns>
        public BllResult Reads(List<base_equipment_prop> equipmentProps)
        {
            var valiResult = Validation(equipmentProps);
            if (!valiResult.Success)
            {
                return BllResultFactory.Error(valiResult.Msg);
            }

            List<HslSiemensDataEntity> list = new List<HslSiemensDataEntity>();
            try
            {
                //缓存中已经存在的
                list.AddRange(_hslSiemensDataEntities.Where(t => equipmentProps.Exists(a => a.id == t.OPCAddressId)).ToList());
            }
            catch (Exception)
            {
                return BllResultFactory.Error($"等待地址初始化完成");
            }
            if (equipmentProps.Count > READLIMIT)
            {
                //进行批量读取
                //构造地址数组与对应字节长度数据
                var adds = new List<string>();
                var sizes = new List<ushort>();
                foreach (var item in list)
                {
                    adds.Add(item.Address);
                    sizes.Add(item.ByteSize);
                }
                var result = siemensTcpNet.Read(adds.ToArray(), sizes.ToArray());
                if (!result.IsSuccess)
                {
                    return BllResultFactory.Error($"读取错误,PLC:{Name},IP:{IP},参考信息:{result.Message}");
                }
                //获取读取后的数组结果
                var bytes = result.Content.ToList();
                foreach (var item in list)
                {
                    //此数据的buffer
                    var buffer = bytes.Take(item.ByteSize);
                    if (buffer.Count() != (int)item.ByteSize)
                    {
                        //如果没有提取出等量的字节
                        return BllResultFactory.Error($"PLC:{Name},IP:{IP}获取的字节序列个数与属性需求字节个数不匹配");
                    }
                    //剩余待解析
                    item.Buffer = buffer.ToArray();
                    var prop = equipmentProps.Find(t => t.id == item.OPCAddressId);
                    bytes = bytes.Skip(item.ByteSize).ToList();
                    var transResult = SiemensHelper.TransferBufferToString(item);
                    if (!transResult.Success)
                    {
                        return BllResultFactory.Error($"读取时,PLC:{Name}IP:{IP},地址{prop.address},转换数据失败:{transResult.Msg}");
                    }
                    prop.value = transResult.Data;
                }

            }
            else
            {
                //进行单个读取
                foreach (var item in list)
                {
                    var prop = equipmentProps.Find(t => t.id == item.OPCAddressId);
                    switch (item.DataType)
                    {
                        case PLCComponent.PLCDataType.BYTE:
                            var result = siemensTcpNet.ReadByte(item.Address);
                            if (!result.IsSuccess)
                            {
                                return BllResultFactory.Error($"读取PLC:{Name},IP:{IP},地址{prop.address}发生错误:{result.Message}");
                            }
                            prop.value = ((int)result.Content).ToString();
                            break;
                        case PLCComponent.PLCDataType.BOOL:
                            var result2 = siemensTcpNet.ReadBool(item.AddressX);
                            if (!result2.IsSuccess)
                            {
                                return BllResultFactory.Error($"读取PLC:{Name},IP:{IP},地址{prop.address}发生错误:{result2.Message}");
                            }
                            prop.value = result2.Content.ToString();
                            break;
                        case PLCComponent.PLCDataType.DWORD:
                            var result3 = siemensTcpNet.ReadUInt32(item.Address);
                            if (!result3.IsSuccess)
                            {
                                return BllResultFactory.Error($"读取PLC:{Name},IP:{IP},地址{prop.address}发生错误:{result3.Message}");
                            }
                            prop.value = result3.Content.ToString();
                            break;
                        case PLCComponent.PLCDataType.WORD:
                            var result4 = siemensTcpNet.ReadUInt16(item.Address);
                            if (!result4.IsSuccess)
                            {
                                return BllResultFactory.Error($"读取PLC:{Name},IP:{IP},地址{prop.address}发生错误:{result4.Message}");
                            }
                            prop.value = result4.Content.ToString();
                            break;
                        case PLCComponent.PLCDataType.INT:
                            var result5 = siemensTcpNet.ReadInt16(item.Address);
                            if (!result5.IsSuccess)
                            {
                                return BllResultFactory.Error($"读取PLC:{Name},IP:{IP},地址{prop.address}发生错误:{result5.Message}");
                            }
                            prop.value = result5.Content.ToString();
                            break;
                        case PLCComponent.PLCDataType.DINT:
                            var result6 = siemensTcpNet.ReadInt32(item.Address);
                            if (!result6.IsSuccess)
                            {
                                return BllResultFactory.Error($"读取PLC:{Name},IP:{IP},地址{prop.address}发生错误:{result6.Message}");
                            }
                            prop.value = result6.Content.ToString();
                            break;
                        case PLCComponent.PLCDataType.CHAR:
                            var result7 = siemensTcpNet.ReadString(item.Address, item.ByteSize);
                            if (!result7.IsSuccess)
                            {
                                return BllResultFactory.Error($"读取PLC:{Name},IP:{IP},地址{prop.address}发生错误:{result7.Message}");
                            }
                            prop.value = result7.Content.ToString();
                            break;
                        default:
                            return BllResultFactory.Error($"未识别的数据类型:{item.DataType}");
                    }
                }
            }

            return BllResultFactory.Success("读取成功");

        }

        /// <summary>
        /// 写入单个属性
        /// </summary>
        /// <param name="equipmentProp"></param>
        /// <returns></returns>
        public BllResult Write(base_equipment_prop equipmentProp)
        {
            return Writes(new List<base_equipment_prop>() { equipmentProp });
        }

        /// <summary>
        /// 写入多个属性
        /// </summary>
        /// <param name="equipmentProps"></param>
        /// <returns></returns>
        public BllResult Writes(List<base_equipment_prop> equipmentProps)
        {
            var valiResult = Validation(equipmentProps);
            if (!valiResult.Success)
            {
                return BllResultFactory.Error(valiResult.Msg);
            }

            List<HslSiemensDataEntity> list = new List<HslSiemensDataEntity>();
            try
            {
                //list.AddRange(equipmentProps.Join(_hslSiemensDataEntities, n => n.Id, m => m.OPCAddressId, (n, m) => m));
                foreach (var prop in equipmentProps)
                {
                    var item = _hslSiemensDataEntities.FirstOrDefault(t => t.OPCAddressId == prop.id);
                    if (item != null)
                    {
                        list.Add(item);
                    }
                }
            }
            catch (Exception)
            {
                return BllResultFactory.Error($"等待地址初始化完成");
            }

            //对于写入,由于地址可能不连续且写入地址不多,会导致覆盖问题,这里不使用批量写入
            foreach (var item in list)
            {
                var prop = equipmentProps.Find(t => t.id == item.OPCAddressId);
                var transResult = SiemensHelper.TransferStringToBuffer(item.DataType, prop.value);
                if (!transResult.Success)
                {
                    return BllResultFactory.Error($"转换到PLC数据类型失败,PLC:{Name},IP:{IP},地址:{prop.address}:{transResult.Msg}");
                }
                var buffer = transResult.Data;
                //如果不是bool,我们都是写字节,如果是bool,我们需要写位,这个调用具体方法实现
                //浮点型暂不考虑
                OperateResult result = null;
                if (item.DataType != PLCDataType.BOOL)
                {
                    result = siemensTcpNet.Write(item.Address, buffer);
                }
                else
                {
                    result = siemensTcpNet.Write(item.AddressX, bool.Parse(prop.value));
                }
                if (!result.IsSuccess)
                {
                    return BllResultFactory.Error($"写入PLC:{Name},ip:{IP},地址:{prop.address},数据:{prop.value}失败:{result.Message}");
                }
            }


            return BllResultFactory.Success("写入成功");

        }

        /// <summary>
        /// 调用读写函数前的校验,此过程包含了解析地址并缓存
        /// </summary>
        /// <param name="equipmentProps"></param>
        /// <returns></returns>
        private BllResult<List<string>> Validation(List<base_equipment_prop> equipmentProps)
        {
            lock (Lock)
            {
                if (equipmentProps == null || equipmentProps.Count() == 0)
                {
                    return BllResultFactory.Error<List<string>>($"未传递属性");
                }
                //选取设备类
                var ips = equipmentProps.Select(t => t.Equipment).Select(t => t.ip).Distinct().ToList();
                var temp = ips.Where(t => siemensTcpNet.IpAddress != t).ToArray();
                if (temp.Length > 0)
                {
                    return BllResultFactory.Error<List<string>>($"存在IP为{string.Join(",", temp)}的属性与当前PLC实例{Name}的IP{siemensTcpNet.IpAddress}不符,请检查传递属性是否有误");
                }

                //缓存中未存在的属性
                var tempProps = equipmentProps.Where(t => !_hslSiemensDataEntities.Exists(a => a.OPCAddressId == t.id)).ToList();
                //解析属性并加入缓存
                foreach (var item in tempProps)
                {
                    var result = SiemensHelper.ParseAddress(item);
                    if (!result.Success)
                    {
                        return BllResultFactory.Error<List<string>>($"设备:{item.Equipment.code},属性:{item.EquipmentTypePropTemplate.code},地址解析错误:{result.Msg}");
                    }
                    _hslSiemensDataEntities.Add(result.Data);
                }
                return BllResultFactory.Success(ips);
            }
        }
    }
}