2026-08-14 14:43:11 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using SimpleCore.Library;
|
|
|
|
|
|
using SimpleLite.RCS.CarTypes;
|
|
|
|
|
|
using StandardScene.Signal.Plc;
|
|
|
|
|
|
|
|
|
|
|
|
namespace StandardScene.Signal.Logic
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2026-08-17 15:20:27 +08:00
|
|
|
|
/// 区域车组限流。对齐反编译 <c>DefaultPlugin.RateFlowControl</c>。
|
2026-08-14 14:43:11 +08:00
|
|
|
|
/// </summary>
|
2026-08-17 15:20:27 +08:00
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// 独立握手类型 <c>RateFlowControl</c> 用分号:<c>区域;车组;限额;点1,点2</c>。
|
|
|
|
|
|
/// 挂在 JGControl 第三段时用短横:<c>区域-车组-限额-点1,点2</c>(因为前两段机构名/动作已经占用分号)。
|
|
|
|
|
|
/// 统计「站点列表上、同车组、且不是本车」的数量,小于限额才放行。
|
|
|
|
|
|
/// 解析异常时放行(return true),避免配错把产线卡死。
|
|
|
|
|
|
/// </remarks>
|
2026-08-14 14:43:11 +08:00
|
|
|
|
public static class RateFlowHandler
|
|
|
|
|
|
{
|
2026-08-17 15:20:27 +08:00
|
|
|
|
/// <summary>独立限流点:通过则停充并启动。</summary>
|
2026-08-14 14:43:11 +08:00
|
|
|
|
public static void OnStopped(Car car, SignalStopSpec spec, IDictionary<string, int> logByArea)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (AllowPass(car, spec.SignalParams, logByArea, sep: ';'))
|
|
|
|
|
|
{
|
|
|
|
|
|
SignalCarAdapter.StopCharge(car);
|
|
|
|
|
|
SignalCarAdapter.Start(car);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 15:20:27 +08:00
|
|
|
|
/// <summary>JGControl 第三段限流,短横分隔。不通过时调用方应直接 return,不启动。</summary>
|
2026-08-14 14:43:11 +08:00
|
|
|
|
public static bool AllowPass(Car car, string rateFlowString, IDictionary<string, int> logByArea) =>
|
|
|
|
|
|
AllowPass(car, rateFlowString, logByArea, sep: '-');
|
|
|
|
|
|
|
2026-08-17 15:20:27 +08:00
|
|
|
|
/// <param name="sep">独立类型用 ';',JGControl 挂载用 '-'。</param>
|
2026-08-14 14:43:11 +08:00
|
|
|
|
private static bool AllowPass(Car car, string raw, IDictionary<string, int> logByArea, char sep)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var parts = (raw ?? "").Split(sep);
|
|
|
|
|
|
var area = parts.Length > 0 ? parts[0] : "";
|
|
|
|
|
|
var group = parts.Length > 1 ? parts[1] : "";
|
|
|
|
|
|
var limit = 0;
|
|
|
|
|
|
if (parts.Length > 2)
|
|
|
|
|
|
int.TryParse(parts[2], out limit);
|
|
|
|
|
|
var sites = new List<int>();
|
|
|
|
|
|
if (parts.Length > 3)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var s in parts[3].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (int.TryParse(s.Trim(), out var id))
|
|
|
|
|
|
sites.Add(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var others = SignalCarAdapter.CarsAt(sites)
|
|
|
|
|
|
.Where(x => x.id != car.id && string.Equals(SignalCarAdapter.GroupId(x), group, StringComparison.Ordinal))
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
var count = others.Count;
|
|
|
|
|
|
if (count < limit)
|
|
|
|
|
|
{
|
|
|
|
|
|
Diagnosis.Log(
|
|
|
|
|
|
$"限流区域{area}限制AGV组{group}流量{limit}辆AGV,当前停放{count},放行AGV{car.id}",
|
|
|
|
|
|
"RateFlow", true);
|
|
|
|
|
|
Note(logByArea, area, car, raw);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Diagnosis.Log(
|
|
|
|
|
|
$"限流区域{area}限制AGV组{group}流量{limit}辆AGV,当前停放{count},无法放行",
|
|
|
|
|
|
"RateFlow", true);
|
|
|
|
|
|
Note(logByArea, area, car, raw);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Diagnosis.Log($"处理区域限流业务异常:{ex.Message}", "RateFlow", true);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-17 15:20:27 +08:00
|
|
|
|
/// <summary>同一限流区域仅在「决策车号变化」时再打一条带原始参数的日志。</summary>
|
2026-08-14 14:43:11 +08:00
|
|
|
|
private static void Note(IDictionary<string, int> logByArea, string area, Car car, string raw)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (logByArea == null) return;
|
|
|
|
|
|
var changed = !logByArea.TryGetValue(area, out var last) || last != car.id;
|
|
|
|
|
|
logByArea[area] = car.id;
|
|
|
|
|
|
if (changed)
|
|
|
|
|
|
Diagnosis.Log($"AGV{car.id}停在地标{SignalCarAdapter.PositionId(car)},配置参数:【{raw}】", "RateFlow", true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|