update
This commit is contained in:
parent
511614df72
commit
b2df5b22b7
|
|
@ -5,7 +5,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--mud-palette-surface);
|
background-color: #808080;
|
||||||
border-radius: var(--mud-default-borderradius);
|
border-radius: var(--mud-default-borderradius);
|
||||||
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||||
box-shadow: var(--mud-elevation-10);
|
box-shadow: var(--mud-elevation-10);
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,29 @@
|
||||||
|
|
||||||
public enum RootStateType
|
public enum RootStateType
|
||||||
{
|
{
|
||||||
Booting,
|
System,
|
||||||
Operational,
|
Auto,
|
||||||
|
Manual,
|
||||||
|
Service,
|
||||||
|
Stop,
|
||||||
|
Fault,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum OperationalStateType
|
public enum SystemStateType
|
||||||
{
|
{
|
||||||
|
Initializing,
|
||||||
|
Standby,
|
||||||
|
Shutting_Down,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AutomationStateType
|
public enum AutoStateType
|
||||||
{
|
{
|
||||||
Idle,
|
Idle,
|
||||||
Executing,
|
Executing,
|
||||||
Paused,
|
Paused,
|
||||||
Charging,
|
Holding,
|
||||||
Error,
|
Canceling,
|
||||||
|
Recovering,
|
||||||
Remote_Override,
|
Remote_Override,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,13 +34,60 @@ public enum ManualStateType
|
||||||
Active,
|
Active,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SafetyStateType
|
public enum ServiceStateType
|
||||||
{
|
{
|
||||||
Init,
|
Idle,
|
||||||
Run_Ok,
|
Active,
|
||||||
SS1,
|
}
|
||||||
STO,
|
|
||||||
PDS,
|
public enum StopStateType
|
||||||
SLS,
|
{
|
||||||
Error,
|
EMC,
|
||||||
|
Protective,
|
||||||
|
Manual,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FaultStateType
|
||||||
|
{
|
||||||
|
Navigation,
|
||||||
|
Localization,
|
||||||
|
Shielf,
|
||||||
|
Battery,
|
||||||
|
Driver,
|
||||||
|
Peripherals,
|
||||||
|
Safety,
|
||||||
|
Communication,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ExecutingStateType
|
||||||
|
{
|
||||||
|
Planning,
|
||||||
|
Moving,
|
||||||
|
ACT,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ACTStateType
|
||||||
|
{
|
||||||
|
Docking,
|
||||||
|
Docked,
|
||||||
|
Charging,
|
||||||
|
Undocking,
|
||||||
|
Loading,
|
||||||
|
Unloading,
|
||||||
|
TechAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MoveStateType
|
||||||
|
{
|
||||||
|
Navigation,
|
||||||
|
Avoidance,
|
||||||
|
Approach,
|
||||||
|
Tracking,
|
||||||
|
Repositioning,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PlanStateType
|
||||||
|
{
|
||||||
|
Task,
|
||||||
|
Path,
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,14 @@ namespace RobotApp.Common.Shares;
|
||||||
|
|
||||||
public static class MathExtensions
|
public static class MathExtensions
|
||||||
{
|
{
|
||||||
|
public static double NormalizeAngle(double angle)
|
||||||
|
{
|
||||||
|
angle = angle % 360;
|
||||||
|
if (angle > 180) angle -= 360;
|
||||||
|
else if (angle < -180) angle += 360;
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
public static (double x, double y) CurveDegreeTwo(double t, double x1, double y1, double controlPointX, double controlPointY, double x2, double y2)
|
public static (double x, double y) CurveDegreeTwo(double t, double x1, double y1, double controlPointX, double controlPointY, double x2, double y2)
|
||||||
{
|
{
|
||||||
var x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * controlPointX + t * t * x2;
|
var x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * controlPointX + t * t * x2;
|
||||||
|
|
@ -77,4 +85,21 @@ public static class MathExtensions
|
||||||
}
|
}
|
||||||
return distance;
|
return distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static double GetVectorAngle(double originNodeX, double originNodeY, double vector1X, double vector1Y, double vector2X, double vector2Y)
|
||||||
|
{
|
||||||
|
double BA_x = vector1X - originNodeX;
|
||||||
|
double BA_y = vector1Y - originNodeY;
|
||||||
|
double BC_x = vector2X - originNodeX;
|
||||||
|
double BC_y = vector2Y - originNodeY;
|
||||||
|
// Tính độ dài của các vector AB và BC
|
||||||
|
double lengthAB = Math.Sqrt(BA_x * BA_x + BA_y * BA_y);
|
||||||
|
double lengthBC = Math.Sqrt(BC_x * BC_x + BC_y * BC_y);
|
||||||
|
// Tính tích vô hướng của AB và BC
|
||||||
|
double dotProduct = BA_x * BC_x + BA_y * BC_y;
|
||||||
|
if (lengthAB * lengthBC == 0) return 0;
|
||||||
|
if (dotProduct / (lengthAB * lengthBC) > 1) return 0;
|
||||||
|
if (dotProduct / (lengthAB * lengthBC) < -1) return 180;
|
||||||
|
return Math.Acos(dotProduct / (lengthAB * lengthBC)) * (180.0 / Math.PI);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,31 @@
|
||||||
namespace RobotApp.Interfaces;
|
namespace RobotApp.Interfaces;
|
||||||
|
|
||||||
|
public enum BatteryStatus
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Charging,
|
||||||
|
Discharging,
|
||||||
|
Full,
|
||||||
|
NotCharging,
|
||||||
|
Fault,
|
||||||
|
Standby
|
||||||
|
}
|
||||||
|
|
||||||
public interface IBattery
|
public interface IBattery
|
||||||
{
|
{
|
||||||
|
bool IsReady { get; }
|
||||||
|
double Voltage { get; }
|
||||||
|
double Current { get; }
|
||||||
|
double SOC { get; }
|
||||||
|
double SOH { get; }
|
||||||
|
BatteryStatus Status { get; }
|
||||||
|
int ChargeTime { get; }
|
||||||
|
int DischargeTime { get; }
|
||||||
|
double Temperature { get; }
|
||||||
|
double RemainingCapacity { get; }
|
||||||
|
double RemainingEnergy { get; }
|
||||||
|
int DischargeCycles { get; }
|
||||||
|
int ChargeCycles { get; }
|
||||||
|
bool IsCharging { get; }
|
||||||
|
string[] ErrorStatus { get; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,6 @@ public interface IError
|
||||||
{
|
{
|
||||||
bool HasFatalError { get; }
|
bool HasFatalError { get; }
|
||||||
void AddError(Error error, TimeSpan? clearAfter = null);
|
void AddError(Error error, TimeSpan? clearAfter = null);
|
||||||
|
void DeleteError(string errorType);
|
||||||
|
void ClearAllErrors();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
|
|
||||||
public interface IInfomation
|
public interface IInfomation
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using Action = RobotApp.VDA5050.InstantAction.Action;
|
||||||
|
|
||||||
namespace RobotApp.Interfaces;
|
namespace RobotApp.Interfaces;
|
||||||
|
|
||||||
public interface IInstanceActions
|
public interface IInstantActions
|
||||||
{
|
{
|
||||||
ActionState[] ActionStates { get; }
|
ActionState[] ActionStates { get; }
|
||||||
bool HasActionRunning { get; }
|
bool HasActionRunning { get; }
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
namespace RobotApp.Interfaces;
|
using RobotApp.Common.Shares;
|
||||||
|
|
||||||
|
namespace RobotApp.Interfaces;
|
||||||
|
|
||||||
public interface ILocalization
|
public interface ILocalization
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Trạng thái sẵn sàng của hệ thống định vị.
|
||||||
|
/// </summary>
|
||||||
|
bool IsReady { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Vị trí hiện tại của robot trong hệ tọa độ toàn cục theo trục X (đơn vị: mét).
|
/// Vị trí hiện tại của robot trong hệ tọa độ toàn cục theo trục X (đơn vị: mét).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -13,10 +20,36 @@ public interface ILocalization
|
||||||
double Y { get; }
|
double Y { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hướng hiện tại của robot trong hệ tọa độ toàn cục (đơn vị: độ).
|
/// Hướng hiện tại của robot trong hệ tọa độ toàn cục (đơn vị: radian).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
double Theta { get; }
|
double Theta { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trang thái SLAM hiện tại của robot.
|
||||||
|
/// </summary>
|
||||||
|
string SlamState { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thông tin chi tiết về trạng thái SLAM hiện tại của robot.
|
||||||
|
/// </summary>
|
||||||
|
string SlamStateDetail { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bản đồ đang được sử dụng để định vị hiện tại.
|
||||||
|
/// </summary>
|
||||||
|
string CurrentActiveMap { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Độ tin cậy của vị trí định vị hiện tại (0.0 - 100.0).
|
||||||
|
/// </summary>
|
||||||
|
double Reliability { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Độ phù hợp của vị trí định vị hiện tại so với bản đồ (0.0 - 100.0).
|
||||||
|
/// </summary>
|
||||||
|
double MatchingScore { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Khởi tạo vị trí của robot trong hệ tọa độ toàn cục.
|
/// Khởi tạo vị trí của robot trong hệ tọa độ toàn cục.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -24,5 +57,57 @@ public interface ILocalization
|
||||||
/// <param name="y">đơn vị: mét</param>
|
/// <param name="y">đơn vị: mét</param>
|
||||||
/// <param name="theta">đơn vị: độ</param>
|
/// <param name="theta">đơn vị: độ</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
bool InitializePosition(double x, double y, double theta);
|
MessageResult SetInitializePosition(double x, double y, double theta);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bắt đầu quá trình lập bản đồ với độ phân giải chỉ định.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resolution">đơn vị mét/px</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
MessageResult StartMapping(double resolution);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dừng quá trình lập bản đồ hiện tại và lưu bản đồ với tên chỉ định.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mapName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
MessageResult StopMapping(string mapName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bắt đầu quá trình định vị sử dụng bản đồ đã lưu.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
MessageResult StartLocalization();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dừng quá trình định vị hiện tại.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
MessageResult StopLocalization();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kích hoạt bản đồ đã lưu để sử dụng trong quá trình định vị.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mapName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
MessageResult ActivateMap(string mapName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chuyển sang bản đồ đã lưu và khởi tạo vị trí của robot trên bản đồ đó.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mapName"></param>
|
||||||
|
/// <param name="x"></param>
|
||||||
|
/// <param name="y"></param>
|
||||||
|
/// <param name="theta"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
MessageResult SwitchMap(string mapName, double x, double y, double theta);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thay đổi gốc tọa độ của bản đồ hiện tại.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x"></param>
|
||||||
|
/// <param name="y"></param>
|
||||||
|
/// <param name="theta"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
MessageResult ChangeMapOrigin(double x, double y, double theta);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ namespace RobotApp.Interfaces;
|
||||||
public enum NavigationState
|
public enum NavigationState
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Initializing,
|
|
||||||
Initialized,
|
|
||||||
Idle,
|
Idle,
|
||||||
|
Initializing,
|
||||||
|
Waiting,
|
||||||
Moving,
|
Moving,
|
||||||
Rotating,
|
Rotating,
|
||||||
|
Canceled,
|
||||||
Paused,
|
Paused,
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
@ -25,13 +26,19 @@ public enum NavigationProccess
|
||||||
|
|
||||||
public interface INavigation
|
public interface INavigation
|
||||||
{
|
{
|
||||||
|
bool IsReady { get; }
|
||||||
bool Driving { get; }
|
bool Driving { get; }
|
||||||
|
double VelocityX { get; }
|
||||||
|
double VelocityY { get; }
|
||||||
|
double Omega { get; }
|
||||||
NavigationState State { get; }
|
NavigationState State { get; }
|
||||||
void Move(Node[] nodes, Edge[] edges);
|
void Move(Node[] nodes, Edge[] edges);
|
||||||
void MoveStraight(double x, double y);
|
void MoveStraight(double x, double y);
|
||||||
void Rotate(double angle);
|
void Rotate(double angle);
|
||||||
void Paused();
|
void Paused();
|
||||||
void Resume();
|
void Resume();
|
||||||
void UpdateOrder(int lastBaseSequence);
|
void UpdateOrder(string lastBaseNodeId);
|
||||||
|
void RefreshOrder(Node[] nodes, Edge[] edges);
|
||||||
|
void Refresh();
|
||||||
void CancelMovement();
|
void CancelMovement();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,35 @@
|
||||||
namespace RobotApp.Interfaces;
|
namespace RobotApp.Interfaces;
|
||||||
|
|
||||||
public enum OperatingMode
|
public enum PeripheralMode
|
||||||
{
|
{
|
||||||
AUTOMATIC,
|
AUTOMATIC,
|
||||||
MANUAL,
|
MANUAL,
|
||||||
SEMIAUTOMATIC,
|
|
||||||
TEACHIN,
|
|
||||||
SERVICE,
|
SERVICE,
|
||||||
}
|
}
|
||||||
public interface IPeripheral
|
public interface IPeripheral
|
||||||
{
|
{
|
||||||
OperatingMode OperatingMode { get; }
|
bool IsReady { get; }
|
||||||
|
PeripheralMode PeripheralMode { get; }
|
||||||
|
bool Emergency { get; }
|
||||||
|
bool Bumper { get; }
|
||||||
|
bool LiftedUp { get; }
|
||||||
|
bool LiftedDown { get; }
|
||||||
|
bool LiftHome { get; }
|
||||||
|
bool LeftMotorReady { get; }
|
||||||
|
bool RightMotorReady { get; }
|
||||||
|
bool LiftMotorReady { get; }
|
||||||
|
bool SwitchLock { get; }
|
||||||
|
bool SwitchManual { get; }
|
||||||
|
bool SwitchAuto { get; }
|
||||||
|
bool ButtonStart { get; }
|
||||||
|
bool ButtonStop { get; }
|
||||||
|
bool ButtonReset { get; }
|
||||||
|
bool HasLoad { get; }
|
||||||
|
bool MutedBase { get; }
|
||||||
|
bool MutedLoad { get; }
|
||||||
|
bool StartedCharging { get; }
|
||||||
|
|
||||||
|
bool SetSytemState();
|
||||||
|
bool SetProccessState();
|
||||||
|
bool SetOnCharging();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,29 @@
|
||||||
namespace RobotApp.Interfaces
|
namespace RobotApp.Interfaces;
|
||||||
|
|
||||||
|
public enum RFHandlerMode
|
||||||
{
|
{
|
||||||
public class IRFHandler
|
Default,
|
||||||
{
|
Maintenance,
|
||||||
}
|
Override,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IRFHandler
|
||||||
|
{
|
||||||
|
bool IsConnected { get; }
|
||||||
|
bool IsEnabled { get; }
|
||||||
|
bool IsLocked { get; }
|
||||||
|
bool EStop { get; }
|
||||||
|
bool LiftUp { get; }
|
||||||
|
bool LiftDown { get; }
|
||||||
|
int Speed { get; }
|
||||||
|
bool Forward { get; }
|
||||||
|
bool Backward { get; }
|
||||||
|
bool RotateLeft { get; }
|
||||||
|
bool RotateRight { get; }
|
||||||
|
bool Left { get; }
|
||||||
|
bool Right { get; }
|
||||||
|
RFHandlerMode Mode { get; }
|
||||||
|
event Action<RFHandlerMode> OnStateChanged;
|
||||||
|
event Action<bool>? OnConnectionChanged;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,17 @@
|
||||||
|
|
||||||
public interface ISafety
|
public interface ISafety
|
||||||
{
|
{
|
||||||
|
bool SpeedVerySlow { get; }
|
||||||
|
bool SpeedSlow { get; }
|
||||||
|
bool SpeedNormal { get; }
|
||||||
|
bool SpeedMedium { get; }
|
||||||
|
bool SpeedOptimal { get; }
|
||||||
|
bool SpeedFast { get; }
|
||||||
|
bool SpeedVeryFast { get; }
|
||||||
|
bool LidarFrontProtectField { get; }
|
||||||
|
bool LidarBackProtectField { get; }
|
||||||
|
bool LidarFrontTimProtectField { get; }
|
||||||
|
bool SetMutedLoad(bool muted);
|
||||||
|
bool SetMutedBase(bool muted);
|
||||||
|
bool SetHorizontalLoad();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISensorIMU
|
public interface ISensorIMU
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Trạng thái sẵn sàng của cảm biến IMU
|
||||||
|
/// </summary>
|
||||||
|
bool IsReady { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Góc xoay của robot (đơn vị độ)
|
/// Góc xoay của robot (đơn vị độ)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ using MudBlazor.Services;
|
||||||
using RobotApp.Components;
|
using RobotApp.Components;
|
||||||
using RobotApp.Components.Account;
|
using RobotApp.Components.Account;
|
||||||
using RobotApp.Data;
|
using RobotApp.Data;
|
||||||
|
using RobotApp.Services;
|
||||||
using RobotApp.Services.Robot;
|
using RobotApp.Services.Robot;
|
||||||
|
using RobotApp.Services.Robot.Simulation;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
|
@ -46,7 +48,9 @@ builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSe
|
||||||
|
|
||||||
builder.Services.AddSingleton(typeof(RobotApp.Services.Logger<>));
|
builder.Services.AddSingleton(typeof(RobotApp.Services.Logger<>));
|
||||||
builder.Services.AddSingleton<RobotConnection>();
|
builder.Services.AddSingleton<RobotConnection>();
|
||||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<RobotConnection>());
|
|
||||||
|
builder.Services.AddRobotSimulation();
|
||||||
|
builder.Services.AddRobot();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
await app.Services.SeedApplicationDbAsync();
|
await app.Services.SeedApplicationDbAsync();
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,4 @@
|
||||||
<PackageReference Include="MQTTnet" Version="5.0.1.1416" />
|
<PackageReference Include="MQTTnet" Version="5.0.1.1416" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Services\Robot\Simulation\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
namespace RobotApp.Services.Exceptions;
|
using RobotApp.VDA5050.State;
|
||||||
|
|
||||||
public class ActionException : OrderException
|
namespace RobotApp.Services.Exceptions;
|
||||||
|
|
||||||
|
public class ActionException : Exception
|
||||||
{
|
{
|
||||||
|
public ActionException(string message) : base(message) { }
|
||||||
|
public ActionException(string message, Exception inner) : base(message, inner) { }
|
||||||
|
public ActionException() : base() { }
|
||||||
|
public ActionException(Error error) : base()
|
||||||
|
{
|
||||||
|
Error = error;
|
||||||
|
}
|
||||||
|
public Error? Error { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
namespace RobotApp.Services.Exceptions;
|
using RobotApp.VDA5050.State;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Exceptions;
|
||||||
|
|
||||||
public class ModbusException : Exception
|
public class ModbusException : Exception
|
||||||
{
|
{
|
||||||
public ModbusException()
|
public ModbusException(string message) : base(message) { }
|
||||||
{
|
public ModbusException(string message, Exception inner) : base(message, inner) { }
|
||||||
}
|
public ModbusException() : base() { }
|
||||||
public ModbusException(string message)
|
public ModbusException(Error error) : base()
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
public ModbusException(string message, Exception inner)
|
|
||||||
: base(message, inner)
|
|
||||||
{
|
{
|
||||||
|
Error = error;
|
||||||
}
|
}
|
||||||
|
public Error? Error { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
RobotApp/Services/Exceptions/PathPlannerException.cs
Normal file
15
RobotApp/Services/Exceptions/PathPlannerException.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
using RobotApp.VDA5050.State;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Exceptions;
|
||||||
|
|
||||||
|
public class PathPlannerException : Exception
|
||||||
|
{
|
||||||
|
public PathPlannerException(string message) : base(message) { }
|
||||||
|
public PathPlannerException(string message, Exception inner) : base(message, inner) { }
|
||||||
|
public PathPlannerException() : base() { }
|
||||||
|
public PathPlannerException(Error error) : base()
|
||||||
|
{
|
||||||
|
Error = error;
|
||||||
|
}
|
||||||
|
public Error? Error { get; set; }
|
||||||
|
}
|
||||||
15
RobotApp/Services/Exceptions/SimulationException.cs
Normal file
15
RobotApp/Services/Exceptions/SimulationException.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
using RobotApp.VDA5050.State;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Exceptions;
|
||||||
|
|
||||||
|
public class SimulationException : Exception
|
||||||
|
{
|
||||||
|
public SimulationException(string message) : base(message) { }
|
||||||
|
public SimulationException(string message, Exception inner) : base(message, inner) { }
|
||||||
|
public SimulationException() : base() { }
|
||||||
|
public SimulationException(Error error) : base()
|
||||||
|
{
|
||||||
|
Error = error;
|
||||||
|
}
|
||||||
|
public Error? Error { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -13,11 +13,12 @@ public class ModbusTcpClient(string IpAddress, int Port, byte ClientId) : IDispo
|
||||||
private NetworkStream? stream;
|
private NetworkStream? stream;
|
||||||
private bool disposed = false;
|
private bool disposed = false;
|
||||||
|
|
||||||
|
private readonly Lock LockObject = new();
|
||||||
private uint transactionIdentifierInternal = 0;
|
private uint transactionIdentifierInternal = 0;
|
||||||
private const int connectTimeout = 3000;
|
private const int connectTimeout = 3000;
|
||||||
private const int readTimeout = 500;
|
private const int readTimeout = 500;
|
||||||
private const int writeTimeout = 500;
|
private const int writeTimeout = 500;
|
||||||
private int numberOfRetries { get; set; } = 3;
|
private const int numberOfRetries = 3;
|
||||||
|
|
||||||
~ModbusTcpClient()
|
~ModbusTcpClient()
|
||||||
{
|
{
|
||||||
|
|
@ -100,10 +101,29 @@ public class ModbusTcpClient(string IpAddress, int Port, byte ClientId) : IDispo
|
||||||
disposed = true;
|
disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private uint GetNextTransactionId()
|
||||||
|
{
|
||||||
|
lock (LockObject)
|
||||||
|
{
|
||||||
|
return ++transactionIdentifierInternal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldRetry(Exception ex)
|
||||||
|
{
|
||||||
|
return ex is ConnectionAbortedException ||
|
||||||
|
ex is TimeoutException ||
|
||||||
|
ex is SocketException ||
|
||||||
|
ex is InvalidOperationException ||
|
||||||
|
(ex is IOException && ex.InnerException is SocketException);
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] Write(byte functionCode, ushort startingAddress, ushort quantity, CancellationToken? cancellationToken = null)
|
private byte[] Write(byte functionCode, ushort startingAddress, ushort quantity, CancellationToken? cancellationToken = null)
|
||||||
=> Write(functionCode, startingAddress, quantity, [], cancellationToken);
|
=> Write(functionCode, startingAddress, quantity, [], cancellationToken);
|
||||||
private byte[] Write(byte functionCode, ushort startingAddress, ushort quantity, byte[] multipleData, CancellationToken? cancellationToken = null)
|
private byte[] Write(byte functionCode, ushort startingAddress, ushort quantity, byte[] multipleData, CancellationToken? cancellationToken = null)
|
||||||
|
=> Write(functionCode, startingAddress, quantity, multipleData, 0, cancellationToken);
|
||||||
|
private byte[] Write(byte functionCode, ushort startingAddress, ushort quantity, byte[] multipleData, int retryAttempt, CancellationToken? cancellationToken = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -112,11 +132,11 @@ public class ModbusTcpClient(string IpAddress, int Port, byte ClientId) : IDispo
|
||||||
if (!Connect() || !IsConnected || stream is null) throw new ConnectionAbortedException();
|
if (!Connect() || !IsConnected || stream is null) throw new ConnectionAbortedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionIdentifierInternal++;
|
var transactionId = GetNextTransactionId();
|
||||||
byte[] writeData = new byte[12 + multipleData.Length];
|
byte[] writeData = new byte[12 + multipleData.Length];
|
||||||
var dataLength = multipleData.Length + 6;
|
var dataLength = multipleData.Length + 6;
|
||||||
writeData[0] = (byte)(transactionIdentifierInternal >> 8);
|
writeData[0] = (byte)(transactionId >> 8);
|
||||||
writeData[1] = (byte)(transactionIdentifierInternal & 0xFF);
|
writeData[1] = (byte)(transactionId & 0xFF);
|
||||||
writeData[2] = 0x00;
|
writeData[2] = 0x00;
|
||||||
writeData[3] = 0x00;
|
writeData[3] = 0x00;
|
||||||
writeData[4] = (byte)(dataLength >> 8);
|
writeData[4] = (byte)(dataLength >> 8);
|
||||||
|
|
@ -171,6 +191,11 @@ public class ModbusTcpClient(string IpAddress, int Port, byte ClientId) : IDispo
|
||||||
Array.Copy(readData, 9, receiveData, 0, receiveData.Length);
|
Array.Copy(readData, 9, receiveData, 0, receiveData.Length);
|
||||||
return receiveData;
|
return receiveData;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex) when (ShouldRetry(ex) && retryAttempt >= numberOfRetries)
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
return Write(functionCode, startingAddress, quantity, multipleData, retryAttempt + 1, cancellationToken);
|
||||||
|
}
|
||||||
catch (Exception ex) when (!(ex is ModbusException || ex is TimeoutException))
|
catch (Exception ex) when (!(ex is ModbusException || ex is TimeoutException))
|
||||||
{
|
{
|
||||||
throw new ModbusException("Communication error", ex);
|
throw new ModbusException("Communication error", ex);
|
||||||
|
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
//namespace RobotApp.Services.Navigation.Algorithm;
|
|
||||||
//public class PurePursuit
|
|
||||||
//{
|
|
||||||
// private double MaxAngularVelocity = 1.5;
|
|
||||||
// private double LookaheadDistance = 0.5;
|
|
||||||
// private List<NavigationNode> Waypoints_Value = [];
|
|
||||||
// public int OnNodeIndex = 0;
|
|
||||||
// public int GoalIndex = 0;
|
|
||||||
// public NavigationNode? Goal;
|
|
||||||
|
|
||||||
// public PurePursuit WithLookheadDistance(double distance)
|
|
||||||
// {
|
|
||||||
// LookaheadDistance = distance;
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public PurePursuit WithMaxAngularVelocity(double vel)
|
|
||||||
// {
|
|
||||||
// MaxAngularVelocity = vel;
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public PurePursuit WithPath(NavigationNode[] path)
|
|
||||||
// {
|
|
||||||
// Waypoints_Value = [.. path];
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void UpdatePath(NavigationNode[] path)
|
|
||||||
// {
|
|
||||||
// Waypoints_Value = [.. path];
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void UpdateGoal(NavigationNode goal)
|
|
||||||
// {
|
|
||||||
// Goal = goal;
|
|
||||||
// GoalIndex = Waypoints_Value.IndexOf(Goal);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public (NavigationNode node, int index) GetOnNode(double x, double y)
|
|
||||||
// {
|
|
||||||
// double minDistance = double.MaxValue;
|
|
||||||
// NavigationNode onNode = Waypoints_Value[0];
|
|
||||||
// int index = 0;
|
|
||||||
// for (int i = 1; i < Waypoints_Value.Count; i++)
|
|
||||||
// {
|
|
||||||
// var distance = Math.Sqrt(Math.Pow(x - Waypoints_Value[i].X, 2) + Math.Pow(y - Waypoints_Value[i].Y, 2));
|
|
||||||
// if (distance < minDistance)
|
|
||||||
// {
|
|
||||||
// onNode = Waypoints_Value[i];
|
|
||||||
// minDistance = distance;
|
|
||||||
// index = i;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return (onNode, index);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private (NavigationNode? node, int index) OnNode(double x, double y)
|
|
||||||
// {
|
|
||||||
// if (Waypoints_Value is null || Waypoints_Value.Count == 0) return (null, 0);
|
|
||||||
// double minDistance = double.MaxValue;
|
|
||||||
// int index = 0;
|
|
||||||
// NavigationNode? onNode = null;
|
|
||||||
// if (Goal is null) return (onNode, index);
|
|
||||||
// for (int i = OnNodeIndex; i < Waypoints_Value.IndexOf(Goal); i++)
|
|
||||||
// {
|
|
||||||
// var distance = Math.Sqrt(Math.Pow(x - Waypoints_Value[i].X, 2) + Math.Pow(y - Waypoints_Value[i].Y, 2));
|
|
||||||
// if (distance < minDistance)
|
|
||||||
// {
|
|
||||||
// onNode = Waypoints_Value[i];
|
|
||||||
// minDistance = distance;
|
|
||||||
// index = i;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return (onNode, index);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public double PurePursuit_step(double X_Ref, double Y_Ref, double Angle_Ref)
|
|
||||||
// {
|
|
||||||
// if (Waypoints_Value is null || Waypoints_Value.Count < 2) return 0;
|
|
||||||
// NavigationNode? lookaheadStartPt = null;
|
|
||||||
// var (onNode, index) = OnNode(X_Ref, Y_Ref);
|
|
||||||
// if (onNode is null || Goal is null) return 0;
|
|
||||||
// OnNodeIndex = index;
|
|
||||||
// double lookDistance = 0;
|
|
||||||
// for (int i = OnNodeIndex + 1; i < Waypoints_Value.IndexOf(Goal); i++)
|
|
||||||
// {
|
|
||||||
// lookDistance += Math.Sqrt(Math.Pow(Waypoints_Value[i - 1].X - Waypoints_Value[i].X, 2) + Math.Pow(Waypoints_Value[i - 1].Y - Waypoints_Value[i].Y, 2));
|
|
||||||
// if (lookDistance >= LookaheadDistance || Waypoints_Value[i].Direction != onNode.Direction)
|
|
||||||
// {
|
|
||||||
// lookaheadStartPt = Waypoints_Value[i];
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// lookaheadStartPt ??= Goal;
|
|
||||||
// if (onNode.Direction == RobotDirection.BACKWARD)
|
|
||||||
// {
|
|
||||||
// if (Angle_Ref > Math.PI) Angle_Ref -= Math.PI * 2;
|
|
||||||
// else if (Angle_Ref < -Math.PI) Angle_Ref += Math.PI * 2;
|
|
||||||
// Angle_Ref += Math.PI;
|
|
||||||
// if (Angle_Ref > Math.PI) Angle_Ref -= Math.PI * 2;
|
|
||||||
// }
|
|
||||||
// var distance = Math.Atan2(lookaheadStartPt.Y - Y_Ref, lookaheadStartPt.X - X_Ref) - Angle_Ref;
|
|
||||||
|
|
||||||
// if (Math.Abs(distance) > Math.PI)
|
|
||||||
// {
|
|
||||||
// double minDistance;
|
|
||||||
// if (distance + Math.PI == 0.0) minDistance = 0.0;
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// double data = (distance + Math.PI) / (2 * Math.PI);
|
|
||||||
// if (data < 0) data = Math.Round(data + 0.5);
|
|
||||||
// else data = Math.Round(data - 0.5);
|
|
||||||
// minDistance = distance + Math.PI - data * (2 * Math.PI);
|
|
||||||
// double checker = 0;
|
|
||||||
// if (minDistance != 0.0)
|
|
||||||
// {
|
|
||||||
// checker = Math.Abs((distance + Math.PI) / (2 * Math.PI));
|
|
||||||
// }
|
|
||||||
// if (!(Math.Abs(checker - Math.Floor(checker + 0.5)) > 2.2204460492503131E-16 * checker))
|
|
||||||
// {
|
|
||||||
// minDistance = 0.0;
|
|
||||||
// }
|
|
||||||
// else if (distance + Math.PI < 0.0)
|
|
||||||
// {
|
|
||||||
// minDistance += Math.PI * 2;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (minDistance == 0.0 && distance + Math.PI > 0.0)
|
|
||||||
// {
|
|
||||||
// minDistance = Math.PI * 2;
|
|
||||||
// }
|
|
||||||
// distance = minDistance - Math.PI;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var AngularVelocity = 2.0 * 0.5 * Math.Sin(distance) / LookaheadDistance;
|
|
||||||
// if (Math.Abs(AngularVelocity) > MaxAngularVelocity)
|
|
||||||
// {
|
|
||||||
// if (AngularVelocity < 0.0)
|
|
||||||
// {
|
|
||||||
// AngularVelocity = -1.0;
|
|
||||||
// }
|
|
||||||
// else if (AngularVelocity > 0.0)
|
|
||||||
// {
|
|
||||||
// AngularVelocity = 1.0;
|
|
||||||
// }
|
|
||||||
// else if (AngularVelocity == 0.0)
|
|
||||||
// {
|
|
||||||
// AngularVelocity = 0.0;
|
|
||||||
// }
|
|
||||||
// AngularVelocity *= MaxAngularVelocity;
|
|
||||||
// }
|
|
||||||
// return AngularVelocity;
|
|
||||||
// }
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
using RobotApp.Interfaces;
|
|
||||||
|
|
||||||
namespace RobotApp.Services.Robot.Navigation;
|
|
||||||
|
|
||||||
public class DifferentialNavigation(Logger<NavigationController> navLogger,
|
|
||||||
Logger<DifferentialNavigation> Logger,
|
|
||||||
IDriver Driver,
|
|
||||||
ISafety Safety,
|
|
||||||
ISensorIMU SensorIMU) : NavigationController(navLogger)
|
|
||||||
{
|
|
||||||
protected override void NavigationHandler()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Implement differential drive navigation logic here
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Write($"Error in DifferentialNavigation: {ex.Message}", LogLevel.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
using RobotApp.Interfaces;
|
|
||||||
using RobotApp.VDA5050.Order;
|
|
||||||
|
|
||||||
namespace RobotApp.Services.Robot.Navigation;
|
|
||||||
|
|
||||||
public class NavigationController(Logger<NavigationController> Logger) : INavigation
|
|
||||||
{
|
|
||||||
public NavigationState State { get; private set; } = NavigationState.None;
|
|
||||||
public bool Driving { get; private set; }
|
|
||||||
|
|
||||||
protected const int CycleHandlerMilliseconds = 20;
|
|
||||||
private WatchTimer<NavigationController>? NavigationTimer;
|
|
||||||
|
|
||||||
protected Node[] Nodes = [];
|
|
||||||
protected Edge[] Edges = [];
|
|
||||||
|
|
||||||
protected void HandleNavigationStart()
|
|
||||||
{
|
|
||||||
NavigationTimer = new(CycleHandlerMilliseconds, NavigationHandler, Logger);
|
|
||||||
NavigationTimer.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void HandleNavigationStop()
|
|
||||||
{
|
|
||||||
NavigationTimer?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void NavigationHandler() { }
|
|
||||||
|
|
||||||
public void CancelMovement()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Move(Node[] nodes, Edge[] edges)
|
|
||||||
{
|
|
||||||
Nodes = nodes;
|
|
||||||
Edges = edges;
|
|
||||||
State = NavigationState.Initializing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MoveStraight(double x, double y)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Paused()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Resume()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Rotate(double angle)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateOrder(int lastBaseSequence)
|
|
||||||
{
|
|
||||||
State = NavigationState.Initialized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace RobotApp.Services.Robot.Navigation
|
|
||||||
{
|
|
||||||
public class NavigationManager
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,7 @@ using RobotApp.VDA5050.State;
|
||||||
|
|
||||||
namespace RobotApp.Services.Robot;
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
public class RobotAction : IInstanceActions
|
public class RobotAction : IInstantActions
|
||||||
{
|
{
|
||||||
public ActionState[] ActionStates { get; private set; } = [];
|
public ActionState[] ActionStates { get; private set; } = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,36 @@
|
||||||
namespace RobotApp.Services.Robot
|
using RobotApp.Interfaces;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
|
public class RobotBattery : IBattery
|
||||||
{
|
{
|
||||||
public class RobotBattery
|
public bool IsReady { get; private set; } = true;
|
||||||
{
|
|
||||||
}
|
public double Voltage => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public double Current => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public double SOC => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public double SOH => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public BatteryStatus Status => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public int ChargeTime => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public int DischargeTime => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public double Temperature => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public double RemainingCapacity => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public double RemainingEnergy => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public int DischargeCycles => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public int ChargeCycles => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public bool IsCharging => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public string[] ErrorStatus => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,17 @@ public class RobotConfiguration
|
||||||
{
|
{
|
||||||
public static string SerialNumber { get; set; } = "Robot-Demo";
|
public static string SerialNumber { get; set; } = "Robot-Demo";
|
||||||
public static NavigationType NavigationType { get; set; } = NavigationType.Differential;
|
public static NavigationType NavigationType { get; set; } = NavigationType.Differential;
|
||||||
|
public static VDA5050.VDA5050Setting VDA5050Setting { get; set; } = new()
|
||||||
|
{
|
||||||
|
HostServer = "172.20.235.170",
|
||||||
|
Port = 1885,
|
||||||
|
UserName = "robotics",
|
||||||
|
Password = "robotics",
|
||||||
|
Manufacturer = "PhenikaaX",
|
||||||
|
Version = "0.0.1",
|
||||||
|
PublishRepeat = 2,
|
||||||
|
};
|
||||||
|
public static string PLCAddress { get; set; } = "127.0.0.1";
|
||||||
|
public static int PLCPort { get; set; } = 502;
|
||||||
|
public static int PLCUnitId { get; set; } = 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,13 @@ using System.Text.Json;
|
||||||
|
|
||||||
namespace RobotApp.Services.Robot;
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
public class RobotConnection(Logger<RobotConnection> Logger, Logger<MQTTClient> MQTTClientLogger) : BackgroundService
|
public class RobotConnection(Logger<RobotConnection> Logger, Logger<MQTTClient> MQTTClientLogger)
|
||||||
{
|
{
|
||||||
private readonly VDA5050Setting VDA5050Setting = new()
|
private readonly VDA5050Setting VDA5050Setting = RobotConfiguration.VDA5050Setting;
|
||||||
{
|
|
||||||
HostServer = "172.20.235.170",
|
|
||||||
Port = 1886,
|
|
||||||
};
|
|
||||||
private MQTTClient? MqttClient;
|
private MQTTClient? MqttClient;
|
||||||
|
|
||||||
public bool IsConnected => MqttClient is not null && MqttClient.IsConnected;
|
public bool IsConnected => MqttClient is not null && MqttClient.IsConnected;
|
||||||
public readonly static string SerialNumber = "RobotDemo";
|
public readonly static string SerialNumber = RobotConfiguration.SerialNumber;
|
||||||
|
|
||||||
private void OrderChanged(string data)
|
private void OrderChanged(string data)
|
||||||
{
|
{
|
||||||
|
|
@ -50,20 +46,17 @@ public class RobotConnection(Logger<RobotConnection> Logger, Logger<MQTTClient>
|
||||||
return new(false, "Chưa có kết nối tới broker");
|
return new(false, "Chưa có kết nối tới broker");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
public async Task StartConnection(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
|
||||||
|
|
||||||
MqttClient = new MQTTClient(SerialNumber, VDA5050Setting, MQTTClientLogger);
|
MqttClient = new MQTTClient(SerialNumber, VDA5050Setting, MQTTClientLogger);
|
||||||
MqttClient.OrderChanged += OrderChanged;
|
MqttClient.OrderChanged += OrderChanged;
|
||||||
MqttClient.InstanceActionsChanged += InstanceActionsChanged;
|
MqttClient.InstanceActionsChanged += InstanceActionsChanged;
|
||||||
await MqttClient.ConnectAsync(stoppingToken);
|
await MqttClient.ConnectAsync(cancellationToken);
|
||||||
await MqttClient.SubscribeAsync(stoppingToken);
|
await MqttClient.SubscribeAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
public async Task StopConnection()
|
||||||
{
|
{
|
||||||
if(MqttClient is not null) await MqttClient.DisposeAsync();
|
if (MqttClient is not null) await MqttClient.DisposeAsync();
|
||||||
await base.StopAsync(cancellationToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
using RobotApp.Interfaces;
|
using RobotApp.Interfaces;
|
||||||
using RobotApp.Services.Exceptions;
|
using RobotApp.Services.Exceptions;
|
||||||
|
using RobotApp.Services.State;
|
||||||
|
using RobotApp.VDA5050.InstantAction;
|
||||||
using RobotApp.VDA5050.Order;
|
using RobotApp.VDA5050.Order;
|
||||||
using RobotApp.VDA5050.State;
|
|
||||||
|
|
||||||
namespace RobotApp.Services.Robot;
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
public class RobotController(IOrder OrderManager,
|
public class RobotController(IOrder OrderManager,
|
||||||
INavigation NavigationManager,
|
INavigation NavigationManager,
|
||||||
IInstanceActions ActionManager,
|
IInstantActions ActionManager,
|
||||||
IBattery BatteryManager,
|
IBattery BatteryManager,
|
||||||
ILocalization Localization,
|
ILocalization Localization,
|
||||||
IPeripheral PeripheralManager,
|
IPeripheral PeripheralManager,
|
||||||
|
|
@ -15,7 +16,8 @@ public class RobotController(IOrder OrderManager,
|
||||||
IError ErrorManager,
|
IError ErrorManager,
|
||||||
IInfomation InfomationManager,
|
IInfomation InfomationManager,
|
||||||
Logger<RobotController> Logger,
|
Logger<RobotController> Logger,
|
||||||
RobotConnection ConnectionManager) : BackgroundService
|
RobotConnection ConnectionManager,
|
||||||
|
RobotStateMachine StateManager) : BackgroundService
|
||||||
{
|
{
|
||||||
private readonly Mutex NewOrderMutex = new();
|
private readonly Mutex NewOrderMutex = new();
|
||||||
private readonly Mutex NewInstanceMutex = new();
|
private readonly Mutex NewInstanceMutex = new();
|
||||||
|
|
@ -25,11 +27,25 @@ public class RobotController(IOrder OrderManager,
|
||||||
|
|
||||||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
|
InitializationingHandler();
|
||||||
|
|
||||||
UpdateStateTimer = new(UpdateStateInterval, UpdateStateHandler, Logger);
|
UpdateStateTimer = new(UpdateStateInterval, UpdateStateHandler, Logger);
|
||||||
UpdateStateTimer.Start();
|
UpdateStateTimer.Start();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InitializationingHandler()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
StateManager.InitializeHierarchyStates();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateStateHandler()
|
private void UpdateStateHandler()
|
||||||
{
|
{
|
||||||
// xử lý cập nhật trạng thái robot và gửi thông tin qua kết nối
|
// xử lý cập nhật trạng thái robot và gửi thông tin qua kết nối
|
||||||
|
|
@ -43,13 +59,13 @@ public class RobotController(IOrder OrderManager,
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(OrderManager.OrderId))
|
if (!string.IsNullOrEmpty(OrderManager.OrderId))
|
||||||
{
|
{
|
||||||
if (order.OrderId != OrderManager.OrderId) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1001", ErrorLevel.WARNING, $"Có order đang được thực hiện. OrderId: {OrderManager.OrderId}, OrderId mới: {order.OrderId}"));
|
if (order.OrderId != OrderManager.OrderId) throw new OrderException(RobotErrors.Error1001(OrderManager.OrderId, order.OrderId));
|
||||||
OrderManager.UpdateOrder(order.OrderUpdateId, order.Nodes, order.Edges);
|
OrderManager.UpdateOrder(order.OrderUpdateId, order.Nodes, order.Edges);
|
||||||
}
|
}
|
||||||
else if (PeripheralManager.OperatingMode != Interfaces.OperatingMode.AUTOMATIC) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1006", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi chế độ vận hành không phải là TỰ ĐỘNG. Chế độ hiện tại: {PeripheralManager.OperatingMode}"));
|
else if (PeripheralManager.PeripheralMode != PeripheralMode.AUTOMATIC) throw new OrderException(RobotErrors.Error1006(PeripheralManager.PeripheralMode));
|
||||||
else if(ActionManager.HasActionRunning) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1007", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi có action đang thực hiện. Vui lòng chờ hoàn thành các action hiện tại."));
|
else if (ActionManager.HasActionRunning) throw new OrderException(RobotErrors.Error1007());
|
||||||
else if(ErrorManager.HasFatalError) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1008", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi có lỗi nghiêm trọng. Vui lòng kiểm tra và xử lý lỗi."));
|
else if (ErrorManager.HasFatalError) throw new OrderException(RobotErrors.Error1008());
|
||||||
else if(NavigationManager.Driving) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1009", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi robot đang di chuyển. Vui lòng dừng robot."));
|
else if (NavigationManager.Driving) throw new OrderException(RobotErrors.Error1009());
|
||||||
else OrderManager.StartOrder(order.OrderId, order.Nodes, order.Edges);
|
else OrderManager.StartOrder(order.OrderId, order.Nodes, order.Edges);
|
||||||
}
|
}
|
||||||
catch (OrderException orEx)
|
catch (OrderException orEx)
|
||||||
|
|
@ -71,7 +87,7 @@ public class RobotController(IOrder OrderManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NewInstanceActionUpdated()
|
public void NewInstanceActionUpdated(InstantActionsMsg action)
|
||||||
{
|
{
|
||||||
if (NewInstanceMutex.WaitOne(2000))
|
if (NewInstanceMutex.WaitOne(2000))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using RobotApp.VDA5050.State;
|
using Microsoft.AspNetCore.Components;
|
||||||
using RobotApp.VDA5050.Type;
|
using RobotApp.Interfaces;
|
||||||
|
using RobotApp.VDA5050.State;
|
||||||
|
|
||||||
namespace RobotApp.Services.Robot;
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
|
|
@ -38,4 +39,35 @@ public class RobotErrors
|
||||||
ErrorReferences = []
|
ErrorReferences = []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Error Error1001(string oldOrderId, string newOrderId)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1001", ErrorLevel.WARNING, $"Có order đang được thực hiện. OrderId: {oldOrderId}, OrderId mới: {newOrderId}");
|
||||||
|
public static Error Error1002(int nodesLength)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1002", ErrorLevel.WARNING, $"Order Nodes không hợp lệ. Kích thước: {nodesLength}");
|
||||||
|
public static Error Error1003(int edgesLength)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1003", ErrorLevel.WARNING, $"Order Edges không hợp lệ. Kích thước: {edgesLength}");
|
||||||
|
public static Error Error1004(int nodesLength, int edgesLength)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1004", ErrorLevel.WARNING, $"Order không hợp lệ do kích thước giữa Nodes và Edges không phù hợp. Kích thước Edges: {edgesLength}, kích thước nodes: {nodesLength}");
|
||||||
|
public static Error Error1005()
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1005", ErrorLevel.WARNING, $"Không có order đang được thực hiện.");
|
||||||
|
public static Error Error1006(PeripheralMode peripheralMode)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1006", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi chế độ vận hành không phải là TỰ ĐỘNG. Chế độ hiện tại: {peripheralMode}");
|
||||||
|
public static Error Error1007()
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1007", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi có action đang thực hiện. Vui lòng chờ hoàn thành các action hiện tại.");
|
||||||
|
public static Error Error1008()
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1008", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi có lỗi nghiêm trọng. Vui lòng kiểm tra và xử lý lỗi.");
|
||||||
|
public static Error Error1009()
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1009", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi robot đang di chuyển. Vui lòng dừng robot.");
|
||||||
|
public static Error Error1010(string nodeId, int sequenceId, int correctIndex)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1010", ErrorLevel.WARNING, $"Order Nodes không đúng thứ tự. NodeId: {nodeId}, SequenceId: {sequenceId}, Vị trí đúng: {correctIndex}");
|
||||||
|
public static Error Error1011(string edgeId, int sequenceId, int correctIndex)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1011", ErrorLevel.WARNING, $"Order Edges không đúng thứ tự. EdgeId: {edgeId}, SequenceId: {sequenceId}, Vị trí đúng: {correctIndex}");
|
||||||
|
public static Error Error1012(NavigationState state)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1012", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi hệ thống điều hướng không ở trạng thái sẵn sàng. Trạng thái hiện tại: {state}");
|
||||||
|
public static Error Error1013()
|
||||||
|
=> new();
|
||||||
|
public static Error Error1014(string edgeId, string nodeId)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1014", ErrorLevel.WARNING, $"Edge {edgeId} chứa StartNodeId {nodeId} không tồn tại trong Nodes");
|
||||||
|
public static Error Error1015(string edgeId, string nodeId)
|
||||||
|
=> CreateError(ErrorType.INITIALIZE_ORDER, "1015", ErrorLevel.WARNING, $"Edge {edgeId} chứa {nodeId} không tồn tại trong Nodes");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
namespace RobotApp.Services.Robot
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
|
public class RobotLoads
|
||||||
{
|
{
|
||||||
public class RobotLoads
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
83
RobotApp/Services/Robot/RobotLocalization.cs
Normal file
83
RobotApp/Services/Robot/RobotLocalization.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
using RobotApp.Common.Shares;
|
||||||
|
using RobotApp.Interfaces;
|
||||||
|
using RobotApp.Services.Robot.Simulation;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
|
public class Xloc
|
||||||
|
{
|
||||||
|
public double X { get; set; }
|
||||||
|
public double Y { get; set; }
|
||||||
|
public double Theta { get; set; }
|
||||||
|
public string SlamState { get; set; } = string.Empty;
|
||||||
|
public string SlamStateDetail { get; set; } = string.Empty;
|
||||||
|
public string CurrentActiveMap { get; set; } = string.Empty;
|
||||||
|
public double Reliability { get; set; }
|
||||||
|
public double MatchingScore { get; set; }
|
||||||
|
public bool IsReady { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RobotLocalization(IConfiguration Configuration, SimulationVisualization SimVisualization) : ILocalization
|
||||||
|
{
|
||||||
|
public double X => IsSimulation ? SimVisualization.X : Xloc.X;
|
||||||
|
|
||||||
|
public double Y => IsSimulation ? SimVisualization.Y : Xloc.Y;
|
||||||
|
|
||||||
|
public double Theta => IsSimulation ? SimVisualization.Theta * Math.PI / 180 : Xloc.Theta;
|
||||||
|
|
||||||
|
public string SlamState => IsSimulation ? "Localization" : Xloc.SlamState;
|
||||||
|
|
||||||
|
public string SlamStateDetail => IsSimulation ? "" : Xloc.SlamStateDetail;
|
||||||
|
|
||||||
|
public string CurrentActiveMap => IsSimulation ? "" : Xloc.CurrentActiveMap;
|
||||||
|
|
||||||
|
public double Reliability => IsSimulation ? 100 : Xloc.Reliability;
|
||||||
|
|
||||||
|
public double MatchingScore => IsSimulation ? 100 : Xloc.MatchingScore;
|
||||||
|
|
||||||
|
public bool IsReady => IsSimulation || Xloc.IsReady;
|
||||||
|
|
||||||
|
|
||||||
|
private readonly Xloc Xloc = new();
|
||||||
|
private readonly bool IsSimulation = Configuration.GetValue<bool>("Robot:Simulation", false);
|
||||||
|
|
||||||
|
public MessageResult ActivateMap(string mapName)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageResult ChangeMapOrigin(double x, double y, double theta)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageResult SetInitializePosition(double x, double y, double theta)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageResult StartLocalization()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageResult StartMapping(double resolution)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageResult StopLocalization()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageResult StopMapping(string mapName)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageResult SwitchMap(string mapName, double x, double y, double theta)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
71
RobotApp/Services/Robot/RobotNavigation.cs
Normal file
71
RobotApp/Services/Robot/RobotNavigation.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
using RobotApp.Interfaces;
|
||||||
|
using RobotApp.Services.Robot.Simulation;
|
||||||
|
using RobotApp.Services.Robot.Simulation.Navigation;
|
||||||
|
using RobotApp.VDA5050.Order;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
|
public class RobotNavigation(SimulationModel SimModel) : INavigation
|
||||||
|
{
|
||||||
|
public bool IsReady => IsSimulation;
|
||||||
|
public bool Driving => IsSimulation ? SimNavigation is not null ? SimNavigation.Driving : false : false;
|
||||||
|
public double VelocityX => IsSimulation ? SimNavigation is not null ? SimNavigation.VelocityX : 0 : 0;
|
||||||
|
public double VelocityY => IsSimulation ? SimNavigation is not null ? SimNavigation.VelocityY : 0 : 0;
|
||||||
|
public double Omega => IsSimulation ? SimNavigation is not null ? SimNavigation.Omega : 0 : 0;
|
||||||
|
public NavigationState State => IsSimulation ? SimNavigation is not null ? SimNavigation.State : NavigationState.None : NavigationState.None;
|
||||||
|
|
||||||
|
|
||||||
|
private SimulationNavigation? SimNavigation;
|
||||||
|
private bool IsSimulation => SimModel.Enable;
|
||||||
|
|
||||||
|
|
||||||
|
public void CancelMovement()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Move(Node[] nodes, Edge[] edges)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveStraight(double x, double y)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Paused()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resume()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rotate(double angle)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOrder(int lastBaseSequence)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshOrder(Node[] nodes, Edge[] edges)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOrder(string lastBaseNodeId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ using Action = RobotApp.VDA5050.InstantAction.Action;
|
||||||
|
|
||||||
namespace RobotApp.Services.Robot;
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
public class RobotOrderController(INavigation NavigationManager, IInstanceActions ActionManager, IError ErrorManager, Logger<RobotOrderController> Logger) : IOrder
|
public class RobotOrderController(INavigation NavigationManager, IInstantActions ActionManager, IError ErrorManager, Logger<RobotOrderController> Logger) : IOrder
|
||||||
{
|
{
|
||||||
public string OrderId { get; private set; } = string.Empty;
|
public string OrderId { get; private set; } = string.Empty;
|
||||||
public int OrderUpdateId { get; private set; }
|
public int OrderUpdateId { get; private set; }
|
||||||
|
|
@ -22,17 +22,18 @@ public class RobotOrderController(INavigation NavigationManager, IInstanceAction
|
||||||
private WatchTimer<RobotOrderController>? OrderTimer;
|
private WatchTimer<RobotOrderController>? OrderTimer;
|
||||||
|
|
||||||
private int BaseSequenceId = 0;
|
private int BaseSequenceId = 0;
|
||||||
|
|
||||||
public void StartOrder(string orderId, Node[] nodes, Edge[] edges)
|
public void StartOrder(string orderId, Node[] nodes, Edge[] edges)
|
||||||
{
|
{
|
||||||
if (OrderMutex.WaitOne(2000))
|
if (OrderMutex.WaitOne(2000))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(OrderId) && orderId != OrderId) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1001", ErrorLevel.WARNING, $"Có order đang được thực hiện. OrderId: {OrderId}, OrderId mới: {orderId}"));
|
if (!string.IsNullOrEmpty(OrderId) && orderId != OrderId) throw new OrderException(RobotErrors.Error1001(OrderId, orderId));
|
||||||
if (nodes.Length < 2) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1002", ErrorLevel.WARNING, $"Order Nodes không hợp lệ. Kích thước: {nodes.Length}"));
|
if (nodes.Length < 2) throw new OrderException(RobotErrors.Error1002(nodes.Length));
|
||||||
if (edges.Length < 1) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1003", ErrorLevel.WARNING, $"Order Edges không hợp lệ. Kích thước: {edges.Length}"));
|
if (edges.Length < 1) throw new OrderException(RobotErrors.Error1003(edges.Length));
|
||||||
if (edges.Length != nodes.Length - 1) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1004", ErrorLevel.WARNING, $"Order không hợp lệ do kích thước giữa Nodes và Edges không phù hợp. Kích thước Edges: {edges.Length}, kích thước nodes: {nodes.Length}"));
|
if (edges.Length != nodes.Length - 1) throw new OrderException(RobotErrors.Error1004(nodes.Length, edges.Length));
|
||||||
if (NavigationManager.State != NavigationState.Idle) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1012", ErrorLevel.WARNING, $"Không thể khởi tạo order mới khi hệ thống điều hướng không ở trạng thái sẵn sàng. Trạng thái hiện tại: {NavigationManager.State}"));
|
if (NavigationManager.State != NavigationState.Idle) throw new OrderException(RobotErrors.Error1012(NavigationManager.State));
|
||||||
|
|
||||||
for (int i = 0; i < nodes.Length; i++)
|
for (int i = 0; i < nodes.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -52,12 +53,12 @@ public class RobotOrderController(INavigation NavigationManager, IInstanceAction
|
||||||
}
|
}
|
||||||
OrderActions.TryAdd(nodes[i].NodeId, edges[i].Actions);
|
OrderActions.TryAdd(nodes[i].NodeId, edges[i].Actions);
|
||||||
}
|
}
|
||||||
if (nodes[i].SequenceId != i) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1010", ErrorLevel.WARNING, $"Order Nodes không đúng thứ tự. NodeId: {nodes[i].NodeId}, SequenceId: {nodes[i].SequenceId}, Vị trí đúng: {i}"));
|
if (nodes[i].SequenceId != i) throw new OrderException(RobotErrors.Error1010(nodes[i].NodeId, nodes[i].SequenceId, i));
|
||||||
if (i < nodes.Length - 1 && edges[i].SequenceId != i) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1011", ErrorLevel.WARNING, $"Order Edges không đúng thứ tự. EdgeId: {edges[i].EdgeId}, SequenceId: {edges[i].SequenceId}, Vị trí đúng: {i}"));
|
if (i < nodes.Length - 1 && edges[i].SequenceId != i) throw new OrderException(RobotErrors.Error1011(edges[i].EdgeId, edges[i].SequenceId, i));
|
||||||
if (nodes[i].Released) BaseSequenceId = nodes[i].SequenceId;
|
if (nodes[i].Released) BaseSequenceId = nodes[i].SequenceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeStates = nodes.Select(n => new NodeState
|
NodeStates = [.. nodes.Select(n => new NodeState
|
||||||
{
|
{
|
||||||
NodeId = n.NodeId,
|
NodeId = n.NodeId,
|
||||||
Released = n.Released,
|
Released = n.Released,
|
||||||
|
|
@ -70,15 +71,15 @@ public class RobotOrderController(INavigation NavigationManager, IInstanceAction
|
||||||
Theta = n.NodePosition.Theta,
|
Theta = n.NodePosition.Theta,
|
||||||
MapId = n.NodePosition.MapId
|
MapId = n.NodePosition.MapId
|
||||||
}
|
}
|
||||||
}).ToArray();
|
})];
|
||||||
EdgeStates = edges.Select(e => new EdgeState
|
EdgeStates = [.. edges.Select(e => new EdgeState
|
||||||
{
|
{
|
||||||
EdgeId = e.EdgeId,
|
EdgeId = e.EdgeId,
|
||||||
Released = e.Released,
|
Released = e.Released,
|
||||||
EdgeDescription = e.EdgeDescription,
|
EdgeDescription = e.EdgeDescription,
|
||||||
SequenceId = e.SequenceId,
|
SequenceId = e.SequenceId,
|
||||||
Trajectory = e.Trajectory
|
Trajectory = e.Trajectory
|
||||||
}).ToArray();
|
})];
|
||||||
|
|
||||||
OrderId = orderId;
|
OrderId = orderId;
|
||||||
ActionManager.AddOrderActions([.. OrderActions.Values.SelectMany(a => a)]);
|
ActionManager.AddOrderActions([.. OrderActions.Values.SelectMany(a => a)]);
|
||||||
|
|
@ -111,7 +112,7 @@ public class RobotOrderController(INavigation NavigationManager, IInstanceAction
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(OrderId)) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1005", ErrorLevel.WARNING, $"Không có order đang được thực hiện."));
|
if (string.IsNullOrEmpty(OrderId)) throw new OrderException(RobotErrors.Error1005());
|
||||||
if (orderUpdateId > OrderUpdateId)
|
if (orderUpdateId > OrderUpdateId)
|
||||||
{
|
{
|
||||||
OrderUpdateId = orderUpdateId;
|
OrderUpdateId = orderUpdateId;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
using RobotApp.Common.Shares.Enums;
|
using RobotApp.Common.Shares.Enums;
|
||||||
using RobotApp.Common.Shares.Model;
|
using RobotApp.Common.Shares.Model;
|
||||||
using RobotApp.Services.Exceptions;
|
using RobotApp.Services.Exceptions;
|
||||||
using RobotApp.Services.Robot.Navigation;
|
using RobotApp.Services.Robot.Simulation.Navigation;
|
||||||
using RobotApp.VDA5050.Order;
|
using RobotApp.VDA5050.Order;
|
||||||
using RobotApp.VDA5050.State;
|
using RobotApp.VDA5050.State;
|
||||||
|
|
||||||
|
|
@ -14,20 +14,20 @@ public class RobotPathPlanner(IConfiguration Configuration)
|
||||||
|
|
||||||
public NavigationNode[] GetNavigationNode(double currentTheta, Node[] nodes, Edge[] edges)
|
public NavigationNode[] GetNavigationNode(double currentTheta, Node[] nodes, Edge[] edges)
|
||||||
{
|
{
|
||||||
if (nodes.Length < 2) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1002", ErrorLevel.WARNING, $"Order Nodes không hợp lệ. Kích thước: {nodes.Length}"));
|
if (nodes.Length < 2) throw new PathPlannerException(RobotErrors.Error1002(nodes.Length));
|
||||||
if (edges.Length < 1) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1003", ErrorLevel.WARNING, $"Order Edges không hợp lệ. Kích thước: {edges.Length}"));
|
if (edges.Length < 1) throw new PathPlannerException(RobotErrors.Error1003(edges.Length));
|
||||||
if (edges.Length != nodes.Length - 1) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1004", ErrorLevel.WARNING, $"Order không hợp lệ do kích thước giữa Nodes và Edges không phù hợp. Kích thước Edges: {edges.Length}, kích thước nodes: {nodes.Length}"));
|
if (edges.Length != nodes.Length - 1) throw new PathPlannerException(RobotErrors.Error1004(nodes.Length, edges.Length));
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public NavigationNode[] PathSplit(NavigationNode[] nodes, Edge[] edges)
|
public NavigationNode[] PathSplit(NavigationNode[] nodes, Edge[] edges)
|
||||||
{
|
{
|
||||||
if (nodes.Length < 2) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1002", ErrorLevel.WARNING, $"Order Nodes không hợp lệ. Kích thước: {nodes.Length}"));
|
if (nodes.Length < 2) throw new PathPlannerException(RobotErrors.Error1002(nodes.Length));
|
||||||
if (edges.Length < 1) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1003", ErrorLevel.WARNING, $"Order Edges không hợp lệ. Kích thước: {edges.Length}"));
|
if (edges.Length < 1) throw new PathPlannerException(RobotErrors.Error1003(edges.Length));
|
||||||
if (edges.Length != nodes.Length - 1) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1004", ErrorLevel.WARNING, $"Order không hợp lệ do kích thước giữa Nodes và Edges không phù hợp. Kích thước Edges: {edges.Length}, kích thước nodes: {nodes.Length}"));
|
if (edges.Length != nodes.Length - 1) throw new PathPlannerException(RobotErrors.Error1004(nodes.Length, edges.Length));
|
||||||
|
|
||||||
List<NavigationNode> navigationNode = [new()
|
List <NavigationNode> navigationNode = [new()
|
||||||
{
|
{
|
||||||
NodeId = nodes[0].NodeId,
|
NodeId = nodes[0].NodeId,
|
||||||
X = nodes[0].X,
|
X = nodes[0].X,
|
||||||
|
|
@ -40,8 +40,8 @@ public class RobotPathPlanner(IConfiguration Configuration)
|
||||||
{
|
{
|
||||||
var startNode = nodes.FirstOrDefault(n => n.NodeId == edge.StartNodeId);
|
var startNode = nodes.FirstOrDefault(n => n.NodeId == edge.StartNodeId);
|
||||||
var endNode = nodes.FirstOrDefault(n => n.NodeId == edge.EndNodeId);
|
var endNode = nodes.FirstOrDefault(n => n.NodeId == edge.EndNodeId);
|
||||||
if (startNode is null) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1014", ErrorLevel.WARNING, $"Edge chứa StartNodeId không tồn tại trong Nodes . StartNodeId: {edge.StartNodeId}"));
|
if (startNode is null) throw new PathPlannerException(RobotErrors.Error1014(edge.EdgeId, edge.StartNodeId));
|
||||||
if (endNode is null) throw new OrderException(RobotErrors.CreateError(ErrorType.INITIALIZE_ORDER, "1015", ErrorLevel.WARNING, $"Edge chứa EndNodeId không tồn tại trong Nodes . EndNodeId: {edge.EndNodeId}"));
|
if (endNode is null) throw new PathPlannerException(RobotErrors.Error1014(edge.EdgeId, edge.EndNodeId));
|
||||||
|
|
||||||
var EdgeCalculatorModel = new EdgeCalculatorModel()
|
var EdgeCalculatorModel = new EdgeCalculatorModel()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
77
RobotApp/Services/Robot/RobotPeripheral.cs
Normal file
77
RobotApp/Services/Robot/RobotPeripheral.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
using RobotApp.Interfaces;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
|
public class RobotPeripheral : BackgroundService, IPeripheral, ISafety
|
||||||
|
{
|
||||||
|
public PeripheralMode PeripheralMode { get; private set; } = PeripheralMode.SERVICE;
|
||||||
|
public bool IsReady { get; private set; } = true;
|
||||||
|
public bool Emergency => throw new NotImplementedException();
|
||||||
|
public bool Bumper => throw new NotImplementedException();
|
||||||
|
public bool LiftedUp => throw new NotImplementedException();
|
||||||
|
public bool LiftedDown => throw new NotImplementedException();
|
||||||
|
public bool LiftHome => throw new NotImplementedException();
|
||||||
|
public bool LidarFrontProtectField => throw new NotImplementedException();
|
||||||
|
public bool LidarBackProtectField => throw new NotImplementedException();
|
||||||
|
public bool LidarFrontTimProtectField => throw new NotImplementedException();
|
||||||
|
public bool LeftMotorReady => throw new NotImplementedException();
|
||||||
|
public bool RightMotorReady => throw new NotImplementedException();
|
||||||
|
public bool LiftMotorReady => throw new NotImplementedException();
|
||||||
|
public bool SwitchLock => throw new NotImplementedException();
|
||||||
|
public bool SwitchManual => throw new NotImplementedException();
|
||||||
|
public bool SwitchAuto => throw new NotImplementedException();
|
||||||
|
public bool ButtonStart => throw new NotImplementedException();
|
||||||
|
public bool ButtonStop => throw new NotImplementedException();
|
||||||
|
public bool ButtonReset => throw new NotImplementedException();
|
||||||
|
public bool HasLoad => throw new NotImplementedException();
|
||||||
|
public bool MutedBase => throw new NotImplementedException();
|
||||||
|
public bool MutedLoad => throw new NotImplementedException();
|
||||||
|
public bool StartedCharging => throw new NotImplementedException();
|
||||||
|
public bool SpeedVerySlow => throw new NotImplementedException();
|
||||||
|
public bool SpeedSlow => throw new NotImplementedException();
|
||||||
|
public bool SpeedNormal => throw new NotImplementedException();
|
||||||
|
public bool SpeedMedium => throw new NotImplementedException();
|
||||||
|
public bool SpeedOptimal => throw new NotImplementedException();
|
||||||
|
public bool SpeedFast => throw new NotImplementedException();
|
||||||
|
public bool SpeedVeryFast => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public bool SetHorizontalLoad()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetMutedBase(bool muted)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetMutedLoad(bool muted)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetOnCharging()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetProccessState()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetSytemState()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return base.StopAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace RobotApp.Services.Robot
|
|
||||||
{
|
|
||||||
public class RobotSafety
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,34 @@
|
||||||
namespace RobotApp.Services.Robot
|
using RobotApp.Interfaces;
|
||||||
|
using RobotApp.VDA5050.Visualization;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot;
|
||||||
|
|
||||||
|
public class RobotVisualization(ILocalization Localization)
|
||||||
{
|
{
|
||||||
public class RobotVisualization
|
public VisualizationMsg Visualization => GetVisualizationMsg();
|
||||||
|
public string SerialNumber { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private uint HeaderId;
|
||||||
|
private VisualizationMsg GetVisualizationMsg()
|
||||||
{
|
{
|
||||||
|
return new VisualizationMsg()
|
||||||
|
{
|
||||||
|
HeaderId = HeaderId++,
|
||||||
|
SerialNumber = SerialNumber,
|
||||||
|
MapId = Localization.CurrentActiveMap,
|
||||||
|
MapDescription = string.Empty,
|
||||||
|
AgvPosition = new AgvPosition()
|
||||||
|
{
|
||||||
|
X = Localization.X,
|
||||||
|
Y = Localization.Y,
|
||||||
|
Theta = Localization.Theta
|
||||||
|
},
|
||||||
|
Velocity = new Velocity()
|
||||||
|
{
|
||||||
|
Vx = 0,
|
||||||
|
Vy = 0,
|
||||||
|
Omega = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace RobotApp.Services.Robot.Navigation.Algorithm;
|
namespace RobotApp.Services.Robot.Simulation.Navigation.Algorithm;
|
||||||
|
|
||||||
public class FuzzyLogic
|
public class FuzzyLogic
|
||||||
{
|
{
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace RobotApp.Services.Robot.Navigation.Algorithm;
|
namespace RobotApp.Services.Robot.Simulation.Navigation.Algorithm;
|
||||||
|
|
||||||
public class PID
|
public class PID
|
||||||
{
|
{
|
||||||
|
|
@ -29,13 +29,12 @@ public class PID
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double PID_step(double error, double min, double max)
|
public double PID_step(double error, double min, double max, double timeSample)
|
||||||
{
|
{
|
||||||
DateTime CurrentTime = DateTime.Now;
|
DateTime CurrentTime = DateTime.Now;
|
||||||
double TimeSample = (CurrentTime - PreTime).TotalSeconds;
|
|
||||||
double P_part = Kp * (error - Pre_Error);
|
double P_part = Kp * (error - Pre_Error);
|
||||||
double I_part = 0.5 * Ki * TimeSample * (error + Pre_Error);
|
double I_part = 0.5 * Ki * timeSample * (error + Pre_Error);
|
||||||
double D_part = Kd / TimeSample * (error - 2 * Pre_Error + Pre_Pre_Error);
|
double D_part = Kd / timeSample * (error - 2 * Pre_Error + Pre_Pre_Error);
|
||||||
double Out = Pre_Out + P_part + I_part + D_part;
|
double Out = Pre_Out + P_part + I_part + D_part;
|
||||||
Pre_Pre_Error = Pre_Error;
|
Pre_Pre_Error = Pre_Error;
|
||||||
Pre_Error = error;
|
Pre_Error = error;
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot.Simulation.Navigation.Algorithm;
|
||||||
|
public class PurePursuit
|
||||||
|
{
|
||||||
|
private double MaxAngularVelocity = 1.5;
|
||||||
|
private double LookaheadDistance = 0.5;
|
||||||
|
private List<NavigationNode> Waypoints_Value = [];
|
||||||
|
public int OnNodeIndex = 0;
|
||||||
|
public int GoalIndex = 0;
|
||||||
|
public NavigationNode? Goal;
|
||||||
|
|
||||||
|
public PurePursuit WithLookheadDistance(double distance)
|
||||||
|
{
|
||||||
|
LookaheadDistance = distance;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PurePursuit WithMaxAngularVelocity(double vel)
|
||||||
|
{
|
||||||
|
MaxAngularVelocity = vel;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PurePursuit WithPath(NavigationNode[] path)
|
||||||
|
{
|
||||||
|
Waypoints_Value = [.. path];
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatePath(NavigationNode[] path)
|
||||||
|
{
|
||||||
|
Waypoints_Value = [.. path];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateGoal(NavigationNode goal)
|
||||||
|
{
|
||||||
|
Goal = goal;
|
||||||
|
GoalIndex = Waypoints_Value.IndexOf(Goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (NavigationNode node, int index) GetOnNode(double x, double y)
|
||||||
|
{
|
||||||
|
double minDistance = double.MaxValue;
|
||||||
|
NavigationNode onNode = Waypoints_Value[0];
|
||||||
|
int index = 0;
|
||||||
|
for (int i = 1; i < Waypoints_Value.Count; i++)
|
||||||
|
{
|
||||||
|
var distance = Math.Sqrt(Math.Pow(x - Waypoints_Value[i].X, 2) + Math.Pow(y - Waypoints_Value[i].Y, 2));
|
||||||
|
if (distance < minDistance)
|
||||||
|
{
|
||||||
|
onNode = Waypoints_Value[i];
|
||||||
|
minDistance = distance;
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (onNode, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (NavigationNode? node, int index) OnNode(double x, double y)
|
||||||
|
{
|
||||||
|
if (Waypoints_Value is null || Waypoints_Value.Count == 0) return (null, 0);
|
||||||
|
double minDistance = double.MaxValue;
|
||||||
|
int index = 0;
|
||||||
|
NavigationNode? onNode = null;
|
||||||
|
if (Goal is null) return (onNode, index);
|
||||||
|
for (int i = OnNodeIndex; i < Waypoints_Value.IndexOf(Goal); i++)
|
||||||
|
{
|
||||||
|
var distance = Math.Sqrt(Math.Pow(x - Waypoints_Value[i].X, 2) + Math.Pow(y - Waypoints_Value[i].Y, 2));
|
||||||
|
if (distance < minDistance)
|
||||||
|
{
|
||||||
|
onNode = Waypoints_Value[i];
|
||||||
|
minDistance = distance;
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (onNode, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double PurePursuit_step(double X_Ref, double Y_Ref, double Angle_Ref)
|
||||||
|
{
|
||||||
|
if (Waypoints_Value is null || Waypoints_Value.Count < 2) return 0;
|
||||||
|
NavigationNode? lookaheadStartPt = null;
|
||||||
|
var (onNode, index) = OnNode(X_Ref, Y_Ref);
|
||||||
|
if (onNode is null || Goal is null) return 0;
|
||||||
|
OnNodeIndex = index;
|
||||||
|
double lookDistance = 0;
|
||||||
|
for (int i = OnNodeIndex + 1; i < Waypoints_Value.IndexOf(Goal); i++)
|
||||||
|
{
|
||||||
|
lookDistance += Math.Sqrt(Math.Pow(Waypoints_Value[i - 1].X - Waypoints_Value[i].X, 2) + Math.Pow(Waypoints_Value[i - 1].Y - Waypoints_Value[i].Y, 2));
|
||||||
|
if (lookDistance >= LookaheadDistance || Waypoints_Value[i].Direction != onNode.Direction)
|
||||||
|
{
|
||||||
|
lookaheadStartPt = Waypoints_Value[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lookaheadStartPt ??= Goal;
|
||||||
|
if (onNode.Direction == RobotDirection.BACKWARD)
|
||||||
|
{
|
||||||
|
if (Angle_Ref > Math.PI) Angle_Ref -= Math.PI * 2;
|
||||||
|
else if (Angle_Ref < -Math.PI) Angle_Ref += Math.PI * 2;
|
||||||
|
Angle_Ref += Math.PI;
|
||||||
|
if (Angle_Ref > Math.PI) Angle_Ref -= Math.PI * 2;
|
||||||
|
}
|
||||||
|
var distance = Math.Atan2(lookaheadStartPt.Y - Y_Ref, lookaheadStartPt.X - X_Ref) - Angle_Ref;
|
||||||
|
|
||||||
|
if (Math.Abs(distance) > Math.PI)
|
||||||
|
{
|
||||||
|
double minDistance;
|
||||||
|
if (distance + Math.PI == 0.0) minDistance = 0.0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double data = (distance + Math.PI) / (2 * Math.PI);
|
||||||
|
if (data < 0) data = Math.Round(data + 0.5);
|
||||||
|
else data = Math.Round(data - 0.5);
|
||||||
|
minDistance = distance + Math.PI - data * (2 * Math.PI);
|
||||||
|
double checker = 0;
|
||||||
|
if (minDistance != 0.0)
|
||||||
|
{
|
||||||
|
checker = Math.Abs((distance + Math.PI) / (2 * Math.PI));
|
||||||
|
}
|
||||||
|
if (!(Math.Abs(checker - Math.Floor(checker + 0.5)) > 2.2204460492503131E-16 * checker))
|
||||||
|
{
|
||||||
|
minDistance = 0.0;
|
||||||
|
}
|
||||||
|
else if (distance + Math.PI < 0.0)
|
||||||
|
{
|
||||||
|
minDistance += Math.PI * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (minDistance == 0.0 && distance + Math.PI > 0.0)
|
||||||
|
{
|
||||||
|
minDistance = Math.PI * 2;
|
||||||
|
}
|
||||||
|
distance = minDistance - Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
var AngularVelocity = 2.0 * 0.5 * Math.Sin(distance) / LookaheadDistance;
|
||||||
|
if (Math.Abs(AngularVelocity) > MaxAngularVelocity)
|
||||||
|
{
|
||||||
|
if (AngularVelocity < 0.0)
|
||||||
|
{
|
||||||
|
AngularVelocity = -1.0;
|
||||||
|
}
|
||||||
|
else if (AngularVelocity > 0.0)
|
||||||
|
{
|
||||||
|
AngularVelocity = 1.0;
|
||||||
|
}
|
||||||
|
else if (AngularVelocity == 0.0)
|
||||||
|
{
|
||||||
|
AngularVelocity = 0.0;
|
||||||
|
}
|
||||||
|
AngularVelocity *= MaxAngularVelocity;
|
||||||
|
}
|
||||||
|
return AngularVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
using RobotApp.Interfaces;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot.Simulation.Navigation;
|
||||||
|
|
||||||
|
public class DifferentialNavigation : SimulationNavigation
|
||||||
|
{
|
||||||
|
private readonly Logger<DifferentialNavigation> Logger;
|
||||||
|
|
||||||
|
public DifferentialNavigation(IServiceProvider ServiceProvider) : base(ServiceProvider)
|
||||||
|
{
|
||||||
|
using var scope = ServiceProvider.CreateScope();
|
||||||
|
Logger = scope.ServiceProvider.GetRequiredService<Logger<DifferentialNavigation>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void NavigationHandler()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (NavState == NavigationState.Rotating)
|
||||||
|
{
|
||||||
|
if (RotatePID is not null)
|
||||||
|
{
|
||||||
|
double Error = Visualization.Theta - TargetAngle;
|
||||||
|
if (Error > 180) Error -= 360;
|
||||||
|
else if (Error < -180) Error += 360;
|
||||||
|
if (Math.Abs(Error) < 1)
|
||||||
|
{
|
||||||
|
if (NavigationPath is not null && NavigationPath.Length > 2) NavState = NavigationState.Moving;
|
||||||
|
else Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var SpeedCal = RotatePID.PID_step(Error * Math.PI / 180, AngularVelocity, -AngularVelocity, CycleHandlerMilliseconds / 1000.0);
|
||||||
|
VelocityController.SetSpeed(SpeedCal, SpeedCal, CycleHandlerMilliseconds / 1000.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (NavState == NavigationState.Moving)
|
||||||
|
{
|
||||||
|
if (NavigationPath is not null && SimulationOrderNodes.Length > 1 && CurrentBaseNode is not null)
|
||||||
|
{
|
||||||
|
if (MovePID is not null && MoveFuzzy is not null && MovePurePursuit is not null)
|
||||||
|
{
|
||||||
|
var DistanceToGoal = Math.Sqrt(Math.Pow(Visualization.X - SimulationOrderNodes[^1].X, 2) + Math.Pow(Visualization.Y - SimulationOrderNodes[^1].Y, 2));
|
||||||
|
var DistanceToCheckingNode = Math.Sqrt(Math.Pow(Visualization.X - CurrentBaseNode.X, 2) + Math.Pow(Visualization.Y - CurrentBaseNode.Y, 2));
|
||||||
|
var deviation = CurrentBaseNode.NodeId == SimulationOrderNodes[^1].NodeId ? 0.02 : 0.1;
|
||||||
|
if (DistanceToCheckingNode > deviation)
|
||||||
|
{
|
||||||
|
double SpeedTarget = MovePID.PID_step(DistanceToCheckingNode, SimulationModel.MaxVelocity, 0, CycleHandlerMilliseconds / 1000.0);
|
||||||
|
double AngularVel = MovePurePursuit.PurePursuit_step(Visualization.X, Visualization.Y, Visualization.Theta * Math.PI / 180);
|
||||||
|
AngularVel *= NavigationPath[MovePurePursuit.OnNodeIndex].Direction == RobotDirection.FORWARD ? 1 : -1;
|
||||||
|
(double AngularVelocityLeft, double AngularVelocityRight) = MoveFuzzy.Fuzzy_step(SpeedTarget, AngularVel, CycleHandlerMilliseconds / 1000.0);
|
||||||
|
|
||||||
|
if (NavigationPath[MovePurePursuit.OnNodeIndex].Direction == RobotDirection.FORWARD)
|
||||||
|
{
|
||||||
|
AngularVelocityLeft /= SimulationModel.RadiusWheel;
|
||||||
|
AngularVelocityRight = AngularVelocityRight / SimulationModel.RadiusWheel * -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AngularVelocityLeft = AngularVelocityLeft / SimulationModel.RadiusWheel * -1;
|
||||||
|
AngularVelocityRight /= SimulationModel.RadiusWheel;
|
||||||
|
}
|
||||||
|
VelocityController.SetSpeed(AngularVelocityLeft, AngularVelocityRight, CycleHandlerMilliseconds / 1000.0);
|
||||||
|
}
|
||||||
|
else if (DistanceToGoal < 0.02) Dispose();
|
||||||
|
else NavState = NavigationState.Waiting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Write($"Error in DifferentialNavigation: {ex.Message}", LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
namespace RobotApp.Services.Robot.Simulation.Navigation;
|
||||||
|
|
||||||
|
public class ForkliftNavigation : SimulationNavigation
|
||||||
|
{
|
||||||
|
private readonly Logger<ForkliftNavigation> Logger;
|
||||||
|
public ForkliftNavigation(IServiceProvider ServiceProvider) : base(ServiceProvider)
|
||||||
|
{
|
||||||
|
using var scope = ServiceProvider.CreateScope();
|
||||||
|
Logger = scope.ServiceProvider.GetRequiredService<Logger<ForkliftNavigation>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void NavigationHandler()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Implement differential drive navigation logic here
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Write($"Error in ForkliftNavigationSevice: {ex.Message}", LogLevel.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using RobotApp.Common.Shares.Enums;
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
namespace RobotApp.Services.Robot.Navigation;
|
namespace RobotApp.Services.Robot.Simulation.Navigation;
|
||||||
|
|
||||||
public class NavigationNode
|
public class NavigationNode
|
||||||
{
|
{
|
||||||
|
|
@ -9,5 +9,7 @@ public class NavigationNode
|
||||||
public double Y { get; set; }
|
public double Y { get; set; }
|
||||||
public double Theta { get; set; }
|
public double Theta { get; set; }
|
||||||
public RobotDirection Direction { get; set; }
|
public RobotDirection Direction { get; set; }
|
||||||
|
public double AllowedDeviationXY { get; set; }
|
||||||
|
public double AllowedDeviationTheta { get; set; }
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using RobotApp.Common.Shares;
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
using RobotApp.Interfaces;
|
||||||
|
using RobotApp.Services.Exceptions;
|
||||||
|
using RobotApp.Services.Robot.Simulation.Navigation.Algorithm;
|
||||||
|
using RobotApp.VDA5050.Order;
|
||||||
|
using RobotApp.VDA5050.State;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot.Simulation.Navigation;
|
||||||
|
|
||||||
|
public class SimulationNavigation : INavigation, IDisposable
|
||||||
|
{
|
||||||
|
public NavigationState State => NavState;
|
||||||
|
public bool Driving => NavDriving;
|
||||||
|
public bool IsReady => true;
|
||||||
|
public double VelocityX => Visualization.Vx;
|
||||||
|
public double VelocityY => Visualization.Vy;
|
||||||
|
public double Omega => Visualization.Omega;
|
||||||
|
|
||||||
|
protected NavigationState NavState = NavigationState.None;
|
||||||
|
protected bool NavDriving = false;
|
||||||
|
|
||||||
|
protected const int CycleHandlerMilliseconds = 50;
|
||||||
|
private WatchTimer<SimulationNavigation>? NavigationTimer;
|
||||||
|
|
||||||
|
protected double TargetAngle = 0;
|
||||||
|
protected PID? RotatePID;
|
||||||
|
protected readonly double AngularVelocity;
|
||||||
|
|
||||||
|
protected PID? MovePID;
|
||||||
|
protected FuzzyLogic? MoveFuzzy;
|
||||||
|
protected PurePursuit? MovePurePursuit;
|
||||||
|
|
||||||
|
protected Node[] OrderNodes = [];
|
||||||
|
protected Edge[] OrderEdges = [];
|
||||||
|
protected NavigationNode[] SimulationOrderNodes = [];
|
||||||
|
|
||||||
|
protected readonly SimulationVisualization Visualization;
|
||||||
|
protected readonly SimulationVelocity VelocityController;
|
||||||
|
protected readonly SimulationModel SimulationModel;
|
||||||
|
protected readonly RobotPathPlanner PathPlanner;
|
||||||
|
protected NavigationNode[] NavigationPath = [];
|
||||||
|
protected NavigationNode? CurrentBaseNode;
|
||||||
|
protected NavigationState ResumeState = NavigationState.None;
|
||||||
|
|
||||||
|
private readonly Logger<SimulationNavigation> Logger;
|
||||||
|
private bool IsIdle => NavState == NavigationState.Idle || NavState == NavigationState.Canceled;
|
||||||
|
|
||||||
|
public SimulationNavigation(IServiceProvider ServiceProvider)
|
||||||
|
{
|
||||||
|
using var scope = ServiceProvider.CreateScope();
|
||||||
|
Logger = scope.ServiceProvider.GetRequiredService<Logger<SimulationNavigation>>();
|
||||||
|
Visualization = scope.ServiceProvider.GetRequiredService<SimulationVisualization>();
|
||||||
|
SimulationModel = scope.ServiceProvider.GetRequiredService<SimulationModel>();
|
||||||
|
PathPlanner = scope.ServiceProvider.GetRequiredService<RobotPathPlanner>();
|
||||||
|
VelocityController = new(Visualization, SimulationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void HandleNavigationStart()
|
||||||
|
{
|
||||||
|
NavigationTimer = new(CycleHandlerMilliseconds, NavigationHandler, Logger);
|
||||||
|
NavigationTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void HandleNavigationStop()
|
||||||
|
{
|
||||||
|
NavigationTimer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void NavigationHandler() { }
|
||||||
|
|
||||||
|
public void CancelMovement()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
NavState = NavigationState.Canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Move(Node[] nodes, Edge[] edges)
|
||||||
|
{
|
||||||
|
if (!IsIdle) throw new SimulationException(RobotErrors.Error1012(NavState));
|
||||||
|
NavState = NavigationState.Initializing;
|
||||||
|
if (nodes.Length < 2) throw new SimulationException(RobotErrors.Error1002(nodes.Length));
|
||||||
|
if (edges.Length < 1) throw new SimulationException(RobotErrors.Error1003(edges.Length));
|
||||||
|
if (edges.Length != nodes.Length - 1) throw new SimulationException(RobotErrors.Error1004(nodes.Length, edges.Length));
|
||||||
|
|
||||||
|
var pathDirection = PathPlanner.GetNavigationNode(Visualization.Theta, nodes, edges);
|
||||||
|
NavigationPath = PathPlanner.PathSplit(pathDirection, edges);
|
||||||
|
|
||||||
|
MovePID = new PID().WithKp(1).WithKi(0.0001).WithKd(0.6);
|
||||||
|
MoveFuzzy = new FuzzyLogic().WithGainP(1.1);
|
||||||
|
MovePurePursuit = new PurePursuit().WithLookheadDistance(0.35).WithPath([.. NavigationPath]);
|
||||||
|
|
||||||
|
double angleFoward = Math.Atan2(NavigationPath[1].Y - NavigationPath[0].Y, NavigationPath[1].X - NavigationPath[0].X) * 180 / Math.PI;
|
||||||
|
double angleBacward = Math.Atan2(NavigationPath[0].Y - NavigationPath[1].Y, NavigationPath[0].X - NavigationPath[1].X) * 180 / Math.PI;
|
||||||
|
OrderNodes = nodes;
|
||||||
|
OrderEdges = edges;
|
||||||
|
SimulationOrderNodes = pathDirection;
|
||||||
|
Rotate(SimulationOrderNodes[0].Direction == RobotDirection.FORWARD ? angleFoward : angleBacward);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveStraight(double x, double y)
|
||||||
|
{
|
||||||
|
if (!IsIdle) throw new SimulationException(RobotErrors.Error1012(NavState));
|
||||||
|
var headRobotNode = new NavigationNode()
|
||||||
|
{
|
||||||
|
X = Visualization.X * Math.Acos(Visualization.Theta * Math.PI / 180),
|
||||||
|
Y = Visualization.Y * Math.Asin(Visualization.Theta * Math.PI / 180),
|
||||||
|
};
|
||||||
|
var goalNode = new NavigationNode()
|
||||||
|
{
|
||||||
|
NodeId = "RobotGoal",
|
||||||
|
X = x,
|
||||||
|
Y = y,
|
||||||
|
};
|
||||||
|
var currentRobotNode = new NavigationNode()
|
||||||
|
{
|
||||||
|
NodeId = "RobotCurrentNode",
|
||||||
|
X = Visualization.X,
|
||||||
|
Y = Visualization.Y,
|
||||||
|
};
|
||||||
|
goalNode.Theta = MathExtensions.GetVectorAngle(currentRobotNode.X, currentRobotNode.Y, headRobotNode.X, headRobotNode.Y, goalNode.X, goalNode.Y) > 90 ?
|
||||||
|
Math.Atan2(currentRobotNode.Y - goalNode.Y, currentRobotNode.X - goalNode.X) :
|
||||||
|
Math.Atan2(goalNode.Y - currentRobotNode.Y, goalNode.X - currentRobotNode.X);
|
||||||
|
currentRobotNode.Theta = goalNode.Theta;
|
||||||
|
NavigationPath = PathPlanner.PathSplit([currentRobotNode, goalNode], [new Edge()
|
||||||
|
{
|
||||||
|
EdgeId = "Straight edge",
|
||||||
|
Trajectory = new Trajectory()
|
||||||
|
{
|
||||||
|
Degree = 1,
|
||||||
|
ControlPoints = []
|
||||||
|
},
|
||||||
|
StartNodeId = currentRobotNode.NodeId,
|
||||||
|
EndNodeId = goalNode.NodeId,
|
||||||
|
}]);
|
||||||
|
MovePID = new PID().WithKp(1).WithKi(0.0001).WithKd(0.6);
|
||||||
|
MoveFuzzy = new FuzzyLogic().WithGainP(1.1);
|
||||||
|
MovePurePursuit = new PurePursuit().WithLookheadDistance(0.35).WithPath(NavigationPath);
|
||||||
|
double Angle = Math.Atan2(NavigationPath[1].Y - NavigationPath[0].Y, NavigationPath[1].X - NavigationPath[0].X);
|
||||||
|
Rotate(Angle * 180 / Math.PI);
|
||||||
|
UpdateOrder(goalNode.NodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Paused()
|
||||||
|
{
|
||||||
|
ResumeState = State;
|
||||||
|
NavState = NavigationState.Paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resume()
|
||||||
|
{
|
||||||
|
NavState = ResumeState;
|
||||||
|
ResumeState = NavigationState.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rotate(double angle)
|
||||||
|
{
|
||||||
|
if (!IsIdle) throw new SimulationException(RobotErrors.Error1012(NavState));
|
||||||
|
RotatePID = new PID().WithKp(10).WithKi(0.01).WithKd(0.1);
|
||||||
|
TargetAngle = MathExtensions.NormalizeAngle(angle);
|
||||||
|
NavState = NavigationState.Rotating;
|
||||||
|
HandleNavigationStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOrder(string lastBaseNodeId)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshOrder(Node[] nodes, Edge[] edges)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
HandleNavigationStop();
|
||||||
|
VelocityController.Stop();
|
||||||
|
OrderNodes = [];
|
||||||
|
OrderEdges = [];
|
||||||
|
SimulationOrderNodes = [];
|
||||||
|
NavDriving = false;
|
||||||
|
NavState = NavigationState.None;
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
NavState = NavigationState.Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot.Simulation.Navigation;
|
||||||
|
|
||||||
|
public class SimulationNavigationManager
|
||||||
|
{
|
||||||
|
public static SimulationNavigation GetNavigation(NavigationType type, IServiceProvider ServiceProvider)
|
||||||
|
{
|
||||||
|
if (type == NavigationType.Forklift) return new ForkliftNavigation(ServiceProvider);
|
||||||
|
return new DifferentialNavigation(ServiceProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
RobotApp/Services/Robot/Simulation/SimulationExtensions.cs
Normal file
13
RobotApp/Services/Robot/Simulation/SimulationExtensions.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot.Simulation;
|
||||||
|
|
||||||
|
public static class SimulationExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddRobotSimulation(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<SimulationVisualization>();
|
||||||
|
services.AddSingleton<SimulationModel>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
RobotApp/Services/Robot/Simulation/SimulationModel.cs
Normal file
16
RobotApp/Services/Robot/Simulation/SimulationModel.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.Robot.Simulation;
|
||||||
|
|
||||||
|
public class SimulationModel(IConfiguration Configuration)
|
||||||
|
{
|
||||||
|
public readonly bool Enable = Configuration.GetValue("Simulation:Enable", false);
|
||||||
|
public readonly double RadiusWheel = Configuration.GetValue("Simulation:RadiusWheel", 0.1);
|
||||||
|
public readonly double Width = Configuration.GetValue("Simulation:Width", 0.606);
|
||||||
|
public readonly double Length = Configuration.GetValue("Simulation:Length", 1.106);
|
||||||
|
public readonly double MaxVelocity = Configuration.GetValue("Simulation:MaxVelocity", 1.5);
|
||||||
|
public readonly double MaxAngularVelocity = Configuration.GetValue("Simulation:MaxAngularVelocity", 0.3);
|
||||||
|
public readonly double Acceleration = Configuration.GetValue("Simulation:Acceleration", 2);
|
||||||
|
public readonly double Deceleration = Configuration.GetValue("Simulation:Deceleration", 1);
|
||||||
|
public readonly NavigationType NavigationType = Configuration.GetValue("Simulation:NavigationType", NavigationType.Differential);
|
||||||
|
}
|
||||||
56
RobotApp/Services/Robot/Simulation/SimulationVelocity.cs
Normal file
56
RobotApp/Services/Robot/Simulation/SimulationVelocity.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
namespace RobotApp.Services.Robot.Simulation;
|
||||||
|
|
||||||
|
public class SimulationVelocity(SimulationVisualization Visualization, SimulationModel Model)
|
||||||
|
{
|
||||||
|
private readonly double Acceleration = Model.Acceleration;
|
||||||
|
private readonly double Deceleration = Model.Deceleration;
|
||||||
|
private double AngularVelLeft;
|
||||||
|
private double AngularVelRight;
|
||||||
|
|
||||||
|
private (double angularVelLeft, double angularVelRight) AccelerationCalculator(double wL, double wR, double wL_Current, double wR_Current)
|
||||||
|
{
|
||||||
|
var angularVelLeft = wL_Current;
|
||||||
|
var angularVelRight = wR_Current;
|
||||||
|
if (wL_Current == 0 || wL / wL_Current < 0)
|
||||||
|
{
|
||||||
|
if (wL != 0) angularVelLeft += wL / Math.Abs(wL) * Acceleration;
|
||||||
|
else angularVelLeft = wL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Math.Abs(wL) - Math.Abs(wL_Current) > Acceleration) angularVelLeft += Acceleration * wL_Current / Math.Abs(wL_Current);
|
||||||
|
else if (Math.Abs(wL_Current) - Math.Abs(wL) > Deceleration) angularVelLeft -= Deceleration * wL_Current / Math.Abs(wL_Current);
|
||||||
|
else angularVelLeft = wL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wR_Current == 0 || wR / wR_Current < 0)
|
||||||
|
{
|
||||||
|
if (wR != 0) angularVelRight += wR / Math.Abs(wR) * Acceleration;
|
||||||
|
else angularVelRight = wR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Math.Abs(wR) - Math.Abs(wR_Current) > Acceleration) angularVelRight += Acceleration * wR_Current / Math.Abs(wR_Current);
|
||||||
|
else if (Math.Abs(wR_Current) - Math.Abs(wR) > Deceleration) angularVelRight -= Deceleration * wR_Current / Math.Abs(wR_Current);
|
||||||
|
else angularVelRight = wR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.Abs(angularVelLeft) > Math.Abs(wL)) angularVelLeft = wL;
|
||||||
|
if (Math.Abs(angularVelRight) > Math.Abs(wR)) angularVelRight = wR;
|
||||||
|
return (angularVelLeft, angularVelRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetSpeed(double angularVelLeft, double angularVelRight, double sampleTime)
|
||||||
|
{
|
||||||
|
(AngularVelLeft, AngularVelRight) = AccelerationCalculator(angularVelLeft, angularVelRight, AngularVelLeft, AngularVelRight);
|
||||||
|
//Console.WriteLine($"AngularVelLeft = {AngularVelLeft:0.####}, AngularVelRight = {AngularVelRight:0.####}");
|
||||||
|
_ = Visualization.UpdatePosition(AngularVelLeft, AngularVelRight, sampleTime);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
(AngularVelLeft, AngularVelRight) = (0, 0);
|
||||||
|
_ = Visualization.UpdatePosition(AngularVelLeft, AngularVelRight, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
namespace RobotApp.Services.Robot.Simulation;
|
||||||
|
|
||||||
|
public class SimulationVisualization(SimulationModel Model)
|
||||||
|
{
|
||||||
|
public double X { get; private set; }
|
||||||
|
public double Y { get; private set; }
|
||||||
|
public double Theta { get; private set; }
|
||||||
|
public double Vx { get; private set; }
|
||||||
|
public double Vy { get; private set; }
|
||||||
|
public double Omega { get; private set; }
|
||||||
|
|
||||||
|
private readonly double RadiusWheel = Model.RadiusWheel;
|
||||||
|
private readonly double RadiusRobot = Model.Width / 2;
|
||||||
|
|
||||||
|
public (double x, double y, double angle) UpdatePosition(double wL, double wR, double time)
|
||||||
|
{
|
||||||
|
Theta = (Theta + time * (-wR - wL) * RadiusWheel / RadiusRobot * 180 / Math.PI) % 360;
|
||||||
|
X += time * (-wR + wL) * RadiusWheel * Math.Cos(Theta * Math.PI / 180) / 2;
|
||||||
|
Y += time * (-wR + wL) * RadiusWheel * Math.Sin(Theta * Math.PI / 180) / 2;
|
||||||
|
_ = UpdateVelocity(wL, wR);
|
||||||
|
if (Theta > 180) Theta -= 360;
|
||||||
|
else if (Theta < -180) Theta += 360;
|
||||||
|
return (X, Y, Theta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (double vx, double vy, double omega) UpdateVelocity(double wL, double wR)
|
||||||
|
{
|
||||||
|
Vx = (-wR + wL) * RadiusWheel / 2;
|
||||||
|
Omega = (-wR - wL) * RadiusWheel / RadiusRobot;
|
||||||
|
return (Vx, 0, Omega);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LocalizationInitialize(double x, double y, double theta)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Theta = theta;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
RobotApp/Services/RobotExtensions.cs
Normal file
39
RobotApp/Services/RobotExtensions.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using RobotApp.Interfaces;
|
||||||
|
using RobotApp.Services.Robot;
|
||||||
|
using RobotApp.Services.State;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace RobotApp.Services;
|
||||||
|
|
||||||
|
public static class RobotExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddRobot(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<RobotStateMachine>();
|
||||||
|
|
||||||
|
services.AddInterfaceServiceSingleton<ILocalization, RobotLocalization>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddInterfaceServiceSingleton<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this IServiceCollection services) where TService : class where TImplementation : class, TService
|
||||||
|
{
|
||||||
|
services.AddSingleton<TImplementation>();
|
||||||
|
services.AddSingleton<TService>(sp => sp.GetRequiredService<TImplementation>());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddHostedServiceSingleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services) where THostedService : class, IHostedService
|
||||||
|
{
|
||||||
|
services.AddSingleton<THostedService>();
|
||||||
|
services.AddHostedService(sp => sp.GetRequiredService<THostedService>());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddHostedInterfaceServiceSingleton<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services) where TService : class where THostedService : class, IHostedService, TService
|
||||||
|
{
|
||||||
|
services.AddSingleton<THostedService>();
|
||||||
|
services.AddSingleton<TService>(sp => sp.GetRequiredService<THostedService>());
|
||||||
|
services.AddHostedService(sp => sp.GetRequiredService<THostedService>());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
RobotApp/Services/State/ACTState.cs
Normal file
7
RobotApp/Services/State/ACTState.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class ACTState<T>(ACTStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
}
|
||||||
7
RobotApp/Services/State/AutoState.cs
Normal file
7
RobotApp/Services/State/AutoState.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class AutoState<T>(AutoStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
}
|
||||||
7
RobotApp/Services/State/ExecutingState.cs
Normal file
7
RobotApp/Services/State/ExecutingState.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class ExecutingState<T>(ExecutingStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
}
|
||||||
7
RobotApp/Services/State/FaultState.cs
Normal file
7
RobotApp/Services/State/FaultState.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class FaultState<T>(FaultStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
}
|
||||||
21
RobotApp/Services/State/IRobotState.cs
Normal file
21
RobotApp/Services/State/IRobotState.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public interface IRobotState
|
||||||
|
{
|
||||||
|
Enum Name { get; }
|
||||||
|
Type Type { get; }
|
||||||
|
IRobotState? SuperState { get; }
|
||||||
|
List<IRobotState> SubStates { get; set; }
|
||||||
|
IRobotState? ActiveSubState { get;}
|
||||||
|
IRobotState? HistoryState { get;}
|
||||||
|
bool HasSubStates => SubStates.Count > 0;
|
||||||
|
bool IsActive => ActiveSubState != null;
|
||||||
|
|
||||||
|
event Action<IRobotState>? OnEnter;
|
||||||
|
event Action<IRobotState>? OnExit;
|
||||||
|
event Action<IRobotState , IRobotState>? OnTransition;
|
||||||
|
void Enter();
|
||||||
|
void Exit();
|
||||||
|
bool CanTransitionTo(IRobotState targetState);
|
||||||
|
void Update();
|
||||||
|
}
|
||||||
7
RobotApp/Services/State/ManualState.cs
Normal file
7
RobotApp/Services/State/ManualState.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class ManualState<T>(ManualStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
}
|
||||||
7
RobotApp/Services/State/MoveState.cs
Normal file
7
RobotApp/Services/State/MoveState.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class MoveState<T>(MoveStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
}
|
||||||
7
RobotApp/Services/State/PlanState.cs
Normal file
7
RobotApp/Services/State/PlanState.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class PlanState<T>(PlanStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,38 @@
|
||||||
namespace RobotApp.Services.State;
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
public abstract class RobotState<T>(T type) where T : Enum
|
public abstract class RobotState<T>(Enum name, IRobotState? superState = null) : IRobotState where T : Enum
|
||||||
{
|
{
|
||||||
public readonly T Type = type;
|
public Enum Name { get; private set; } = name;
|
||||||
public virtual void Enter() { }
|
public Type Type { get; private set; } = typeof(T);
|
||||||
public virtual void Exit() { }
|
public IRobotState? SuperState { get; private set; } = superState;
|
||||||
|
public List<IRobotState> SubStates { get; set; } = [];
|
||||||
|
public IRobotState? ActiveSubState { get; protected set; }
|
||||||
|
public IRobotState? HistoryState { get; protected set; }
|
||||||
|
public bool HasSubStates => SubStates.Count > 0;
|
||||||
|
public bool IsActive => ActiveSubState != null;
|
||||||
|
public override string ToString() => $"{GetType().Name}({Name})";
|
||||||
|
|
||||||
|
public event Action<IRobotState>? OnEnter;
|
||||||
|
public event Action<IRobotState>? OnExit;
|
||||||
|
public event Action<IRobotState, IRobotState>? OnTransition;
|
||||||
|
|
||||||
|
public virtual void Enter()
|
||||||
|
{
|
||||||
|
OnEnter?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Exit()
|
||||||
|
{
|
||||||
|
OnExit?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool CanTransitionTo(IRobotState targetState)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update()
|
||||||
|
{
|
||||||
|
ActiveSubState?.Update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,352 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public record RobotStateMachine(Logger<RobotStateMachine> Logger) : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Dictionary<Type, Dictionary<Enum, IRobotState>> StateRegistry = [];
|
||||||
|
|
||||||
|
public IRobotState? CurrentState { get; private set; }
|
||||||
|
public event Action<IRobotState?, IRobotState>? OnStateChanged;
|
||||||
|
public bool IsInitialized = false;
|
||||||
|
|
||||||
|
private readonly Lock StateLock = new();
|
||||||
|
|
||||||
|
private void PrintStateAdded()
|
||||||
|
{
|
||||||
|
Console.WriteLine("=== All Registered States ===");
|
||||||
|
var allStates = StateRegistry.Values
|
||||||
|
.SelectMany(dict => dict.Values)
|
||||||
|
.Where(state => state.SuperState == null)
|
||||||
|
.OrderBy(state => state.Type.Name)
|
||||||
|
.ThenBy(state => state.Name.ToString());
|
||||||
|
|
||||||
|
foreach (var rootState in allStates)
|
||||||
|
{
|
||||||
|
PrintStateSimple(rootState, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalStates = StateRegistry.Values.Sum(dict => dict.Count);
|
||||||
|
Console.WriteLine($"\nTotal: {totalStates} states registered");
|
||||||
|
Console.WriteLine("==============================\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintStateSimple(IRobotState state, int depth)
|
||||||
|
{
|
||||||
|
var indent = new string(' ', depth * 4);
|
||||||
|
var marker = state == CurrentState ? " [CURRENT]" : "";
|
||||||
|
var activeMarker = state.SuperState?.ActiveSubState == state ? " [ACTIVE]" : "";
|
||||||
|
|
||||||
|
Console.WriteLine($"{indent}- {state.Name}{marker}{activeMarker}");
|
||||||
|
|
||||||
|
foreach (var subState in state.SubStates.OrderBy(s => s.Name.ToString()))
|
||||||
|
{
|
||||||
|
PrintStateSimple(subState, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeHierarchyStates()
|
||||||
|
{
|
||||||
|
if (IsInitialized) return;
|
||||||
|
RegisterState(new RootState<RootStateType>(RootStateType.System));
|
||||||
|
RegisterState(new RootState<RootStateType>(RootStateType.Auto));
|
||||||
|
RegisterState(new RootState<RootStateType>(RootStateType.Manual));
|
||||||
|
RegisterState(new RootState<RootStateType>(RootStateType.Service));
|
||||||
|
RegisterState(new RootState<RootStateType>(RootStateType.Stop));
|
||||||
|
RegisterState(new RootState<RootStateType>(RootStateType.Fault));
|
||||||
|
SetupSystemState();
|
||||||
|
SetupAutoState();
|
||||||
|
SetupManualState();
|
||||||
|
SetupServiceState();
|
||||||
|
SetupStopState();
|
||||||
|
SetupFaultState();
|
||||||
|
|
||||||
|
var systemState = GetState(RootStateType.System);
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
systemState.Enter();
|
||||||
|
CurrentState = systemState;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintStateAdded();
|
||||||
|
IsInitialized = true;
|
||||||
|
Logger.Info($"Khởi tạo thành công State Machine với State hiện tại: {GetCurrentStatePath()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupSystemState()
|
||||||
|
{
|
||||||
|
var systemlState = GetState(RootStateType.System) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var initializingState = new SystemState<SystemStateType>(SystemStateType.Initializing, systemlState);
|
||||||
|
var standbyState = new SystemState<SystemStateType>(SystemStateType.Standby, systemlState);
|
||||||
|
var shutting_DownState = new SystemState<SystemStateType>(SystemStateType.Shutting_Down, systemlState);
|
||||||
|
|
||||||
|
systemlState.SubStates.AddRange([initializingState, standbyState, shutting_DownState]);
|
||||||
|
|
||||||
|
RegisterState(initializingState);
|
||||||
|
RegisterState(standbyState);
|
||||||
|
RegisterState(shutting_DownState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupAutoState()
|
||||||
|
{
|
||||||
|
var autoState = GetState(RootStateType.Auto) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var idleState = new AutoState<AutoStateType>(AutoStateType.Idle, autoState);
|
||||||
|
var executingState = new AutoState<AutoStateType>(AutoStateType.Executing, autoState);
|
||||||
|
var pausedState = new AutoState<AutoStateType>(AutoStateType.Paused, autoState);
|
||||||
|
var holdingState = new AutoState<AutoStateType>(AutoStateType.Holding, autoState);
|
||||||
|
var cancelingState = new AutoState<AutoStateType>(AutoStateType.Canceling, autoState);
|
||||||
|
var recoveringState = new AutoState<AutoStateType>(AutoStateType.Recovering, autoState);
|
||||||
|
var remote_Override_State = new AutoState<AutoStateType>(AutoStateType.Remote_Override, autoState);
|
||||||
|
|
||||||
|
autoState.SubStates.AddRange([ idleState, executingState, pausedState, holdingState,
|
||||||
|
cancelingState, recoveringState, remote_Override_State ]);
|
||||||
|
|
||||||
|
RegisterState(idleState);
|
||||||
|
RegisterState(executingState);
|
||||||
|
RegisterState(pausedState);
|
||||||
|
RegisterState(holdingState);
|
||||||
|
RegisterState(cancelingState);
|
||||||
|
RegisterState(recoveringState);
|
||||||
|
RegisterState(remote_Override_State);
|
||||||
|
|
||||||
|
SetupExecutingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupManualState()
|
||||||
|
{
|
||||||
|
var manualState = GetState(RootStateType.Manual) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var idleState = new ManualState<ManualStateType>(ManualStateType.Idle, manualState);
|
||||||
|
var executingState = new ManualState<ManualStateType>(ManualStateType.Active, manualState);
|
||||||
|
|
||||||
|
manualState.SubStates.AddRange([idleState, executingState]);
|
||||||
|
|
||||||
|
RegisterState(idleState);
|
||||||
|
RegisterState(executingState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupServiceState()
|
||||||
|
{
|
||||||
|
var serviceState = GetState(RootStateType.Service) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var idleState = new ServiceState<ServiceStateType>(ServiceStateType.Idle, serviceState);
|
||||||
|
var executingState = new ServiceState<ServiceStateType>(ServiceStateType.Active, serviceState);
|
||||||
|
|
||||||
|
serviceState.SubStates.AddRange([idleState, executingState]);
|
||||||
|
|
||||||
|
RegisterState(idleState);
|
||||||
|
RegisterState(executingState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupStopState()
|
||||||
|
{
|
||||||
|
var stopState = GetState(RootStateType.Stop) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var emcStopState = new StopState<StopStateType>(StopStateType.EMC, stopState);
|
||||||
|
var protectiveState = new StopState<StopStateType>(StopStateType.Protective, stopState);
|
||||||
|
var manualStopState = new StopState<StopStateType>(StopStateType.Manual, stopState);
|
||||||
|
|
||||||
|
stopState.SubStates.AddRange([emcStopState, protectiveState, manualStopState]);
|
||||||
|
|
||||||
|
RegisterState(emcStopState);
|
||||||
|
RegisterState(protectiveState);
|
||||||
|
RegisterState(manualStopState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupFaultState()
|
||||||
|
{
|
||||||
|
var faultState = GetState(RootStateType.Fault) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var navigationFaultState = new FaultState<FaultStateType>(FaultStateType.Navigation, faultState);
|
||||||
|
var localizationFaultState = new FaultState<FaultStateType>(FaultStateType.Localization, faultState);
|
||||||
|
var shelftFaultState = new FaultState<FaultStateType>(FaultStateType.Shielf, faultState);
|
||||||
|
var batteryFaultState = new FaultState<FaultStateType>(FaultStateType.Battery, faultState);
|
||||||
|
var driverFaultState = new FaultState<FaultStateType>(FaultStateType.Driver, faultState);
|
||||||
|
var peripheralsFaultState = new FaultState<FaultStateType>(FaultStateType.Peripherals, faultState);
|
||||||
|
var safetyFaultState = new FaultState<FaultStateType>(FaultStateType.Safety, faultState);
|
||||||
|
var comunicationFaultState = new FaultState<FaultStateType>(FaultStateType.Communication, faultState);
|
||||||
|
|
||||||
|
faultState.SubStates.AddRange([navigationFaultState, localizationFaultState, shelftFaultState,
|
||||||
|
batteryFaultState, driverFaultState, peripheralsFaultState, safetyFaultState,
|
||||||
|
comunicationFaultState]);
|
||||||
|
|
||||||
|
RegisterState(navigationFaultState);
|
||||||
|
RegisterState(localizationFaultState);
|
||||||
|
RegisterState(shelftFaultState);
|
||||||
|
RegisterState(batteryFaultState);
|
||||||
|
RegisterState(driverFaultState);
|
||||||
|
RegisterState(peripheralsFaultState);
|
||||||
|
RegisterState(safetyFaultState);
|
||||||
|
RegisterState(comunicationFaultState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupExecutingState()
|
||||||
|
{
|
||||||
|
var executingState = GetState(AutoStateType.Executing) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var planningState = new ExecutingState<ExecutingStateType>(ExecutingStateType.Planning, executingState);
|
||||||
|
var movingState = new ExecutingState<ExecutingStateType>(ExecutingStateType.Moving, executingState);
|
||||||
|
var actStopState = new ExecutingState<ExecutingStateType>(ExecutingStateType.ACT, executingState);
|
||||||
|
|
||||||
|
executingState.SubStates.AddRange([planningState, movingState, actStopState]);
|
||||||
|
|
||||||
|
RegisterState(planningState);
|
||||||
|
RegisterState(movingState);
|
||||||
|
RegisterState(actStopState);
|
||||||
|
|
||||||
|
SetupACTState();
|
||||||
|
SetupMoveState();
|
||||||
|
SetupPlanState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupACTState()
|
||||||
|
{
|
||||||
|
var ACTState = GetState(ExecutingStateType.ACT) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var dockingState = new ACTState<ACTStateType>(ACTStateType.Docking, ACTState);
|
||||||
|
var dockedState = new ACTState<ACTStateType>(ACTStateType.Docked, ACTState);
|
||||||
|
var chargingState = new ACTState<ACTStateType>(ACTStateType.Charging, ACTState);
|
||||||
|
var undockingState = new ACTState<ACTStateType>(ACTStateType.Undocking, ACTState);
|
||||||
|
var loadingState = new ACTState<ACTStateType>(ACTStateType.Loading, ACTState);
|
||||||
|
var unLoadingState = new ACTState<ACTStateType>(ACTStateType.Unloading, ACTState);
|
||||||
|
var techActionState = new ACTState<ACTStateType>(ACTStateType.TechAction, ACTState);
|
||||||
|
|
||||||
|
ACTState.SubStates.AddRange([dockingState, dockedState, chargingState, undockingState,
|
||||||
|
loadingState, unLoadingState, techActionState]);
|
||||||
|
|
||||||
|
RegisterState(dockingState);
|
||||||
|
RegisterState(dockedState);
|
||||||
|
RegisterState(chargingState);
|
||||||
|
RegisterState(undockingState);
|
||||||
|
RegisterState(loadingState);
|
||||||
|
RegisterState(unLoadingState);
|
||||||
|
RegisterState(techActionState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupMoveState()
|
||||||
|
{
|
||||||
|
var moveState = GetState(ExecutingStateType.Moving) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var navigationState = new MoveState<MoveStateType>(MoveStateType.Navigation, moveState);
|
||||||
|
var avoidabceState = new MoveState<MoveStateType>(MoveStateType.Avoidance, moveState);
|
||||||
|
var approachState = new MoveState<MoveStateType>(MoveStateType.Approach, moveState);
|
||||||
|
var trackingState = new MoveState<MoveStateType>(MoveStateType.Tracking, moveState);
|
||||||
|
var repositioningState = new MoveState<MoveStateType>(MoveStateType.Repositioning, moveState);
|
||||||
|
|
||||||
|
moveState.SubStates.AddRange([navigationState, avoidabceState, approachState, trackingState, repositioningState]);
|
||||||
|
|
||||||
|
RegisterState(navigationState);
|
||||||
|
RegisterState(avoidabceState);
|
||||||
|
RegisterState(approachState);
|
||||||
|
RegisterState(trackingState);
|
||||||
|
RegisterState(repositioningState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupPlanState()
|
||||||
|
{
|
||||||
|
var planState = GetState(ExecutingStateType.Planning) ?? throw new InvalidOperationException($"Failed to get state RootStateType.Auto");
|
||||||
|
var taskPlanState = new PlanState<PlanStateType>(PlanStateType.Task, planState);
|
||||||
|
var pathPlanState = new PlanState<PlanStateType>(PlanStateType.Path, planState);
|
||||||
|
|
||||||
|
planState.SubStates.AddRange([taskPlanState, pathPlanState]);
|
||||||
|
|
||||||
|
RegisterState(taskPlanState);
|
||||||
|
RegisterState(pathPlanState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterState<T>(RobotState<T> state) where T : Enum
|
||||||
|
{
|
||||||
|
var enumType = typeof(T);
|
||||||
|
StateRegistry.TryAdd(enumType, []);
|
||||||
|
StateRegistry[enumType][state.Name] = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RobotState<T>? TryGetState<T>(T stateType) where T : Enum
|
||||||
|
{
|
||||||
|
var enumType = typeof(T);
|
||||||
|
if (StateRegistry.TryGetValue(enumType, out var states) && states.TryGetValue(stateType, out var state) && state is RobotState<T> robotState)
|
||||||
|
{
|
||||||
|
return robotState;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RobotState<T> GetState<T>(T stateType) where T : Enum
|
||||||
|
{
|
||||||
|
return TryGetState(stateType) ?? throw new ArgumentException($"Failed to get state {stateType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TransitionTo<T>(T stateType) where T : Enum
|
||||||
|
{
|
||||||
|
if (!IsInitialized)
|
||||||
|
{
|
||||||
|
Logger.Warning("State Machine chưa được khởi tạo");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var targetState = TryGetState(stateType);
|
||||||
|
if (targetState == null)
|
||||||
|
{
|
||||||
|
Logger.Warning($"State {stateType} không được tìm thấy");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentState != null)
|
||||||
|
{
|
||||||
|
if (!CurrentState.CanTransitionTo(targetState))
|
||||||
|
{
|
||||||
|
Logger.Warning($"Không thể chuyển State từ {CurrentState.Name} thành {targetState.Name}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentState.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousState = CurrentState;
|
||||||
|
CurrentState = targetState;
|
||||||
|
CurrentState.Enter();
|
||||||
|
|
||||||
|
Logger.Info($"Chuyển đổi State thành công: {GetStatePath(previousState)} -> {GetStatePath(targetState)}");
|
||||||
|
OnStateChanged?.Invoke(previousState, CurrentState);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error($"Lỗi khi chuyển đổi State: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (!IsInitialized)
|
||||||
|
{
|
||||||
|
Logger.Warning("State Machine chưa được khởi tạo");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lock (StateLock)
|
||||||
|
{
|
||||||
|
CurrentState?.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetCurrentStatePath()
|
||||||
|
{
|
||||||
|
if (CurrentState == null) return "No State";
|
||||||
|
return GetStatePath(CurrentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetStatePath(IRobotState? state)
|
||||||
|
{
|
||||||
|
var path = new List<string>();
|
||||||
|
var current = state;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
path.Insert(0, current.Name.ToString());
|
||||||
|
current = current.SuperState;
|
||||||
|
}
|
||||||
|
return string.Join(".", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
RobotApp/Services/State/RootState.cs
Normal file
36
RobotApp/Services/State/RootState.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class RootState<T>(RootStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
public override void Enter()
|
||||||
|
{
|
||||||
|
switch(Name)
|
||||||
|
{
|
||||||
|
case RootStateType.System:
|
||||||
|
ActiveSubState = SubStates.FirstOrDefault(s => s.Name.Equals(SystemStateType.Initializing));
|
||||||
|
ActiveSubState?.Enter();
|
||||||
|
break;
|
||||||
|
case RootStateType.Auto:
|
||||||
|
if(HistoryState is not null) ActiveSubState = HistoryState;
|
||||||
|
else ActiveSubState = SubStates.FirstOrDefault(s => s.Name.Equals(AutoStateType.Idle));
|
||||||
|
ActiveSubState?.Enter();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Exit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanTransitionTo(IRobotState targetState)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
ActiveSubState?.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
7
RobotApp/Services/State/ServiceState.cs
Normal file
7
RobotApp/Services/State/ServiceState.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class ServiceState<T>(ServiceStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
}
|
||||||
8
RobotApp/Services/State/StopState.cs
Normal file
8
RobotApp/Services/State/StopState.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State
|
||||||
|
{
|
||||||
|
public class StopState<T>(StopStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
21
RobotApp/Services/State/SystemState.cs
Normal file
21
RobotApp/Services/State/SystemState.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using RobotApp.Common.Shares.Enums;
|
||||||
|
|
||||||
|
namespace RobotApp.Services.State;
|
||||||
|
|
||||||
|
public class SystemState<T>(SystemStateType state, IRobotState? superState = null) : RobotState<T>(state, superState) where T : Enum
|
||||||
|
{
|
||||||
|
public override void Enter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override void Exit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public override bool CanTransitionTo(IRobotState targetState)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
ActiveSubState?.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,8 +5,20 @@
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning",
|
||||||
|
"Microsoft.EntityFrameworkCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"Simulation": {
|
||||||
|
"Enable": true,
|
||||||
|
"RadiusWheel": 0.1,
|
||||||
|
"Width": 0.606,
|
||||||
|
"Length": 1.106,
|
||||||
|
"MaxVelocity": 1.5,
|
||||||
|
"MaxAngularVelocity": 0.3,
|
||||||
|
"Acceleration": 2,
|
||||||
|
"Deceleration": 10,
|
||||||
|
"NavigationType": "Differential"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user