From dc7269290ff2f7a6c86e5d3b97289d34658f8193 Mon Sep 17 00:00:00 2001 From: xingheng Date: Fri, 26 Sep 2025 11:04:02 +0800 Subject: [PATCH] 09 25 --- .../AppApi/IOModuleApiController.cs | 415 +++++++++++++----- 1 file changed, 300 insertions(+), 115 deletions(-) diff --git a/Learun.Application.Web/AppApi/IOModuleApiController.cs b/Learun.Application.Web/AppApi/IOModuleApiController.cs index ad0ae97d..7d3e611a 100644 --- a/Learun.Application.Web/AppApi/IOModuleApiController.cs +++ b/Learun.Application.Web/AppApi/IOModuleApiController.cs @@ -422,10 +422,22 @@ namespace Learun.Application.Web.AppApi cablesNeedAssigned = cablesNeedAssigned.Where(x => CableIds.Contains(x.CableID)).ToList(); #endregion //涉及到哪些箱子 - var allPanel = SqlSugarHelper.Db.Queryable().AS(panelTable). + List allPanel = SqlSugarHelper.Db.Queryable().AS(panelTable). InnerJoin((a, b) => a.EngineerDataID == b.EngineDataID).AS(tagTable). InnerJoin((a, b, c) => b.ObjectTypeID == c.ObjectTypeID).AS(typeTable). Where((a, b, c) => c.specialType == GlobalEnum.specialType.系统柜). + Select((a, b, c) => new ec_PanelEntity + { + allowedIOTypes = a.allowedIOTypes, + DefaultBreakerType = a.DefaultBreakerType, + EngineerDataID = a.EngineerDataID, + MaxStripNumber = a.MaxStripNumber, + ObjectTypeName = c.ObjectTypeName, + PanelID = a.PanelID, + Panel_Loc_ID = a.Panel_Loc_ID, + TagNumber = b.TagNumber, + + }). ToList();//这里要过滤一下,根据object type里的specialType,而不是所有的panel var allPanelProp = SqlSugarHelper.Db.Queryable().AS(propTable) @@ -514,6 +526,11 @@ namespace Learun.Application.Web.AppApi var signalTb = ProjectSugar.TableName(projId); var allSignals = SqlSugarHelper.Db.Queryable().AS(signalTb).ToList(); var allUsedCH = allSignals.Where(x => !string.IsNullOrEmpty(x.ChannelID)).Select(x => x.ChannelID).Distinct().ToList(); + //??这里有问题。通道是否被占用,需要看signal和set双重(在io分配界面) + //1. 信号有, 电缆set有,占了 + //2.信号no,电缆set有,占了 + //3.信号有,电缆setno,没占 + //4.信号no,电缆setno,没占 //1.2 流程图 分组原则为同一信号类型、同一系统的为一组 var cablesGrouped = cablesNeedAssigned.OrderBy(x => x.PanelID).ThenBy(x => x.PreAssignIOType).ThenBy(x => x.System).ToList(); @@ -523,7 +540,7 @@ namespace Learun.Application.Web.AppApi var allPanelIds = cablesGrouped.Select(x => x.PanelID).Distinct(); var stripBll = new ec_PanelStripBLL(); - var allStrips = stripBll.GetList("{ProjectId:\"" + projId + "\"}",OnlySelf:false) + var allStrips = stripBll.GetList("{ProjectId:\"" + projId + "\"}", OnlySelf: false) .Where(x => allPanelIds.Contains(x.PanelID)) .GroupBy(x => x.PanelID) .ToDictionary(x => x.Key, x => x.ToList()); @@ -552,11 +569,10 @@ namespace Learun.Application.Web.AppApi // sameGroup = false;//换组了 //} #endregion - //1.2 流程图 读取有提前选好箱子的信号 - GlobalEnum.IOType ioTypeOnCable = default; + //1.2 流程图 读取有提前选好箱子的信号 var setsSpared = cable.Sets.Where(x => string.IsNullOrEmpty(x.PreAssignGroup_Desc) || string.IsNullOrEmpty(x.PreAssignInOrOut)).ToList(); - var setsIn = cable.Sets.Where(x => x.PreAssignInOrOut == SWS.Share.Enum.inOrOut.输入.ToString()).ToList(); - var setsOut = cable.Sets.Where(x => x.PreAssignInOrOut == SWS.Share.Enum.inOrOut.输出.ToString()).ToList(); + var setsIn = cable.Sets.Where(x => !string.IsNullOrEmpty(x.PreAssignGroup_Desc) && x.PreAssignInOrOut == SWS.Share.Enum.inOrOut.输入.ToString()).ToList(); + var setsOut = cable.Sets.Where(x => !string.IsNullOrEmpty(x.PreAssignGroup_Desc) && x.PreAssignInOrOut == SWS.Share.Enum.inOrOut.输出.ToString()).ToList(); #region 1.2.4 同一根电缆,如果带公共端的报警信号,必须接到一个模块里面 if (cable.PreAssignIOType.ToLower() == GlobalEnum.signalType.Digital.ToString().ToLower()) @@ -571,154 +587,316 @@ namespace Learun.Application.Web.AppApi } #endregion + #region inputNew + var resIn = AutoAssignCore(GlobalEnum.inOrOut.输入, setsIn, setsSpared); + #endregion #region input - ec_PanelStripEntity curStrip = null; - var matchedStrips = curStrips.Where(x => x.IO_TYPE == ioTypeOnCable.ToString()).ToList(); - if (matchedStrips != null && lastUsedStrip != null && lastUsedStrip.IO_TYPE == ioTypeOnCable.ToString()) - { - //1.2.2 优先使用上一个模块,直到模块满了再用下一个模块。 - matchedStrips.Insert(0, lastUsedStrip); - } - if (setsIn.Count + setsSpared.Count() > 10) - { - //另外如果电缆对数大于10对,即像12*2*0.75这种,无论它预分配了多少根电缆对,永远预留4个以下的空白通道。 - //意思就是一个模块里面够4个或4个以上空白通道就留4个空白通道, - //如果不够4个就留4个以下即可。 - } - else + //ec_PanelStripEntity curStrip = null; + //var matchedStrips = curStrips.Where(x => x.IO_TYPE == ioTypeOnCable.ToString()).ToList(); + //if (matchedStrips != null && lastUsedStrip != null && lastUsedStrip.IO_TYPE == ioTypeOnCable.ToString()) + //{ + // //1.2.2 优先使用上一个模块,直到模块满了再用下一个模块。 + // matchedStrips.Insert(0, lastUsedStrip); + //} + //if (setsIn.Count == 0 ) + //{ + // //相当于没有 + //} + //else if (setsIn.Count + setsSpared.Count() > 10) + //{ + // //另外如果电缆对数大于10对,即像12*2*0.75这种,无论它预分配了多少根电缆对,永远预留4个以下的空白通道。 + // //意思就是一个模块里面够4个或4个以上空白通道就留4个空白通道, + // //如果不够4个就留4个以下即可。 + //} + //else + //{ + // string PanelName = cable.AssignedPanel.TagNumber;//当前电缆预分配的箱子名称 + // switch (cable.PreAssignIOType.ToLower()) + // { + // case "digital": + // //数字量 + // ioTypeOnCable = GlobalEnum.IOType.DI; + // break; + // case "4~20ma": + // //模拟量4-20mA + // ioTypeOnCable = GlobalEnum.IOType.AI; + // break; + // case "10v": + // ioTypeOnCable = GlobalEnum.IOType.TenVolt; + // break; + // case "pt100": + // ioTypeOnCable = GlobalEnum.IOType.PT100; + // break; + // case "pulse": + // ioTypeOnCable = GlobalEnum.IOType.PULSE; + // break; + // default: + // //通讯类 485 422啥的 + // continue; + // break; + // } + + // //1.2 流程图 箱子里是否已经存在端子排(io匹配) + + // bool alreadyInsertNewTs = false; + // if (matchedStrips == null || matchedStrips.Count == 0) + // { + // //没有端子排 + // //1.2 流程图 根据信号数里自动新建端子排,端子排通道数里根据箱子模板中的默认值获得 + // var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnCable.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnCable); + // alreadyInsertNewTs = true; + // matchedStrips.Add(newTS); + // } + + // #region 1.2 流程图 空的通道数够不够 + // if (cable.CableClass == SWS.Share.Enum.cableClass.homerun.ToString()) + // { + // //1.2.3(2)如果模块是通讯信号(像RS485这种就是通讯信号),则不用管预留空白通道这个事情,看到有空的通道就放。 + // //当前这个端子排就是可用的 + + // curStrip = matchedStrips.First(); + + + // } + // else + // { + // //1.2.3 (1) 非通讯,如果空的通道大于整个模块的5%,则可以利用,利用到小于5%了则停止利用,如果本身空通道数占的比例就小于5%,则不利用。 + + // curStrip = FindNextAvailableTS(matchedStrips, allUsedCH); + + // if (curStrip == null) + // { + // var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnCable.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnCable); + // alreadyInsertNewTs = true; + // matchedStrips.Add(newTS); + // curStrip = newTS; + + // } + // } + // var usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); + // var notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); + // #endregion + // #region 1.2 流程图 按规则先放几个信号进去 + // if (notUsedChs.Count < setsIn.Count + setsSpared.Count()) + // { + // //不够放,又要新建? + // if (alreadyInsertNewTs) + // { + // //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 + // continue; + // } + // else + // { + // //相当于前面5%的判断过了,但是呢放不下所有需要的set + // var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnCable.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnCable); + // alreadyInsertNewTs = true; + // matchedStrips.Add(newTS); + // curStrip = newTS; + // usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); + // notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); + // if (notUsedChs.Count < setsIn.Count + setsSpared.Count()) + // { + // //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 + // continue; + // } + // } + // } + // lastUsedStrip = curStrip; + // //能放下 + // for (int i = 0; i < setsIn.Count; i++) + // { + // var set = setsIn[i]; + // var ch = notUsedChs[i]; + // set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}"; + // //更新全局已使用通道 + // allUsedCH.Add(ch.ChannelID); + // } + // var chOffIdx = setsIn.Count; + // for (int i = 0; i < setsSpared.Count(); i++) + // { + // var set = setsSpared[i]; + // var ch = notUsedChs[chOffIdx + i]; + // set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}/冗余"; + // //更新全局已使用通道 + // allUsedCH.Add(ch.ChannelID); + // } + // #endregion + //} + #endregion + #region output + var resOut = AutoAssignCore(GlobalEnum.inOrOut.输出, setsOut, setsSpared); + + + #endregion + + //in 和 out都会执行这个 + bool AutoAssignCore(GlobalEnum.inOrOut inOrOut, List sets, List sets_Spared) { + GlobalEnum.IOType ioTypeOnC = default; + ec_PanelStripEntity curStrip = null; + var matchedStrips = curStrips.Where(x => x.IO_TYPE == ioTypeOnC.ToString()).ToList(); + if (matchedStrips != null && lastUsedStrip != null && lastUsedStrip.IO_TYPE == ioTypeOnC.ToString()) + { + //1.2.2 优先使用上一个模块,直到模块满了再用下一个模块。 + matchedStrips.Insert(0, lastUsedStrip); + } string PanelName = cable.AssignedPanel.TagNumber;//当前电缆预分配的箱子名称 switch (cable.PreAssignIOType.ToLower()) { case "digital": //数字量 - ioTypeOnCable = GlobalEnum.IOType.DI; + if (inOrOut == GlobalEnum.inOrOut.输入) + { + ioTypeOnC = GlobalEnum.IOType.DI; + } + else + { + ioTypeOnC = GlobalEnum.IOType.DO; + } break; case "4~20ma": //模拟量4-20mA - ioTypeOnCable = GlobalEnum.IOType.AI; + if (inOrOut == GlobalEnum.inOrOut.输入) + { + ioTypeOnC = GlobalEnum.IOType.AI; + } + else + { + ioTypeOnC = GlobalEnum.IOType.AO; + } + break; case "10v": - ioTypeOnCable = GlobalEnum.IOType.TenVolt; + ioTypeOnC = GlobalEnum.IOType.TenVolt; break; case "pt100": - ioTypeOnCable = GlobalEnum.IOType.PT100; + ioTypeOnC = GlobalEnum.IOType.PT100; break; case "pulse": - ioTypeOnCable = GlobalEnum.IOType.PULSE; + ioTypeOnC = GlobalEnum.IOType.PULSE; break; default: //通讯类 485 422啥的 - continue; - break; + return false; } - - //1.2 流程图 箱子里是否已经存在端子排(io匹配) - - bool alreadyInsertNewTs = false; - if (matchedStrips == null || matchedStrips.Count == 0) + if (sets.Count == 0) { - //没有端子排 - //1.2 流程图 根据信号数里自动新建端子排,端子排通道数里根据箱子模板中的默认值获得 - var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnCable.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnCable); - alreadyInsertNewTs = true; - matchedStrips.Add(newTS); + //相当于没有 } - - #region 1.2 流程图 空的通道数够不够 - if (cable.CableClass == SWS.Share.Enum.cableClass.homerun.ToString()) - { - //1.2.3(2)如果模块是通讯信号(像RS485这种就是通讯信号),则不用管预留空白通道这个事情,看到有空的通道就放。 - //当前这个端子排就是可用的 - - curStrip = matchedStrips.First(); + //else if (sets.Count + setsSpared.Count() > 10) + //{ - } + //} else { - //1.2.3 (1) 非通讯,如果空的通道大于整个模块的5%,则可以利用,利用到小于5%了则停止利用,如果本身空通道数占的比例就小于5%,则不利用。 + var totalSets = sets.Count + setsSpared.Count(); + //另外如果电缆对数大于10对,即像12*2*0.75这种,无论它预分配了多少根电缆对,永远预留4个以下的空白通道。 + //意思就是一个模块里面够4个或4个以上空白通道就留4个空白通道, + //如果不够4个就留4个以下即可。 - curStrip = FindNextAvailableTS(matchedStrips, allUsedCH); - if (curStrip == null) + //1.2 流程图 箱子里是否已经存在端子排(io匹配) + + bool alreadyInsertNewTs = false; + if (matchedStrips == null || matchedStrips.Count == 0) { - var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnCable.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnCable); + //没有端子排 + //1.2 流程图 根据信号数里自动新建端子排,端子排通道数里根据箱子模板中的默认值获得 + var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnC.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnC); alreadyInsertNewTs = true; matchedStrips.Add(newTS); - curStrip = newTS; - } - } - var usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); - var notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); - #endregion - #region 1.2 流程图 按规则先放几个信号进去 - if (notUsedChs.Count < setsIn.Count + setsSpared.Count()) - { - //不够放,又要新建? - if (alreadyInsertNewTs) + + #region 1.2 流程图 空的通道数够不够 + if (cable.CableClass == SWS.Share.Enum.cableClass.homerun.ToString()) { - //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 - continue; + //1.2.3(2)如果模块是通讯信号(像RS485这种就是通讯信号),则不用管预留空白通道这个事情,看到有空的通道就放。 + //当前这个端子排就是可用的 + + curStrip = matchedStrips.First(); + + } else { - //相当于前面5%的判断过了,但是呢放不下所有需要的set - var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnCable.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnCable); - alreadyInsertNewTs = true; - matchedStrips.Add(newTS); - curStrip = newTS; - usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); - notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); - if (notUsedChs.Count < setsIn.Count + setsSpared.Count()) + //1.2.3 (1) 非通讯,如果空的通道大于整个模块的5%,则可以利用,利用到小于5%了则停止利用,如果本身空通道数占的比例就小于5%,则不利用。 + + curStrip = FindNextAvailableTS(matchedStrips, allUsedCH); + + if (curStrip == null) { - //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 - continue; + var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnC.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnC); + alreadyInsertNewTs = true; + matchedStrips.Add(newTS); + curStrip = newTS; + + } + } + var usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); + var notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); + #endregion + #region 1.2 流程图 按规则先放几个信号进去 + if (notUsedChs.Count < totalSets) + { + //不够放,又要新建? + if (alreadyInsertNewTs) + { + //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 + return false; + } + else + { + //相当于前面5%的判断过了,但是呢放不下所有需要的set + var newTS = CreatePanelStripByProfile2(projId, "TS_" + ioTypeOnC.ToString() + "_" + newTSSeq++, curPanelId, ioTypeOnC); + alreadyInsertNewTs = true; + matchedStrips.Add(newTS); + curStrip = newTS; + usedChs = curStrip.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); + notUsedChs = curStrip.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); + if (notUsedChs.Count < sets.Count + setsSpared.Count()) + { + //压根就有问题,说明电缆的set太多了,就算是新建的模块,一个channel都没有使用的情况下,都塞不下。 + return false; + } + } + } + lastUsedStrip = curStrip; + //能放下 + if (cable.Sets.Count > 10) + { + //另外如果电缆对数大于10对,即像12*2*0.75这种,无论它预分配了多少根电缆对,永远预留4个以下的空白通道。意思就是一个模块里面够4个或4个以上空白通道就留4个空白通道,如果不够4个就留4个以下即可 + + } + else + { + for (int i = 0; i < sets.Count; i++) + { + var set = sets[i]; + var ch = notUsedChs[i]; + set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}"; + //更新全局已使用通道 + allUsedCH.Add(ch.ChannelID); + } + var chOffIdx = sets.Count; + for (int i = 0; i < setsSpared.Count(); i++) + { + var set = setsSpared[i]; + var ch = notUsedChs[chOffIdx + i]; + set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}/冗余"; + //更新全局已使用通道 + allUsedCH.Add(ch.ChannelID); } } - } - lastUsedStrip = curStrip; - //能放下 - for (int i = 0; i < setsIn.Count; i++) - { - var set = setsIn[i]; - var ch = notUsedChs[i]; - set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}"; - //更新全局已使用通道 - allUsedCH.Add(ch.ChannelID); - } - var chOffIdx = setsIn.Count; - for (int i = 0; i < setsSpared.Count(); i++) - { - var set = setsSpared[i]; - var ch = notUsedChs[chOffIdx + i]; - set.ConnectionInfo = $"采集箱:{PanelName}/模块(端子排):{curStrip.StripName}/通道:{ch.ChannelName}/冗余"; - //更新全局已使用通道 - allUsedCH.Add(ch.ChannelID); - } - #endregion - } - #endregion - #region output - if (setsOut.Count + setsSpared.Count() > 10) - { - //另外如果电缆对数大于10对,即像12*2*0.75这种,无论它预分配了多少根电缆对,永远预留4个以下的空白通道。 - //意思就是一个模块里面够4个或4个以上空白通道就留4个空白通道, - //如果不够4个就留4个以下即可。 - } - else - { - //空白通道原则 - //如5*2*0.75规格的电缆,电缆对就是5对,但可能它只预分配了2个电缆对,即只有2个信号被预分配了, - //但考虑模块通道的时候是考虑5对,而不是只考虑已经预分配的2个电缆对 + #endregion - //目前还剩多少 - //空的通道够不够 - int sparedChCount = 0; + + + } + return true; } - #endregion + } } @@ -737,17 +915,24 @@ namespace Learun.Application.Web.AppApi { foreach (var TS in TSs) { - var usedChs = TS.Channels.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); - var notUsedChs = TS.Channels.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); - double sparedRate = notUsedChs.Count * 1.0 / TS.Channels.Count * 1.0; + var validChs = TS.Channels.Where(x => x.lock_flg == 0).ToList();//过滤掉锁定的通道 + var usedChs = validChs.Where(x => allUsedCH.Contains(x.ChannelID)).Select(x => x.ChannelID).ToList(); + var notUsedChs = validChs.Where(x => !usedChs.Contains(x.ChannelID)).ToList(); + + + double sparedRate = notUsedChs.Count * 1.0 / validChs.Count * 1.0; if (sparedRate < 0.05) { //没有空闲通道 next } else { + TS.Channels = validChs.OrderBy(X => X.Channel_Seq).ToList(); + var seq = TSs.IndexOf(TS); return TS;//找到了 } + + }