refactor: 插件 UI 从 WinForms 迁移到 CycleGUI,并修复代码质量问题
将 StandardScene 各插件的配置/监控窗体从 WinForms 迁移到 CycleGUI(删除 .Designer.cs/.resx,重写为 PanelBuilder 立即模式 UI,新增 CycleUiHelper 统一对话框)。 同时修复代码审核中的问题: - 后台文件写入加锁 + try/catch(ButtonBoxManager / DoorManager,对齐 LoopViewer.SaveTasks 模式) - CoderFieldsMetadata.cs 启用 #nullable enable,消除 CS8632 警告 - DummyCar 移除已废弃的 rightClickAction()/SetPosition() - CarRemoteHelper.OpenVehicleWebPage 的 Process.Start 加 try/catch - 重命名名不副实的 Mstsc()(现为打开网页) - 统一弃元命名为 _ - TrafficInterlockViewer 改用稳定 Id(GUID)做选择/编辑,替代行索引 - csproj 改用 $(CGUILibDir) 解析 CycleGUI,绝对路径收敛到 Directory.Build.props 构建:dotnet build StandardScene.sln → 0 错误,30 警告(均为历史遗留)。 注:static 单例状态重构(审核第 8 项)暂未处理,留待单独任务。
This commit is contained in:
@@ -1,692 +0,0 @@
|
||||
using LessokajiWeaverUtilities.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
using SimpleLite;
|
||||
using SimpleLite.RCS;
|
||||
using SimpleLite.RCS.CarTypes;
|
||||
using SimpleLite.CADTools;
|
||||
using SimpleLite.Props;
|
||||
using SimpleLite.UI;
|
||||
using SimpleCore;
|
||||
using SimpleCore.Compiler;
|
||||
using SimpleCore.Extras;
|
||||
using SimpleCore.Library;
|
||||
using SimpleCore.PropType;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using StandardScene.Model;
|
||||
using StandardScene.Coders;
|
||||
using StandardScene.Magnetic.Coders;
|
||||
using Track = SimpleCore.PropType.Track;
|
||||
|
||||
namespace StandardScene.CarTypes
|
||||
{
|
||||
// MagneticTrackCoder 已物理迁出至 StandardScene.Magnetic.Coders(scene.mag 平台)。
|
||||
// Kiva 系字段袋(KivaCarFields/KivaSiteFields/KivaTrackFields/KivaPlanFields)下沉至
|
||||
// 基座 StandardScene.Core\CarTypes\KivaFields.cs(ArmCar 等跨平台车型继承它们)。
|
||||
|
||||
//标定轮里程计
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 10,
|
||||
useVerb = "track.CalibrateWheelEncoder && track.ReverseDst != dst.id",
|
||||
blockVerb = "true",
|
||||
templateString = "agv.CalibrateWheelEncoder(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id});",
|
||||
siteFields = typeof(KivaSiteFields),
|
||||
trackFields = typeof(KivaTrackFields))]
|
||||
|
||||
// 通用避障/IO/纠偏 coder 已抽离为 StandardScene.Coders.*(见类前 [ProgramTrackCoderSettings] 引用)
|
||||
|
||||
// 避障区尺寸(2参)切换 coder 已抽离为 StandardScene.Coders.AvoidanceParamLWCoder(方案 B;见下方 [ProgramTrackCoderSettings] 引用)
|
||||
[ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceParamLWCoder))]
|
||||
|
||||
//plan.SegN 是指 当前路径所有点线的数量
|
||||
//plan.SegN-2 就是终点倒数第二个站点
|
||||
//[TemplateTrackCoderSettings(
|
||||
// priority = 1,
|
||||
// templateString = "agv.BasicGo(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id}," +
|
||||
// "${track.id},${track.Speed},${track.Reverse||(track.ReverseDst==dst.id)}," +
|
||||
// "${track.CarDirectionBias},${track.EnableCarAbsoluteDirection},${track.CarAbsoluteDirection},${track.typeInfo});",
|
||||
// siteFields = typeof(KivaSiteFields),
|
||||
// trackFields = typeof(KivaTrackFields),
|
||||
// planFields = typeof(KivaPlanFields))]
|
||||
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 30,
|
||||
useVerb = "dst.tag>0 && src.tag>0",
|
||||
templateString = "agv.QrGo(${src.x},${src.y},${src.id},${src.tag},${dst.x},${dst.y},${dst.id},${dst.tag},${track.id}," +
|
||||
"${track.Speed},${track.Reverse || (track.ReverseDst == dst.id)}," +
|
||||
"${track.CarDirectionBias},${track.EnableCarAbsoluteDirection},${track.CarAbsoluteDirection}," +
|
||||
"${track.typeInfo});",
|
||||
blockVerb = "true",
|
||||
siteFields = typeof(KivaSiteFields),
|
||||
trackFields = typeof(KivaTrackFields),
|
||||
planFields = typeof(KivaPlanFields))]
|
||||
//[TemplateTrackCoderSettings(
|
||||
// priority = 2,
|
||||
// useVerb = "track.ReverseDst==dst.id",
|
||||
// templateString = "agv.BasicGo(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id},${track.id},${track.speed},0,true);",
|
||||
// blockVerb = "true",
|
||||
// siteFields = typeof(KivaSiteFields),
|
||||
// trackFields = typeof(KivaTrackFields))]
|
||||
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 20,
|
||||
useVerb = "plan.action=='fetch' && dst.Shelf&&plan.curSeg==plan.segN-2",
|
||||
templateString = "agv.Wait();" +
|
||||
"agv.Fetch(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id}," +
|
||||
"${dst.FetchSpeed},${dst.FetchLidarArea},${dst.FetchIOArea},${dst.FetchReverse}," +
|
||||
"${dst.FetchBlindMoveDist},${dst.FetchLiftDownTarget},${dst.FetchLiftUpTarget}," +
|
||||
"${dst.FetchUseQr},${dst.FetchQrMode},${dst.FetchIsUpQr},${dst.FetchUseDetector},${dst.FetchDetector},${dst.FetchDetectWidth},${dst.FetchDetectDepth}," +
|
||||
"${plan.CarLength},${plan.CarWidth},${dst.FetchLeaveSrcEarly},${dst.FetchShieldObstacleDist});" +
|
||||
"agv.Wait();",
|
||||
blockVerb = "true",
|
||||
siteFields = typeof(KivaSiteFields),
|
||||
trackFields = typeof(KivaTrackFields),
|
||||
planFields = typeof(KivaPlanFields))]
|
||||
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 20,
|
||||
useVerb = "plan.action=='put' && dst.Shelf&&plan.curSeg==plan.segN-2",
|
||||
templateString = "agv.Wait();" +
|
||||
"agv.Put(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id}," +
|
||||
"${dst.PutSpeed},${dst.PutLidarArea},${dst.PutIOArea},${dst.PutReverse},${dst.PutSyncRotate}," +
|
||||
"${dst.PutBlindMoveDist},${dst.PutLiftDownTarget},${dst.PutLiftUpTarget}," +
|
||||
"${dst.PutUseQr},${dst.PutQrMode},${dst.PutIsUpQr},${dst.PutUseDetector},${dst.PutDetector}," +
|
||||
"${dst.PutDetectWidth},${dst.PutDetectDepth},${dst.PutLeaveSrcEarly},${dst.PutShieldObstacleDist});" +
|
||||
"agv.Wait();",
|
||||
blockVerb = "true",
|
||||
siteFields = typeof(KivaSiteFields),
|
||||
trackFields = typeof(KivaTrackFields),
|
||||
planFields = typeof(KivaPlanFields))]
|
||||
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 22,
|
||||
useVerb = "src.Shelf&&plan.curSeg==1",
|
||||
templateString = "agv.LeaveShelf(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id}," +
|
||||
"${src.LeaveShelfSpeed},${src.LeaveShelfLidarArea},${src.LeaveShelfIOArea},${src.LeaveShelfReverse},${src.LeaveShelfSyncRotate}," +
|
||||
"${src.LeaveShelfBlindMoveDist},${src.LeaveShelfLiftDownTarget},${src.LeaveShelfRecoveryObstacleDist},);",
|
||||
blockVerb = "true",
|
||||
siteFields = typeof(KivaSiteFields),
|
||||
trackFields = typeof(KivaTrackFields),
|
||||
planFields = typeof(KivaPlanFields))]
|
||||
|
||||
[TemplateSiteCoderSettings(
|
||||
priority = 25,
|
||||
useVerb = "plan.action=='fetch'&& plan.segN == 1 &&dst.Shelf",
|
||||
templateString =
|
||||
"agv.FetchInPlace(${dst.FetchUseQr},${dst.FetchQrMode},${dst.FetchIsUpQr},${dst.FetchLiftUpTarget},${plan.CarLength},${plan.CarWidth});" +
|
||||
"agv.Wait();",
|
||||
blockVerb = "true",
|
||||
siteFields = typeof(KivaSiteFields),
|
||||
trackFields = typeof(KivaTrackFields),
|
||||
planFields = typeof(KivaPlanFields))]
|
||||
//[TemplateSiteCoderSettings(
|
||||
// priority = 100,
|
||||
// useVerb = "plan.curSeg == 0",
|
||||
// templateString = "agv.StartingMission();agv.Wait();",
|
||||
// blockVerb = "false",
|
||||
// siteFields = typeof(KivaSiteFields),
|
||||
// trackFields = typeof(KivaTrackFields),
|
||||
// planFields = typeof(KivaPlanFields))]
|
||||
|
||||
|
||||
|
||||
[ProgramTrackCoderSettings(priority = 5, program = typeof(KivaCarTrackCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 19, program = typeof(MagneticTrackCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 17, program = typeof(LidarAreaSwitchCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceDistanceCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 20, program = typeof(IoAreaSwitchCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 20, program = typeof(TrackingErrThreshCoder))]
|
||||
[CarType(Name = "Kiva", editor = typeof(Kiva))]
|
||||
[I18N.DocumentTranslation(Name = "Kiva", locale = "en")]
|
||||
[EnvelopConfig(centerX = 0, centerY = 0, lengthX = 950, lengthY = 650)]
|
||||
public class Kiva : GhostCar, IScriptErrorRecoverable
|
||||
{
|
||||
/// <summary>脚本异常自恢复(AbstractLoopMission 经 IScriptErrorRecoverable 调用),转调 newReset。</summary>
|
||||
public void RecoverReset(int resetSiteId = 0) => newReset(resetSiteId);
|
||||
|
||||
[FieldMember] public float CarLength = 2000;
|
||||
[FieldMember] public float CarWidth = 1400;
|
||||
public class RotateSiteEnvelope : SiteEnvelopeDefinition<Kiva>
|
||||
{
|
||||
public override bool Use() => true;
|
||||
|
||||
public override void Prompt()
|
||||
{
|
||||
if (curSeg == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (plan.codeArr == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var carConfig = usingCar.GetType().GetCustomAttribute<EnvelopConfigAttribute>();
|
||||
var carLength = carConfig.lengthX;
|
||||
var carWidth = carConfig.lengthY;
|
||||
var code = plan.codeArr[curSeg - 1];
|
||||
if (code.Contains("Fetch"))
|
||||
{
|
||||
var lengthX = plan.fields.TryGetValue("MaterialLength", out var materialLength)
|
||||
? (int.TryParse(materialLength, out var lengthResult) ? lengthResult > 0 ? lengthResult : carLength : carLength)
|
||||
: carLength;
|
||||
var lengthY = plan.fields.TryGetValue("MaterialWidth", out var materialWidth)
|
||||
? (int.TryParse(materialWidth, out var widthResult) ? widthResult > 0 ? widthResult : carWidth : carWidth)
|
||||
: carWidth;
|
||||
reshape(lengthX, lengthY, 0, 0);
|
||||
}
|
||||
else if (code.Contains("Put"))
|
||||
{
|
||||
reshape(carLength, carWidth, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Block() => false;
|
||||
|
||||
public override int priority => 1;
|
||||
}
|
||||
|
||||
public static async Task<Kiva> Create()
|
||||
{
|
||||
var kiva = new Kiva()
|
||||
{
|
||||
lstatus = "连接中",
|
||||
address = "127.0.0.1",
|
||||
name = $"Kiva",
|
||||
haveCoordination = true,
|
||||
speed = 1,
|
||||
};
|
||||
return kiva;
|
||||
}
|
||||
|
||||
private const float DrawWidth = 950, DrawLength = 650;
|
||||
private readonly Pen orientPen = new Pen(Color.White, 3);
|
||||
private bool twinkle = false;
|
||||
|
||||
protected override void draw(Graphics eGraphics)
|
||||
{
|
||||
try
|
||||
{
|
||||
var DrawWidth = CarLength;
|
||||
var DrawLength = CarWidth;
|
||||
var lineCap = new AdjustableArrowCap(5, 5, true);
|
||||
orientPen.CustomEndCap = lineCap;
|
||||
orientPen.StartCap = LineCap.RoundAnchor;
|
||||
var alarmLevel = Commons.GetCarStatus(this, "AlarmLevel");
|
||||
var soc = Commons.CarValue((Car)this, "Soc");
|
||||
var electricCurrent = Commons.CarValue((Car)this, "ElectricCurrent");
|
||||
var thresSpeed = Commons.CarValue(this, "ThresSpeed");
|
||||
LadderLogic.FlipFlop(ref twinkle, 500, true, false);
|
||||
Rectangle destRect = new Rectangle(
|
||||
(int)((int)(-DrawWidth / 2)),
|
||||
(int)((int)(-DrawLength / 2)),
|
||||
(int)((int)DrawWidth),
|
||||
(int)((int)DrawLength)
|
||||
);
|
||||
if (int.TryParse(alarmLevel, out var alarm) && alarm > 0)
|
||||
{
|
||||
eGraphics.FillRectangle(twinkle ? Brushes.DarkRed : Brushes.Green, destRect);
|
||||
}
|
||||
else if (alarmLevel == "-1" || this.siteID == -1)
|
||||
eGraphics.FillRectangle(Brushes.LightSeaGreen, destRect);
|
||||
else if (electricCurrent > 1)
|
||||
eGraphics.FillRectangle(Brushes.YellowGreen, destRect);
|
||||
else if (thresSpeed <= 0)
|
||||
{
|
||||
eGraphics.FillRectangle(twinkle ? Brushes.YellowGreen : Brushes.Green, destRect);
|
||||
}
|
||||
else if (soc < 25)
|
||||
{
|
||||
eGraphics.FillRectangle(twinkle ? Brushes.DarkRed : Brushes.LightSeaGreen, destRect);
|
||||
}
|
||||
else
|
||||
eGraphics.FillRectangle(Brushes.Green, destRect);
|
||||
|
||||
eGraphics.DrawEllipse(Pens.Yellow, -160, -160, 320, 320);
|
||||
eGraphics.DrawLine(orientPen, 0, 0, 480, 0);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Diagnosis.Post("绘制小车异常" + ExceptionFormatter.FormatEx(e), "绘制小车异常", true);
|
||||
}
|
||||
}
|
||||
|
||||
private bool LoopTestRunning = false;
|
||||
[MethodMember(Name = "循环测试")]
|
||||
[I18N.DocumentTranslation(Name = "Loop Test", locale = "en")]
|
||||
public async void LoopTest()
|
||||
{
|
||||
LoopTestRunning = true;
|
||||
|
||||
var a = SimpleLib.GetAllSites().First(s => s.name == "A");
|
||||
var b = SimpleLib.GetAllSites().First(s => s.name == "B");
|
||||
|
||||
var plan1 = new SegmentPlan() { usingCar = this };
|
||||
plan1.fields["action"] = "fetch";
|
||||
plan1.FindRoute(SimpleLib.GetSite(GetLastSite()), a);
|
||||
await plan1.Compile("fetch").Queue();
|
||||
|
||||
var plan2 = new SegmentPlan() { usingCar = this };
|
||||
plan2.fields["action"] = "go";
|
||||
plan2.FindRoute(SimpleLib.GetSite(GetLastSite()), b);
|
||||
await plan2.Compile("go").Queue();
|
||||
|
||||
var plan3 = new SegmentPlan() { usingCar = this };
|
||||
plan3.fields["action"] = "put";
|
||||
plan3.FindRoute(SimpleLib.GetSite(GetLastSite()), a);
|
||||
await plan3.Compile("put").Queue();
|
||||
|
||||
var plan4 = new SegmentPlan() { usingCar = this };
|
||||
plan4.fields["action"] = "go";
|
||||
plan4.FindRoute(SimpleLib.GetSite(GetLastSite()), b);
|
||||
await plan4.Compile("go").Queue();
|
||||
|
||||
}
|
||||
|
||||
[MethodMember(Name = "循环停止测试")]
|
||||
[I18N.DocumentTranslation(Name = "Stop Loop Test", locale = "en")]
|
||||
public void LoopTestStop()
|
||||
{
|
||||
LoopTestRunning = false;
|
||||
}
|
||||
public override string SetDisplayInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
var str = $"{name}({id})\n";
|
||||
status.enums.TryGetValue("ChassisMode", out var manual);
|
||||
var ms = manual == "0" ? "自动" : "手动";
|
||||
status.enums.TryGetValue("Soc", out var soc);
|
||||
str = $"{str}{ms}|soc:{soc}";
|
||||
var alarmStr = Commons.GetCarStatus(this, "AlarmInfo");
|
||||
if (alarmStr != "")
|
||||
str = $"{str}|{alarmStr}";
|
||||
//if (status.enums.TryGetValue("mAlarm", out var mAlarm) && mAlarm != "")
|
||||
// str = $"{str}|M:{mAlarm}";
|
||||
//var hasC = status.enums.TryGetValue("cAlarm", out var cAlarm) && cAlarm != "";
|
||||
//if (mAlarm != "" && hasC)
|
||||
// str = $"{str}|";
|
||||
//if (hasC)
|
||||
// str = $"{str}C:{cAlarm}";
|
||||
//var L_step = status.enums.TryGetValue("ls", out var ls) && ls != "";
|
||||
//if (L_step)
|
||||
//{
|
||||
// str = $"{str}|ls:{ls}";
|
||||
//}
|
||||
if (status.enums.TryGetValue("ElectricCurrent", out var electricCurrent) &&
|
||||
!string.IsNullOrEmpty(electricCurrent))
|
||||
{
|
||||
var charging = float.Parse(electricCurrent) * 0.01f > 0 ? "充电中" : "未充电";
|
||||
str = $"{str}|charge:{charging}";
|
||||
}
|
||||
|
||||
var thresSpeed = Commons.CarValue(this, "ThresSpeed");
|
||||
str = $"{str}{((thresSpeed <= 0) ? "|避障触发" : "")}";
|
||||
return str;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return "bad car";
|
||||
}
|
||||
}
|
||||
|
||||
[MethodMember(Name = "进入小车远程", Description = "进入小车远程桌面")]
|
||||
public void Mstsc()
|
||||
{
|
||||
var ip = this.address;
|
||||
// 启动mstsc并传递IP地址
|
||||
Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "mstsc",
|
||||
Arguments = $"/v:{ip}",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[MethodMember(Name = "显示车辆监控", Description = "打开车辆状态监控窗口")]
|
||||
public void ShowVehicleMonitor()
|
||||
{
|
||||
VehicleMonitor.ShowMonitor();
|
||||
}
|
||||
|
||||
[MethodMember(Name = "模拟电量", Description = "/")]
|
||||
public void SimarSoc()
|
||||
{
|
||||
if (InputBox.ShowDialog("模拟电量", "模拟电量Soc", "0", InputBox.Buttons.OkCancel) == SimpleLite.DialogResult.Cancel) return;
|
||||
string value = InputBox.ResultValue;
|
||||
try
|
||||
{
|
||||
status.enums["Soc"] = (60 + int.Parse(value)).ToString();
|
||||
status.enums["Voltage"] = (50 + int.Parse(value)).ToString();
|
||||
status.enums["ElectricCurrent"] = (40 + int.Parse(value)).ToString();
|
||||
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
[MethodMember(Name = "立即强制结束new", Description = "立刻终止小车运行,重启C,并让小车下线")]
|
||||
[I18N.DocumentTranslation(Name = "Force Stop new", Description = "Stop car & reset clumsy", locale = "en")]
|
||||
public new async void ForceStop()
|
||||
{
|
||||
try
|
||||
{
|
||||
NoSchedule(true);
|
||||
siteID = -1;
|
||||
Commons.ClearTags(tags);
|
||||
new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
AppendDebug("restarting clumsy");
|
||||
Get("reset");
|
||||
AppendDebug("wait for any pending task to flush.");
|
||||
try
|
||||
{
|
||||
status.programs.task.Wait();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
AppendDebug("Clumsy Restarted");
|
||||
NoSchedule(true);
|
||||
siteID = -1;
|
||||
Commons.DeleteTag(tags, "occupied");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(
|
||||
$@"未能立即强制结束小车{name}({id}),原因:{ExceptionFormatter.FormatEx(ex)}"
|
||||
);
|
||||
}
|
||||
})
|
||||
{
|
||||
Name = $"ForceStop:{name}({id})"
|
||||
}.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodMember(Name = "返厂检修new", Description = "不再刷新小车状态,当然也不再被调度")]
|
||||
[I18N.DocumentTranslation(Name = "Blown new", Description = "Car no longer refresh status and scheduling", locale = "en")]
|
||||
public new void Blown()
|
||||
{
|
||||
AppendDebug("ui-blown");
|
||||
Diagnosis.Post($"Car {name}({id}) blown");
|
||||
NoSchedule();
|
||||
siteID = -1;
|
||||
tags.Clear();
|
||||
status.usage.AddUsage(
|
||||
"base",
|
||||
new CarUsage.CarUsageInfo { scheduling = false, refreshing = false }
|
||||
);
|
||||
lstatus = "返厂检修";
|
||||
}
|
||||
|
||||
[MethodMember(Name = "现场检修new ", Description = "让小车不再被调度,但仍然刷新状态,并且当前点不再使用")]
|
||||
[I18N.DocumentTranslation(Name = "Repair new", Description = "Car no longer scheduling but still refresh status", locale = "en")]
|
||||
public new void Repair()
|
||||
{
|
||||
AppendDebug("ui-repair");
|
||||
Diagnosis.Post($"Repair Car {name}({id})");
|
||||
NoSchedule();
|
||||
siteID = -1;
|
||||
Commons.DeleteTag(tags, "occupied");
|
||||
|
||||
lstatus = "现场检修";
|
||||
}
|
||||
|
||||
public void newReset(int resetId = 0)
|
||||
{
|
||||
//if (!status.programs.task.IsCompleted &&
|
||||
// MessageBox.Show($"小车{name}({id})当前有任务:{status.programs.printStatus()},确认初始化?", "确认",
|
||||
// MessageBoxButtons.YesNo) == DialogResult.No) return;
|
||||
try
|
||||
{
|
||||
if (GetLastSite() != -1 && Commons.GetVehicleStatus((Car)this)!= VehicleStatus.NeedInit)
|
||||
{
|
||||
Diagnosis.Post($"Car{id}初始化失败:已初始化");
|
||||
return;
|
||||
}
|
||||
var debug = "ui invoke reset. ";
|
||||
|
||||
// todo: 找点和线段的最近点。。。
|
||||
// lock
|
||||
UISite site1 = null;
|
||||
float dist = float.MaxValue;
|
||||
List<(float, Site, Site)> trackList = new List<(float, Site, Site)>();
|
||||
if (resetId == 0)
|
||||
{
|
||||
|
||||
(float bias, Vector2 hPnt, float fd) Project2DLine(
|
||||
Vector2 pnt,
|
||||
Vector2 segSt,
|
||||
Vector2 segEnd
|
||||
)
|
||||
{
|
||||
var dir = Vector2.Normalize(segEnd - segSt);
|
||||
var fd = Vector2.Dot(pnt - segSt, dir);
|
||||
var hPnt = segSt + fd * dir;
|
||||
var bias = dir.X * (pnt.Y - segSt.Y) - (pnt.X - segSt.X) * dir.Y;
|
||||
return (bias, hPnt, fd);
|
||||
}
|
||||
|
||||
foreach (var allTrack in SimpleLib.GetAllTracks())
|
||||
{
|
||||
var s = SimpleLib.GetSite(allTrack.siteA);
|
||||
var e = SimpleLib.GetSite(allTrack.siteB);
|
||||
var (b, h, fd) = Project2DLine(
|
||||
new Vector2(x, y),
|
||||
new Vector2(s.x, s.y),
|
||||
new Vector2(e.x, e.y)
|
||||
);
|
||||
b = Math.Abs(b);
|
||||
if (fd < 0 || fd > 1)
|
||||
continue;
|
||||
if (dist > b)
|
||||
{
|
||||
UISite ss;
|
||||
if (fd < 0.5)
|
||||
ss = (UISite)s;
|
||||
else
|
||||
ss = (UISite)e;
|
||||
|
||||
foreach (var ccar in SimpleLib.GetAllCars())
|
||||
{
|
||||
if (ccar == this)
|
||||
continue;
|
||||
if (ccar.status.holdingLocks.Contains(s.id))
|
||||
goto next;
|
||||
}
|
||||
|
||||
dist = b;
|
||||
site1 = ss;
|
||||
next:
|
||||
;
|
||||
}
|
||||
}
|
||||
float radius = 5000;
|
||||
//根据小车为圆心找到半径radius内的站点
|
||||
var surroundSites = SimpleLib.GetAllSites().ToList().FindAll(p => LessMath.dist(x, y, p.x, p.y) <= radius);
|
||||
foreach (var allTrack in SimpleLib.GetAllTracks())
|
||||
{
|
||||
var s = SimpleLib.GetSite(allTrack.siteA);
|
||||
var e = SimpleLib.GetSite(allTrack.siteB);
|
||||
if (!surroundSites.Contains(s) && !surroundSites.Contains(e) && surroundSites.Count != 0) continue;
|
||||
|
||||
var (b, h, fd) = Project2DLine(new Vector2(x, y), new Vector2(s.x, s.y), new Vector2(e.x, e.y));
|
||||
b = Math.Abs(b);
|
||||
double pathAngle = Double.NaN;
|
||||
if (allTrack.direction == 1)
|
||||
pathAngle = Math.Atan2((e.y - s.y), (e.x - s.x)) / Math.PI * 180;
|
||||
else if (allTrack.direction == 2)
|
||||
pathAngle = Math.Atan2((e.y - s.y), (e.x - s.x)) / Math.PI * 180 + 180;
|
||||
else
|
||||
trackList.Add((b, s, e));
|
||||
if (Math.Abs(this.th - pathAngle) < 30)
|
||||
trackList.Add((b, s, e));
|
||||
foreach (var ccar in SimpleLib.GetAllCars())
|
||||
{
|
||||
if (ccar == this) continue;
|
||||
if (ccar.status.holdingLocks.Contains(s.id)) goto next;
|
||||
}
|
||||
next:;
|
||||
|
||||
}
|
||||
//var sites = trackList.OrderBy(p => p.Item1).ToArray();
|
||||
List<(double, Site)> distList = new List<(double, Site)>();
|
||||
foreach (var site in trackList)
|
||||
{
|
||||
if (site.Item2.fields.ContainsKey("no_reset") || site.Item3.fields.ContainsKey("no_reset"))
|
||||
continue;
|
||||
foreach (var ccar in SimpleLib.GetAllCars())
|
||||
{
|
||||
if (ccar == this) continue;
|
||||
if (ccar.status.holdingLocks.Contains(site.Item2.id) || ccar.status.holdingLocks.Contains(site.Item3.id)) goto next;
|
||||
}
|
||||
distList.Add((LessMath.dist(site.Item2.x, site.Item2.y, x, y), site.Item2));
|
||||
distList.Add((LessMath.dist(site.Item3.x, site.Item3.y, x, y), site.Item3));
|
||||
next:;
|
||||
}
|
||||
|
||||
site1 = (UISite)distList.OrderBy(p => p.Item1).First().Item2;
|
||||
|
||||
//foreach (var site in SimpleLib.GetAllSites())
|
||||
//{
|
||||
// if (site.fields.ContainsKey("no_reset"))
|
||||
// continue;
|
||||
// foreach (var ccar in SimpleLib.GetAllCars())
|
||||
// {
|
||||
// if (ccar == this)
|
||||
// continue;
|
||||
// if (ccar.status.holdingLocks.Contains(site.id))
|
||||
// goto next;
|
||||
// }
|
||||
|
||||
// var d = LessMath.dist(site.x, site.y, x, y);
|
||||
// if (d < dist)
|
||||
// {
|
||||
// dist = (float)d;
|
||||
// site1 = (UISite)site;
|
||||
// }
|
||||
// next:
|
||||
// ;
|
||||
//}
|
||||
TrafficReset(site1, true, strict: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
TrafficReset(SimpleLib.GetSite(resetId), true, strict: false);
|
||||
}
|
||||
|
||||
|
||||
siteID = site1.id;
|
||||
|
||||
lstatus = "上线";
|
||||
debug += $"site={site1.id}";
|
||||
AppendDebug(debug);
|
||||
G.pushStatus($"Car {name}({id}) reset to Site {site1.id}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Diagnosis.Post($"{this.name}---初始化失败--{e.Message}", $"{this.name}", true);
|
||||
}
|
||||
}
|
||||
|
||||
HttpClient hc = new HttpClient() { Timeout = TimeSpan.FromMilliseconds(300) };
|
||||
// [MethodMember(name = "小车暂停", desc = "小车暂停")]
|
||||
public void EmergencyStop(string reason)
|
||||
{
|
||||
Diagnosis.Post($"car{name}:EmergencyStop");
|
||||
hc.GetStringAsync($"http://{address}:8008/setValue?FieldName=ISRelease&Value=1");
|
||||
}
|
||||
|
||||
//[MethodMember(name = "小车暂停恢复", desc = "小车暂停恢复")]
|
||||
public void EmergencyRelease()
|
||||
{
|
||||
Diagnosis.Post($"car{name}:EmergencyStop release");
|
||||
hc.GetStringAsync($"http://{address}:8008/setValue?FieldName=ISRelease&Value=0");
|
||||
}
|
||||
//[MethodMember(name = "小车重启C", desc = "小车重启Clumsy")]
|
||||
public void ResetClumsy()
|
||||
{
|
||||
Diagnosis.Post($"car{name}-{address}:ResetClumsy");
|
||||
hc.GetStringAsync($"http://{address}:8008/reset");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 专门用于控制turn的问题
|
||||
/// </summary>
|
||||
public class KivaCarTrackCoder : ITrackCoder
|
||||
{
|
||||
public bool toBlock()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// 用于记录上一路径的角度
|
||||
/// </summary>
|
||||
public static double lastTh = -1;
|
||||
|
||||
public bool Code(SegmentPlan plan, Track track, Site src, Site dst, int i)
|
||||
{
|
||||
var trackInfo = StringDictConvert<KivaTrackFields>.Convert(track.fields);
|
||||
var srcInfo = StringDictConvert<KivaSiteFields>.Convert(src.fields);
|
||||
Console.WriteLine($@"srcInfo.Turn:{srcInfo.Turn},i={1}");
|
||||
if (!srcInfo.Turn) //不包含旋转指令,直接跳过
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (i == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var lastSite = plan.segments[i - 3] as Site;
|
||||
if (lastSite == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var lastPathDir = Math.Round(Math.Atan2(src.y - lastSite.y, src.x - lastSite.x) / Math.PI * 2) * 90;
|
||||
var curPathDir = Math.Round(Math.Atan2(dst.y - src.y, dst.x - src.x) / Math.PI * 2) * 90;
|
||||
if (Math.Abs(curPathDir - lastPathDir) < 45)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var angleTarget = trackInfo.ReverseDst == dst.id || trackInfo.Reverse ? curPathDir + 180 : curPathDir;
|
||||
|
||||
plan.codeArr[i] += $"agv.Wait(); agv.SyncRotate({angleTarget}); agv.Wait(); ";
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using LessokajiWeaverUtilities.Utilities;
|
||||
using SimpleLite;
|
||||
using SimpleLite.RCS;
|
||||
using SimpleLite.RCS.CarTypes;
|
||||
using SimpleLite.Props;
|
||||
using SimpleLite.UI;
|
||||
using SimpleCore;
|
||||
using SimpleCore.Compiler;
|
||||
using SimpleCore.Library;
|
||||
using SimpleCore.PropType;
|
||||
using StandardScene.Magnetic.Coders;
|
||||
using StandardScene.Utils;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StandardScene.CarTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// 磁导航专用车型(scene.mag 平台)。
|
||||
/// <para>与激光/二维码车(Kiva、叉车等走 HTTP:8008)不同:磁导航车与下位机通过 <b>UDP 协议</b> 交互;
|
||||
/// 路径由 <see cref="MagneticTrackCoder"/> 生成 <c>agv.MagGo</c> / <c>agv.NaiveMagGo</c> 脚本
|
||||
/// (按 <c>track.Magnet</c> 触发)。本类只承载磁导航相关能力。</para>
|
||||
/// <para>⚠ UDP 报文的端口 / 字节布局 / 命令码因下位机协议而异,下方以常量 + TODO 标注,
|
||||
/// 待按实际磁导航 AGV 协议填充 <see cref="ParseStatus"/> 与各控制命令的编码。</para>
|
||||
/// </summary>
|
||||
[ProgramTrackCoderSettings(priority = 19, program = typeof(MagneticTrackCoder))]
|
||||
[CarType(Name = "磁导航车", editor = typeof(MagCar))]
|
||||
[I18N.DocumentTranslation(Name = "Magnetic AGV", locale = "en")]
|
||||
[EnvelopConfig(centerX = 0, centerY = 0, lengthX = 1000, lengthY = 600)]
|
||||
public class MagCar : GhostCar
|
||||
{
|
||||
// ── 磁导航 UDP 通信参数(默认值待按实际下位机协议确认)──────────────────
|
||||
/// <summary>下位机 UDP 命令端口(RCS → AGV 下发)。</summary>
|
||||
[FieldMember] public int UdpCommandPort = 5000;
|
||||
|
||||
/// <summary>本机 UDP 状态监听端口(AGV → RCS 上报)。</summary>
|
||||
[FieldMember] public int UdpStatusPort = 5001;
|
||||
|
||||
[FieldMember] public float CarLength = 1000;
|
||||
[FieldMember] public float CarWidth = 600;
|
||||
|
||||
private UdpClient _udp;
|
||||
private CancellationTokenSource _cts;
|
||||
private readonly object _sendLock = new object();
|
||||
|
||||
public static async Task<MagCar> Create()
|
||||
{
|
||||
var car = new MagCar
|
||||
{
|
||||
lstatus = "连接中",
|
||||
address = "127.0.0.1",
|
||||
name = "磁导航车",
|
||||
haveCoordination = true,
|
||||
speed = 1,
|
||||
};
|
||||
car.StartUdp();
|
||||
return car;
|
||||
}
|
||||
|
||||
// ── UDP 通信:协作式停止 + 单帧异常隔离(参考 ChargeUdpService 范式)─────────
|
||||
/// <summary>启动磁导航 UDP 通信(幂等:会先停止已有连接)。</summary>
|
||||
public void StartUdp()
|
||||
{
|
||||
StopUdp();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
Task.Run(() => ReceiveLoop(token), token);
|
||||
}
|
||||
|
||||
/// <summary>停止磁导航 UDP 通信并释放资源。</summary>
|
||||
public void StopUdp()
|
||||
{
|
||||
try { _cts?.Cancel(); } catch { /* 取消令牌释放兜底 */ }
|
||||
try { _udp?.Close(); } catch { /* 关闭套接字兜底 */ }
|
||||
_udp = null;
|
||||
_cts = null;
|
||||
}
|
||||
|
||||
private async Task ReceiveLoop(CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
_udp = new UdpClient(UdpStatusPort);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 多车共用监听端口时此处会绑定失败——若实际协议为单端口多车,应改为单例监听 + 按来源 IP 分发
|
||||
Diagnosis.Log($"磁导航车{name}({id}) UDP 监听端口 {UdpStatusPort} 绑定失败: {ex.Message}", "MagCar", true);
|
||||
return;
|
||||
}
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _udp.ReceiveAsync();
|
||||
// 多车共用监听端口时按来源 IP 过滤,仅处理本车报文
|
||||
if (!string.IsNullOrEmpty(address) &&
|
||||
!string.Equals(result.RemoteEndPoint.Address.ToString(), address, StringComparison.Ordinal))
|
||||
continue;
|
||||
ParseStatus(result.Buffer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 单帧异常(越界 / 半包 / 套接字关闭)不得中断本车 UDP 接收循环
|
||||
if (token.IsCancellationRequested) break;
|
||||
Diagnosis.Log($"磁导航车{name}({id}) UDP 接收异常: {e.Message}", "MagCar", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析下位机 UDP 状态报文并写入 <c>status.enums</c>。
|
||||
/// TODO(待协议确认):下方字节偏移为占位,请按实际磁导航 AGV 协议替换。
|
||||
/// </summary>
|
||||
private void ParseStatus(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length < 4) return; // 长度校验,避免半包 / 越界
|
||||
// —— 按实际协议填充,例如:——
|
||||
// status.enums["ChassisMode"] = data[?].ToString();
|
||||
// status.enums["Soc"] = data[?].ToString();
|
||||
// status.enums["AlarmLevel"] = data[?].ToString();
|
||||
// status.enums["AlarmInfo"] = ...;
|
||||
// 若磁导航上报绝对位姿,可在此回填 x / y / th(坐标系需与地图一致)。
|
||||
}
|
||||
|
||||
/// <summary>向下位机发送 UDP 命令。TODO:命令字节编码按实际协议实现。</summary>
|
||||
private void SendUdp(byte[] payload)
|
||||
{
|
||||
if (payload == null || payload.Length == 0) return;
|
||||
try
|
||||
{
|
||||
lock (_sendLock)
|
||||
{
|
||||
using var sender = new UdpClient();
|
||||
sender.Send(payload, payload.Length, address, UdpCommandPort);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Diagnosis.Log($"磁导航车{name}({id}) UDP 发送失败: {e.Message}", "MagCar", true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void draw(Graphics eGraphics)
|
||||
{
|
||||
try
|
||||
{
|
||||
var w = CarLength;
|
||||
var h = CarWidth;
|
||||
var rect = new Rectangle((int)(-w / 2), (int)(-h / 2), (int)w, (int)h);
|
||||
eGraphics.FillRectangle(Brushes.SteelBlue, rect);
|
||||
eGraphics.DrawRectangle(Pens.White, rect);
|
||||
using var orientPen = new Pen(Color.White, 3);
|
||||
eGraphics.DrawLine(orientPen, 0, 0, (int)(w / 2), 0); // 朝向指示
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Diagnosis.Post("绘制磁导航车异常" + ExceptionFormatter.FormatEx(e), "绘制小车异常", true);
|
||||
}
|
||||
}
|
||||
|
||||
public override string SetDisplayInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
var str = $"{name}({id})\n";
|
||||
status.enums.TryGetValue("ChassisMode", out var manual);
|
||||
var ms = manual == "0" ? "自动" : "手动";
|
||||
status.enums.TryGetValue("Soc", out var soc);
|
||||
str = $"{str}{ms}|soc:{soc}";
|
||||
var alarmStr = Commons.GetCarStatus(this, "AlarmInfo");
|
||||
if (!string.IsNullOrEmpty(alarmStr))
|
||||
str = $"{str}|{alarmStr}";
|
||||
return str;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return "bad car";
|
||||
}
|
||||
}
|
||||
|
||||
[MethodMember(Name = "进入小车管理界面", Description = "在浏览器中打开小车 Web 页面")]
|
||||
[I18N.DocumentTranslation(Name = "Open vehicle management page", Description = "Open the car's management page in browser", locale = "en")]
|
||||
public void OpenVehicleManagementPage()
|
||||
{
|
||||
CarRemoteHelper.OpenVehicleWebPage(address);
|
||||
}
|
||||
|
||||
[MethodMember(Name = "显示车辆监控", Description = "打开车辆状态监控窗口")]
|
||||
public void ShowVehicleMonitor() => CarRemoteHelper.OpenVehicleWebPage(address);
|
||||
|
||||
[MethodMember(Name = "重连通信", Description = "重启磁导航 UDP 通信")]
|
||||
[I18N.DocumentTranslation(Name = "Reconnect", Description = "Restart magnetic UDP link", locale = "en")]
|
||||
public void ReconnectUdp() => StartUdp();
|
||||
}
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
using CommonUsage.Mathematics;
|
||||
using LessokajiWeaverUtilities.Utilities;
|
||||
using Nancy.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using SimpleLite;
|
||||
using SimpleLite.RCS;
|
||||
using SimpleLite.RCS.CarTypes;
|
||||
using SimpleLite.CADTools;
|
||||
using SimpleLite.Props;
|
||||
using SimpleLite.UI;
|
||||
using SimpleCore;
|
||||
using SimpleCore.Compiler;
|
||||
using SimpleCore.Library;
|
||||
using SimpleCore.PropType;
|
||||
using StandardScene.Model;
|
||||
using StandardScene.Coders;
|
||||
using StandardScene.Magnetic.Coders;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Numerics;
|
||||
using System.Runtime;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Track = SimpleCore.PropType.Track;
|
||||
|
||||
namespace StandardScene.CarTypes
|
||||
{
|
||||
// 磁循迹器已与 Kiva 版去重合并为唯一 StandardScene.Magnetic.Coders.MagneticTrackCoder。
|
||||
// 原 MWL 版 NaiveMagGo 缺少 ${track.Speed},合并后统一为含 Speed 的版本(用户确认)。
|
||||
// MultiWheelLifter 系字段袋下沉至基座 StandardScene.Core\CarTypes\MultiWheelLifterFields.cs
|
||||
// (MultiVehicleCar 等跨平台车型引用它们)。
|
||||
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 30,
|
||||
useVerb = "track.SleepTime!=0",
|
||||
templateString = "agv.Wait();agv.Sleep(${track.SleepTime});agv.Wait();",
|
||||
blockVerb = "false",
|
||||
siteFields = typeof(MultiWheelLifterSiteFields),
|
||||
trackFields = typeof(MultiWheelLifterTrackFields),
|
||||
planFields = typeof(MultiWheelLifterPlanFields))]
|
||||
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 30,
|
||||
useVerb = "track.TrayTarget!=0",
|
||||
templateString = "agv.Wait();agv.TrayControl(${track.TrayTarget});agv.Wait();",
|
||||
blockVerb = "false",
|
||||
siteFields = typeof(MultiWheelLifterSiteFields),
|
||||
trackFields = typeof(MultiWheelLifterTrackFields),
|
||||
planFields = typeof(MultiWheelLifterPlanFields))]
|
||||
|
||||
//调整方向+钻车
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 19,
|
||||
useVerb = "dst.name=='FetchSite'&&plan.action=='fetch'",
|
||||
templateString = "agv.Wait();agv.RotateToTarget(${plan.AngleTarget});agv.Wait();agv.TireFollowing(${plan.TireNum},${plan.FrontLidarDetect},${plan.FirstTire});agv.Wait();",
|
||||
blockVerb = "false",
|
||||
siteFields = typeof(MultiWheelLifterSiteFields),
|
||||
trackFields = typeof(MultiWheelLifterTrackFields),
|
||||
planFields = typeof(MultiWheelLifterPlanFields))]
|
||||
//[TemplateTrackCoderSettings(
|
||||
// priority = 19,
|
||||
// useVerb = "dst.FetchPlace && plan.action=='fetch'",
|
||||
// templateString = "agv.Wait();agv.RotateToTarget(${plan.AngleTarget});agv.Wait();",
|
||||
// blockVerb = "false",
|
||||
// siteFields = typeof(MultiWheelLifterSiteFields),
|
||||
// trackFields = typeof(MultiWheelLifterTrackFields),
|
||||
// planFields = typeof(MultiWheelLifterPlanFields))]
|
||||
|
||||
//下使能+夹抱+上使能
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 18,
|
||||
useVerb = "dst.name=='FetchSite'&&plan.action=='fetch'",
|
||||
templateString = "agv.DriverDisable();agv.Wait();agv.ClamptoTarget(${dst.ClampClose});agv.Wait();agv.DriverAble();agv.Wait();",
|
||||
blockVerb = "true",
|
||||
siteFields = typeof(MultiWheelLifterSiteFields),
|
||||
trackFields = typeof(MultiWheelLifterTrackFields),
|
||||
planFields = typeof(MultiWheelLifterPlanFields))]
|
||||
|
||||
[TemplateTrackCoderSettings(
|
||||
priority = 19,
|
||||
useVerb = "dst.tag>0 && src.tag>0",
|
||||
templateString = "agv.QrGo(${src.x},${src.y},${src.id},${src.tag},${dst.x},${dst.y},${dst.id},${dst.tag},${track.id}," +
|
||||
"${track.Speed},${track.Reverse || (track.ReverseDst == dst.id)}," +
|
||||
"${track.CarDirectionBias},${track.EnableCarAbsoluteDirection},${track.CarAbsoluteDirection}," +
|
||||
"${track.typeInfo});",
|
||||
blockVerb = "true",
|
||||
siteFields = typeof(MultiWheelLifterSiteFields),
|
||||
trackFields = typeof(MultiWheelLifterTrackFields),
|
||||
planFields = typeof(MultiWheelLifterPlanFields))]
|
||||
|
||||
// 通用避障/IO coder 已抽离为 StandardScene.Coders.*(见类前 [ProgramTrackCoderSettings] 引用)
|
||||
|
||||
// 避障区尺寸(4参,含中心点)切换 coder 已抽离为 StandardScene.Coders.AvoidanceParamCoder(见类前 [ProgramTrackCoderSettings] 引用)
|
||||
|
||||
// 纠偏阈值 coder 已抽离为 StandardScene.Coders.TrackingErrThreshCoder
|
||||
|
||||
[ProgramTrackCoderSettings(priority = 19, program = typeof(MagneticTrackCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 27, program = typeof(LidarAreaSwitchCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceDistanceCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 20, program = typeof(IoAreaSwitchCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 20, program = typeof(TrackingErrThreshCoder))]
|
||||
[ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceParamCoder))]
|
||||
|
||||
[EnvelopConfig(lengthX = 3000, lengthY = 1300, centerX = 0, centerY = 0)]
|
||||
|
||||
[CarType(Name = "多舵轮顶升车")]
|
||||
[I18N.DocumentTranslation(Name = "MultiWheel Lifter Car", locale = "en")]
|
||||
public class MultiWheelLifterCar : GhostCar
|
||||
{
|
||||
[FieldMember]public float CarLength = 2000;
|
||||
[FieldMember] public float CarWidth = 1400;
|
||||
public static async Task<MultiWheelLifterCar> Create()
|
||||
{
|
||||
var car = new MultiWheelLifterCar()
|
||||
{
|
||||
lstatus = "连接中",
|
||||
address = "127.0.0.1",
|
||||
name = "多舵轮顶升车"
|
||||
};
|
||||
return car;
|
||||
}
|
||||
|
||||
[MethodMember(Name = "进入小车远程", Description = "进入小车远程桌面")]
|
||||
[I18N.DocumentTranslation(Name = "Open remote desktop",Description = "Open the car's remote desktop", locale = "en")]
|
||||
public void Mstsc()
|
||||
{
|
||||
var ip = this.address;
|
||||
// 启动mstsc并传递IP地址
|
||||
Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "mstsc",
|
||||
Arguments = $"/v:{ip}",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void newTrafficReset(int resetId = 0)
|
||||
{
|
||||
//if (!status.programs.task.IsCompleted &&
|
||||
// MessageBox.Show($"小车{name}({id})当前有任务:{status.programs.printStatus()},确认初始化?", "确认",
|
||||
// MessageBoxButtons.YesNo) == DialogResult.No) return;
|
||||
try
|
||||
{
|
||||
if (GetLastSite() != -1 && Commons.GetVehicleStatus((Car)this) != VehicleStatus.NeedInit)
|
||||
{
|
||||
Diagnosis.Post($"Car{id}初始化失败:已初始化");
|
||||
return;
|
||||
}
|
||||
var debug = "ui invoke reset. ";
|
||||
|
||||
// todo: 找点和线段的最近点。。。
|
||||
// lock
|
||||
UISite site1 = null;
|
||||
float dist = float.MaxValue;
|
||||
List<(float, Site, Site)> trackList = new List<(float, Site, Site)>();
|
||||
if (resetId == 0)
|
||||
{
|
||||
|
||||
(float bias, Vector2 hPnt, float fd) Project2DLine(
|
||||
Vector2 pnt,
|
||||
Vector2 segSt,
|
||||
Vector2 segEnd
|
||||
)
|
||||
{
|
||||
var dir = Vector2.Normalize(segEnd - segSt);
|
||||
var fd = Vector2.Dot(pnt - segSt, dir);
|
||||
var hPnt = segSt + fd * dir;
|
||||
var bias = dir.X * (pnt.Y - segSt.Y) - (pnt.X - segSt.X) * dir.Y;
|
||||
return (bias, hPnt, fd);
|
||||
}
|
||||
|
||||
foreach (var allTrack in SimpleLib.GetAllTracks())
|
||||
{
|
||||
var s = SimpleLib.GetSite(allTrack.siteA);
|
||||
var e = SimpleLib.GetSite(allTrack.siteB);
|
||||
var (b, h, fd) = Project2DLine(
|
||||
new Vector2(x, y),
|
||||
new Vector2(s.x, s.y),
|
||||
new Vector2(e.x, e.y)
|
||||
);
|
||||
b = Math.Abs(b);
|
||||
if (fd < 0 || fd > 1)
|
||||
continue;
|
||||
if (dist > b)
|
||||
{
|
||||
UISite ss;
|
||||
if (fd < 0.5)
|
||||
ss = (UISite)s;
|
||||
else
|
||||
ss = (UISite)e;
|
||||
|
||||
foreach (var ccar in SimpleLib.GetAllCars())
|
||||
{
|
||||
if (ccar == this)
|
||||
continue;
|
||||
if (ccar.status.holdingLocks.Contains(s.id))
|
||||
goto next;
|
||||
}
|
||||
|
||||
dist = b;
|
||||
site1 = ss;
|
||||
next:
|
||||
;
|
||||
}
|
||||
}
|
||||
float radius = 5000;
|
||||
//根据小车为圆心找到半径radius内的站点
|
||||
var surroundSites = SimpleLib.GetAllSites().ToList().FindAll(p => LessMath.dist(x, y, p.x, p.y) <= radius);
|
||||
foreach (var allTrack in SimpleLib.GetAllTracks())
|
||||
{
|
||||
var s = SimpleLib.GetSite(allTrack.siteA);
|
||||
var e = SimpleLib.GetSite(allTrack.siteB);
|
||||
if (!surroundSites.Contains(s) && !surroundSites.Contains(e) && surroundSites.Count != 0) continue;
|
||||
|
||||
var (b, h, fd) = Project2DLine(new Vector2(x, y), new Vector2(s.x, s.y), new Vector2(e.x, e.y));
|
||||
b = Math.Abs(b);
|
||||
double pathAngle = Double.NaN;
|
||||
if (allTrack.direction == 1)
|
||||
pathAngle = Math.Atan2((e.y - s.y), (e.x - s.x)) / Math.PI * 180;
|
||||
else if (allTrack.direction == 2)
|
||||
pathAngle = Math.Atan2((e.y - s.y), (e.x - s.x)) / Math.PI * 180 + 180;
|
||||
else
|
||||
trackList.Add((b, s, e));
|
||||
if (Math.Abs(this.th - pathAngle) < 30)
|
||||
trackList.Add((b, s, e));
|
||||
foreach (var ccar in SimpleLib.GetAllCars())
|
||||
{
|
||||
if (ccar == this) continue;
|
||||
if (ccar.status.holdingLocks.Contains(s.id)) goto next;
|
||||
}
|
||||
next:;
|
||||
|
||||
}
|
||||
//var sites = trackList.OrderBy(p => p.Item1).ToArray();
|
||||
List<(double, Site)> distList = new List<(double, Site)>();
|
||||
foreach (var site in trackList)
|
||||
{
|
||||
if (site.Item2.fields.ContainsKey("no_reset") || site.Item3.fields.ContainsKey("no_reset"))
|
||||
continue;
|
||||
foreach (var ccar in SimpleLib.GetAllCars())
|
||||
{
|
||||
if (ccar == this) continue;
|
||||
if (ccar.status.holdingLocks.Contains(site.Item2.id) || ccar.status.holdingLocks.Contains(site.Item3.id)) goto next;
|
||||
}
|
||||
distList.Add((LessMath.dist(site.Item2.x, site.Item2.y, x, y), site.Item2));
|
||||
distList.Add((LessMath.dist(site.Item3.x, site.Item3.y, x, y), site.Item3));
|
||||
next:;
|
||||
}
|
||||
|
||||
site1 = (UISite)distList.OrderBy(p => p.Item1).First().Item2;
|
||||
|
||||
//foreach (var site in SimpleLib.GetAllSites())
|
||||
//{
|
||||
// if (site.fields.ContainsKey("no_reset"))
|
||||
// continue;
|
||||
// foreach (var ccar in SimpleLib.GetAllCars())
|
||||
// {
|
||||
// if (ccar == this)
|
||||
// continue;
|
||||
// if (ccar.status.holdingLocks.Contains(site.id))
|
||||
// goto next;
|
||||
// }
|
||||
|
||||
// var d = LessMath.dist(site.x, site.y, x, y);
|
||||
// if (d < dist)
|
||||
// {
|
||||
// dist = (float)d;
|
||||
// site1 = (UISite)site;
|
||||
// }
|
||||
// next:
|
||||
// ;
|
||||
//}
|
||||
TrafficReset(site1, true, strict: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
TrafficReset(SimpleLib.GetSite(resetId), true, strict: false);
|
||||
}
|
||||
|
||||
|
||||
siteID = site1.id;
|
||||
|
||||
lstatus = "上线";
|
||||
debug += $"site={site1.id}";
|
||||
AppendDebug(debug);
|
||||
G.pushStatus($"Car {name}({id}) reset to Site {site1.id}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Diagnosis.Post($"{this.name}---初始化失败--{e.Message}", $"{this.name}", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void draw(Graphics eGraphics)
|
||||
{
|
||||
var HalfCarLength = CarLength / 2;
|
||||
var HalfCarWidth = CarWidth / 2;
|
||||
eGraphics.FillRectangle(Brushes.Gray, -HalfCarLength, -HalfCarWidth, HalfCarLength * 2, HalfCarWidth * 2);
|
||||
eGraphics.DrawRectangle(Pens.White, -HalfCarLength, -HalfCarWidth, HalfCarLength * 2, HalfCarWidth * 2);
|
||||
eGraphics.DrawLine(Pens.White, HalfCarLength - HalfCarWidth, -HalfCarWidth, HalfCarLength, 0);
|
||||
eGraphics.DrawLine(Pens.White, HalfCarLength - HalfCarWidth, HalfCarWidth, HalfCarLength, 0);
|
||||
}
|
||||
|
||||
public override string SetDisplayInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
var str = $"{name}({id})\n";
|
||||
status.enums.TryGetValue("ChassisMode", out var manual);
|
||||
var ms = manual == "0" ? "自动" : "手动";
|
||||
status.enums.TryGetValue("Soc", out var soc);
|
||||
str = $"{str}{ms}|soc:{soc}";
|
||||
var alarmStr = Commons.GetCarStatus(this, "AlarmInfo");
|
||||
if (alarmStr != "")
|
||||
str = $"{str}|{alarmStr}";
|
||||
if (status.enums.TryGetValue("ElectricCurrent", out var electricCurrent) &&
|
||||
!string.IsNullOrEmpty(electricCurrent))
|
||||
{
|
||||
var charging = float.Parse(electricCurrent) * 0.01f > 0 ? "充电中" : "未充电";
|
||||
str = $"{str}|charge:{charging}";
|
||||
}
|
||||
|
||||
var thresSpeed = Commons.CarValue(this, "ThresSpeed");
|
||||
str = $"{str}{((thresSpeed <= 0) ? "|避障触发" : "")}";
|
||||
return str;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return "bad car";
|
||||
}
|
||||
}
|
||||
|
||||
private bool _running = false;
|
||||
|
||||
[MethodMember(Name = "测试", Description = "循环")]
|
||||
[I18N.DocumentTranslation(Name = "Test", Description = "Looping", locale = "en")]
|
||||
public async void Test()
|
||||
{
|
||||
var siteA = SimpleLib.GetAllSites().First(s => s.name == "A");
|
||||
var siteB = SimpleLib.GetAllSites().First(s => s.name == "B");
|
||||
_running = true;
|
||||
while (_running)
|
||||
{
|
||||
var plan = new SegmentPlan() { usingCar = this };
|
||||
plan.FindRoute(SimpleLib.GetSite(this.GetLastSite()), siteA);
|
||||
await plan.Compile("GoA").Queue();
|
||||
var planB = new SegmentPlan() { usingCar = this };
|
||||
planB.FindRoute(SimpleLib.GetSite(this.GetLastSite()), siteB);
|
||||
await planB.Compile("GoB").Queue();
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodMember(Name = "停止测试")]
|
||||
[I18N.DocumentTranslation(Name = "Stop Test", Description = "Stop Looping", locale = "en")]
|
||||
public void Stop()
|
||||
{
|
||||
_running = false;
|
||||
}
|
||||
HttpClient hc = new HttpClient() { Timeout = TimeSpan.FromMilliseconds(300) };
|
||||
[MethodMember(Name = "小车暂停", Description = "小车暂停")]
|
||||
[I18N.DocumentTranslation(Name = "Emergency Stop",Description = "Emergency Stop the car", locale = "en")]
|
||||
public void EmergencyStop(string reason)
|
||||
{
|
||||
Diagnosis.Post($"car{name}:EmergencyStop");
|
||||
hc.GetStringAsync($"http://{address}:8008/setValue?FieldName=ISRelease&Value=1");
|
||||
}
|
||||
|
||||
[MethodMember(Name = "小车暂停恢复", Description = "小车暂停恢复")]
|
||||
[I18N.DocumentTranslation(Name = "Emergency Release",Description = "Release Emergency Stop of the car", locale = "en")]
|
||||
public void EmergencyRelease()
|
||||
{
|
||||
Diagnosis.Post($"car{name}:EmergencyStop release");
|
||||
hc.GetStringAsync($"http://{address}:8008/setValue?FieldName=ISRelease&Value=0");
|
||||
}
|
||||
[MethodMember(Name = "小车重启C", Description = "小车重启Clumsy")]
|
||||
[I18N.DocumentTranslation(Name = "Reset Clumsy",Description = "Reset Clumsy of the car", locale = "en")]
|
||||
public void ResetClumsy()
|
||||
{
|
||||
Diagnosis.Post($"car{name}-{address}:ResetClumsy");
|
||||
hc.GetStringAsync($"http://{address}:8008/reset");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ using StandardScene.CarTypes;
|
||||
namespace StandardScene.Magnetic
|
||||
{
|
||||
/// <summary>
|
||||
/// scene.mag 平台画像:磁导航场景插件(磁条循迹为主,兼容二维码地标段)。
|
||||
/// scene.mag 平台画像:磁导航场景插件。
|
||||
/// <para>车型为磁导航专用 <see cref="MagCar"/>(与下位机通过 UDP 协议交互);路径由
|
||||
/// MagneticTrackCoder 生成 agv.MagGo / agv.NaiveMagGo 脚本(按 track.Magnet 触发)。</para>
|
||||
/// 宿主(SimpleLite)加载本 dll 后反射实例化并 OnActivate / 注册。
|
||||
/// </summary>
|
||||
public sealed class MagneticSceneProfile : NavigationProfileBase
|
||||
@@ -19,13 +21,12 @@ namespace StandardScene.Magnetic
|
||||
|
||||
public override IReadOnlyList<Type> CarTypes => new[]
|
||||
{
|
||||
typeof(Kiva),
|
||||
typeof(MultiWheelLifterCar),
|
||||
typeof(MagCar),
|
||||
};
|
||||
|
||||
public override void OnActivate(ISceneContext context)
|
||||
{
|
||||
context.Log($"{DisplayName} 已激活(车型:Kiva / 多舵轮顶升车)");
|
||||
context.Log($"{DisplayName} 已激活(车型:磁导航车 MagCar)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<RootNamespace>StandardScene.Magnetic</RootNamespace>
|
||||
<AssemblyName>StandardScene.Magnetic</AssemblyName>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"coreVersion": ">=1.0.0",
|
||||
"requiresCore": "StandardScene.dll",
|
||||
"provides": {
|
||||
"carTypes": [ "Kiva", "MultiWheelLifterCar" ],
|
||||
"carTypes": [ "MagCar" ],
|
||||
"missionTypes": []
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user