Initial commit from MyParking project
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050
|
||||
{
|
||||
public class CommunicationProtocolFactory
|
||||
{
|
||||
public static IVDACommunicationProtocol CreateProtocol(string protocolType, string host, int port)
|
||||
{
|
||||
return protocolType.ToLower() switch
|
||||
{
|
||||
"http" => new HTTPCommunication(host, port),
|
||||
"mqtt" => new MQTTCommunication(host, port),
|
||||
_ => throw new NotSupportedException($"Protocol {protocolType} is not supported")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommonUsage.Protocols.VDA5050.Messages;
|
||||
using FundamentalLib;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050
|
||||
{
|
||||
public class HTTPCommunication : IVDACommunicationProtocol
|
||||
{
|
||||
private readonly string _host;
|
||||
private readonly int _port;
|
||||
|
||||
public HTTPCommunication(string host, int port)
|
||||
{
|
||||
_host = host;
|
||||
_port = port;
|
||||
}
|
||||
|
||||
public async Task PublishConnectionStatus(string status)
|
||||
{
|
||||
var message = new connectionMessage()
|
||||
{
|
||||
serialNumber = "test-01",
|
||||
headerId = 1,
|
||||
timestamp = DateTime.Now,
|
||||
connectionState = status
|
||||
};
|
||||
|
||||
await SendMessageAsync(message, "vda5050/connection");
|
||||
}
|
||||
|
||||
public void SetupOrderListener(Action<orderMessage> orderReceived)
|
||||
{
|
||||
PicoHttpServer.AddPostTextHandler("/order", new { }, (_, str) =>
|
||||
{
|
||||
var order = JsonConvert.DeserializeObject<orderMessage>(str);
|
||||
orderReceived(order);
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
public void SetUpInstanActionListener(Action<instanceAction> onInstanceActionReceived)
|
||||
{
|
||||
PicoHttpServer.AddPostTextHandler("/instanceAction", new { }, (_, str) =>
|
||||
{
|
||||
var instanceAction = JsonConvert.DeserializeObject<instanceAction>(str);
|
||||
onInstanceActionReceived(instanceAction);
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
public void SetUpImmediateCommandListener(Action<string> onChangeCarFieldsReceived)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task SendMessageAsync<T>(T message, string topic)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = $"http://{_host}:{_port}/{topic}";
|
||||
using var client = new HttpClient();
|
||||
var response = await client.PostAsync(url, new StringContent(JsonConvert.SerializeObject(message), Encoding.UTF8, "application/json"));
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($" >> Sending Message: Failed to send message. Status Code: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error in sending message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendVisualizationMessageAsync<T>(T msg, string topic)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetupTestListener(Action<string> testMsg)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public async Task PublishFactSheet(factsheetMessage message)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommonUsage.Protocols.VDA5050.Messages;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050
|
||||
{
|
||||
public interface IVDACommunicationProtocol
|
||||
{
|
||||
Task PublishConnectionStatus(string status);
|
||||
Task PublishFactSheet(factsheetMessage msg);
|
||||
void SetupOrderListener(Action<orderMessage> orderReceived);
|
||||
void SetUpInstanActionListener(Action<instanceAction> onInstanceActionReceived);
|
||||
void SetUpImmediateCommandListener(Action<string> onChangeCarFieldsReceived);
|
||||
Task SendMessageAsync<T>(T message, string topic);
|
||||
Task SendVisualizationMessageAsync<T>(T message, string topic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommonUsage.Protocols.VDA5050.Messages;
|
||||
using MQTTnet;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions.ManagedClient;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using Newtonsoft.Json;
|
||||
using FundamentalLib;
|
||||
using CommonUsage.Protocols.VDA5050.Objects;
|
||||
using FundamentalLib.MiscHelpers;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050
|
||||
{
|
||||
public class MQTTCommunication : IVDACommunicationProtocol
|
||||
{
|
||||
private readonly string _host;
|
||||
private readonly int _port;
|
||||
private readonly string _orderTopic = "vda5050/frldAGV/order";
|
||||
private readonly string _connectionTopic = "vda5050/frldAGV/connection";
|
||||
private readonly string _instanceAction = "vda5050/frldAGV/instantActions";
|
||||
private readonly string _factsheet = "vda5050/frldAGV/factsheet";
|
||||
private readonly string _changeCarFields = "vda5050/frldAGV/changeCarFields";
|
||||
|
||||
private IManagedMqttClient _client;
|
||||
private IManagedMqttClient _visualizationClient;
|
||||
|
||||
public MQTTCommunication(string host, int port)
|
||||
{
|
||||
_host = host;
|
||||
_port = port;
|
||||
InitializeClient();
|
||||
InitializeVisualizationClient();
|
||||
}
|
||||
|
||||
private void InitializeClient()
|
||||
{
|
||||
var willMessage = new connectionMessage()
|
||||
{
|
||||
headerId = 1,
|
||||
timestamp = DateTime.Now,
|
||||
version = "00",
|
||||
manufacturer = "frld",
|
||||
serialNumber = "test-01",
|
||||
connectionState = "CONNECTIONBROKEN"
|
||||
};
|
||||
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithClientId("AGV-Client-frldAGV")
|
||||
.WithTcpServer(_host, _port)
|
||||
.WithWillTopic(_connectionTopic)
|
||||
.WithWillPayload(JsonConvert.SerializeObject(willMessage))
|
||||
.WithWillRetain(true)
|
||||
.Build();
|
||||
|
||||
var managedMqttClientOptions = new ManagedMqttClientOptionsBuilder()
|
||||
.WithClientOptions(mqttClientOptions)
|
||||
.WithMaxPendingMessages(20)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
|
||||
.Build();
|
||||
|
||||
_client = new MqttFactory().CreateManagedMqttClient();
|
||||
_client.StartAsync(managedMqttClientOptions).GetAwaiter().GetResult();
|
||||
Console.WriteLine($" >> MQTT client initialized and connected to broker at {_host} - {_port}");
|
||||
}
|
||||
|
||||
private void InitializeVisualizationClient()
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder()
|
||||
.WithClientId("AGV-Visualization")
|
||||
.WithTcpServer(_host, _port)
|
||||
.Build();
|
||||
|
||||
var managedMqttClientOptions = new ManagedMqttClientOptionsBuilder()
|
||||
.WithClientOptions(mqttClientOptions)
|
||||
.WithMaxPendingMessages(10) // Prevent overloading
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
|
||||
.Build();
|
||||
|
||||
_visualizationClient = new MqttFactory().CreateManagedMqttClient();
|
||||
_visualizationClient.StartAsync(managedMqttClientOptions).GetAwaiter().GetResult();
|
||||
Console.WriteLine("MQTT Visualization client initialized.");
|
||||
}
|
||||
|
||||
public void SetUpImmediateCommandListener(Action<string> onChangeCarFieldsReceived)
|
||||
{
|
||||
Console.WriteLine($"Subscribed to the topic: {_changeCarFields}");
|
||||
_client.SubscribeAsync(_changeCarFields).GetAwaiter().GetResult();
|
||||
|
||||
_client.ApplicationMessageReceivedAsync += async e =>
|
||||
{
|
||||
if (e.ApplicationMessage.Topic == _changeCarFields)
|
||||
{
|
||||
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
|
||||
LogMessage($"RECEIVE-{e.ApplicationMessage.Topic}", e.ApplicationMessage.Topic, payload);
|
||||
//var script = JsonConvert.DeserializeObject<string>(payload);
|
||||
Console.WriteLine($"Change car field: {payload}");
|
||||
onChangeCarFieldsReceived(payload);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public void SetUpInstanActionListener(Action<instanceAction> onInstanceActionReceived)
|
||||
{
|
||||
Console.WriteLine($"Subscribed to the topic: {_instanceAction}");
|
||||
|
||||
// Subscribe to the instanceAction topic
|
||||
_client.SubscribeAsync(_instanceAction).GetAwaiter().GetResult();
|
||||
|
||||
_client.ApplicationMessageReceivedAsync += async e =>
|
||||
{
|
||||
if (e.ApplicationMessage.Topic == _instanceAction)
|
||||
{
|
||||
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
|
||||
LogMessage($"RECEIVE-{e.ApplicationMessage.Topic}", e.ApplicationMessage.Topic, payload);
|
||||
var actions = JsonConvert.DeserializeObject<instanceAction>(payload);
|
||||
Console.WriteLine($"Received instance action: Header ID = {actions.headerId}, Timestamp = {actions.timestamp}");
|
||||
foreach (var action in actions.actions)
|
||||
{
|
||||
Console.WriteLine($"Action ID: {action.actionId}, Type: {action.actionType}");
|
||||
}
|
||||
|
||||
onInstanceActionReceived(actions);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public async Task PublishConnectionStatus(string status)
|
||||
{
|
||||
var message = new connectionMessage()
|
||||
{
|
||||
headerId = 1,
|
||||
timestamp = DateTime.Now,
|
||||
version = "00",
|
||||
manufacturer = "frld",
|
||||
serialNumber = "test-01",
|
||||
connectionState = status
|
||||
};
|
||||
|
||||
var payload = JsonConvert.SerializeObject(message);
|
||||
|
||||
var content = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(_connectionTopic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.WithRetainFlag(true)
|
||||
.Build();
|
||||
|
||||
await _client.EnqueueAsync(content);
|
||||
LogMessage($"SEND-{_connectionTopic}", _connectionTopic, payload);
|
||||
|
||||
//await SendMessageAsync(message, _connectionTopic);
|
||||
}
|
||||
|
||||
public async Task PublishFactSheet(factsheetMessage message)
|
||||
{
|
||||
|
||||
//await SendMessageAsync(message, _factsheet);
|
||||
var payload = JsonConvert.SerializeObject(message);
|
||||
|
||||
var content = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(_factsheet)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
|
||||
//.WithRetainFlag(true)
|
||||
.Build();
|
||||
|
||||
await _client.EnqueueAsync(content);
|
||||
LogMessage($"SEND-{_factsheet}", _factsheet, payload);
|
||||
|
||||
}
|
||||
|
||||
public void SetupOrderListener(Action<orderMessage> orderReceived)
|
||||
{
|
||||
// Subscribe to the orders topic
|
||||
_client.SubscribeAsync(_orderTopic, MqttQualityOfServiceLevel.AtMostOnce).GetAwaiter().GetResult();
|
||||
_client.ApplicationMessageReceivedAsync += async e =>
|
||||
{
|
||||
if (e.ApplicationMessage.Topic == _orderTopic)
|
||||
{
|
||||
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
|
||||
LogMessage($"RECEIVE-{e.ApplicationMessage.Topic}", e.ApplicationMessage.Topic, payload);
|
||||
var order = JsonConvert.DeserializeObject<orderMessage>(payload);
|
||||
|
||||
// Save the order message to a file for debugging
|
||||
// SaveOrderToFile(payload);
|
||||
orderReceived(order);
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
private void SaveOrderToFile(string orderJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Specify the file path (e.g., orders_log.txt in the current directory)
|
||||
string filePath = "orders_log.txt";
|
||||
|
||||
// Append the order JSON along with a timestamp
|
||||
File.AppendAllText(filePath, $"{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss} - {orderJson}{Environment.NewLine}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle any exceptions that occur while writing to the file
|
||||
Console.WriteLine($"Failed to save order to file: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendMessageAsync<T>(T message, string topic)
|
||||
{
|
||||
|
||||
var payload = JsonConvert.SerializeObject(message);
|
||||
|
||||
var content = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce)
|
||||
.Build();
|
||||
|
||||
await _client.EnqueueAsync(content);
|
||||
|
||||
if (topic != "vda5050/frldAGV/visualization")
|
||||
{
|
||||
|
||||
LogMessage($"SEND-{topic}", topic, payload);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task SendVisualizationMessageAsync<T>(T message, string topic)
|
||||
{
|
||||
if (_visualizationClient == null) return; // Ensure client is initialized
|
||||
|
||||
var payload = JsonConvert.SerializeObject(message);
|
||||
|
||||
var content = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(topic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce) // QoS 0 for lightweight visualization
|
||||
.Build();
|
||||
|
||||
if (_visualizationClient.PendingApplicationMessagesCount < 5) // Prevent flooding
|
||||
{
|
||||
await _visualizationClient.EnqueueAsync(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Skipping visualization update to avoid MQTT congestion.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void LogMessage(string direction, string topic, string payload)
|
||||
{
|
||||
string formattedPayload = payload;
|
||||
string logDirectory = "Logs"; // Directory for log files
|
||||
var filePreName = direction;
|
||||
filePreName = filePreName.Replace("/", "_");
|
||||
string logFilePath = Path.Combine(logDirectory, filePreName + $"-{DateTime.Now:yyyy-MM-dd}.log");
|
||||
|
||||
if(!Directory.Exists(logDirectory)) Directory.CreateDirectory(logDirectory);
|
||||
// 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;
|
||||
}
|
||||
|
||||
string logMessage = $"[{DateTime.Now:HH:mm:ss}] [{direction}] Topic: {topic}, Payload:\n{formattedPayload}\n";
|
||||
RotateLogFile(logFilePath, logDirectory);
|
||||
|
||||
try
|
||||
{
|
||||
File.AppendAllText(logFilePath, logMessage + Environment.NewLine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error writing to log file: {ex.Message}");
|
||||
}
|
||||
// Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] [{direction}] Topic: {topic}, Payload: {formattedPayload}");
|
||||
// DLog.Log($"[{DateTime.Now:HH:mm:ss}] [{direction}] Topic: {topic}, Payload: {formattedPayload}");
|
||||
}
|
||||
|
||||
|
||||
private void RotateLogFile(string logFilePath, string logDirectory)
|
||||
{
|
||||
const long maxFileSize = 10 * 1024 * 1024; // 10 MB in bytes
|
||||
|
||||
FileInfo fileInfo = new FileInfo(logFilePath);
|
||||
if (fileInfo.Exists && fileInfo.Length > maxFileSize)
|
||||
{
|
||||
string archivePath = Path.Combine(logDirectory, $"log_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(logFilePath, archivePath); // Rename the current log file
|
||||
Console.WriteLine($"Log file rotated: {archivePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error rotating log file: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Messages
|
||||
{
|
||||
public class connectionMessage
|
||||
{
|
||||
public int headerId;
|
||||
public DateTime timestamp;
|
||||
public string version = "";
|
||||
public string manufacturer = "";
|
||||
public string serialNumber = "";
|
||||
public string connectionState = ""; // Enum: {'ONLINE', 'OFFLINE', 'CONNECTIONBROKEN'}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Messages
|
||||
{
|
||||
public class errorMessage
|
||||
{
|
||||
public string serialNumber = "";
|
||||
public string errorCode = ""; // Unique error code
|
||||
public string description = ""; // Error description
|
||||
public string severity = ""; // Enum {'WARNING', 'FATAL'}
|
||||
public DateTime timestamp; // Time of the error
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using CommonUsage.Protocols.VDA5050.Objects;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Messages
|
||||
{
|
||||
public class factsheetMessage
|
||||
{
|
||||
public int headerId;
|
||||
public DateTime timestamp;
|
||||
public string version = "";
|
||||
public string manufacturer = "";
|
||||
public string serialNumber = "";
|
||||
|
||||
public typeSpecification typeSpecification;
|
||||
public physicalParameters physicalParameters;
|
||||
public protocolLimits protocolLimits;
|
||||
public protocolFeatures protocolFeatures;
|
||||
public agvGeometry agvGeometry;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using CommonUsage.Protocols.VDA5050.Objects;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Messages
|
||||
{
|
||||
public class instanceAction
|
||||
{
|
||||
|
||||
public uint headerId { get; set; } // Incremented for each new message.
|
||||
public string timestamp { get; set; } // ISO 8601 UTC timestamp.
|
||||
public string version { get; set; } // Protocol version.
|
||||
public string manufacturer { get; set; } // AGV manufacturer.
|
||||
public string serialNumber { get; set; } // Unique AGV serial number.
|
||||
|
||||
public List<actionState> actions { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using CommonUsage.Protocols.VDA5050.Objects;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Messages
|
||||
{
|
||||
public class orderMessage
|
||||
{
|
||||
public uint headerId;
|
||||
public string timestamp = "";
|
||||
public string version = "";
|
||||
public string manufacturer = "";
|
||||
public string serialNumber = "";
|
||||
|
||||
public string orderId { get; set; }
|
||||
|
||||
public uint orderUpdateId { get; set; }
|
||||
|
||||
public node[] nodes { get; set; }
|
||||
|
||||
public edge[] edges { get; set; }
|
||||
|
||||
//public action[] action { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using CommonUsage.Protocols.VDA5050.Objects;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// 6.10 Topic: "state" (from AGV to master control)
|
||||
/// todo: complete all fields required by VDA5050
|
||||
/// </summary>
|
||||
public class stateMessage
|
||||
{
|
||||
public uint headerId;
|
||||
public string timestamp = "";
|
||||
public string version = "";
|
||||
public string manufacturer = "";
|
||||
public string serialNumber = "";
|
||||
/// <summary>
|
||||
/// Unique order identification of the current order or the previously finished order.
|
||||
/// The orderId is kept until a new order is received.
|
||||
/// Empty string (""), if no previous orderId is available.
|
||||
/// </summary>
|
||||
public string orderId = "";
|
||||
|
||||
/// <summary>
|
||||
/// Order update identification to identify, that an order update has been accepted by the AGV.
|
||||
/// "0" if no previous orderUpdateId is available.
|
||||
/// </summary>
|
||||
public uint orderUpdatedId = 0;
|
||||
|
||||
public string lastNodeId;
|
||||
|
||||
public uint lastNodeSequenceId;
|
||||
|
||||
/// <summary>
|
||||
/// Array of nodeState objects that need to be traversed for fulfilling the order (empty array if idle)
|
||||
/// </summary>
|
||||
public nodeState[] nodeStates = [];
|
||||
|
||||
/// <summary>
|
||||
/// Array of edgeState objects that need to be traversed for fulfilling the order (empty array if idle)
|
||||
/// </summary>
|
||||
public edgeState[] edgeStates = [];
|
||||
|
||||
public agvPosition agvPosition;
|
||||
|
||||
public velocity velocity;
|
||||
public load[] loads = [];
|
||||
|
||||
public bool driving;
|
||||
|
||||
public bool paused;
|
||||
|
||||
public bool newBaseRequest;
|
||||
|
||||
public double distanceSinceLastNode;
|
||||
|
||||
public batteryState batteryState;
|
||||
|
||||
public actionState[] actionStates = Array.Empty<actionState>();
|
||||
public string operatingMode = "";
|
||||
public List<errorState> errors { get; set; } = new List<errorState>(); // Array of errorState objects
|
||||
|
||||
public info[] information = [];
|
||||
public safetyState safetyState;
|
||||
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using CommonUsage.Protocols.VDA5050.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Messages
|
||||
{
|
||||
public class visualizationMessage
|
||||
{
|
||||
public agvPosition agvPosition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class action
|
||||
{
|
||||
|
||||
public string actionId { get; set; }
|
||||
|
||||
public string actionType { get; set; }
|
||||
public string actionDescription { get; set; }
|
||||
|
||||
public string blockingType { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class actionState
|
||||
{
|
||||
public actionState(action action,ActionStateEnum state)
|
||||
{
|
||||
actionId = action.actionId;
|
||||
actionDescription = action.actionDescription;
|
||||
actionType = action.actionType;
|
||||
actionStatus = state;
|
||||
}
|
||||
|
||||
public actionState()
|
||||
{
|
||||
}
|
||||
|
||||
public string actionId { get; set; }
|
||||
|
||||
public string actionType { get; set; }
|
||||
public string actionDescription { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public ActionStateEnum actionStatus { get; set; }
|
||||
|
||||
public string resultDescription { get; set; }
|
||||
|
||||
public enum ActionStateEnum
|
||||
{
|
||||
WAITING,
|
||||
INITIALIZING,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
FINISHED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static CommonUsage.Protocols.VDA5050.Objects.agvGeometry;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class agvGeometry
|
||||
{
|
||||
// Wheel Definitions
|
||||
public List<wheelDefinition> wheelDefinitions { get; set; } = new List<wheelDefinition>();
|
||||
|
||||
// 2D Envelopes
|
||||
public List<envelope2D> envelopes2D { get; set; } = new List<envelope2D>();
|
||||
|
||||
// 3D Envelopes
|
||||
public List<envelope3D> envelopes3D { get; set; } = new List<envelope3D>();
|
||||
|
||||
public class wheelDefinition
|
||||
{
|
||||
public enum WheelType { DRIVE, CASTER, FIXED, MECANUM }
|
||||
|
||||
public WheelType type { get; set; }
|
||||
public bool isActiveDriven { get; set; }
|
||||
public bool isActiveSteered { get; set; }
|
||||
|
||||
// Wheel Position
|
||||
public double positionX { get; set; }
|
||||
public double positionY { get; set; }
|
||||
public double positionTheta { get; set; } // Required for fixed wheels
|
||||
|
||||
// Wheel Properties
|
||||
public double diameter { get; set; }
|
||||
public double width { get; set; }
|
||||
public double centerDisplacement { get; set; } = 0; // Default to 0 if not defined
|
||||
public string constraints { get; set; }
|
||||
}
|
||||
|
||||
public class envelope2D
|
||||
{
|
||||
public string set { get; set; }
|
||||
public List<polygonPoint> polygonPoints { get; set; } = new List<polygonPoint>();
|
||||
public string description { get; set; }
|
||||
|
||||
public class polygonPoint
|
||||
{
|
||||
public double x { get; set; }
|
||||
public double y { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class envelope3D
|
||||
{
|
||||
public string set { get; set; }
|
||||
public string format { get; set; }
|
||||
public object data { get; set; } // JSON object for 3D envelope data
|
||||
public string url { get; set; }
|
||||
public string description { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class agvPosition
|
||||
{
|
||||
public bool positionInitialized;
|
||||
|
||||
public double x;
|
||||
|
||||
public double y;
|
||||
|
||||
public double theta;
|
||||
public double localizationScore;
|
||||
public double deviationRange;
|
||||
public string mapId = "";
|
||||
public string mapDescription = "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class batteryState
|
||||
{
|
||||
public double batteryCharge;
|
||||
public double batteryVoltage;
|
||||
public double batteryHealth;
|
||||
public bool charging;
|
||||
public int reach;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class boundingBoxReference
|
||||
{
|
||||
public double X { get; set; } // Reference point X in AGV coordinate system
|
||||
public double Y { get; set; } // Reference point Y in AGV coordinate system
|
||||
public double Z { get; set; } // Reference point Z in AGV coordinate system
|
||||
public double Theta { get; set; } // Orientation of the load bounding box
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class controlPoint
|
||||
{
|
||||
public float x;
|
||||
|
||||
public float y;
|
||||
|
||||
public float weight;
|
||||
public controlPoint(float x, float y, float weight)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.weight = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class edge : sequenceItem
|
||||
{
|
||||
public string edgeId;
|
||||
|
||||
public string edgeDescription;
|
||||
|
||||
public string startNodeId;
|
||||
|
||||
public string endNodeId;
|
||||
|
||||
public double maxSpeed;
|
||||
|
||||
public double orientation;
|
||||
|
||||
public trajectory? trajectory;
|
||||
|
||||
public float[] trackTypeInfo;
|
||||
// public List<Vector2> controlPoints;
|
||||
//
|
||||
// public List<float> weights;
|
||||
|
||||
public action[] action = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class edgeState : sequenceItem
|
||||
{
|
||||
public string edgeId;
|
||||
|
||||
public string edgeDescription;
|
||||
|
||||
public trajectory trajectory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class errorReference
|
||||
{
|
||||
public string referenceKey { get; set; } // Type of reference (e.g., nodeId, edgeId, actionId)
|
||||
public string referenceValue { get; set; } // Value corresponding to the referenceKey
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class errorState
|
||||
{
|
||||
public List<errorReference> errorReferences { get; set; } = new List<errorReference>(); // Array of references
|
||||
public string errorType { get; set; } // Required: Type/name of the error
|
||||
public string errorDescription { get; set; } // Verbose description of the error
|
||||
public string errorHint { get; set; } // Hint for resolving the error
|
||||
public string errorLevel { get; set; } // Required: WARNING or FATAL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class info
|
||||
{
|
||||
public string infoType { get; set; } // Type/name of the information
|
||||
public List<infoReference> infoReferences { get; set; } = new List<infoReference>(); // List of references
|
||||
public string infoDescription { get; set; } // Description of the information
|
||||
public infoLevelEnum infoLevel { get; set; } // Debugging or visualization level
|
||||
|
||||
public class infoReference
|
||||
{
|
||||
public string ReferenceKey { get; set; } // Reference type (e.g., headerId, orderId)
|
||||
public string ReferenceValue { get; set; } // The actual referenced field value
|
||||
}
|
||||
|
||||
public enum infoLevelEnum
|
||||
{
|
||||
DEBUG, // Used for debugging
|
||||
INFO // Used for visualization
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class load
|
||||
{
|
||||
public string loadId { get; set; } // Unique ID (barcode, RFID, etc.)
|
||||
public string loadType { get; set; } // Type of load
|
||||
public string loadPosition { get; set; } // Load handling position (e.g., "front", "back")
|
||||
public boundingBoxReference boundingBoxReference { get; set; } = new boundingBoxReference();
|
||||
public loadDimensions loadDimensions { get; set; } = new loadDimensions();
|
||||
public double weight { get; set; } // Weight of load in kg (0.0 to ∞)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class loadDimensions
|
||||
{
|
||||
public double Length { get; set; } // Length of the bounding box
|
||||
public double Width { get; set; } // Width of the bounding box
|
||||
public double Height { get; set; } // Height of the bounding box (optional)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class node : sequenceItem
|
||||
{
|
||||
public string nodeId;
|
||||
|
||||
public string nodeDescription;
|
||||
|
||||
public nodePosition nodePosition;
|
||||
|
||||
public action[] actions = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the position on a map in a global project-specific world coordinate system.
|
||||
/// Each floor has its own map.
|
||||
/// All maps shall use the same project-specific global origin.
|
||||
/// </summary>
|
||||
public class nodePosition
|
||||
{
|
||||
/// <summary>
|
||||
/// X-position on the map in reference to the map coordinate system.
|
||||
/// Precision is up to the specific implementation.
|
||||
/// </summary>
|
||||
public double x;
|
||||
|
||||
/// <summary>
|
||||
/// Y-position on the map in reference to the map coordinate system.
|
||||
/// Precision is up to the specific implementation.
|
||||
/// </summary>
|
||||
public double y;
|
||||
|
||||
/// <summary>
|
||||
/// Range: [-Pi ... Pi]
|
||||
/// Absolute orientation of the AGV on the node.
|
||||
/// Optional: vehicle can plan the path by itself. If defined, the AGV has to assume the theta angle on this node.
|
||||
/// If previous edge disallows rotation, the AGV shall rotate on the node.
|
||||
/// If following edge has a differing orientation defined but disallows rotation,
|
||||
/// the AGV is to rotate on the node to the edges desired rotation before entering the edge.
|
||||
/// </summary>
|
||||
public double theta;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class nodeState : sequenceItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique node identification.
|
||||
/// </summary>
|
||||
public string nodeId;
|
||||
|
||||
/// <summary>
|
||||
/// Additional information on the node.
|
||||
/// </summary>
|
||||
public string nodeDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Node position.
|
||||
/// The object is defined in 6.6 Topic: "order" (from master control to AGV)
|
||||
/// Optional: Master control has this information. Can be sent additionally, e.g., for debugging purposes.
|
||||
/// </summary>
|
||||
public nodePosition nodePosition;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class physicalParameters
|
||||
{
|
||||
public double speedMin;
|
||||
public double speedMax;
|
||||
public double angularSpeedMin;
|
||||
public double angularSpeedMax;
|
||||
public double accelerationMax;
|
||||
public double decelerationMax;
|
||||
public double heightMin;
|
||||
public double heightMax;
|
||||
public double width;
|
||||
public double length;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class protocolFeatures
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class protocolLimits
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class safetyState
|
||||
{
|
||||
public eStopEnum eStop { get; set; } // Emergency stop status
|
||||
public bool fieldViolation { get; set; } // "true" if a safety field is violated, "false" otherwise
|
||||
|
||||
public enum eStopEnum
|
||||
{
|
||||
AUTOACK, // Auto-acknowledged emergency stop (e.g., triggered by a bumper)
|
||||
MANUAL, // Manually confirmed emergency stop
|
||||
REMOTE, // Remote-confirmed emergency stop
|
||||
NONE // No emergency stop activated
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class sequenceItem
|
||||
{
|
||||
public uint sequenceId;
|
||||
|
||||
public bool released;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class trajectory
|
||||
{
|
||||
public float degree;
|
||||
|
||||
public float[] knotVector;
|
||||
|
||||
public controlPoint[] controlPoints;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class typeSpecification
|
||||
{
|
||||
public string agvKinemantic = "";
|
||||
public string agvClass = "";
|
||||
public double maxLoadMass;
|
||||
public string[] localizationTypes; // Simplified description of localization type (e.g., NATURAL, REFLECTOR, RFID, DMC, GRID)
|
||||
public string[] navigationTypes; // Path planning types (e.g., 'AUTONOMOUS', 'VIRTUAL_LINE_GUIDED')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050.Objects
|
||||
{
|
||||
public class velocity
|
||||
{
|
||||
public double vx;
|
||||
|
||||
public double vy;
|
||||
|
||||
public double omega;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
//using CommonUsage.Protocols.VDA5050.Messages;
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Net.Http.Headers;
|
||||
//using System.Runtime.CompilerServices;
|
||||
//using System.Text;
|
||||
//using System.Threading;
|
||||
//using System.Threading.Tasks;
|
||||
//using CommonUsage.Protocols.VDA5050.Objects;
|
||||
//using ClumsyCore.Utilities;
|
||||
//using System.Linq;
|
||||
//using ClumsyCore.Pilot;
|
||||
//using System.Numerics;
|
||||
|
||||
//namespace CommonUsage.Protocols.VDA5050
|
||||
//{
|
||||
// public abstract class VDA5050Basic
|
||||
// {
|
||||
// protected static IVDACommunicationProtocol _communicationProtocol;
|
||||
|
||||
// public void Enable(IVDACommunicationProtocol protocol)
|
||||
// {
|
||||
// _communicationProtocol = protocol;
|
||||
// Task.Run(() => ManageConnection());
|
||||
// StartVisualizationLoop();
|
||||
// }
|
||||
// private void StartVisualizationLoop()
|
||||
// {
|
||||
// Console.WriteLine("Visualization in CommonUsage");
|
||||
// new Thread(async () =>
|
||||
// {
|
||||
// while (true)
|
||||
// {
|
||||
// var position = GetAGVPosition();
|
||||
// if (position != null)
|
||||
// {
|
||||
// var msg = new stateMessage()
|
||||
// {
|
||||
// serialNumber = "test-01",
|
||||
// agvPosition = new()
|
||||
// {
|
||||
// x = position.Value.X,
|
||||
// y = position.Value.Y,
|
||||
// theta = position.Value.Theta,
|
||||
// positionInitialized = true
|
||||
// }
|
||||
|
||||
// };
|
||||
// await _communicationProtocol.SendMessageAsync(msg, "vda5050/frldAGV/visualization");
|
||||
// }
|
||||
|
||||
// Thread.Sleep(100);
|
||||
// }
|
||||
// })
|
||||
// { Name = "VDA5050TopicVisualization" }.Start();
|
||||
// }
|
||||
|
||||
// private void ManageConnection()
|
||||
// {
|
||||
// while (true)
|
||||
// {
|
||||
// var status = CheckConnectionStatus() ? "ONLINE" : "OFFLINE";
|
||||
// _communicationProtocol.PublishConnectionStatus(status);
|
||||
// Thread.Sleep(1000);
|
||||
// }
|
||||
// }
|
||||
|
||||
// protected List<sequenceItem> OrganizeReceivedSequence(orderMessage order)
|
||||
// {
|
||||
// List<sequenceItem> receivedSequence = new();
|
||||
|
||||
// int ii = 0, jj = 0;
|
||||
// while (true)
|
||||
// {
|
||||
// var edge = order.edges[ii];
|
||||
// var node = order.nodes[jj];
|
||||
// var takeEdge = edge.sequenceId < node.sequenceId;
|
||||
|
||||
// if (takeEdge)
|
||||
// {
|
||||
// receivedSequence.Add(edge);
|
||||
// ii++;
|
||||
// if (ii == order.edges.Length) break;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// receivedSequence.Add(node);
|
||||
// jj++;
|
||||
// if (jj == order.nodes.Length) break;
|
||||
// }
|
||||
// }
|
||||
// for (var i = ii; i < order.edges.Length; ++i) receivedSequence.Add(order.edges[i]);
|
||||
// for (var j = jj; j < order.nodes.Length; ++j) receivedSequence.Add(order.nodes[j]);
|
||||
|
||||
// for (var i = 1; i < receivedSequence.Count; i++)
|
||||
// {
|
||||
// if (receivedSequence[i - 1].sequenceId + 1 != receivedSequence[i].sequenceId)
|
||||
// throw new Exception("stateMessage not continuous!");
|
||||
// }
|
||||
|
||||
// return receivedSequence;
|
||||
// }
|
||||
|
||||
// public virtual bool CheckConnectionStatus()
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// protected abstract Vector3? GetAGVPosition();
|
||||
// public struct Vector3
|
||||
// {
|
||||
// public double X;
|
||||
// public double Y;
|
||||
// public double Theta;
|
||||
// }
|
||||
// }
|
||||
|
||||
//}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUsage.Protocols.VDA5050
|
||||
{
|
||||
public class VDA5050Helper
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user