init commit

This commit is contained in:
zhaowei.huang
2026-06-14 11:19:15 +08:00
parent e79a3815a5
commit c8e540d272
174 changed files with 60830 additions and 39 deletions
@@ -0,0 +1,60 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>StandardScene</RootNamespace>
<AssemblyName>StandardScene.Protocol.VDA5050</AssemblyName>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Platforms>AnyCPU;x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<Deterministic>true</Deterministic>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<NoWarn>$(NoWarn);NU1701;CS0618;CS0612;MSB3277;CA1416</NoWarn>
<AssemblySearchPaths>{HintPathFromItem};{TargetFrameworkDirectory};{RawFileName};{GAC}</AssemblySearchPaths>
</PropertyGroup>
<!-- 插件清单:随 dll 输出到 plugins/(约定 <dll>.scene.json,对接 SimpleLite /scenes 选择性加载) -->
<ItemGroup>
<None Update="StandardScene.Protocol.VDA5050.scene.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<!-- 基座依赖:导航无关基础类型/字段袋来自 Core(通过 InternalsVisibleTo 访问其 internal 类型) -->
<ItemGroup>
<ProjectReference Include="..\StandardScene.Core\StandardScene.Core.csproj" />
</ItemGroup>
<!-- 程序集引用:与 Core 保持一致的本地契约/工具 dll -->
<ItemGroup>
<Reference Include="SimpleLite">
<HintPath>E:\Work\Core\Simple-FR\Simple\SimpleLite\bin\Debug\SimpleLite.dll</HintPath>
</Reference>
<Reference Include="SimpleCore">
<HintPath>E:\Work\Core\Simple-FR\Simple\SimpleCore\bin\Debug\netstandard2.0\SimpleCore.dll</HintPath>
</Reference>
<Reference Include="CommonUsage">
<HintPath>D:\MDCS\Dependencies\Commons\CommonUsage.dll</HintPath>
</Reference>
<Reference Include="Topaz">
<HintPath>E:\Work\Core\Simple-FR\Simple\tools\Topaz.dll</HintPath>
</Reference>
<Reference Include="LessokajiWeaverUtilities">
<HintPath>E:\Work\Core\Simple-FR\Simple\SimpleLite\bin\Debug\LessokajiWeaverUtilities.dll</HintPath>
</Reference>
</ItemGroup>
<!-- NuGetVDA5050/MQTT 协议栈所需 -->
<ItemGroup>
<PackageReference Include="MQTTnet" Version="4.3.6.1152" />
<PackageReference Include="MQTTnet.Extensions.ManagedClient" Version="4.3.6.1152" />
<PackageReference Include="Nancy" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Jint" Version="4.6.3" />
</ItemGroup>
</Project>
@@ -0,0 +1,10 @@
{
"id": "scene.vda5050",
"displayName": "VDA5050 协议车型",
"assembly": "StandardScene.Protocol.VDA5050.dll",
"coreVersion": ">=1.0.0",
"requiresCore": "StandardScene.dll",
"provides": {
"carTypes": [ "VDA5050Car" ]
}
}
@@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommonUsage.Protocols.VDA5050.Messages;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Protocol;
using MQTTnet.Server;
using Newtonsoft.Json;
using SimpleCore;
using SimpleCore.Library;
namespace StandardScene.CarTypes
{
public class MasterMQTTCommunication
{
private IManagedMqttClient _client;
private IManagedMqttClient _visualizationClient; // Separate client for visualization
private const string OrderTopic = "vda5050/frldAGV/order";
private const string StateTopic = "vda5050/frldAGV/state";
private const string VisualizationTopic = "vda5050/frldAGV/visualization";
private readonly string _connectionTopic = "vda5050/frldAGV/connection";
private readonly string _instantAction = "vda5050/frldAGV/instantActions";
private readonly string _factsheet = "vda5050/frldAGV/factsheet";
private readonly string _carFields = "vda5050/frldAGV/carFields";
//192.168.123.5
public MasterMQTTCommunication()
{
// Initialize MQTT client and connect to broker
var mqttFactory = new MqttFactory();
_client = mqttFactory.CreateManagedMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", 1883) // Connect to the local broker
.WithCleanSession(true)
.WithCleanStart(true)
.Build();
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithClientOptions(mqttClientOptions)
.WithMaxPendingMessages(20)
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
.Build();
_client.StartAsync(managedOptions).GetAwaiter().GetResult();
Console.WriteLine(" >>> Master-Control MQTT client connected to broker.");
// Initialize separate client for visualization
_visualizationClient = mqttFactory.CreateManagedMqttClient();
var visualizationOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", 1883)
.WithClientId("Master-Visualization")
.Build();
var visualizationManagedOptions = new ManagedMqttClientOptionsBuilder()
.WithClientOptions(visualizationOptions)
.WithMaxPendingMessages(10) // Lower queue size for visualization
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
.Build();
_visualizationClient.StartAsync(visualizationManagedOptions).GetAwaiter().GetResult();
Console.WriteLine(" >>> Master-Control Visualization MQTT client initialized.");
}
public async Task RestartClient()
{
Console.WriteLine(" >>> Restarting MQTT client...");
var stopTask = _client.StopAsync();
if (await Task.WhenAny(stopTask, Task.Delay(5000)) == stopTask)
{
Console.WriteLine(" >>> MQTT client stopped successfully.");
}
else
{
Console.WriteLine(" >>> Timeout while stopping MQTT client.");
}
// Dispose and reinitialize the client
//_client.Dispose();
var mqttFactory = new MqttFactory();
var newClient = mqttFactory.CreateManagedMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", 1883)
.Build();
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithClientOptions(mqttClientOptions)
.Build();
await newClient.StartAsync(managedOptions);
Console.WriteLine(" >>> New MQTT client started.");
}
public void SubsribeToConnectionTopic()
{
_client.SubscribeAsync(_connectionTopic, MqttQualityOfServiceLevel.AtLeastOnce).GetAwaiter().GetResult();
_client.ApplicationMessageReceivedAsync += async e =>
{
if (e.ApplicationMessage.Topic == _connectionTopic)
{
Console.WriteLine($" >>>>>>> Subscribed to topic: {e.ApplicationMessage.Topic}");
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
//LogMessage("RECEIVED", e.ApplicationMessage.Topic, payload);
var connectionStatusMessage = JsonConvert.DeserializeObject<connectionMessage>(payload);
var car = (VDA5050Car)SimpleLib.GetAllCars()
.FirstOrDefault(cc => cc is VDA5050Car vdaCar);
if (car == null)
{
Diagnosis.Post($"no car of serialNumber {connectionStatusMessage.serialNumber}");
}
car.ConnectionStatus = connectionStatusMessage.connectionState;
Console.WriteLine($">>>> Connection status: {connectionStatusMessage.connectionState}");
}
};
}
public void SubscribeToCarFields()
{
_client.SubscribeAsync(_carFields,MqttQualityOfServiceLevel.AtLeastOnce).GetAwaiter().GetResult();
_client.ApplicationMessageReceivedAsync += async e =>
{
if (e.ApplicationMessage.Topic == _carFields)
{
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
Console.WriteLine(payload);
//TODO:处理字符串,拿想要的值
}
};
}
//public void SubsribeToFactSheetTopic()
//{
// _client.SubscribeAsync(_factsheet, MqttQualityOfServiceLevel.AtMostOnce).GetAwaiter().GetResult();
// _client.ApplicationMessageReceivedAsync += async e =>
// {
// if (e.ApplicationMessage.Topic == _factsheet)
// {
// //Console.WriteLine($" >>>>>>> Subscribed to topic: {e.ApplicationMessage.Topic}");
// var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
// //LogMessage("RECEIVED FactSheet", e.ApplicationMessage.Topic, payload);
// // Console.WriteLine($">>>> Connection status: {connectionStatusMessage.connectionState}");
// }
// };
//}
public void SubscribeToVisualization()
{
_visualizationClient.SubscribeAsync(VisualizationTopic, MqttQualityOfServiceLevel.AtMostOnce).GetAwaiter().GetResult();
Console.WriteLine($" >>> Subscribed to {VisualizationTopic}");
_visualizationClient.ApplicationMessageReceivedAsync += async e =>
{
if(_visualizationClient.PendingApplicationMessagesCount > 5)
{
Console.WriteLine($"[WARNING] Dropping old visualization message. Pending: {_visualizationClient.PendingApplicationMessagesCount}");
return; // Skip processing to catch up
}
if (e.ApplicationMessage.Topic == VisualizationTopic)
{
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
//Console.WriteLine($"[DEBUG] Received Visualization Message: {payload}");
var state = JsonConvert.DeserializeObject<visualizationMessage>(payload);
var car = (VDA5050Car)SimpleLib.GetAllCars()
.FirstOrDefault(cc => cc is VDA5050Car vdaCar);
if (car != null)
{
car.UpdatePosition(state);
}
}
await Task.CompletedTask;
};
}
public void SubscribeToState()
{
_client.SubscribeAsync(StateTopic, MqttQualityOfServiceLevel.AtMostOnce).GetAwaiter().GetResult();
Console.WriteLine($"Subscribed to topic: {StateTopic}");
_client.ApplicationMessageReceivedAsync += async e =>
{
Console.WriteLine("e.ApplicationMessage.Topic: " + e.ApplicationMessage.Topic + " StateTopic: " + StateTopic);
if (e.ApplicationMessage.Topic == StateTopic)
{
Console.WriteLine($" >>>>>>> Subscribed to topic: {e.ApplicationMessage.Topic}");
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
//LogMessage("RECEIVED", e.ApplicationMessage.Topic, payload);
// Console.WriteLine($" >> Topic: [{e.ApplicationMessage.Topic}] -- {payload}");
var state = JsonConvert.DeserializeObject<stateMessage>(payload);
var car = (VDA5050Car)SimpleLib.GetAllCars()
.FirstOrDefault(cc => cc is VDA5050Car vdaCar);
if (car == null)
{
Diagnosis.Post($"no car of serialNumber {state.serialNumber}");
}
car.IsPaused = state.paused;
car.UpdateState(state);
//car.UpdatePosition(state);
//Console.WriteLine($">> Received message on topic '{StateTopic}': {payload}");
// onTopicReceived(state);
}
await Task.CompletedTask;
};
}
//public void SubscribeToState()
//{
// _client.SubscribeAsync(StateTopic, MqttQualityOfServiceLevel.AtMostOnce).GetAwaiter().GetResult();
// _client.ApplicationMessageReceivedAsync += async e =>
// {
// if (e.ApplicationMessage.Topic == StateTopic)
// {
// var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
// var state = JsonConvert.DeserializeObject<stateMessage>(payload);
// var car = (VDA5050Car)SimpleLib.GetAllCars().FirstOrDefault(cc => cc is VDA5050Car);
// if (car == null)
// {
// Diagnosis.Post($"no car of serialNumber {state.serialNumber}");
// }
// else
// {
// // Instead of calling UpdateState/UpdatePosition directly,
// // simply store the latest state message.
// car.SetLatestState(state);
// }
// }
// await Task.CompletedTask;
// };
//}
public async Task PublishTo(string order)
{
var message = new MqttApplicationMessageBuilder()
.WithTopic(OrderTopic)
.WithPayload(order)
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
.Build();
//Console.WriteLine($"[MQTT-Master] Pending Messages before sending: {_client.PendingApplicationMessagesCount}");
await _client.EnqueueAsync(message);
//Console.WriteLine($"[MQTT-Master] Sent order message. Pending Messages after sending: {_client.PendingApplicationMessagesCount}");
//LogMessage("Sent Order", OrderTopic, order);
}
public async Task PublishInstantActions(string actions)
{
var message = new MqttApplicationMessageBuilder()
.WithTopic(_instantAction)
.WithPayload(actions)
.Build();
await _client.EnqueueAsync(message);
}
public void LogMessage(string direction, string topic, string payload)
{
string formattedPayload = payload;
// Try to parse the payload as JSON and pretty-print it
try
{
var jsonObject = JsonConvert.DeserializeObject(payload);
formattedPayload = JsonConvert.SerializeObject(jsonObject, Formatting.Indented);
}
catch (JsonReaderException)
{
// If the payload is not valid JSON, just leave it as is
formattedPayload = payload;
}
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] [{direction}] Topic: {topic}, Payload: {formattedPayload}");
}
}
}
@@ -0,0 +1,58 @@
namespace StandardScene.CarTypes
{
partial class TextViewer
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.richTextBox1 = new System.Windows.Forms.RichTextBox();
this.SuspendLayout();
//
// richTextBox1
//
this.richTextBox1.Location = new System.Drawing.Point(12, 12);
this.richTextBox1.Name = "richTextBox1";
this.richTextBox1.Size = new System.Drawing.Size(776, 1032);
this.richTextBox1.TabIndex = 0;
this.richTextBox1.Text = "";
//
// TextViewer
//
this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 1056);
this.Controls.Add(this.richTextBox1);
this.Name = "TextViewer";
this.Text = "TextViewer";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.RichTextBox richTextBox1;
}
}
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace StandardScene.CarTypes
{
public partial class TextViewer : Form
{
public TextViewer()
{
InitializeComponent();
}
public void UpdateText(string str)
{
richTextBox1.Invoke((Action)delegate
{
richTextBox1.Text = str;
});
}
}
}
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommonUsage.Protocols.VDA5050.Messages;
using CommonUsage.Protocols.VDA5050.Objects;
namespace StandardScene.CarTypes
{
public class VDA5050Commons
{
private static uint _headerId = 0; // Tracks the current header ID.
private static readonly string _protocolVersion = "1.0"; // Example protocol version.
private static readonly string _manufacturer = "YourManufacturer"; // Replace with your manufacturer.
private static readonly string _serialNumber = "test-01"; // Replace with your AGV's serial number.
public static instanceAction CreateInstanceAction(string actionId, string actionType)
{
return new instanceAction
{
headerId = ++_headerId, // Increment header ID for each message.
timestamp = DateTime.UtcNow.ToString("o"), // ISO 8601 timestamp.
version = _protocolVersion,
manufacturer = _manufacturer,
serialNumber = _serialNumber,
actions = new List<actionState>
{
new actionState
{
actionId = actionId,
actionType = actionType,
}
}
};
}
}
}
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SimpleCore;
using SimpleCore.PropType;
namespace StandardScene.CarTypes
{
internal class VDA5050Helper
{
/// <summary>
/// If true, use Simple "id" field as VDA5050 id.
/// If false, use Simple "name" field as VDA5050 id.
/// </summary>
public static bool UseSimpleId = true;
public static T FindByVDA5050Id<T>(string vda5050Id) where T : Prop
{
Func<Prop, bool> testFunc = UseSimpleId ? prop => $"{prop.id}" == vda5050Id : prop => prop.name == vda5050Id;
var matches = SimpleLib.Things().OfType<T>().Where(prop => testFunc(prop)).ToList();
if (matches.Count == 0) return null;
if (matches.Count > 1)
throw new Exception(
$"multiple Props have same name {vda5050Id}, matches: {string.Join(", ", matches.Select(pp => $"{pp.id}({pp.GetType()})"))}");
return matches[0];
}
public static string GetVDA5050Id(int propId)
{
return UseSimpleId ? $"{propId}" : SimpleLib.Things().Where(pp => pp.id == propId).FirstOrDefault().name;
}
public static string GetVDA5050Id(Prop prop)
{
return UseSimpleId ? $"{prop.id}" : prop.name;
}
public static Site FindSiteByVDA5050Id(string vda5050Id)
{
return FindByVDA5050Id<Site>(vda5050Id);
}
public static Track FindTrackByVDA5050Id(string vda5050Id)
{
return FindByVDA5050Id<Track>(vda5050Id);
}
public static AbstractCar FindCarByVDA5050Id(string vda5050Id)
{
return FindByVDA5050Id<AbstractCar>(vda5050Id);
}
}
}
@@ -0,0 +1,177 @@
using SimpleCore.BasicProps;
using SimpleCore.Library;
using SimpleCore.Traffic;
using SimpleCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CommonUsage.Protocols.VDA5050.Objects;
using System.Security.Cryptography.X509Certificates;
using System.Numerics;
using System.Threading;
using System.Net;
using System.Net.Http;
using Nancy.Routing;
using Newtonsoft.Json;
using SimpleLite;
using System.Windows.Forms;
namespace StandardScene.CarTypes
{
internal class VDA5050Interface : AGVInterface
{
public static int TaskId = 0;
HttpClient hc = new HttpClient();
public override bool TryLock(int siteId)
{
if (!_car.status.usage.Get().scheduling) throw new Exception("abandoned");
var siteId_temp = siteId;
var pendinglocks0 = _car.status.pendingLocks[0];
if (siteId_temp != pendinglocks0)
{
throw new Exception("lock not according to sequence");
}
return TrafficControl.TryLock(_car, siteId);
}
public override void Leave(int siteId)
{
TrafficControl.Leave(_car, siteId);
}
public VDA5050Interface(int id)
{
_car = (VDA5050Car)SimpleLib.GetCar(id);
// _car.RouteCache = new();
// _car.TaskId = TaskId++;
if (_car.RouteCache.Count ==0||_car.RouteCache.Count>0&&_car.RouteCache.Last().State==VDA5050Segment.SegState.Executed)//接续任务
{
// _car.RouteCache = new();
// _car.ResetLastNodeSequenceId();
_car.ReseData();
_car.TaskId = TaskId;
TaskId += 1;
}
}
public void Go(int srcId, int dstId, int trackId, int speed = -1, bool reverse = false, float[] trackTypeInfo = null)
{
VDA5050Segment src, dst, track;
lock (_car.RouteCache)
{
//Console.WriteLine("_car.RouteCache.Count: " + _car.RouteCache.Count);
if (_car.RouteCache.Count == 0)
{
src = new(new node()
{
nodeId = VDA5050Helper.GetVDA5050Id(srcId),
nodePosition = new() { x = SimpleLib.GetSite(srcId).x, y = SimpleLib.GetSite(srcId).y }
}, VDA5050Segment.SegState.Waiting);
_car.RouteCache.Add(src);
}
else src = _car.RouteCache.Last();
if (trackTypeInfo.Length != 0 && trackTypeInfo[0] == 3)
{
var controlPointNum = (int)trackTypeInfo[1];
var controlPoints = new controlPoint[controlPointNum];
for (int pt = 2; pt < controlPointNum * 2 + 2; pt += 2)
{
controlPoints[pt/2-1] = new controlPoint(trackTypeInfo[pt], trackTypeInfo[pt + 1], trackTypeInfo[1 + controlPointNum * 2 + pt / 2]);
}
var knotVector = new float[controlPointNum*2];
for (int i = 0; i < controlPointNum; i++)
{
knotVector[i] = 0f;
knotVector[i + controlPointNum] = 1f;
}
var trajectory = new trajectory(){controlPoints = controlPoints,degree = controlPointNum,knotVector = knotVector };
track = new(new edge()
{
edgeId = VDA5050Helper.GetVDA5050Id(trackId),
startNodeId = VDA5050Helper.GetVDA5050Id(srcId),
endNodeId = VDA5050Helper.GetVDA5050Id(dstId),
trajectory = trajectory,
orientation = reverse ? Math.PI : 0.0,
action = [new action() { actionDescription = $"{VDA5050Helper.GetVDA5050Id(trackId)}", actionId = Guid.NewGuid().ToString() }]
}, VDA5050Segment.SegState.Waiting);
}
else
{
track = new(new edge()
{
edgeId = VDA5050Helper.GetVDA5050Id(trackId),
startNodeId = VDA5050Helper.GetVDA5050Id(srcId),
endNodeId = VDA5050Helper.GetVDA5050Id(dstId),
trackTypeInfo = trackTypeInfo,
orientation = reverse ? Math.PI : 0.0,
action = [new action() { actionDescription = $"{VDA5050Helper.GetVDA5050Id(trackId)}", actionId = Guid.NewGuid().ToString() }]
}, VDA5050Segment.SegState.Waiting);
}
dst = new(new node()
{
nodeId = VDA5050Helper.GetVDA5050Id(dstId),
nodePosition = new() { x = SimpleLib.GetSite(dstId).x, y = SimpleLib.GetSite(dstId).y },
actions = [new action() { actionDescription = $"{VDA5050Helper.GetVDA5050Id(dstId)}", actionId = Guid.NewGuid().ToString() }]
}, VDA5050Segment.SegState.Waiting);
_car.RouteCache.Add(track);
_car.RouteCache.Add(dst);
}
Queue(async () =>
{
while (!TryLock(dstId))
await Task.Delay(10);
lock (_car.RouteCache)
{
if (src.Item.sequenceId == 0)
{
src.State = VDA5050Segment.SegState.BaseSending;
}
track.State = VDA5050Segment.SegState.BaseSending;
dst.State = VDA5050Segment.SegState.BaseSending;
src.Item.released = true;
track.Item.released = true;
dst.Item.released = true;
}
}, async () =>
{
await src.FinishToken.Task;
Leave(srcId);
});
}
public void ChangeDI41Signal()
{
hc.GetStringAsync($"http://{_car.address}:8008/setValue?FieldName=DI41&Value=True");
Diagnosis.Log("将DI41置为True", "VDA5050", true);
Thread.Sleep(1000);
hc.GetStringAsync($"http://{_car.address}:8008/setValue?FieldName=DI41&Value=False");
Diagnosis.Log("1秒后将DI41置为False", "VDA5050", true);
}
public void Sleep(int SleepTime)
{
Diagnosis.Log("小车开始Sleep" + SleepTime + "ms", "VDA5050", true);
Thread.Sleep(SleepTime);
Diagnosis.Log("小车结束Sleep", "VDA5050", true);
}
public void WaitAO3Signal()
{
while(_car.AO3 == 0)
{
Thread.Sleep(200);
}
Diagnosis.Log($"AO3: " + _car.AO3, "VDA5050", true);
Diagnosis.Log("获取到AO3信号为1,继续下个任务", "VDA5050", true);
}
private VDA5050Car _car;
}
}
@@ -0,0 +1,77 @@
using System;
using System.Threading.Tasks;
using CommonUsage.Protocols.VDA5050.Objects;
using SimpleLite.CADTools;
using SimpleLite.Props;
using SimpleLite.UI;
using SimpleCore;
using SimpleCore.Library;
using SimpleCore.PropType;
namespace StandardScene.CarTypes
{
/// <summary>
/// Intermediate data structure for linking VDA5050 nodes & edges with MDCS sites & tracks.
/// </summary>
internal class VDA5050Segment
{
public VDA5050Segment(sequenceItem item, SegState state)
{
Item = item;
State = state;
if (IsNode()) FinishToken = new TaskCompletionSource<int>();
}
public enum SegState
{
Waiting,
BaseSending,
BaseAcknowledged,
HorizonSending,
HorizonAcknowledged,
Executed,
}
public bool IsNode()
{
return Item is node || Item is nodeState;
}
public float GetTrackLength()
{
var track = VDA5050Helper.FindTrackByVDA5050Id(((edge)Item).edgeId);
if (track is UITrack lineTrack)
{
var sa = SimpleLib.GetSite(track.siteA);
var sb = SimpleLib.GetSite(track.siteB);
return (float)LessMath.dist(sa.x, sa.y, sb.x, sb.y);
}
else if (track is UICircularArcTrack arcTrack)
{
throw new Exception("not implemented yet");
}
else if (track is UIBezierTrack bezierTrack)
{
var sa = SimpleLib.GetSite(track.siteA);
var sb = SimpleLib.GetSite(track.siteB);
return (float)LessMath.dist(sa.x, sa.y, sb.x, sb.y);
}
else if(track is UINurbsTrack)
{
var sa = SimpleLib.GetSite(track.siteA);
var sb = SimpleLib.GetSite(track.siteB);
return (float)LessMath.dist(sa.x, sa.y, sb.x, sb.y);
}
else throw new Exception("not implemented yet");
}
public sequenceItem Item = null;
public SegState State;
public bool FinishTriggered = false;
public TaskCompletionSource<int> FinishToken = null;
}
}
@@ -0,0 +1,130 @@
//using System;
//using Nancy;
//using System.Linq;
//using CommonUsage.Protocols.VDA5050.Messages;
//using Nancy.Extensions;
//using Newtonsoft.Json;
//using SimpleCore;
//using SimpleCore.Library;
//using System.Collections.Concurrent;
//using System.Timers;
//namespace StandardScene.CarTypes
//{
// public class VDA5050WebApi : NancyModule
// {
// private static ConcurrentDictionary<string, DateTime> agvLastRequestTime =
// new ConcurrentDictionary<string, DateTime>();
// private static TimeSpan offlineThreshold = TimeSpan.FromSeconds(4);
// private static Timer agvStatusCheckTimer;
// public VDA5050WebApi()
// {
// Post["uagv/v2/manufacturer/SN/connection"] = param =>
// {
// var bodyStr = Request.Body.AsString();
// var msg = JsonConvert.DeserializeObject<connectionMessage>(bodyStr);
// var car = (VDA5050Car)SimpleLib.GetAllCars()
// .FirstOrDefault(cc => cc is VDA5050Car vdaCar && vdaCar.SerialNumber == msg.serialNumber);
// if (car == null)
// {
// Diagnosis.Post($"no car of serialNumber {msg.serialNumber}");
// return "";
// }
// Console.WriteLine($"Connection status---------------------{msg.connectionState}");
// car.ConnectionStatus = msg.connectionState;
// return "";
// };
// Post["/registration"] = param =>
// {
// var bodyStr = Request.Body.AsString();
// var msg = JsonConvert.DeserializeObject<factsheetMessage>(bodyStr);
// var car = (VDA5050Car)SimpleLib.GetAllCars()
// .FirstOrDefault(cc => cc is VDA5050Car vdaCar && vdaCar.SerialNumber == msg.serialNumber);
// if (car == null)
// {
// Diagnosis.Post($"no car of serialNumber {msg.serialNumber}");
// return "";
// }
// return "";
// };
// Post["/vda5050/state"] = param =>
// {
// var bodyStr = Request.Body.AsString();
// var msg = JsonConvert.DeserializeObject<stateMessage>(bodyStr);
// var car = (VDA5050Car)SimpleLib.GetAllCars()
// .FirstOrDefault(cc => cc is VDA5050Car vdaCar && vdaCar.SerialNumber == msg.serialNumber);
// if (car == null)
// {
// Diagnosis.Post($"no car of serialNumber {msg.serialNumber}");
// return "";
// }
// Console.WriteLine($"Is the car Paused: {msg.paused} ------------------");
// if (msg.orderUpdatedId == car.OrderUpdateId) car.UpdateState(msg);
// else Diagnosis.Post($"stateMessage orderUpdateId does not match!");
// car.UpdateState(msg); // todo: what if orderUpdateId does not arrive in order?
// return "";
// };
// Post["/vda5050/visualization"] = param =>
// {
// var bodyStr = Request.Body.AsString();
// var msg = JsonConvert.DeserializeObject<stateMessage>(bodyStr);
// var car = (VDA5050Car)SimpleLib.GetAllCars()
// .FirstOrDefault(cc => cc is VDA5050Car vdaCar && vdaCar.SerialNumber == msg.serialNumber);
// if (car == null)
// {
// Diagnosis.Post($"no car of serialNumber {msg.serialNumber}");
// return "";
// }
// car.UpdatePosition(msg);
// return "";
// };
// }
// private static void CheckAgvStatus(object sender, ElapsedEventArgs e)
// {
// var cars = SimpleLib.GetAllCars()
// .OfType<VDA5050Car>(); // Assuming SimpleLib provides a way to get all cars
// foreach (var car in cars)
// {
// if (agvLastRequestTime.TryGetValue(car.SerialNumber, out DateTime lastRequestTime))
// {
// // Check if the time since the last request exceeds the offline threshold
// if ((DateTime.Now - lastRequestTime) > offlineThreshold)
// {
// if (car.ConnectionStatus == "Online")
// {
// car.ConnectionStatus = "Connection Broken";
// Diagnosis.Post($"AGV {car.SerialNumber} is now offline (no requests received).");
// }
// }
// }
// else
// {
// // If theres no record of the last request, the AGV is considered offline
// if (car.ConnectionStatus == "Online")
// {
// car.Online = false;
// Diagnosis.Post($"AGV {car.SerialNumber} is disconnected unexpectedly.");
// }
// }
// }
// }
// }
//}