对齐 FairyView 宿主命名。环线/充电/按钮/门进程的 Stop 改为 override,避免隐藏基类;Kiva.ForceStop 改为 override。 Co-authored-by: Cursor <cursoragent@cursor.com>
414 lines
12 KiB
C#
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);
|
|
};
|
|
}
|
|
}
|
|
}
|