Files
StandardSence/StandardScene.Core/InterLock/AbstractInterlockMission.cs
T
黄兆尉andCursor e075bc0d7d refactor: CycleUiHelper 重命名为 FairyUiHelper
对齐 FairyView 宿主命名。环线/充电/按钮/门进程的 Stop 改为 override,避免隐藏基类;Kiva.ForceStop 改为 override。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 22:49:07 +08:00

414 lines
12 KiB
C#

using SimpleCore.Traffic;
using System;
using System.Collections.Generic;
using System.Linq;
using Simple3.RCS;
using Simple3.RCS.CarTypes;
using SimpleCore.Library;
using SimpleCore.PropType;
using SimpleCore;
using System.Threading;
using Simple3.CADTools;
using Simple3.Props;
using Simple3.UI;
using Simple3;
using StandardScene.Utils;
using Newtonsoft.Json;
namespace StandardScene.InterLock
{
public class MyDictionary<TKey, TItem> where TItem : new()
{
private readonly Dictionary<TKey, TItem> dictionary = new();
public TItem this[TKey key]
{
get
{
if (dictionary.TryGetValue(key, out var item)) return item;
var newItem = new TItem();
dictionary[key] = newItem;
return newItem;
}
set => dictionary[key] = value;
}
public ICollection<TKey> Keys
{
get => dictionary.Keys;
}
}
public class AbstractInterlockMission : Mission
{
public bool GetAskEnter(int siteId)
{
if (!SiteFilter(siteId)) return false;
lock (sync) return askEnter[siteId];
}
public void SetAllowEnter(int siteId, bool value)
{
if (!SiteFilter(siteId)) return;
lock (sync) allowEnter[siteId] = value;
}
public bool GetAskExit(int siteId)
{
if (!SiteFilter(siteId)) return false;
lock (sync) return askExit[siteId];
}
public void SetAllowExit(int siteId, bool value)
{
if (!SiteFilter(siteId)) return;
lock (sync) allowExit[siteId] = value;
}
public bool GetReportLeave(int siteId)
{
if (!SiteFilter(siteId)) return false;
lock (sync) return reportLeave[siteId];
}
public void SetAcknowledgeLeave(int siteId, bool value)
{
if (!SiteFilter(siteId)) return;
lock (sync) acknowledgeLeave[siteId] = value;
}
public int GetInSiteCarId(int siteId)
{
if (!SiteFilter(siteId)) return -1;
lock (sync) return inSiteCarId[siteId];
}
// 请求进站(写)
[JsonIgnore] private readonly MyDictionary<int, bool> askEnter = new();
// 允许进站(读)
[JsonIgnore] private readonly MyDictionary<int, bool> allowEnter = new();
// 请求离站(写)
[JsonIgnore] private readonly MyDictionary<int, bool> askExit = new();
// 允许离站(读)
[JsonIgnore] private readonly MyDictionary<int, bool> allowExit = new();
// 报告离开(写)
[JsonIgnore] private readonly MyDictionary<int, bool> reportLeave = new();
// 确认离开(读)
[JsonIgnore] private readonly MyDictionary<int, bool> acknowledgeLeave = new();
// 站点上所在的小车(状态)
[JsonIgnore] private readonly MyDictionary<int, int> inSiteCarId = new();
[JsonIgnore] private object sync = new();
public virtual string GetSiteDisplay(int siteId)
{
return $"{siteId}";
}
/// <summary>
/// 返回为true的站点,是当前互锁机制关注的站点
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
public virtual bool SiteFilter(int siteId)
{
return true;
}
/// <summary>
/// 互锁站点进站条件
/// </summary>
/// <param name="car"></param>
/// <param name="siteId"></param>
/// <returns></returns>
private bool EnterCondition(AbstractCar car, int siteId)
{
// todo: 是否考虑提前更多申请进入,加快节拍?
if (car.status.pendingLocks.First() != siteId) return false;
//DateTime startTime = DateTime.Now;
//bool validTime = false;
lock (sync)
{
// if (askEnter[siteId] == false)
// {
// Diagnosis.Post($"{car.name}({car.id}) 申请进站 {GetSiteDisplay(siteId)}", "intertime", true);
// //startTime = DateTime.Now;
// //validTime = true;
// }
askEnter[siteId] = true;
if (reportLeave[siteId])
{
Diagnosis.Post($"{car.name}({car.id})未被准许进站{GetSiteDisplay(siteId)},前车离开信号未被确认", "interlock", true);
return false;
}
if (allowEnter[siteId])
{
Diagnosis.Post($"{car.name}({car.id})准许进站{GetSiteDisplay(siteId)}", "interlock", true);
// Diagnosis.Post($"{car.name}({car.id}) 准许进站 {GetSiteDisplay(siteId)}", "intertime", true);
lock (sync) askEnter[siteId] = false;
return true;
}
Diagnosis.Post($"{car.name}({car.id})未被准许进站{GetSiteDisplay(siteId)}", "interlock", true);
return false;
}
}
/// <summary>
/// 互锁站点离站条件
/// </summary>
/// <param name="car"></param>
/// <param name="siteId"></param>
/// <returns></returns>
private bool ExitCondition(AbstractCar car, int siteId)
{
lock (sync)
{
askExit[siteId] = true;
if (allowExit[siteId])
{
Diagnosis.Post($"{car.name}({car.id})准许离站{GetSiteDisplay(siteId)}", "interlock", true);
askExit[siteId] = false;
return true;
}
Diagnosis.Post($"{car.name}({car.id})未被准许离站{GetSiteDisplay(siteId)}", "interlock", true);
return false;
}
}
/// <summary>
/// 告知对方设备小车离开,直到对方设备确认收到离开信号
/// </summary>
/// <param name="car"></param>
/// <param name="siteId"></param>
private void LeaveEvent(AbstractCar car, int siteId)
{
new Thread(() =>
{
while (true)
{
Thread.Sleep(100);
lock (sync)
{
if (acknowledgeLeave[siteId]) // 已经确认小车离开
{
Diagnosis.Post($"{GetSiteDisplay(siteId)}确认{car.name}({car.id})离开", "interlock", true);
break;
}
}
Diagnosis.Post($"{car.name}({car.id})告知离开{GetSiteDisplay(siteId)}", "interlock", true);
lock (sync) reportLeave[siteId] = true;
}
lock (sync) reportLeave[siteId] = false;
}).Start();
}
[JsonIgnore] private bool enableSimulation = false;
[JsonIgnore] public bool started = false;
[JsonIgnore] public Thread showT;
[MethodMember(Name = "启动进程", Description = "处理安全互锁")]
public override void Execute()
{
if (started) return;
// started = true;
status.status = "已启动";
showT = new Thread(() =>
{
while (true)
{
Thread.Sleep(300);
try
{
if (fields.TryGetValue("enableSimulation", out var userEnableSimulation))
enableSimulation = bool.Parse(userEnableSimulation);
foreach (var site in SimpleLib.GetAllSites().Where(ss => SiteFilter(ss.id)))
{
var carId = -1;
foreach (var car in SimpleLib.GetAllCars())
{
lock (TrafficControl.syncTrafficSequence)
{
if (car.status.holdingLocks.Length == 1 && car.status.holdingLocks[0] == site.id)
{
carId = car.id;
break;
}
}
}
lock (sync) inSiteCarId[site.id] = carId;
}
// if (!onDisplay) continue;
var painter = SimpleMonitor.getPainter("DemoInterlockMission");
painter.clear();
if (!onDisplay) continue;
//string str;
//lock (sync)
//{
// var keys = askEnter.Keys.Concat(askExit.Keys).Concat(reportLeave.Keys).ToHashSet()
// .OrderBy(kk => int.Parse(SimpleLib.GetSite(kk).fields["ASNub"])).ToList();
// str = $"站点\t\t请求进站\t允许进站\t请求离站\t允许离站\t上报离开\t确认离开\t小车id\n" +
// $"{string.Join("\n", keys.Select(key =>
// $"{GetSiteDisplay(key)}\t{askEnter[key]}\t\t{allowEnter[key]}\t\t" +
// $"{askExit[key]}\t\t{allowExit[key]}\t\t" +
// $"{reportLeave[key]}\t\t{acknowledgeLeave[key]}\t\t{inSiteCarId[key]}"))}";
//}
//painter.drawTextFixed(str, new SolidBrush(Color.Black), VirtualPainter.DrawPosition.RightTop, Color.AliceBlue);
}
catch (Exception ex)
{
Diagnosis.Post(ExceptionFormatter.FormatEx(ex), "interlock", true);
}
}
}) { Name = "InterlockMission" };
showT.Start();
}
[JsonIgnore] private bool onDisplay = true;
[MethodMember(Name = "切换显示", Description = "是否在右下角显示")]
public void SwitchAlwaysOnDisplay()
{
onDisplay = !onDisplay;
}
private async void SimButton(string title, MyDictionary<int, bool> toManipulate)
{
//if (!enableSimulation) return;
G.pushStatus("请选择站点");
var pt = await Startup.UI.getPoint(new UIOps.getPointOptions() { site = true });
var site = SimpleLib.GetSite(pt.site);
if (SiteFilter(site.id))
{
var vv = await Startup.UI.Input("请输入true/false", title, "true");
if (vv == null || !bool.TryParse(vv, out var allow))
{
FairyUiHelper.Alert("错误", "输入错误");
return;
}
lock (sync) toManipulate[site.id] = allow;
}
}
[MethodMember(Name = "模拟准许进站")]
public void SimAllowEnter()
{
SimButton("模拟准许进入状态", allowEnter);
}
[MethodMember(Name = "模拟准许离站")]
public void SimAllowExit()
{
SimButton("模拟准许离站状态", allowExit);
}
[MethodMember(Name = "模拟确认离开")]
public void SimLeaveNoted()
{
SimButton("模拟确认离开状态", acknowledgeLeave);
}
[MethodMember(Name = "模拟发送离开")]
public async void SimReportLeave()
{
// if (!enableSimulation) return;
G.pushStatus("请选择站点");
var pt = await Startup.UI.getPoint(new UIOps.getPointOptions() { site = true });
var site = SimpleLib.GetSite(pt.site);
if (SiteFilter(site.id))
{
lock (sync) reportLeave[site.id] = true;
}
new Thread(() =>
{
var t1 = DateTime.Now;
while (true)
{
Thread.Sleep(100);
lock (sync)
{
var deltaT = (DateTime.Now - t1).TotalSeconds;
if (acknowledgeLeave[site.id]||deltaT>5) // 已经确认小车离开
{
Diagnosis.Post($"{GetSiteDisplay(site.id)} 停止发送离开", "simlikai", true);
break;
}
}
Diagnosis.Post($"模拟告知离开{GetSiteDisplay(site.id)}", "simlikai", true);
lock (sync) reportLeave[site.id] = true;
}
lock (sync) reportLeave[site.id] = false;
}).Start();
// SimButton("模拟确认离开状态", reportLeave);
}
public AbstractInterlockMission()
{
Diagnosis.Post($"InterlockMechanism loaded", "interlock", true);
TrafficControl.BeforeLock += (car, siteId) =>
{
if (SiteFilter(siteId)) // EnterCondition
return EnterCondition(car, siteId);
var site = SimpleLib.GetSite(siteId);
if (site.fields.ContainsKey("PreAskEnterSite") && int.TryParse(site.fields["PreAskEnterSite"], out int PreAskEnterSiteId))
{
var presite = SimpleLib.GetSite(PreAskEnterSiteId);
if (presite != null && SiteFilter(PreAskEnterSiteId))
{
Diagnosis.Post($"{car.name}({car.id})在{siteId}-{GetSiteDisplay(siteId)},提前申请 {PreAskEnterSiteId} 的请求进入", "interlock", true);
askEnter[PreAskEnterSiteId] = true;
}
}
// ExitCondition
lock (TrafficControl.syncTrafficSequence)
if (!car.status.holdingLocks.Any(ss => SiteFilter(ss)))
return true;
var isNeighbor = false;
var lockSiteId = -1;
foreach (var trackId in site.relatedTracks)
{
var track = SimpleLib.GetTrack(trackId);
if (SiteFilter(track.siteA))
{
isNeighbor = true;
lockSiteId = track.siteA;
break;
}
if (SiteFilter(track.siteB))
{
isNeighbor = true;
lockSiteId = track.siteB;
break;
}
}
return !isNeighbor || ExitCondition(car, lockSiteId);
};
TrafficControl.AfterLeave += (car, siteId) =>
{
if (SiteFilter(siteId)) LeaveEvent(car, siteId);
};
}
}
}