Explorar el Código

电动螺丝刀及焊台采集提交

runming56 hace 1 año
commit
ce7ac6baab
Se han modificado 100 ficheros con 10975 adiciones y 0 borrados
  1. 12 0
      .config/dotnet-tools.json
  2. BIN
      .vs/AmrControl/DesignTimeBuild/.dtbcache.v2
  3. BIN
      .vs/AmrControl/FileContentIndex/cf3e29aa-420a-4769-8c18-8d7013bbfa15.vsidx
  4. 1021 0
      .vs/AmrControl/config/applicationhost.config
  5. BIN
      .vs/AmrControl/v17/.futdcache.v2
  6. BIN
      .vs/AmrControl/v17/.suo
  7. BIN
      .vs/AmrControl/v17/Browse.VC.db
  8. BIN
      .vs/AmrControl/v17/Browse.VC.db-shm
  9. 0 0
      .vs/AmrControl/v17/Browse.VC.db-wal
  10. BIN
      .vs/AmrControl/v17/Browse.VC.opendb
  11. BIN
      .vs/AmrControl/v17/ipch/AutoPCH/740c978d67daafcc/JBC.ipch
  12. BIN
      .vs/ProjectEvaluation/amrcontrol.metadata.v5.1
  13. BIN
      .vs/ProjectEvaluation/amrcontrol.metadata.v7.bin
  14. BIN
      .vs/ProjectEvaluation/amrcontrol.projects.v5.1
  15. BIN
      .vs/ProjectEvaluation/amrcontrol.projects.v7.bin
  16. 93 0
      ADS/ChargingStationDataModel.cs
  17. 270 0
      ADS/JGRDataModel.cs
  18. 107 0
      ADS/StationDataModel.cs
  19. 564 0
      ADS/TcClient.cs
  20. 80 0
      ADS/TcModelConverter.cs
  21. 59 0
      ADS/TcValue.cs
  22. 52 0
      AmrControl.csproj
  23. 6 0
      AmrControl.csproj.user
  24. 30 0
      AmrControl.sln
  25. 83 0
      AppHelper.cs
  26. 53 0
      Clients/DeprageElecScrewdriver.cs
  27. 279 0
      Clients/MqttClient.cs
  28. 11 0
      Clients/WsCmd.cs
  29. 166 0
      Clients/WsState.cs
  30. 479 0
      Common/AmrManager.cs
  31. 12 0
      Common/AmrMessage.cs
  32. 52 0
      Common/CRC16.cs
  33. 380 0
      Common/CarCmd.cs
  34. 55 0
      Common/ChatHub.cs
  35. 143 0
      Common/HttpClients/BaseHttpClient.cs
  36. 129 0
      Common/HttpClients/DefaultHttpClient.cs
  37. 43 0
      Common/HttpClients/HttpDelete.cs
  38. 83 0
      Common/HttpClients/HttpGet.cs
  39. 146 0
      Common/HttpClients/HttpPost.cs
  40. 39 0
      Common/HttpClients/HttpPut.cs
  41. 25 0
      Common/HttpClients/HttpRespResult.cs
  42. 25 0
      Common/HttpClients/IHttpRequest.cs
  43. 26 0
      Common/HttpClients/MediaType.cs
  44. 46 0
      Common/IntStringConverter.cs
  45. 333 0
      Common/MockData.cs
  46. 10 0
      Common/MqMessage.cs
  47. 25 0
      Common/Proxy.cs
  48. 557 0
      Common/RS485.cs
  49. 10 0
      Common/ThreadLocalContext.cs
  50. 30 0
      Common/ThreadLocalManager.cs
  51. 207 0
      Controllers/JgrController.cs
  52. 267 0
      Controllers/ProductLineController.cs
  53. 499 0
      Controllers/StorageController.cs
  54. 33 0
      Controllers/UserController.cs
  55. 39 0
      DB/Models/AppConfig.cs
  56. 58 0
      DB/Models/Charger.cs
  57. 25 0
      DB/Models/IssueNote.cs
  58. 28 0
      DB/Models/JGR.cs
  59. 60 0
      DB/Models/Log.cs
  60. 24 0
      DB/Models/Material.cs
  61. 25 0
      DB/Models/Station.cs
  62. 22 0
      DB/Models/Storage.cs
  63. 73 0
      DB/Models/UserModel.cs
  64. 78 0
      DB/MyDbContext.cs
  65. 193 0
      DB/MyDbHandler.cs
  66. 21 0
      Dto/BoxMovingStatusDto.cs
  67. 13 0
      Dto/CallAmrCarDTO.cs
  68. 48 0
      Dto/GridCell.cs
  69. 25 0
      Dto/GridCellsDto.cs
  70. 31 0
      Dto/HttpStatus.cs
  71. 17 0
      Dto/InStorageDTO.cs
  72. 68 0
      Dto/IssueNoteDto.cs
  73. 148 0
      Dto/JgrDebugDto.cs
  74. 25 0
      Dto/JgrDto.cs
  75. 21 0
      Dto/JgrMovingDto.cs
  76. 26 0
      Dto/MoveCarToPosDTO.cs
  77. 17 0
      Dto/MoveStorageDTO.cs
  78. 52 0
      Dto/MovingPath.cs
  79. 21 0
      Dto/OutStorageDTO.cs
  80. 28 0
      Dto/PickingStationStatus.cs
  81. 22 0
      Dto/RetrievalDto.cs
  82. 63 0
      Dto/StorageLocation.cs
  83. 41 0
      Dto/StorageStatus.cs
  84. 26 0
      Dto/UserDto.cs
  85. 2644 0
      JGR/JGRManager.cs
  86. 31 0
      Pages/Debug.cshtml
  87. 12 0
      Pages/Debug.cshtml.cs
  88. 33 0
      Pages/Index.cshtml
  89. 13 0
      Pages/Index.cshtml.cs
  90. 167 0
      Program.cs
  91. 22 0
      Properties/PublishProfiles/FolderProfile.pubxml
  92. 12 0
      Properties/PublishProfiles/FolderProfile.pubxml.user
  93. 31 0
      Properties/launchSettings - 复制.json
  94. 29 0
      Properties/launchSettings.json
  95. 3 0
      README.md
  96. 17 0
      Vo/InStorageVO.cs
  97. 17 0
      Vo/MoveStorageVO.cs
  98. 17 0
      Vo/OutStorageVO.cs
  99. 49 0
      Vo/StationStateVO.cs
  100. 0 0
      ads.json

+ 12 - 0
.config/dotnet-tools.json

@@ -0,0 +1,12 @@
+{
+  "version": 1,
+  "isRoot": true,
+  "tools": {
+    "dotnet-ef": {
+      "version": "6.0.7",
+      "commands": [
+        "dotnet-ef"
+      ]
+    }
+  }
+}

BIN
.vs/AmrControl/DesignTimeBuild/.dtbcache.v2


BIN
.vs/AmrControl/FileContentIndex/cf3e29aa-420a-4769-8c18-8d7013bbfa15.vsidx


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1021 - 0
.vs/AmrControl/config/applicationhost.config


BIN
.vs/AmrControl/v17/.futdcache.v2


BIN
.vs/AmrControl/v17/.suo


BIN
.vs/AmrControl/v17/Browse.VC.db


BIN
.vs/AmrControl/v17/Browse.VC.db-shm


+ 0 - 0
.vs/AmrControl/v17/Browse.VC.db-wal


BIN
.vs/AmrControl/v17/Browse.VC.opendb


BIN
.vs/AmrControl/v17/ipch/AutoPCH/740c978d67daafcc/JBC.ipch


BIN
.vs/ProjectEvaluation/amrcontrol.metadata.v5.1


BIN
.vs/ProjectEvaluation/amrcontrol.metadata.v7.bin


BIN
.vs/ProjectEvaluation/amrcontrol.projects.v5.1


BIN
.vs/ProjectEvaluation/amrcontrol.projects.v7.bin


+ 93 - 0
ADS/ChargingStationDataModel.cs

@@ -0,0 +1,93 @@
+using Newtonsoft.Json;
+using System.Runtime.InteropServices;
+
+namespace AmrControl.ADS
+{
+    /// <summary>
+    /// 充电桩数据模型
+    /// </summary>
+    public class ChargingStationDataModel
+    {
+        //JGR类型  0:仓储车 , 1:运输车, 2:仓储位充电器,3:轨道位充电器
+        public byte csType;
+        //出厂编号,30个长度带'\0',有效最多29个字节
+        public string csSN;
+        //车ID,取值范围0~63,不同类型的车/充电器的csID可以重复
+        public byte csID;
+        //无线模块的地址,长度2字节
+        public ushort rcAddr;
+        //无线模块的频率码,取值0~155,对应频率为 900+RC_FreqCode*0.2,单位MHz,默认频率码为0x32(50)	
+        public byte rcFreqCode;
+        //在线/离线状态 是否在线(通讯是否正常)
+        public byte isOnline;
+        //健康状态		1:正常;   0:异常
+        public byte isHealthy;
+        //充电标志,1:充电中,0:未在充电
+        public byte isCharging;
+        /// <summary>
+        /// 充电头的状态,0:缩回状态,1:伸出状态
+        /// </summary>
+        public byte chargerStatus;
+        //充电电压,单位:V
+        public float chargeVoltage;
+        //充电电流,单位:mA
+        public float chargeCurrent;
+        /// <summary>
+        /// 所在格子的位置X
+        /// </summary>
+        public int positionX;
+        /// <summary>
+        /// 所在格子的位置Y
+        /// </summary>
+        public int positionY;
+        /// <summary>
+        /// 显示位置X,Y坐标
+        /// </summary>
+        public int displayX;
+        public int displayY;
+        /// <summary>
+        /// 连接的小车的编号,预留,现在硬件不支持
+        /// </summary>
+        public string jgrSN;
+        /// <summary>
+        /// 连接的小车的电池剩余电量,取值 0~100, 100表示满电量.
+        /// 预留,现在硬件不支持
+        /// </summary>
+        public byte electricity;
+    }
+
+    //用内存对齐方式并不对,可能是和TC程序接口是C#开发的有关 [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    /// <summary>
+    /// TC程序中定义的充电桩的数据结构
+    /// </summary>
+    [Serializable]
+    public struct Charge_Ads_Struct
+    {
+        //出厂编号,30个长度带'\0',有效最多29个字节
+        [JsonConverter(typeof(TcModelArrayConverter))]
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
+        public byte[] chargeSN;
+        //  0:仓储车 , 1:运输车, 2:仓储位充电器,3:轨道位充电器
+        public byte chargeType;
+        //车ID,取值范围0~63,不同类型的车/充电器的jgrID可以重复
+        public byte chargeID;
+        //无线模块的地址,长度2字节
+        public ushort rcAddr;
+        //无线模块的频率码,取值0~155,对应频率为 900+RC_FreqCode*0.2,单位MHz,默认频率码为0x32(50)	
+        public byte rcFreqCode;
+        //在线/离线状态 是否在线(通讯是否正常)
+        public byte isOnline;
+        //健康状态		true:正常;   false:异常
+        public byte isHealthy;
+        //充电标志,1:充电中,0:未在充电
+        public byte isCharging;
+        /// <summary>
+        /// 充电头的状态,0:缩回状态,1:伸出状态
+        /// </summary>
+        public byte chargerStatus;
+        //充电电压,单位:V
+        public float chargeVoltage;
+        //充电电流,单位:mA
+        public float chargeCurrent;
+    }
+}

+ 270 - 0
ADS/JGRDataModel.cs

@@ -0,0 +1,270 @@
+using AmrControl.Dto;
+using Newtonsoft.Json;
+using NPOI.SS.Formula.Functions;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security.Policy;
+
+namespace AmrControl.ADS
+{
+    [Serializable]
+    public class JGR_Tc_Model
+    {
+        //  0:仓储车 , 1:运输车, 2:仓储位充电器,3:轨道位充电器
+        public byte jgrType;
+        //出厂编号,30个长度带'\0',有效最多29个字节
+        public string jgrSN;
+        //车ID,取值范围0~63,不同类型的车/充电器的jgrID可以重复
+        public byte jgrID;
+        //无线模块的地址,长度2字节
+        public ushort rcAddr;
+        //无线模块的频率码,取值0~155,对应频率为 900+RC_FreqCode*0.2,单位MHz,默认频率码为0x32(50)	
+        public byte rcFreqCode;
+        //在线/离线状态 是否在线(通讯是否正常)
+        public byte isOnline;
+        //健康状态		=0:正常;   >=1:异常,异常编码看接口给
+        public byte isHealthy;
+        //充电标志,1:充电中,0:未在充电
+        public byte isCharging;
+        //充电电压,单位:V
+        public float chargeVoltage;
+        //充电电流,单位:mA
+        public float chargeCurrent;
+        //电池剩余电量,取值 0~100	, 100表示满电量
+        public byte electricity;
+        //驱动器通电/断电状态
+        public byte isDriverPowerOn;
+        //(*工作模式		
+        //1: 正常工作模式,即自动控制模式,只需给出运动的目标坐标,动作序列小车自己控制,正常工作时使用;
+        //2:调试模式,即手动控制模式,用于调试,每一个动作都需要发送指令*)
+        public byte workMode;
+        //车轮位置(车身姿态) =1高姿态,=2中姿态,=3低姿态
+        public byte wheelPosition;
+        //行进/停止状态
+        //(* 车体运动状态
+        //0:静止状态
+        //1:X负向移动
+        //2:X正向移动
+        //3:Y负向移动
+        //4:Y正向移动*)
+        public byte moveStatus;
+        //移动速度,单位:m/s
+        public float moveSpeed;
+        //当前位置X
+        public short currLocation_X;
+        //当前位置Y
+        public short currLocation_Y;
+        //目标位置X
+        public short destLocation_X;
+        //目标位置Y
+        public short destLocation_Y;
+        /// <summary>
+        /// 显示位置X,Y坐标
+        /// </summary>
+        public int displayX;
+        public int displayY;
+        //车轮是否在位置切换中
+        public byte isWheelAltering;
+        //4个碰撞传感器,true:碰撞,false:未碰撞
+        public byte[] collideSensor;
+        //X加速度,单位 m/s2
+        public float acceleration_X;
+        //Y加速度,单位 m/s2
+        public float acceleration_Y;
+        //Z加速度,单位 m/s2
+        public float acceleration_Z;
+        //XZ角度,单位:°
+        public float angleXZ;
+        //YZ角度,单位:°
+        public float angleYZ;
+        //吊篮限位标志,吊篮处于的位置	0:最高点;		1~7层
+        public byte basketPosition;
+        //吊篮抓夹状态 	1:闭合状态;	2:张开状态
+        public byte basketGripperStatus;
+        //吊篮是否在移动,吊篮运动状态	吊篮运动状态 0:静止;    1:正在上提;  2:正在下放
+        public byte basketMoveStatus;
+        //RFID
+        public string rfid;
+        /// <summary>
+        /// 需要移动的路径
+        /// </summary>
+        [JsonIgnore]
+        public List<MovingPath> movingPaths;
+
+        public JGR_Tc_Model()
+        {
+            collideSensor = new byte[4];
+            movingPaths = new List<MovingPath>();
+        }
+
+        public int GetPathCounts()
+        {
+            int count = 0;
+            lock(this)
+            {
+                count = movingPaths.Count;
+            }
+
+            return count;
+        }
+
+        public MovingPath GetFirstPath()
+        {
+            MovingPath path = null;
+            lock (this)
+            {
+                if(movingPaths.Count > 0)
+                {
+                    path = movingPaths[0];
+                }
+            }
+
+            return path;
+        }
+
+        public MovingPath RemoveFirstPath()
+        {
+            MovingPath path = null;
+            lock (this)
+            {
+                if (movingPaths.Count > 0)
+                {
+                    path = movingPaths[0];
+                    movingPaths.RemoveAt(0);
+                }
+            }
+            return path;
+        }
+
+        public void ClearPath()
+        {
+            lock (this)
+            {
+                movingPaths.Clear();
+            }
+        }
+
+        /// <summary>
+        /// 如果对象为空,可以加入,表示当前这辆车只能执行一组动作
+        /// </summary>
+        /// <param name="paths"></param>
+        public void AddPath(List<MovingPath> paths)
+        {
+            lock (this)
+            {
+                if(movingPaths.Count == 0)
+                {
+                    movingPaths.AddRange(paths);
+                }
+            }
+        }
+    }
+
+    //用内存对齐方式并不对,可能是和TC程序接口是C#开发的有关 [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    /// <summary>
+    /// TC程序中定义的JGR小车的数据结构
+    /// </summary>
+    [Serializable]
+    public struct JGR_Ads_Struct
+    {
+        //出厂编号,30个长度带'\0',有效最多29个字节
+        [JsonConverter(typeof(TcModelArrayConverter))]
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
+        public byte[] jgrSN;
+        //  0:仓储车 , 1:运输车, 2:仓储位充电器,3:轨道位充电器
+        public byte jgrType;
+        //车ID,取值范围0~63,不同类型的车/充电器的jgrID可以重复
+        public byte jgrID;
+        //无线模块的地址,长度2字节
+        public ushort rcAddr;
+        //无线模块的频率码,取值0~155,对应频率为 900+RC_FreqCode*0.2,单位MHz,默认频率码为0x32(50)	
+        public byte rcFreqCode;
+        //在线/离线状态 是否在线(通讯是否正常)
+        public byte isOnline;
+        //健康状态		true:正常;   false:异常
+        public byte isHealthy;
+        //充电标志,1:充电中,0:未在充电
+        public byte isCharging;
+        //充电电压,单位:V
+        public float chargeVoltage;
+        //充电电流,单位:mA
+        public float chargeCurrent;
+        //电池剩余电量,取值 0~100	, 100表示满电量
+        public byte electricity;
+        //驱动器通电/断电状态
+        public byte isDriverPowerOn;
+        //(*工作模式		
+        //1: 正常工作模式,即自动控制模式,只需给出运动的目标坐标,动作序列小车自己控制,正常工作时使用;
+        //2:调试模式,即手动控制模式,用于调试,每一个动作都需要发送指令*)
+        public byte workMode;
+        //车轮位置(车身姿态) =1高姿态,=2中姿态,=3低姿态
+        public byte wheelPosition;
+        //行进/停止状态
+        //(* 车体运动状态
+        //0:静止状态
+        //1:X负向移动
+        //2:X正向移动
+        //3:Y负向移动
+        //4:Y正向移动*)
+        public byte moveStatus;
+        //移动速度,单位:m/s
+        public float moveSpeed;
+        //当前位置X
+        public short currLocation_X;
+        //当前位置Y
+        public short currLocation_Y;
+        //目标位置X
+        public short destLocation_X;
+        //目标位置Y
+        public short destLocation_Y;
+        //车轮是否在位置切换中
+        public byte isWheelAltering;
+
+        //4个碰撞传感器,true:碰撞,false:未碰撞
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+        public byte[] collideSensor;
+        //X加速度,单位 m/s2
+        public float acceleration_X;
+        //Y加速度,单位 m/s2
+        public float acceleration_Y;
+        //Z加速度,单位 m/s2
+        public float acceleration_Z;
+        //XZ角度,单位:°
+        public float angleXZ;
+        //YZ角度,单位:°
+        public float angleYZ;
+        //吊篮限位标志,吊篮处于的位置	1:最高点;		2:最低点
+        public byte basketPosition;
+        //吊篮抓夹状态 	1:闭合状态;	2:张开状态
+        public byte basketGripperStatus;
+        //吊篮是否在移动,吊篮运动状态	吊篮运动状态 0:静止;    1:正在上提;  2:正在下放
+        public byte basketMoveStatus;
+        //RFID
+        [JsonConverter(typeof(TcModelArrayConverter))]
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
+        public byte[] rfid;
+    }
+
+    /// <summary>
+    /// 车、充电桩的类型
+    /// </summary>
+    public enum JgrType : byte
+    {
+        /// <summary>
+        /// 仓储车
+        /// </summary>
+        StorageCar = 0,
+        /// <summary>
+        /// 轨道车
+        /// </summary>
+        RailCar = 1,
+        /// <summary>
+        /// 仓储充电桩
+        /// </summary>
+        StorageCharging = 2,
+        /// <summary>
+        /// 轨道充电桩
+        /// </summary>
+        RailCharging = 3
+    }
+}

+ 107 - 0
ADS/StationDataModel.cs

@@ -0,0 +1,107 @@
+namespace AmrControl.ADS
+{
+    /// <summary>
+    /// 工位数据模型
+    /// </summary>
+    public class StationDataModel
+    {
+        //工位ID,可以任务是资产编号
+        public string stationID { get; set; }
+        //工位类型,最好来自于字典
+        public StationType stationType { get; set; }
+        /// <summary>
+        /// 显示的名字
+        /// </summary>
+        public string displayName { get; set; }
+        //是否人员在位
+        public bool userOnPosition { get; set; }
+        //在位人员名称
+        public string userName { get; set; }
+        //是否通电
+        public bool isPowerOn { get; set; }
+        //功耗
+        public double dissipation { get; set; }
+        //静电手腕是否佩戴
+        public bool antiStaticWrist { get; set; }
+        //是否有电络铁
+        public bool electricIron { get; set; }
+        //电络铁温度
+        public double eIronTemp { get; set; }
+        /// <summary>
+        /// 今日任务数量
+        /// </summary>
+        public int taskCount { get; set; }
+        /// <summary>
+        /// 已经完成的数量
+        /// </summary>
+        public int finishedCount { get; set; }
+        /// <summary>
+        /// 工位所处的轨道的位置X,需要引导小车到这个位置
+        /// </summary>
+        public int positionX;
+        /// <summary>
+        /// 工位所处的轨道的位置Y,需要引导小车到这个位置
+        /// </summary>
+        public int positionY;
+        /// <summary>
+        /// 显示位置X,Y坐标
+        /// </summary>
+        public int displayX;
+        public int displayY;
+    }
+
+    /// <summary>
+    /// 工位类型
+    /// </summary>
+    public enum StationType : int
+    {
+        /// <summary>
+        /// 齐套工位
+        /// </summary>
+        WS_QT = 1,
+        /// <summary>
+        /// 装配工位
+        /// </summary>
+        WS_ZP = 2,
+        /// <summary>
+        ///装配工位 ,机装
+        /// </summary>
+        WS_ZP_JZ = 3,
+        /// <summary>
+        ///装配工位 ,电装
+        /// </summary>
+        WS_ZP_DZ = 4,
+        /// <summary>
+        ///装配工位 ,钳工
+        /// </summary>
+        WS_ZP_QZ = 5,
+        /// <summary>
+        ///预处理工位
+        /// </summary>
+        WS_YCL = 6,
+        /// <summary>
+        /// 单板调试
+        /// </summary>
+        WS_DBTS = 7,
+        /// <summary>
+        /// 射频调试
+        /// </summary>
+        WS_SPTS = 8,
+        /// <summary>
+        /// 常温测试
+        /// </summary>
+        WS_CWCS = 9,
+        /// <summary>
+        /// 装配-封盖
+        /// </summary>
+        WS_ZP_FG = 10,
+        /// <summary>
+        /// 仓储,捡料工位
+        /// </summary>
+        WS_CC_JL = 11,
+        /// <summary>
+        /// 仓储,派发工位
+        /// </summary>
+        WS_CC_PF = 12
+    }
+}

+ 564 - 0
ADS/TcClient.cs

@@ -0,0 +1,564 @@
+using Microsoft.AspNetCore.SignalR.Protocol;
+using MQTTnet.AspNetCore.Client.Tcp;
+using Newtonsoft.Json;
+using NPOI.SS.Formula.Functions;
+using System;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Text;
+using TwinCAT.Ads;
+
+namespace AmrControl.ADS
+{
+    public class TcClient
+    {
+        protected static TcClient Instance = null;
+        /// <summary>
+        /// ADS 通信模块
+        /// </summary>
+        AdsClient tcAdsClient;
+        List<TcValue> tcConfigs = new List<TcValue>();
+        string netId = "169.254.88.162.1.1";
+        int port = 851;
+        private readonly static object balanceLock = new object();
+
+        public bool State = false;
+
+        public static TcClient GetInstance()
+        {
+            lock(balanceLock)
+            {
+                if (Instance == null)
+                    Instance = new TcClient();
+            }
+           
+            return Instance;
+        }
+
+        private TcClient()
+        {
+            LoadConfig();
+            netId = AppHelper.ReadAppSettings("Ads", "netID");
+            string strPort = AppHelper.ReadAppSettings("Ads", "port");
+            port = int.Parse(strPort);
+            InitAds();
+        }
+
+        /// <summary>
+        /// 初始化ADS
+        /// </summary>
+        /// <returns></returns>
+        internal bool InitAds()
+        {
+            State = false;
+            try
+            {
+                if (tcAdsClient != null)
+                {
+                    tcAdsClient.Disconnect();
+                    tcAdsClient.Dispose();
+                }
+            }
+            catch (Exception e)
+            {
+               throw new Exception("Initads 中发生异常,尝试关闭ads连接的时候发生:" + e.Message);
+            }
+
+            try
+            {
+                tcAdsClient = new AdsClient();
+                tcAdsClient.Connect(netId, port);
+                if (tcAdsClient.IsConnected == false)
+                {
+                    throw new Exception("Initads 中IsConnected == false");
+                    return false;
+                }
+
+                StateInfo state =  tcAdsClient.ReadState();
+                if (state.AdsState == AdsState.Run)
+                {
+                    State = true;
+                    return true;
+                }
+                else
+                {
+                    throw new Exception("Initads中,AdsState != AdsState.Run ,AdsState = " + state.AdsState.ToString());
+                    return false;
+                }
+            }
+            catch(Exception ex)
+            {
+                throw new Exception("Initads 中发生异常,创建新的ads连接的时候发生:" + ex.Message);
+                return false;
+            }
+        }
+
+        internal void Close()
+        {
+            try
+            {
+                State = false;
+                if (tcAdsClient != null)
+                {
+                    tcAdsClient.Disconnect();
+                    tcAdsClient.Dispose();
+                    tcAdsClient = null;
+                }
+            }
+            catch (Exception e)
+            {
+                //throw new Exception("Initads 中发生异常,尝试关闭ads连接的时候发生:" + e.Message);
+            }
+        }
+
+        /// <summary>
+        /// 从配置文件加载硬件的参数配置
+        /// </summary>
+        void LoadConfig()
+        {
+            string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ads.json");
+            if (File.Exists(path) == false)
+                throw new Exception("未找到配置文件ads.json");
+
+            try
+            {
+                FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
+                byte[] data = new byte[fs.Length];
+                fs.Read(data, 0, data.Length);
+                fs.Close();
+                string strConfig = Encoding.UTF8.GetString(data);
+                List<TcValue> ls = JsonConvert.DeserializeObject<List<TcValue>>(strConfig);
+                tcConfigs.Clear();
+                tcConfigs.AddRange(ls);
+            }
+            catch(Exception ee)
+            {
+                Console.WriteLine(ee.Message);
+            }
+        }
+
+        public bool WriteValue<T>(string name , T Data)
+        {
+            uint hvar = 0;
+            bool ok = true;
+
+            if (tcAdsClient == null || string.IsNullOrEmpty(name))
+            {
+                return false;
+            }
+
+            //从配置中找到当前参数的定义
+            TcValue item = tcConfigs.FirstOrDefault(val => val.name.Equals(name));
+            if (item == null)
+            {
+                throw new Exception($"配置文件ads.json中未找到参数:{name}");
+                //return false;
+            }
+
+            try
+            {
+                hvar = tcAdsClient.CreateVariableHandle(name);
+            }
+            catch (Exception err)
+            {
+                throw new Exception("CreateVariableHandle的时候发生异常:" + err.Message);
+            }
+           
+            try
+            {
+                switch(typeof(T).Name)
+                {
+                    case "Boolean":
+                        AdsStream datastream = new AdsStream(1);  //bool值位为1
+                        BinaryWriter binwrite = new BinaryWriter(datastream);
+                        datastream.Position = 0;
+                        binwrite.Write((bool)(object)Data);
+                        ReadOnlyMemory<byte> buffer = new ReadOnlyMemory<byte>(datastream.ToArray());
+                        tcAdsClient.Write(hvar, buffer);
+                        break;
+                    case "Byte":
+                        datastream = new AdsStream(1);
+                        binwrite = new BinaryWriter(datastream);
+                        datastream.Position = 0;
+                        binwrite.Write((byte)(object)Data);
+                        buffer = new ReadOnlyMemory<byte>(datastream.ToArray());
+                        tcAdsClient.Write(hvar, buffer);
+                        break;
+                    case "Int32":
+                        datastream = new AdsStream(4); 
+                        binwrite = new BinaryWriter(datastream);
+                        datastream.Position = 0;
+                        binwrite.Write((int)(object)Data);
+                        buffer = new ReadOnlyMemory<byte>(datastream.ToArray());
+                        tcAdsClient.Write(hvar, buffer);
+                        break;
+                   case "Single":
+                        datastream = new AdsStream(4); 
+                        binwrite = new BinaryWriter(datastream);
+                        datastream.Position = 0;
+                        binwrite.Write((float)(object)Data);
+                        buffer = new ReadOnlyMemory<byte>(datastream.ToArray());
+                        tcAdsClient.Write(hvar, buffer);
+                        break;
+                    case "Double":
+                        datastream = new AdsStream(8);  
+                        binwrite = new BinaryWriter(datastream);
+                        datastream.Position = 0;
+                        binwrite.Write((double)(object)Data);
+                        buffer = new ReadOnlyMemory<byte>(datastream.ToArray());
+                        tcAdsClient.Write(hvar, buffer);
+                        break;
+                    case "Int16":
+                        datastream = new AdsStream(2);  
+                        binwrite = new BinaryWriter(datastream);
+                        datastream.Position = 0;
+                        binwrite.Write((short)(object)Data);
+                        buffer = new ReadOnlyMemory<byte>(datastream.ToArray());
+                        tcAdsClient.Write(hvar, buffer);
+                        break;
+                    case "Byte[]":
+                        byte[] data = (byte[])(object)Data;
+                        int len = 0;
+                        //写入不超过定义的长度
+                        if (data.Length <= item.length)
+                        {
+                            len = data.Length;
+                        }
+                        else
+                        {
+                            len = item.length;
+                            data[len - 1] = 0;
+                        }
+
+                        datastream = new AdsStream(len);  
+                        binwrite = new BinaryWriter(datastream);
+                        datastream.Position = 0;
+                        binwrite.Write(data,0,len);
+                        buffer = new ReadOnlyMemory<byte>(datastream.ToArray());
+                        tcAdsClient.Write(hvar, buffer);
+                        break;
+                    case "String":
+                        string str = (string)(object)Data;
+                        byte[] bufs = Encoding.ASCII.GetBytes(str);
+                        //加一个 '\0'结尾
+                        data = new byte[bufs.Length + 1];
+                        Array.Copy(bufs,data,bufs.Length);
+                        data[data.Length - 1] = 0; // '\0'
+                        len = 0;
+                        //写入不超过定义的长度
+                        if (data.Length <= item.length)
+                        {
+                            len = data.Length;
+                        }
+                        else
+                        {
+                            len = item.length;
+                            data[len - 1] = 0;
+                        }
+
+                        datastream = new AdsStream(len);
+                        binwrite = new BinaryWriter(datastream);
+                        datastream.Position = 0;
+                        binwrite.Write(data, 0, len);
+                        buffer = new ReadOnlyMemory<byte>(datastream.ToArray());
+                        tcAdsClient.Write(hvar, buffer);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            catch (Exception err)
+            {
+                throw new Exception("Write的时候发生异常:" + err.Message);
+                ok = false;
+            }
+
+            try
+            {
+                tcAdsClient.DeleteVariableHandle(hvar);
+            }
+            catch (Exception err)
+            {
+                throw new Exception("DeleteVariableHandle的时候发生异常:" + err.Message);
+            }
+
+            return ok;
+        }
+
+        public T ReadValue<T>(string name)
+        {
+            uint hvar = 0;
+            bool ok = true;
+            T t = default(T);
+
+            if (tcAdsClient == null)
+            {
+                 throw new Exception("ADS未初始化!");
+            }
+
+            if (string.IsNullOrEmpty(name))
+            {
+                throw new Exception("参数名称name = null");
+            }
+
+            //从配置中找到当前参数的定义
+            TcValue item = tcConfigs.FirstOrDefault(val => val.name.Equals(name));
+            if (item == null)
+            {
+                throw new Exception($"配置文件ads.json中未找到参数:{name}");
+                //return false;
+            }
+
+            try
+            {
+                hvar = tcAdsClient.CreateVariableHandle(name);
+            }
+            catch (Exception err)
+            {
+                throw new Exception("CreateVariableHandle的时候发生异常:" + err.Message);
+                //return false;
+            }
+
+            try
+            {
+                switch (typeof(T).Name)
+                {
+                    case "Boolean":
+                        byte[] buffer = new byte[1];
+                        Memory<byte> buf = new Memory<byte>(buffer);
+                        int len = tcAdsClient.Read(hvar,  buf);
+                        if(len > 0)
+                        {
+                            bool val = buf.GetArray()[0] > 0;
+                            t = (T)Convert.ChangeType(val, typeof(T));
+                        }
+                        else
+                        {
+                            throw new Exception("ADS没有读到值!");
+                        }
+                        break;
+                    case "Byte":
+                        buffer = new byte[1];
+                         buf = new Memory<byte>(buffer);
+                        len = tcAdsClient.Read(hvar, buf);
+                        if (len > 0)
+                        {
+                            t = (T)Convert.ChangeType(buf.GetArray()[0], typeof(T));
+                        }
+                        else
+                        {
+                            throw new Exception("ADS没有读到值!");
+                        }
+                        break;
+                    case "Int32":
+                        buffer = new byte[4];
+                        buf = new Memory<byte>(buffer);
+                         len = tcAdsClient.Read(hvar, buf);
+                        if (len > 0)
+                        {
+                            int val  = BitConverter.ToInt32(buf.ToArray(), 0);
+                            t = (T)Convert.ChangeType(val, typeof(T));
+                        }
+                        else
+                        {
+                            throw new Exception("ADS没有读到值!");
+                        }
+                        break;
+                    case "Single":
+                        buffer = new byte[4];
+                        buf = new Memory<byte>(buffer);
+                        len = tcAdsClient.Read(hvar, buf);
+                        if (len > 0)
+                        {
+                            float val = BitConverter.ToSingle(buf.ToArray(), 0);
+                            t = (T)Convert.ChangeType(val, typeof(T));
+                        }
+                        else
+                        {
+                            throw new Exception("ADS没有读到值!");
+                        }
+                        break;
+                    case "Double":
+                        buffer = new byte[8];
+                        buf = new Memory<byte>(buffer);
+                        len = tcAdsClient.Read(hvar, buf);
+                        if (len > 0)
+                        {
+                            double val = BitConverter.ToDouble(buf.ToArray(), 0);
+                            t = (T)Convert.ChangeType(val, typeof(T));
+                        }
+                        else
+                        {
+                            throw new Exception("ADS没有读到值!");
+                        }
+                        break;
+                    case "Int16":
+                        buffer = new byte[2];
+                        buf = new Memory<byte>(buffer);
+                        len = tcAdsClient.Read(hvar, buf);
+                        if (len > 0)
+                        {
+                            short val = BitConverter.ToInt16(buf.ToArray(), 0);
+                            t = (T)Convert.ChangeType(val, typeof(T));
+                        }
+                        else
+                        {
+                            throw new Exception("ADS没有读到值!");
+                        }
+                        break;
+                    case "Byte[]":
+                        buffer = new byte[item.length];
+                        buf = new Memory<byte>(buffer);
+                        len = tcAdsClient.Read(hvar, buf);
+                        if (len > 0)
+                        {
+                            t = (T)Convert.ChangeType(buf.ToArray(), typeof(T));
+                        }
+                        else
+                        {
+                            throw new Exception("ADS没有读到值!");
+                        }
+                        break;
+                    case "String":
+                        buffer = new byte[item.length];
+                        buf = new Memory<byte>(buffer);
+                        len = tcAdsClient.Read(hvar, buf);
+                        if (len > 0)
+                        {
+                            t = (T)Convert.ChangeType(Encoding.ASCII.GetString(buf.ToArray()), typeof(T));
+                        }
+                        else
+                        {
+                            throw new Exception("ADS没有读到值!");
+                        }
+                        break;
+                    default:
+                        break;
+                }
+            }
+            catch (Exception err)
+            {
+                throw new Exception("Read的时候发生异常:" + err.Message);
+                ok = false;
+            }
+
+            try
+            {
+                tcAdsClient.DeleteVariableHandle(hvar);
+            }
+            catch (Exception err)
+            {
+                throw new Exception("DeleteVariableHandle的时候发生异常:" + err.Message);
+            }
+
+            return t;
+        }
+
+        public byte[] ReadBytes(string name,int length)
+        {
+            uint hvar = 0;
+            bool ok = true;
+            byte[] t = new byte[length];
+
+            if (tcAdsClient == null)
+            {
+                throw new Exception("ADS未初始化!");
+            }
+
+            try
+            {
+                hvar = tcAdsClient.CreateVariableHandle(name);
+            }
+            catch (Exception err)
+            {
+                throw new Exception("CreateVariableHandle的时候发生异常:" + err.Message);
+                //return false;
+            }
+
+            try
+            {
+                byte[] buffer = new byte[length];
+                Memory<byte>  buf = new Memory<byte>(buffer);
+                int len = tcAdsClient.Read(hvar, buf);
+                if (len > 0)
+                {
+                    Array.Copy(buf.ToArray(), t, len);
+                    buf = null;
+                    buffer = null;
+                }
+                else
+                {
+                    throw new Exception("ADS没有读到值!");
+                }
+            }
+            catch (Exception err)
+            {
+                throw new Exception("Read的时候发生异常:" + err.Message);
+                ok = false;
+            }
+
+            try
+            {
+                tcAdsClient.DeleteVariableHandle(hvar);
+            }
+            catch (Exception err)
+            {
+                throw new Exception("DeleteVariableHandle的时候发生异常:" + err.Message);
+            }
+
+            return t;
+        }
+
+        /// <summary>
+        /// 不知道异步会不会有问题,看这个逻辑,ADS内部应该是会处理的
+        /// </summary>
+        /// <param name="name"></param>
+        /// <param name="Data"></param>
+        /// <returns></returns>
+        bool WriteBool(string name , bool Data)
+        {
+            if(tcAdsClient == null)
+            {
+                return false;
+            }
+
+            uint hvar = 0;
+            bool ok = true;
+            try
+            {
+                hvar = tcAdsClient.CreateVariableHandle(name);
+            }
+            catch (Exception err)
+            {
+                Console.WriteLine("WriteBool中发生异常,CreateVariableHandle的时候发生:" + err.Message);
+                return false;
+            }
+            AdsStream datastream = new AdsStream(1);  //bool值位为1
+            BinaryWriter binwrite = new BinaryWriter(datastream);
+            datastream.Position = 0;
+
+            try
+            {
+                binwrite.Write(Data);
+                ReadOnlyMemory<byte> buffer = new ReadOnlyMemory<byte>(datastream.ToArray());
+                tcAdsClient.Write(hvar, buffer);
+            }
+            catch (Exception err)
+            {
+                Console.WriteLine("WriteBool中发生异常,Write的时候发生:" + err.Message);
+                ok = false;
+            }
+
+            try
+            {
+                tcAdsClient.DeleteVariableHandle(hvar);
+            }
+            catch (Exception err)
+            {
+                Console.WriteLine("WriteBool中发生异常,DeleteVariableHandle的时候发生:" + err.Message);
+            }
+
+            return ok;
+        }
+    }
+}

+ 80 - 0
ADS/TcModelConverter.cs

@@ -0,0 +1,80 @@
+using Newtonsoft.Json;
+using System.Text;
+
+namespace AmrControl.ADS
+{
+    /// <summary>
+    /// TcModel的JSON转换器
+    /// 这个是把byte[]与ASCII字符串互转
+    /// </summary>
+    public class TcModelArrayConverter : JsonConverter
+    {
+        public override bool CanConvert(Type objectType)
+        {
+            return objectType == typeof(byte[]);
+        }
+
+        /// <summary>
+        /// 把json字符串转对象
+        /// </summary>
+        /// <param name="reader"></param>
+        /// <param name="objectType"></param>
+        /// <param name="existingValue"></param>
+        /// <param name="serializer"></param>
+        /// <returns></returns>
+        /// <exception cref="NotImplementedException"></exception>
+        public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+        {
+            
+            //todo,需要评估一下长度有没有影响,在结构体中限定了长度
+            string str = (string)reader.Value;
+            if(str == null)
+            {
+                //返回一个空数组
+                //return new byte[12];
+                return null;
+            }
+            else
+            {
+                //byte[] chars = Encoding.ASCII.GetBytes(str);
+                //byte[] bytes = new byte[12];
+                //if(chars.Length <= 12)
+                //{
+                //    Array.Copy(chars, bytes, chars.Length);
+                //}
+                //else
+                //{
+                //    Array.Copy(chars, bytes, 12);
+                //}
+
+                //return bytes;
+                return Encoding.ASCII.GetBytes(str);
+            }
+        }
+
+        /// <summary>
+        /// 把对象转json字符串
+        /// </summary>
+        /// <param name="writer"></param>
+        /// <param name="Data"></param>
+        /// <param name="serializer"></param>
+        /// <exception cref="NotImplementedException"></exception>
+        public override void WriteJson(JsonWriter writer, object? Data, JsonSerializer serializer)
+        {
+            if(Data is byte[] bytes)
+            {
+                //判断非0的长度,0是字符串结尾
+                int index = 0;
+                for (int i = 0; i < bytes.Length; i++)
+                {
+                    if (bytes[index++] == 0)
+                    {
+                        break;
+                    }
+                }
+                writer.WriteValue(Encoding.ASCII.GetString(bytes,0,index).Trim());
+            }
+
+        }
+    }
+}

+ 59 - 0
ADS/TcValue.cs

@@ -0,0 +1,59 @@
+namespace AmrControl.ADS
+{
+    /// <summary>
+    /// 和EtherCAT的数据交互格式
+    /// </summary>
+    public class TcValue   //<T> where T : struct
+    {
+        /// <summary>
+        /// 变量名称,是TC程序内定义的名称
+        /// </summary>
+        public string name { get; set; }  
+        /// <summary>
+        /// 数据类型
+        /// </summary>
+        public TcValueType valueType { get; set; }
+       // public T Data { get; set; }
+        public string Data { get; set; }
+        /// <summary>
+        /// 数据长度,一版用于String类型,其他类型都是固定的
+        /// </summary>
+        public int length { get; set; }
+    }
+
+    public enum TcValueType
+    {
+        /// <summary>
+        /// Length = 1
+        /// </summary>
+        Byte,
+        /// <summary>
+        /// Length = 2
+        /// </summary>
+        Short,
+        /// <summary>
+        /// Length = 4
+        /// </summary>
+        Int,
+        /// <summary>
+        /// Length = 1
+        /// </summary>
+        Bool,
+        /// <summary>
+        /// Length = 4
+        /// </summary>
+        Float,
+        /// <summary>
+        /// Length = 8
+        /// </summary>
+        Double,
+        /// <summary>
+        /// 字节数组,长度要根据TC程序预留的空间来确定
+        /// </summary>
+        Array,
+        /// <summary>
+        /// ASCII字符串,长度要根据TC程序预留的空间来确定
+        /// </summary>
+        String
+    }
+}

+ 52 - 0
AmrControl.csproj

@@ -0,0 +1,52 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <PlatformTarget>x86</PlatformTarget>
+    <Platforms>AnyCPU;x86</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Content Remove="Properties\launchSettings - 复制.json" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <_WebToolingArtifacts Remove="Properties\launchSettings - 复制.json" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="Pages\Index.cshtml.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Include="Properties\launchSettings - 复制.json">
+      <CopyToOutputDirectory>Never</CopyToOutputDirectory>
+      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
+    </None>
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Beckhoff.TwinCAT.Ads" Version="6.0.249" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.7" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
+    <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
+    <PackageReference Include="MQTTnet.AspNetCore" Version="4.0.2.221" />
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
+    <PackageReference Include="NPOI" Version="2.6.0" />
+    <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
+    <PackageReference Include="RabbitMQ.Client" Version="6.5.0" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
+    <PackageReference Include="System.IO.Ports" Version="6.0.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Folder Include="jbc\" />
+  </ItemGroup>
+
+  <ProjectExtensions><VisualStudio><UserProperties appsettings_1development_1json__JsonSchema="https://cdn.jsdelivr.net/gh/tarampampam/error-pages@latest/schemas/config/1.0.schema.json" libman_1json__JsonSchema="" /></VisualStudio></ProjectExtensions>
+
+</Project>

+ 6 - 0
AmrControl.csproj.user

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <NameOfLastUsedPublishProfile>C:\Users\JGAI\Desktop\show\工位数据采集\Properties\PublishProfiles\FolderProfile.pubxml</NameOfLastUsedPublishProfile>
+  </PropertyGroup>
+</Project>

+ 30 - 0
AmrControl.sln

@@ -0,0 +1,30 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.32421.90
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AmrControl", "AmrControl.csproj", "{4B98A1EE-1680-4FE0-9C5B-657DB75E5F88}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{4B98A1EE-1680-4FE0-9C5B-657DB75E5F88}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{4B98A1EE-1680-4FE0-9C5B-657DB75E5F88}.Debug|x86.ActiveCfg = Debug|x86
+		{4B98A1EE-1680-4FE0-9C5B-657DB75E5F88}.Debug|x86.Build.0 = Debug|x86
+		{4B98A1EE-1680-4FE0-9C5B-657DB75E5F88}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4B98A1EE-1680-4FE0-9C5B-657DB75E5F88}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4B98A1EE-1680-4FE0-9C5B-657DB75E5F88}.Release|x86.ActiveCfg = Release|x86
+		{4B98A1EE-1680-4FE0-9C5B-657DB75E5F88}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {69DE1F7B-F8E5-46C1-8B2A-5BE70CD31438}
+	EndGlobalSection
+EndGlobal

+ 83 - 0
AppHelper.cs

@@ -0,0 +1,83 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace AmrControl
+{
+    public class AppHelper
+    {
+        private static IConfiguration _config;
+
+        public AppHelper(IConfiguration configuration)
+        {
+            _config = configuration;
+        }
+
+        /// <summary>
+        /// 读取指定节点的字符串
+        /// </summary>
+        /// <param name="sessions"></param>
+        /// <returns></returns>
+        public static string ReadAppSettings(params string[] sessions)
+        {
+            try
+            {
+                if (sessions.Any())
+                {
+                    return _config[string.Join(":", sessions)];
+                }
+            }
+            catch
+            {
+                return "";
+            }
+            return "";
+        }
+
+        /// <summary>
+        /// 读取实体信息
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="session"></param>
+        /// <returns></returns>
+        public static List<T> ReadAppSettings<T>(params string[] session)
+        {
+            List<T> list = new List<T>();
+            _config.Bind(string.Join(":", session), list);
+            return list;
+        }
+
+        public static string BytesToString(byte[] array)
+        {
+            if (array == null)
+                return "";
+
+            StringBuilder strBuilder = new StringBuilder();
+            foreach (var item in array)
+            {
+                strBuilder.Append(item.ToString() + " ");
+            }
+            return strBuilder.ToString().TrimEnd();
+        }
+
+        public static T BytesToStruct<T>(byte[] array)
+        {
+            if (array == null)
+                return default(T);
+
+            int size = Marshal.SizeOf<T>();
+            IntPtr ptr = Marshal.AllocHGlobal(size);
+            if(array.Length <= size)
+            {
+                Marshal.Copy(array,0,ptr,array.Length);
+            }
+            else
+            {
+                Marshal.Copy(array, 0, ptr, size);
+            }
+            T t = Marshal.PtrToStructure<T>(ptr);
+            Marshal.FreeHGlobal(ptr);
+            return t;
+        }
+    }
+   
+}

+ 53 - 0
Clients/DeprageElecScrewdriver.cs

@@ -0,0 +1,53 @@
+namespace AmrControl.Clients
+{
+    /// <summary>
+    /// 德派的电动起子返回的数据结构
+    /// </summary>
+    public class DeprageElecScrewdriver
+    {
+        public GeneralState General { get; set; }
+        //所有的螺丝扭力点监控数据
+        public List<StepState> Steps { get; set; }
+    }
+
+    public class GeneralState
+    {
+        public DateTime Date { get; set; }
+        public int SequenceNumber { get; set; }
+        //状态 “OK/NotOK"
+        public string State { get; set; }
+        //持续运行时间
+        public int Duration { get; set; }
+        //错误码,0为正常,>0为错误
+        public int ErrorCode { get; set; }
+        //"超时"
+        public string ErrorText { get; set; }
+    }
+
+    public class StepState
+    {
+        //步骤点
+        public int StepNumber { get; set; }
+        //扭力
+        public ValueNode Torque { get; set; }
+        //角度
+        public ValueNode Angle { get; set; }
+        //状态 “OK/NotOK"
+        public string State { get; set; }
+
+    }
+
+    public class ValueNode
+    {
+        public string Unit{get;set;}
+        public float Value { get;set; }
+        public TargetNode Target { get;set; }
+    }
+
+    public class TargetNode
+    {
+        public float Target {  get; set; }
+        public float Lower {  get; set; }
+        public float Upper {  get; set; }
+    }
+}

+ 279 - 0
Clients/MqttClient.cs

@@ -0,0 +1,279 @@
+using AmrControl.workstation;
+using MQTTnet;
+using MQTTnet.Client;
+using MQTTnet.Protocol;
+using Newtonsoft.Json;
+using SixLabors.ImageSharp.ColorSpaces.Companding;
+
+namespace AmrControl.Clients
+{
+    /// <summary>
+    /// 来回读配置文件会导致内存异常,这是目前dotnet的问题,因此不要直接引用配置文件的内容
+    /// </summary>
+    public class MqttClient
+    {
+        /// <summary>
+        /// client客户端
+        /// </summary>
+        private IMqttClient mqttClient;
+
+        /// <summary>
+        /// 连接状态
+        /// </summary>
+        private bool linkedState = false;
+        /// <summary>
+        /// 配置信息
+        /// </summary>
+        private readonly IConfiguration _configuration;
+        /// <summary>
+        /// 服务获取
+        /// </summary>
+        private readonly IServiceProvider _service;
+       // protected readonly Microsoft.Extensions.Logging.ILogger _logger;
+        private bool reConnTaskStarted = false;
+
+        private object obj = new object();
+
+        string wsid, send_topic, sub_topic;
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="configuration"></param>
+        /// <param name="service"></param>
+        public MqttClient(IConfiguration configuration, IServiceProvider service)
+        {
+            this._configuration = configuration;
+            this._service = service;
+            wsid = _configuration["Mqtt:id"]; //用户标识ID
+            send_topic = _configuration["Mqtt:send-topic"]; //用户标识ID
+            sub_topic = _configuration["Mqtt:sub-topic"]; //用户标识ID
+        }
+        /// <summary>
+        /// 构建客户端
+        /// </summary>
+        public void CreateClient()
+        {
+            try
+            {
+                if (linkedState && mqttClient != null && mqttClient.IsConnected)
+                {
+                    //启动并且连接成功的服务不再考虑进行连接
+                    return;
+                }
+                if(mqttClient != null)
+                {
+                    mqttClient.Dispose();
+                }
+                MqttClientOptions Option = new MqttClientOptionsBuilder().WithTcpServer(_configuration["Mqtt:url"], int.Parse(_configuration["Mqtt:port"]))
+                .WithClientId(wsid) //客户端标识Id要唯一。
+                //.WithCredentials(_configuration["Mqtt:username"], _configuration["Mqtt:password"]) //用户名,密码
+                .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
+                .WithCleanSession(true)
+                .WithWillQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce)
+                .WithTimeout(new TimeSpan(0, 0, 10))
+                .WithKeepAlivePeriod(TimeSpan.FromSeconds(20))
+                .Build();
+
+                mqttClient = new MqttFactory().CreateMqttClient();
+                mqttClient.ApplicationMessageReceivedAsync += ReceivedHandle;
+                mqttClient.ConnectedAsync += ConnectedHandle;
+                mqttClient.DisconnectedAsync += DisconnectedHandle;
+
+                //调用异步方法连接到服务端
+                mqttClient.ConnectAsync(Option).Wait();
+
+                //订阅主题
+                SubTopics(sub_topic);
+
+            }
+            catch (Exception ex)
+            {
+               // _logger.LogError($"mqttclient connect failed. the cId {CId}, the error: {ex.Message}");
+                linkedState = false;
+            }
+
+        }
+        /// <summary>
+        /// 订阅主题
+        /// </summary>
+        public async void SubTopics(string topic)
+        {
+            topic = topic.Trim();
+            if (string.IsNullOrEmpty(topic))
+            {
+               // _logger.LogWarning("订阅主题不能为空!");
+                return;
+            }
+            topic = string.Format(topic, wsid);
+            // 判断客户端是否连接
+            if (mqttClient == null || !mqttClient.IsConnected)
+            {
+                //_logger.LogWarning("MQTT 客户端尚未连接!");
+                return;
+            }
+
+            // 设置订阅参数
+            var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
+                .WithTopicFilter(topic, MqttQualityOfServiceLevel.ExactlyOnce)
+                .Build();
+            try
+            {
+                // 订阅
+                await mqttClient.SubscribeAsync(
+                        subscribeOptions,
+                        System.Threading.CancellationToken.None);
+                // _logger.LogInformation("监听主题:" + topic);
+                Console.WriteLine("监听主题:" + topic);
+            }
+            catch (Exception e)
+            {
+                // _logger.LogError($"消息监听失败.监听主题:{topic}, 失败原因:{e.Message}");
+                Console.WriteLine($"消息监听失败.监听主题:{topic}, 失败原因:{e.Message}");
+            }
+
+        }
+        /// <summary>
+        /// 处理接收到的消息
+        /// </summary>
+        /// <param name="arg"></param>
+        /// <returns></returns>
+        private Task ReceivedHandle(MqttApplicationMessageReceivedEventArgs arg)
+        {
+            ///收到消息
+            string content = System.Text.Encoding.UTF8.GetString(arg.ApplicationMessage.Payload);
+            //_logger.LogDebug("接收消息:" + content);
+            MsWorkstation ws = _service.GetRequiredService<MsWorkstation>();
+            if(ws != null)
+            {
+                ws.PushMqMessage(arg.ApplicationMessage.Topic, content); 
+            }
+            return Task.CompletedTask;
+        }
+
+        /// <summary>
+        /// 连接丢失处理
+        /// </summary>
+        /// <param name="arg"></param>
+        /// <returns></returns>
+        private Task DisconnectedHandle(MqttClientDisconnectedEventArgs arg)
+        {
+            //_logger.LogWarning("MQTT发生断线重联!");
+            Thread.Sleep(2000);
+            reConnect();
+            return Task.CompletedTask;
+        }
+
+        public async void reConnect()
+        {
+            lock (obj)
+            {
+                if (reConnTaskStarted)
+                    return ;
+
+                //防止多个线程执行
+                reConnTaskStarted = true;
+
+                if (mqttClient != null)
+                {
+                    mqttClient.Dispose();
+                }
+                mqttClient = null;
+            }
+
+            try
+            {
+                linkedState = false;
+                //重连服务
+                CreateClient();
+                reConnTaskStarted = false;
+            }
+            catch
+            {
+
+            }
+
+            return ;
+        }
+
+        /// <summary>
+        /// 连接处理
+        /// </summary>
+        /// <param name="arg"></param>
+        /// <returns></returns>
+        private Task ConnectedHandle(MqttClientConnectedEventArgs arg)
+        {
+            linkedState = true;
+            return Task.CompletedTask;
+        }
+
+        public bool SendToMqtt(string msg)
+        {
+            return SendToMqtt(send_topic, msg);
+        }
+
+        public bool SendToMqtt(string topic, string msg)
+        {
+            try
+            {
+                if (mqttClient == null)
+                    return false;
+
+                if (linkedState || mqttClient.IsConnected)
+                {
+                    //发布消息
+                    ClientPublish(topic, msg);
+                    return true;
+                }
+                else
+                {
+                    return false;
+                }
+            }
+            catch
+            {
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送消息
+        /// </summary>
+        /// <param name="topic"></param>
+        /// <param name="message"></param>
+        private async void ClientPublish(string topic, string message)
+        {
+            topic = topic.Trim();
+            message = message.Trim();
+
+            if (string.IsNullOrEmpty(topic))
+            {
+                // _logger.LogWarning("发送主题不能为空!");
+                return;
+            }
+
+            // 判断客户端是否连接
+            if (!linkedState || mqttClient == null || !mqttClient.IsConnected)
+            {
+                //_logger.LogWarning("MQTT 客户端尚未连接!");
+                return;
+            }
+            // _logger.LogDebug("发送消息:" + message);
+            // 填充消息
+            var applicationMessage = new MqttApplicationMessageBuilder()
+                .WithTopic(topic)       // 主题
+                .WithPayload(message)   // 消息
+                .WithRetainFlag(false)       // 持久化为空,不持久化
+                .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce)
+                .Build();
+
+            try
+            {
+                await mqttClient.PublishAsync(applicationMessage);
+            }
+            catch (Exception)
+            {
+            }
+
+        }
+
+    }
+}

+ 11 - 0
Clients/WsCmd.cs

@@ -0,0 +1,11 @@
+namespace AmrControl.Clients
+{
+    public class WsCmd
+    {
+        public string StationId {  get; set; }
+        /// <summary>
+        /// start / stop
+        /// </summary>
+        public string Cmd {  get; set; }
+    }
+}

+ 166 - 0
Clients/WsState.cs

@@ -0,0 +1,166 @@
+namespace AmrControl.Clients
+{
+    public class WsState
+    {
+        /// <summary>
+        /// 工位id
+        /// </summary>
+        public string wsid { get; set; } = "id001";
+        /// <summary>
+        /// 三色灯关 绿 黄 红:
+        /// off green yellow red
+        /// </summary>
+        public string triCokourLight { get; set; }
+        /// <summary>
+        /// 烟雾净化器开关:
+        /// on  off
+        /// </summary>
+        public string smokePurifier { get; set; }
+        /// <summary>
+        /// 人体感知有无人
+        /// true  false
+        /// </summary>
+        public string bodySensor { get; set; }
+        /// <summary>
+        /// 防静电手环未接入,配套,未佩戴
+        /// off wear uwear
+        /// </summary>
+        public string wristStrap { get; set; }
+
+        /// <summary>
+        /// 电源状态
+        /// </summary>
+        public WsPower power { get; set; }
+        /// <summary>
+        /// 工位电烙铁
+        /// </summary>
+        public  WsElectricIron electricIron { get; set; }
+        /// <summary>
+        /// 电动起子
+        /// </summary>
+        public WsElectricScrewdriver electricScrewdriver { get; set; }
+        /// <summary>
+        /// 助力臂
+        /// </summary>
+        public WsBalanceArm balanceArm { get; set; }
+
+        public WsState() 
+        {
+            power = new WsPower();
+            electricIron = new WsElectricIron();
+            electricScrewdriver = new WsElectricScrewdriver();
+            balanceArm = new WsBalanceArm();
+        }
+    }
+
+    /// <summary>
+    /// 电动起子
+    /// </summary>
+    public class WsElectricScrewdriver
+    {
+        /// <summary>
+        /// 在线状态
+        /// on  off
+        /// </summary>
+        public string state { get; set; }
+        /// <summary>
+        /// 当前扭力
+        /// </summary>
+        public string torqueForce { get; set; }
+        /// <summary>
+        /// 扭力曲线,用逗号来间隔数据
+        /// </summary>
+        public string tfArray { get; set; }
+        /// <summary>
+        /// 设定的扭力上下限
+        /// </summary>
+        public string lowTorqueForce { get; set; }
+        public string highTorqueForce { get; set; }
+        /// <summary>
+        /// 转动角度
+        /// </summary>
+        public string angle { get; set; }
+        /// <summary>
+        /// 是否有警告 true/false
+        /// </summary>
+        public string warning { get; set; }
+    }
+
+    /// <summary>
+    /// 工位电烙铁
+    /// </summary>
+    public class WsElectricIron
+    {
+        /// <summary>
+        /// 在线状态
+        /// on  off
+        /// </summary>
+        public string state { get; set; }
+        /// <summary>
+        /// 设置值
+        /// </summary>
+        public string setTemperature { get; set; }
+        /// <summary>
+        /// 当前温度
+        /// </summary>
+        public string temperature { get; set; }
+        /// <summary>
+        /// 设定的温度上下限
+        /// </summary>
+        public string lowTemp { get; set; }
+        public string highTemp { get; set; }
+        /// <summary>
+        /// 是否有警告, true/false
+        /// </summary>
+        public string warning { get; set; }
+    }
+
+    /// <summary>
+    /// 工位电源
+    /// </summary>
+    public class WsPower
+    {
+        /// <summary>
+        /// 电源状态开 关:
+        /// on  off
+        /// </summary>
+        public string state { get; set; }
+        /// <summary>
+        /// A/B/C相的电压、电流、功率
+        /// </summary>
+        public string voltA { get; set; }
+        public string currA { get; set; }
+        public string powerA { get; set; }
+
+        public string voltB { get; set; }
+        public string currB { get; set; }
+        public string powerB { get; set; }
+
+        public string voltC { get; set; }
+        public string currC { get; set; }
+        public string powerC { get; set; }
+    }
+
+    /// <summary>
+    /// 平衡臂
+    /// </summary>
+    public class WsBalanceArm
+    {
+        /// <summary>
+        /// 在线状态
+        /// on  off
+        /// </summary>
+        public string state { get; set; }
+
+        /// <summary>
+        /// 角度A
+        /// </summary>
+        public string angleA { get; set; }
+        /// <summary>
+        /// 角度B
+        /// </summary>
+        public string angleB { get; set; }
+    }
+
+    
+}

+ 479 - 0
Common/AmrManager.cs

@@ -0,0 +1,479 @@
+using Microsoft.AspNetCore.SignalR;
+using MQTTnet;
+using MQTTnet.Client;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System.Text;
+
+namespace AmrControl.Common
+{
+    /// <summary>
+    /// AMR车的控制器,监听Zigbee的消息,转发mqtt消息
+    /// 在系统中是单例实现
+    /// </summary>
+    public class AmrManager : IAmrManager,IDisposable
+    {
+        public static bool ProcessExit = false;
+
+        public bool LinkState { get; private set; }
+
+        public int AmrOnlineCount { get; private set; }
+
+        private readonly IHubContext<ChatHub> hubContext;
+
+        MqttFactory Factory = null;
+        IMqttClient mqttClient = null;
+        string subscribeTopic = "device/agv-control/handle";
+        string publicTopic = "srv/device/handle";
+        IsoDateTimeConverter timeConverter;
+        RS485 rs485;
+
+        public AmrManager(IHubContext<ChatHub> context)
+        {
+            LinkState = false;
+            AmrOnlineCount = 0;
+            hubContext = context;
+
+            timeConverter = new IsoDateTimeConverter();
+            //这里使用自定义日期格式,如果不使用的话,默认是ISO8601格式  
+            timeConverter.DateTimeFormat = "yyyy-MM-dd HH:mm:ss";
+
+            rs485 = new RS485();
+            //四种事件使用的mqtt主题是一样的,可以改动消息类型来适应
+            rs485.ReceiveCarStateCmdEvent += Rs485_ReceiveCarStateCmdEvent;
+            rs485.CarConnectedEvent += Rs485_CarConnectedEvent;
+            rs485.CarDisconnectedEvent += Rs485_CarDisconnectedEvent;
+            rs485.CarArrivedEvent += Rs485_CarArrivedEvent;
+            //模拟一个数据
+            //cars.Add(new CarStateCmd() { carId="0xfe01", loading =true ,msgType = MessageType.MSG_TYPE_CAR_REPORT, state = CarState.Moving, type = CarType.AMR, speed="0.98", powerLevel="90%", curPos= new Position(0,0,0,"0"),destPos= new Position(10,22,33, "0") });
+           // cars.Add(new CarStateCmd() { carId="0xfe02", loading =true ,msgType = MessageType.MSG_TYPE_CAR_REPORT, state = CarState.Moving, type = CarType.AMR, speed="1.8", powerLevel="85%", curPos= new Position(1,1,1,"0"),destPos= new Position(20,40,60, "0") });
+            //AmrOnlineCount = 2;
+        }
+        
+        //车到达某个指定位置的消息
+        private void Rs485_CarArrivedEvent(CarStateCmd carState)
+        {
+            string str = encodeCarStateCmd(publicTopic, carState);
+            if (str != "")
+            {
+                ClientPublish(publicTopic, str);
+            }
+        }
+
+        //车上线的消息
+        private void Rs485_CarDisconnectedEvent(CarStateCmd carState)
+        {
+            AmrOnlineCount = rs485.CarCount;
+            string str = encodeCarStateCmd(publicTopic, carState);
+            if (str != "")
+            {
+                ClientPublish(publicTopic, str);
+            }
+        }
+
+        //车下线的消息
+        private void Rs485_CarConnectedEvent(CarStateCmd carState)
+        {
+            AmrOnlineCount = rs485.CarCount;
+            string str = encodeCarStateCmd(publicTopic, carState);
+            if (str != "")
+            {
+                ClientPublish(publicTopic, str);
+            }
+        }
+
+        //车定时上报状态的消息
+        private void Rs485_ReceiveCarStateCmdEvent(CarStateCmd carState)
+        {
+            string str = encodeCarStateCmd(publicTopic, carState);
+            if (str != "")
+            {
+                ClientPublish(publicTopic, str);
+            }
+        }
+
+        public bool ResetSystem()
+        {
+            bool ok = CreateMqttClient();
+            if (rs485 != null)
+            {
+                rs485.Stop();
+            }
+
+            rs485.Start();
+
+            if (hubContext != null)
+            {
+                if(ok)
+                {
+                    ChatHubMsg("AmrManager", "mqttclient conntcted success.");
+                    //创建心跳上报的程序,定期上报设备的状态
+                    //CreateHeartBeat();
+                   // encodeCarStateCmd(publicTopic, cars[0]);
+                }
+                else
+                {
+                    ChatHubMsg("AmrManager", "mqttclient conntcted fail.");
+                }
+              
+            }
+            return ok;
+        }
+
+        public IList<AmrMessage> GetAmrMessage()
+        {
+           List<AmrMessage> messages = new List<AmrMessage>();
+            messages.Add(new AmrMessage() { Topic = "amrtopic", Time = DateTime.Now, Message = "amr message" });
+            return messages;
+        }
+
+        public IList<MqMessage> GetMqMessages()
+        {
+            List<MqMessage> messages = new List<MqMessage>();
+            messages.Add(new MqMessage() { Topic = "mqtopic", Time = DateTime.Now, Message = "mq message" });
+            return messages;
+        }
+
+
+        public bool GetLinkState()
+        {
+            return LinkState;
+        }
+        public int GetAmrOnlineCount()
+        {
+            return AmrOnlineCount;
+        }
+
+        public bool SendToMqtt(string topic, string msg)
+        {
+            if(mqttClient.IsConnected)
+            {
+                ClientPublish(topic, msg);
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        bool CreateMqttClient()
+        {
+            try
+            {
+                if(mqttClient != null)
+                {
+                    mqttClient.Dispose();
+                }
+
+                string CId = "AmrManager"; //用户标识ID
+                string userName = "jg-admin"; //用户名
+                string passWord = "jg-admin@2022"; //密码
+
+                MqttClientOptions Option = new MqttClientOptionsBuilder().WithTcpServer("192.168.1.4", 1883)   // ("192.168.1.10", 1883)//地址端口号
+                .WithClientId(CId) //客户端标识Id要唯一。
+                .WithCredentials(userName, passWord) //用户名,密码
+                .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
+                .WithCleanSession(false)
+                .WithWillQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce)
+                .WithTimeout(new TimeSpan(0, 0, 10))
+                .WithKeepAlivePeriod(TimeSpan.FromSeconds(20))
+                .Build();
+
+                Factory = new MqttFactory();
+                 mqttClient = Factory.CreateMqttClient();
+                mqttClient.ApplicationMessageReceivedAsync += MqttClient_ApplicationMessageReceivedAsync;
+                mqttClient.ConnectedAsync += MqttClient_ConnectedAsync;
+                mqttClient.DisconnectedAsync += MqttClient_DisconnectedAsync;
+               
+                //调用异步方法连接到服务端
+                mqttClient.ConnectAsync(Option).Wait();
+                
+                //订阅主题
+                ClientSubscribeTopic(subscribeTopic);
+
+                return mqttClient.IsConnected;
+            }
+            catch (Exception ex)
+            {
+                ChatHubMsg("AmrManager", "mqttclient connect failed.");
+                LinkState = false;
+                return false;
+            }
+        }
+        
+        //失去连接
+        private Task MqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg)
+        {
+            if(mqttClient != null)
+            {
+                mqttClient.Dispose();
+            }
+
+            mqttClient = null;
+            LinkState = false;
+
+            //新建线程定时连接,连接成功,LinkState= true
+            Task.Run(() => { 
+              while(LinkState == false)
+                {
+                    Thread.Sleep(10000);
+                    CreateMqttClient();
+                }
+            });
+            return Task.CompletedTask;
+        }
+
+        //已经连接
+        private Task MqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg)
+        {
+            ChatHubMsg("AmrManager", "mqttclient connect success.");
+            LinkState = true;
+            return Task.CompletedTask;
+        }
+
+        //接收到消息
+        private Task MqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg)
+        {
+            ///收到消息
+            string topic = arg.ApplicationMessage.Topic;
+            string content = System.Text.Encoding.UTF8.GetString(arg.ApplicationMessage.Payload);
+            //转到界面上显示
+            if(topic == "device/agv-control/handle")
+            {
+                decodeMsg(topic,content);
+            }
+            else
+            {
+                 ChatHubMsg(topic , "不识别的主题");
+            }
+
+            return Task.CompletedTask;
+        }
+
+        //mqtt解码为消息对象
+        void decodeMsg(string topic,string s)
+        {
+            try
+            {
+                int index = s.IndexOf('.');
+                string substr = s.Substring(index + 1);
+                string headStr = s.Substring(0, index);
+                byte[] bytes = Convert.FromBase64String(substr);
+                byte[] headBytes = Convert.FromBase64String(headStr);
+                short msgType = (short)((short)(headBytes[3] << 8) + headBytes[4]);
+                MessageType type = (MessageType)Enum.Parse(typeof(MessageType), Enum.GetName(typeof(MessageType), msgType));
+                switch(type)
+                {
+                    case MessageType.MSG_TYPE_CALL_CAR:
+                        CallCarCmd cmdCall = JsonConvert.DeserializeObject<CallCarCmd>(Encoding.UTF8.GetString(bytes));
+                        cmdCall.msgType = MessageType.MSG_TYPE_CALL_CAR;
+                        rs485.SendCarCmd(cmdCall);
+                        break;
+                    case MessageType.MSG_TYPE_STOP_CAR:
+                        StopCarCmd cmdStop = JsonConvert.DeserializeObject<StopCarCmd>(Encoding.UTF8.GetString(bytes));
+                        cmdStop.msgType = MessageType.MSG_TYPE_STOP_CAR;
+                        rs485.SendCarStop(cmdStop);
+                        break;
+                    case MessageType.MSG_TYPE_ENABLE_CAR:
+                        EnableCarCmd cmdEnable = JsonConvert.DeserializeObject<EnableCarCmd>(Encoding.UTF8.GetString(bytes));
+                        cmdEnable.msgType = MessageType.MSG_TYPE_ENABLE_CAR;
+                        rs485.SendCarEnable(cmdEnable);
+                        break;
+                    case MessageType.MSG_TYPE_DISABLE_CAR:
+                        DisableCarCmd cmdDisable = JsonConvert.DeserializeObject<DisableCarCmd>(Encoding.UTF8.GetString(bytes));
+                        cmdDisable.msgType = MessageType.MSG_TYPE_DISABLE_CAR;
+                        rs485.SendCarDisable(cmdDisable);
+                        break;
+                    case MessageType.MSG_TYPE_CAR_SHOW:
+                        //显示图片
+                        CarShowCmd cmdCarshow = JsonConvert.DeserializeObject<CarShowCmd>(Encoding.UTF8.GetString(bytes));
+                        cmdCarshow.msgType = MessageType.MSG_TYPE_CAR_SHOW;
+                        rs485.SendCarShowImage(cmdCarshow);
+                        break;
+                    default:
+                        break;
+                }
+                //转换回字符串
+                //Encoding.UTF8.GetString(bytes)
+            }
+            catch (Exception ex)
+            {
+                ChatHubMsg(topic, "解码失败!");
+            }
+        }
+
+        //消息对象编码为mqtt消息
+        string encodeCarStateCmd(string topic , CarStateCmd carState)
+        {
+            try
+            {
+                string msg = JsonConvert.SerializeObject(carState, Formatting.Indented, timeConverter);
+                string strMsg = Convert.ToBase64String(Encoding.UTF8.GetBytes(msg));
+
+                byte[] headBytes = new byte[7];
+                short msgType = (short)carState.msgType;
+                headBytes[0] = 0x09;
+                headBytes[1] = 0x12;
+                headBytes[2] = 1; //软件版本
+                headBytes[3] = (byte)((msgType >> 8) & 0xff); //消息类型
+                headBytes[4] = (byte)((msgType) & 0xff);
+                headBytes[5] = 1;//加密类型
+                headBytes[6] = 1;//客户端
+                string strHead = Convert.ToBase64String(headBytes);
+
+                return strHead + "." + strMsg;
+            }
+            catch (Exception ex)
+            {
+                ChatHubMsg(topic, "编码失败!");
+                return "";
+            }
+        }
+
+        //消息发送到界面上
+        private bool ChatHubMsg(string topic , string msg)
+        {
+            if (hubContext != null)
+            {
+                hubContext.Clients.All.SendAsync("ReceiveMessage", topic, msg); //SendMessage("ArmMessage", "reset system");
+            }
+            return true;
+        }
+
+        //订阅主题
+        private async void ClientSubscribeTopic(string topic)
+        {
+            topic = topic.Trim();
+            if (string.IsNullOrEmpty(topic))
+            {
+                //Console.Write("订阅主题不能为空!");
+                return;
+            }
+
+            // 判断客户端是否连接
+            if (mqttClient != null && !mqttClient.IsConnected)
+            {
+                //Console.WriteLine("MQTT 客户端尚未连接!");
+                return;
+            }
+
+            // 设置订阅参数
+            var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
+                .WithTopicFilter(topic)
+                .Build();
+
+            try
+            {
+                // 订阅
+                await mqttClient.SubscribeAsync(
+                        subscribeOptions,
+                        System.Threading.CancellationToken.None);
+            }
+            catch (Exception ex)
+            {
+            }
+        }
+
+        private async void ClientUnsubscribeTopic(string topic)
+        {
+            topic = topic.Trim();
+            if (string.IsNullOrEmpty(topic))
+            {
+               // Console.Write("退订主题不能为空!");
+                return;
+            }
+
+            // 判断客户端是否连接
+            if (!mqttClient.IsConnected)
+            {
+              //  Console.WriteLine("MQTT 客户端尚未连接!");
+                return;
+            }
+
+            // 设置订阅参数
+            var subscribeOptions = new MqttClientUnsubscribeOptionsBuilder()
+                .WithTopicFilter(topic)
+                .Build();
+
+            try
+            {
+                // 退订
+                await mqttClient.UnsubscribeAsync(
+                        subscribeOptions,
+                        System.Threading.CancellationToken.None);
+            }
+            catch (Exception ex)
+            {
+            }
+
+        }
+
+        private async void ClientPublish(string topic, string message)
+        {
+            topic = topic.Trim();
+            message = message.Trim();
+
+            if (string.IsNullOrEmpty(topic))
+            {
+               // Console.Write("退订主题不能为空!");
+                return ;
+            }
+
+            // 判断客户端是否连接
+            if (mqttClient == null || !mqttClient.IsConnected)
+            {
+               // Console.WriteLine("MQTT 客户端尚未连接!");
+                return ;
+            }
+
+            // 填充消息
+            var applicationMessage = new MqttApplicationMessageBuilder()
+                .WithTopic(topic)       // 主题
+                .WithPayload(message)   // 消息
+                .WithRetainFlag(false)       // 持久化为空,不持久化
+                .Build();
+
+            try
+            {
+                await mqttClient.PublishAsync(applicationMessage);
+            }
+            catch (Exception ex)
+            {
+            }
+
+        }
+
+        public void Dispose()
+        {
+            if (rs485 != null)
+            {
+                rs485.Dispose();
+            }
+
+            ClientUnsubscribeTopic(subscribeTopic);
+            mqttClient.DisconnectAsync();
+            mqttClient.Dispose();
+        }
+    }
+
+    public interface IAmrManager
+    {
+        bool LinkState { get; }
+        int AmrOnlineCount { get; }
+        bool ResetSystem();
+
+        //msg和mqtt上序列化得消息内容一致
+        // bool SendToAmr(string topic,string msg);
+
+        //msg和mqtt上序列化得消息内容一致
+        bool SendToMqtt(string topic, string msg);
+
+        IList<MqMessage> GetMqMessages();
+
+        IList<AmrMessage> GetAmrMessage();
+
+        bool GetLinkState();
+        int GetAmrOnlineCount();
+    }
+}

+ 12 - 0
Common/AmrMessage.cs

@@ -0,0 +1,12 @@
+namespace AmrControl.Common
+{
+    /// <summary>
+    /// 车辆监控器的实时数据
+    /// </summary>
+    public class AmrMessage
+    {
+        public string Topic { get; set; }
+        public DateTime Time { get; set; }
+        public string Message { get; set; }
+    }
+}

+ 52 - 0
Common/CRC16.cs

@@ -0,0 +1,52 @@
+namespace AmrControl.Common
+{
+    public class CRCHelper
+    {
+        #region CRC16
+        public static byte[] CRC16(byte[] data)
+        {
+            int len = data.Length;
+            if (len > 0)
+            {
+                ushort crc = 0xFFFF;
+
+                for (int i = 0; i < len; i++)
+                {
+                    crc = (ushort)(crc ^ (data[i]));
+                    for (int j = 0; j < 8; j++)
+                    {
+                        crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
+                    }
+                }
+                byte hi = (byte)((crc & 0xFF00) >> 8); //高位置
+                byte lo = (byte)(crc & 0x00FF); //低位置
+
+                return new byte[] { hi, lo };
+            }
+            return new byte[] { 0, 0 };
+        }
+
+        public static byte[] CRC16(byte[] data, int startIndex, int len)
+        {
+            if (len > 0 && data != null)
+            {
+                ushort crc = 0xFFFF;
+
+                for (int i = startIndex; i < startIndex + len && i < data.Length; i++)
+                {
+                    crc = (ushort)(crc ^ (data[i]));
+                    for (int j = 0; j < 8; j++)
+                    {
+                        crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
+                    }
+                }
+                byte hi = (byte)((crc & 0xFF00) >> 8); //高位置
+                byte lo = (byte)(crc & 0x00FF); //低位置
+
+                return new byte[] { hi, lo };
+            }
+            return new byte[] { 0, 0 };
+        }
+        #endregion
+    }
+}

+ 380 - 0
Common/CarCmd.cs

@@ -0,0 +1,380 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace AmrControl.Common
+{
+	[JsonObject]
+	public class MessagePacket
+	{
+		static int msgID = 1;
+
+		/**
+		 * 消息ID,建议使用雪花或者uuid之类的唯一id
+		 * 若没有手动设置,消息发送端自动生成,大家想用string传值
+		 */
+		//[JsonProperty(PropertyName = "newName")]
+		//[JsonConverter(typeof(IntStringConverter))]
+		public string id;
+		/**
+		 * 终端id,连接时自定义,设备端建议同设备id,若工控机为主要控制端,即为工控机id
+		 */
+		public string clientId;
+		/**
+		 * 设备id
+		 */
+		public string deviceId;
+
+		/**
+		 * 消息体摘要,防止消息篡改
+		 */
+		public string md5;
+		/**
+		 * 加密算法,可为空,默认不加密
+		 * 所有加密类型必须为软件维护的具体参见EncryptAlgEnum
+		 */
+		[JsonConverter(typeof(StringEnumConverter))]
+		public EncryptAlgEnum encryptType;
+		/**
+		 * 客户端类型
+		 * 不作为传输使用
+		 */
+		public byte clientType;
+		/**
+		 * 源消息id,用于响应请求
+		 */
+		public string sourceId;
+		/**
+		 * 附带消息
+		 */
+		public Dictionary<string, string> attachments;
+		/**
+		 * 发送时间
+		 */
+		public DateTime sendTime;
+
+		public MessagePacket()
+        {
+			id = msgID.ToString();
+			msgID = (msgID + 1) % int.MaxValue;
+
+			clientId = "AmrManager";
+			attachments = new Dictionary<string, string>();
+			sendTime = DateTime.Now;
+			encryptType = EncryptAlgEnum.NOT;
+		}
+	}
+
+	public enum EncryptAlgEnum : byte
+	{
+		/**
+		 * 不加密
+		 */
+		NOT = 1,
+		/**
+		 * aes对称加密算法
+		 */
+		AES128 = 2,
+	}
+
+	//车辆控制相关
+	public class CallCarCmd : MessagePacket
+	{
+		/**
+		 * 小车id
+		 */
+		public string carId;
+		/**
+		 * 小车类型;0-agv;1-amr
+		 */
+		public CarType type;
+		/**
+		 * 目标位置
+		 */
+		public Position destPos;
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#msgType()
+		 */
+		public MessageType msgType;
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#md5Str()
+		 */
+		public string md5Str;
+	}
+
+	public class StopCarCmd : MessagePacket
+	{
+		/**
+		 * 小车id
+		 */
+		public string carId;
+		/**
+		 * 小车类型;0-agv;1-amr
+		 */
+		public CarType type;
+
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#msgType()
+		 */
+		public MessageType msgType;
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#md5Str()
+		 */
+		public string md5Str;
+
+	}
+
+	public class EnableCarCmd : MessagePacket
+	{
+		/**
+		 * 小车id
+		 */
+		public string carId;
+		/**
+		 * 小车类型;0-agv;1-amr
+		 */
+		public CarType type;
+
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#msgType()
+		 */
+		public MessageType msgType;
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#md5Str()
+		 */
+		public string md5Str;
+
+	}
+
+	public class DisableCarCmd : MessagePacket
+	{
+		/**
+		 * 小车id
+		 */
+		public string carId;
+		/**
+		 * 小车类型;0-agv;1-amr
+		 */
+		public CarType type;
+
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#msgType()
+		 */
+		public MessageType msgType;
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#md5Str()
+		 */
+		public string md5Str;
+
+	}
+
+	public class CarShowCmd : MessagePacket
+	{
+		/**
+		 * 小车id
+		 */
+		public string carId;
+		/**
+		 * 小车类型;0-agv;1-amr
+		 */
+		public CarType type;
+		/**
+		 * 通道id集合,2个字节,车上有两块屏
+		 */
+		public byte[] channelIds;
+		/**
+		 * 图片代码集合,二者长度必须一致,2个字节,车上有两块屏
+		 */
+		public byte[] picCodes;
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#msgType()
+		 */
+		public MessageType msgType;
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#md5Str()
+		 */
+		public string md5Str;
+	}
+
+	[JsonObject]
+	public class CarStateCmd : MessagePacket
+	{
+		//内部使用的一个id号,也算是车的地址,以0xfe01,0xfe02,0xfe03,0xfe04等地址
+		[JsonIgnore]
+		internal int iCarID;
+		/**
+		 * 小车id
+		 */
+		public string carId;
+		/**
+		 * 小车类型;0-agv;1-amr
+		 */
+		public CarType type;
+		/**
+		 * 电量
+		 */
+		public string powerLevel;
+		/**
+		  * 车辆状态;
+		  * 1-运动状态
+		  * 2-停止状态
+		  * 3-中断状态
+		  * 4-故障状态
+		  * 5-到位状态
+		  */
+		public CarState state;
+		/**
+		 * 当前是否载货, 默认false
+		 */
+		public bool loading;
+		/**
+		 * 当前是否使能, 默认false
+		 */
+		public bool isEnable;
+		/**
+		 * 速度;m/s
+		 */
+		public string speed;
+		/**
+		 * 当前位置
+		 */
+		public Position curPos;
+		/**
+		 * 目标位置
+		 */
+		public Position destPos;
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#msgType()
+		 */
+		public MessageType msgType;
+
+		/* (non-Javadoc)
+		 * @see com.jg.network.common.requests.ICmd#md5Str()
+		 */
+		public string md5Str;
+
+	}
+
+	public enum CarType : byte
+    {
+		AMR = 0,
+		AGV = 1,
+    }
+
+	public enum MessageType : int
+	{
+		/**
+		  * ping 消息
+		  */
+		MSG_TYPE_PING = 1,
+
+		/**
+		 * pong 消息
+		 */
+		MSG_TYPE_PONG = 10001,
+
+		/**
+		 * 请求授权消息
+		 */
+		MSG_TYPE_LOGIN = 2,
+		/**
+		 * 小车上报状态信息
+		 */
+		MSG_TYPE_CAR_REPORT = 3,
+
+		/**
+		 * 呼叫小车
+		 */
+		MSG_TYPE_CALL_CAR = 4,
+
+		/**
+		 * 停止小车命令
+		 */
+		MSG_TYPE_STOP_CAR = 5,
+
+			/**
+	  * 小车使能
+	  */
+	 MSG_TYPE_ENABLE_CAR = 6,
+	 /**
+	  * 小车停止使能
+	  */
+	 MSG_TYPE_DISABLE_CAR = 7,
+
+		/**
+		 * 机械臂状态上报
+		 */
+		MSG_TYPE_ROBOT_STATE_REPORT = 8,
+		/**
+  * 通知小车展示图片
+  */
+  MSG_TYPE_CAR_SHOW = 9,
+
+  //车上线
+		MSG_TYPE_CAR_Connected = 10,
+		//车下线
+		MSG_TYPE_CAR_Disconnected = 11,
+		//车到达
+		MSG_TYPE_CAR_Arrived = 12,
+
+
+		/**
+		 * 登录返回消息类型
+		 * 此处规定所有返回消息均为原请求消息的类型前面+1,消息保留4位防止正常消息不足
+		 */
+		MSG_TYPE_LOGIN_RESULT = 10002,
+
+		/**
+		 * 错误响应消息类型
+		 */
+		MSG_TYPE_ERR_RESULT = -1,
+
+		/**
+		 * 通用消息回执类型
+		 */
+		MSG_TYPE_RETURN_RESULT = 0,
+		/**
+		 * 停止小车后的回执消息
+		 */
+		MSG_TYPE_CAR_STOP_RESULT = 10005,
+	}
+
+	public class Position
+	{
+		public int x;
+		public int y;
+		public int z;
+		public string yaw;
+
+		public Position(int x, int y, int z, string yaw)
+		{ 
+			this.x = x; 
+			this.y = y; 
+			this.z = z;
+			this.yaw = yaw;
+		}
+	}
+
+	public enum CarState : byte
+	{
+		//1-运动状态
+		Moving = 1,
+		//  * 2-停止状态
+		Stoped = 2,
+		//  * 3-中断状态,运行过程中被打断
+		Interrupt = 3,
+		//  * 4-故障状态
+		Error = 4,
+		// * 5-到达状态
+		Arrived,
+	}
+
+}

+ 55 - 0
Common/ChatHub.cs

@@ -0,0 +1,55 @@
+using AmrControl.ADS;
+using Microsoft.AspNetCore.SignalR;
+using Newtonsoft.Json;
+using System.Xml.Linq;
+
+namespace AmrControl.Common
+{
+    public class ChatHub :Hub,IChatHub
+    {
+        protected static bool writebool = false;
+
+        public async Task SendMessage(string topic, string message)
+        {
+            //try
+            //{
+            //    //TcClient.GetInstance().WriteBool("MAIN.testBool", writebool);
+            //    //TcClient.GetInstance().WriteValue<bool>("MAIN.testBool", writebool);
+            //    //TcClient.GetInstance().WriteValue<short>("MAIN.curr", 123);
+            //    //TcClient.GetInstance().WriteValue<byte[]>("serial_send_byte.array_send_data", new byte[] { 11, 22, 33, 44 });
+            //    //writebool = !writebool;
+
+            //    //bool val1 = TcClient.GetInstance().ReadValue<bool>("MAIN.testBool");
+            //    //short val2 = TcClient.GetInstance().ReadValue<short>("MAIN.curr");
+            //    //byte[] val3 = TcClient.GetInstance().ReadValue<byte[]>("serial_send_byte.array_send_data");
+            //    //string str = AppHelper.BytesToString(val3);
+            //    //await Clients.All.SendAsync("ReceiveMessage", topic, $"bool:{val1} , curr:{val2} ,send_data:{str}");
+
+            //    byte[] val = TcClient.GetInstance().ReadValue<byte[]>("MAIN.jgrVar");
+            //    JGR_Tc_Model model = AppHelper.BytesToStruct<JGR_Tc_Model>(val);
+            //    string str = JsonConvert.SerializeObject(model);
+            //    await Clients.All.SendAsync("ReceiveMessage", topic, $"jgr小车数据结构:{str}");
+            //}
+            //catch(Exception ex)
+            //{
+            //    await Clients.All.SendAsync("ReceiveMessage", topic, ex.Message);
+            //}
+            await Clients.All.SendAsync("ReceiveMessage", topic, message);
+        }
+
+        public override Task OnConnectedAsync()
+        {
+            return base.OnConnectedAsync();
+        }
+
+        public override Task OnDisconnectedAsync(Exception? exception)
+        {
+            return base.OnDisconnectedAsync(exception);
+        }
+    }
+
+    public interface IChatHub
+    {
+        Task SendMessage(string topic, string message);
+    }
+}

+ 143 - 0
Common/HttpClients/BaseHttpClient.cs

@@ -0,0 +1,143 @@
+using AmrControl.Common.HttpClients;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System.Collections;
+
+namespace AmrControl.Common.HttpClients
+{
+    public abstract class BaseHttpClient : IHttpRequest
+    {
+        /// <summary>
+        /// httpclient
+        /// </summary>
+        protected readonly HttpClient _httpClient;
+        /// <summary>
+        /// 根路径
+        /// </summary>
+        protected readonly string baseUri;
+
+        public BaseHttpClient(HttpClient httpClient, string baseUri)
+        {
+            _httpClient = httpClient;
+            this.baseUri = baseUri;
+        }
+
+        public HttpRespResult sendRequest<T>(string url, T data)
+        {
+            return sendRequest(url, new(), data);
+        }
+
+        public HttpRespResult sendRequest<T>(string url, Dictionary<string, string> headers, T data)
+        {
+            var response = _httpClient.SendAsync(createRequest(reHandleUrl(url, data), headers, getMethod(), handleData(data))).Result;
+            var rs = response.Content.ReadAsStringAsync().Result;
+            return handleResult(response, rs);
+        }
+        /// <summary>
+        /// 处理数据
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        protected abstract HttpContent? handleData<T>(T data);
+
+        /// <summary>
+        /// 当前请求类型
+        /// </summary>
+        /// <returns></returns>
+        protected abstract HttpMethod getMethod();
+
+        /// <summary>
+        /// 处理url,针对get等部分请求可能需要将参数放在url后面,此处做处理
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        protected virtual string reHandleUrl<T>(string url, T data)
+        {
+            if (baseUri.EndsWith("/"))
+            {
+                if (url.StartsWith("/"))
+                {
+                    url = url[1..];
+                }
+                return baseUri + url;
+            }
+            else
+            {
+                if (url.StartsWith("/"))
+                {
+                    return baseUri + url;
+                }
+                else
+                {
+                    return baseUri + "/" + url;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 创建请求对象
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="headers"></param>
+        /// <param name="method"></param>
+        /// <param name="content"></param>
+        /// <returns></returns>
+        protected HttpRequestMessage createRequest(string url, Dictionary<string, string> headers, HttpMethod method, HttpContent? content)
+        {
+            var request = new HttpRequestMessage
+            {
+                Method = method,
+                RequestUri = new Uri(url),
+                Content = content
+            };
+            foreach (var head in headers)
+            {
+                request.Headers.Add(head.Key, head.Value);
+            }
+            return request;
+        }
+        /// <summary>
+        /// 解析结果
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name="rs"></param>
+        /// <returns></returns>
+        protected HttpRespResult handleResult(HttpResponseMessage response, string rs)
+        {
+            return new HttpRespResult()
+            {
+                code = response.IsSuccessStatusCode ? 200 : -1,
+                message = response.IsSuccessStatusCode ? "发送成功" : rs,
+                data = rs
+            };
+        }
+        /// <summary>
+        /// 类型转换
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        protected Dictionary<string, string> convertObj2Dict<T>(T data)
+        {
+            Dictionary<string, string> dict = new Dictionary<string, string>();
+            if (data != null)
+            {
+                if (data is Dictionary<string, string>)
+                {
+                    //若对象本身即是dictionary则直接返回
+                    return data as Dictionary<string, string>;
+                }
+                else if (data is IEnumerable || data.GetType().IsArray)
+                {
+                    throw new Exception("不支持的转换类型");
+                }
+                else
+                {
+                    return JsonConvert.DeserializeObject<Dictionary<string, string>>(JsonConvert.SerializeObject(data, new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }));
+                }
+            }
+            return dict;
+        }
+    }
+}

+ 129 - 0
Common/HttpClients/DefaultHttpClient.cs

@@ -0,0 +1,129 @@
+using Newtonsoft.Json;
+using System.Collections.Concurrent;
+using System.Net;
+using System.Text;
+using AmrControl.Common.HttpClients;
+
+namespace AmrControl.Common.HttpClients
+{
+    /// <summary>
+    /// 请求客户端
+    /// </summary>
+    public class DefaultHttpClient
+    {
+        /// <summary>
+        /// 用于加锁,防止对象重复构建
+        /// </summary>
+        private static object lockObj = new Object();
+        /// <summary>
+        /// httpClient,用于http访问
+        /// </summary>
+        private readonly HttpClient _httpClient;
+        /// <summary>
+        /// get客户端
+        /// </summary>
+        private readonly HttpGet httpGet;
+        /// <summary>
+        /// post客户端
+        /// </summary>
+        private readonly HttpPost httpPost;
+        /// <summary>
+        /// put操作客户端
+        /// </summary>
+        private readonly HttpPut httpPut;
+        /// <summary>
+        /// delete客户端
+        /// </summary>
+        private readonly HttpDelete httpDelete;
+        /// <summary>
+        /// 地址终端维护
+        /// </summary>
+        private static volatile ConcurrentDictionary<string, DefaultHttpClient> clients = new();
+
+        //域名+地址,如http://127.0.0.1:5089/
+        private readonly string baseUri;
+        /// <summary>
+        /// 私有化构造方法
+        /// </summary>
+        private DefaultHttpClient(string hostAddr)
+        {
+            var socketsHttpHandler = new SocketsHttpHandler()
+            {
+                //每个请求最大连接数
+                MaxConnectionsPerServer = 128,
+                //连接池中TCP连接最多可以闲置多久,设置180秒超时回收
+                PooledConnectionIdleTimeout = TimeSpan.FromSeconds(180),
+                //连接超时,默认60s
+                ConnectTimeout = TimeSpan.FromSeconds(60),
+                // http响应头最大字节数(单位:KB)
+                MaxResponseHeadersLength = 128,
+                //启用压缩
+                AutomaticDecompression = DecompressionMethods.GZip,
+            };
+            _httpClient = new(socketsHttpHandler)
+            {
+                //等待响应超时时间,默认:100秒。
+                Timeout = TimeSpan.FromSeconds(60)
+            };
+            this.baseUri = hostAddr;
+            this.httpGet = new HttpGet(_httpClient, hostAddr);
+            this.httpPost = new HttpPost(_httpClient, hostAddr);
+            this.httpPut = new HttpPut(_httpClient, hostAddr);
+            this.httpDelete = new HttpDelete(_httpClient, hostAddr);
+        }
+
+        /// <summary>
+        /// 获取httpGet客户端
+        /// </summary>
+        /// <returns></returns>
+        public HttpGet httpGetClient()
+        {
+            return httpGet;
+        }
+
+        /// <summary>
+        /// 获取httpPost客户端
+        /// </summary>
+        /// <returns></returns>
+        public HttpPost httpPostClient()
+        {
+            return httpPost;
+        }
+        /// <summary>
+        /// 获取httpPut客户端
+        /// </summary>
+        /// <returns></returns>
+        public HttpPut httpPutClient()
+        {
+            return httpPut;
+        }
+        /// <summary>
+        /// 获取httpDelete客户端
+        /// </summary>
+        /// <returns></returns>
+        public HttpDelete httpDeleteClient()
+        {
+            return httpDelete;
+        }
+        /// <summary>
+        /// 获取客户端
+        /// </summary>
+        /// <returns></returns>
+        public static DefaultHttpClient getClient(string baseUri)
+        {
+            if (!clients.ContainsKey(baseUri) || clients[baseUri] == null)
+            {
+                //尝试获取锁
+                lock (lockObj)
+                {
+                    //再次校验,防止重复构建
+                    if (!clients.ContainsKey(baseUri) || clients[baseUri] == null)
+                    {
+                        clients[baseUri] = new DefaultHttpClient(baseUri);
+                    }
+                }
+            }
+            return clients[baseUri];
+        }
+    }
+}

+ 43 - 0
Common/HttpClients/HttpDelete.cs

@@ -0,0 +1,43 @@
+using Newtonsoft.Json;
+
+namespace AmrControl.Common.HttpClients
+{
+    public class HttpDelete : BaseHttpClient
+    {
+        public HttpDelete(HttpClient httpClient, string baseUri) : base(httpClient, baseUri)
+        {
+        }
+
+        protected override HttpMethod getMethod()
+        {
+            return HttpMethod.Delete;
+        }
+
+        protected override HttpContent? handleData<T>(T data)
+        {
+            return null;
+        }
+        protected override string reHandleUrl<T>(string url, T data)
+        {
+            string uri = base.reHandleUrl(url, data);
+            if (data != null)
+            {
+                Dictionary<string, string> dict = convertObj2Dict<T>(data);
+                //将参数放到url中
+                foreach (var item in dict)
+                {
+                    if (uri.IndexOf("?") > 0)
+                    {
+                        uri += "&" + item.Key + "=" + item.Value;
+                    }
+                    else
+                    {
+                        uri += "?" + item.Key + "=" + item.Value;
+                    }
+                }
+            }
+
+            return uri;
+        }
+    }
+}

+ 83 - 0
Common/HttpClients/HttpGet.cs

@@ -0,0 +1,83 @@
+namespace AmrControl.Common.HttpClients
+{
+    /// <summary>
+    /// get请求客户端
+    /// </summary>
+    public class HttpGet : BaseHttpClient
+    {
+        public HttpGet(HttpClient httpClient, string baseUri) : base(httpClient, baseUri)
+        {
+        }
+
+        protected override HttpMethod getMethod()
+        {
+            return HttpMethod.Get;
+        }
+
+        protected override string reHandleUrl<T>(string url, T data)
+        {
+            string uri = base.reHandleUrl(url, data);
+            if(data!=null)
+            {
+                Dictionary<string, string> dict = convertObj2Dict<T>(data);
+                //将参数放到url中
+                foreach (var item in dict)
+                {
+                    if (uri.IndexOf("?") > 0)
+                    {
+                        uri += "&" + item.Key + "=" + item.Value;
+                    }
+                    else
+                    {
+                        uri += "?" + item.Key + "=" + item.Value;
+                    }
+                }
+            }
+           
+            return uri;
+        }
+
+        protected override HttpContent? handleData<T>(T data)
+        {
+            return null;
+        }
+        /// <summary>
+        /// 获取文件
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="filePath"></param>
+        /// <returns></returns>
+        public HttpRespResult getFile(string url, string filePath)
+        {
+            return getFile(url, new(), filePath);
+        }
+        /// <summary>
+        /// 获取文件
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="headers"></param>
+        /// <param name="filePath"></param>
+        /// <returns></returns>
+        public HttpRespResult getFile(string url, Dictionary<string, string> headers, string filePath)
+        {
+            var response = _httpClient.SendAsync(createRequest(reHandleUrl(url, default(Dictionary<string,string>)), headers, getMethod(), handleData(default(Dictionary<string,string>)))).Result;
+            using var fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
+            response.Content.CopyToAsync(fs);
+            return handleResult(response, filePath);
+        }
+
+        /// <summary>
+        /// 下载文件
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="filePath"></param>
+        /// <param name="headers"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        public byte[] getPic<T>(string url)
+        {
+            var response = _httpClient.GetByteArrayAsync(url).Result;
+            return response;
+        }
+    }
+}

+ 146 - 0
Common/HttpClients/HttpPost.cs

@@ -0,0 +1,146 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System.Text;
+
+namespace AmrControl.Common.HttpClients
+{
+    /// <summary>
+    /// post请求客户端
+    /// </summary>
+    public class HttpPost : BaseHttpClient
+    {
+        /// <summary>
+        /// 设置传输类型,请在请求时注意类型,
+        /// </summary>
+        private MediaType mediaType = MediaType.APPLICATION_JSON_VALUE;
+
+        public HttpPost(HttpClient httpClient, string baseUri) : base(httpClient, baseUri)
+        {
+        }
+
+        public void setMediaType(MediaType type)
+        {
+            this.mediaType = type;
+        }
+
+        protected override HttpMethod getMethod()
+        {
+            return HttpMethod.Post;
+        }
+
+        protected override HttpContent? handleData<T>(T data)
+        {
+            if(mediaType == MediaType.APPLICATION_JSON_VALUE)
+            {
+                //请求内容在请求体中时使用
+                return new StringContent(JsonConvert.SerializeObject(data, new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }), Encoding.UTF8, "application/json");
+            } else
+            {
+                return new FormUrlEncodedContent(convertObj2Dict(data));
+            }
+        }
+        /// <summary>
+        /// 下载文件
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="filePath"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        public HttpRespResult downloadFile<T>(string url, string filePath, T data)
+        {
+            return downloadFile(url, filePath, new(), data);
+        }
+        /// <summary>
+        /// 下载文件
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="filePath"></param>
+        /// <param name="headers"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        public HttpRespResult downloadFile<T>(string url, string filePath, Dictionary<string, string> headers, T data)
+        {
+            setMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
+            var response = _httpClient.SendAsync(createRequest(reHandleUrl(url, data), headers, getMethod(), handleData(data))).Result;
+            using var fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
+            response.Content.CopyToAsync(fs);
+            return handleResult(response, filePath);
+        }
+        /// <summary>
+        /// 下载文件
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="filePath"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        public HttpRespResult downloadFileForJson<T>(string url, string filePath, T data)
+        {
+            return downloadFileForJson(url, filePath, new(), data);
+        }
+        /// <summary>
+        /// 下载文件
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="filePath"></param>
+        /// <param name="headers"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        public HttpRespResult downloadFileForJson<T>(string url, string filePath, Dictionary<string, string> headers, T data)
+        {
+            setMediaType(MediaType.APPLICATION_JSON_VALUE);
+            var response = _httpClient.SendAsync(createRequest(reHandleUrl(url, data), headers, getMethod(), handleData(data))).Result;
+            using var fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
+            response.Content.CopyToAsync(fs);
+            return handleResult(response, filePath);
+        }
+        /// <summary>
+        /// 上传文件
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="data"></param>
+        /// <param name="filePath"></param>
+        /// <returns></returns>
+        public HttpRespResult uploadFile<T>(string url, T? data, string filePath)
+        {
+            if (string.IsNullOrEmpty(filePath))
+            {
+                return sendRequest(url, data);
+            }
+            return uploadFile(url, new(), data, filePath);
+        }
+        /// <summary>
+        /// 上传文件
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="headers"></param>
+        /// <param name="data"></param>
+        /// <param name="filePath"></param>
+        /// <returns></returns>
+        public HttpRespResult uploadFile<T>(string url, Dictionary<string, string> headers, T? data, string filePath)
+        {
+            if (string.IsNullOrEmpty(filePath))
+            {
+                return sendRequest(url, headers, data);
+            }
+            var content = new MultipartFormDataContent();
+            if (data != null)
+            {
+                Dictionary<string, string> dict = convertObj2Dict(data);
+                foreach (var item in dict)
+                {
+                    if (string.IsNullOrEmpty(item.Value) || string.IsNullOrWhiteSpace(item.Value))
+                    {
+                        continue;
+                    }
+                    content.Add(new StringContent(item.Value), item.Key);
+                }
+            }
+            content.Add(new ByteArrayContent(System.IO.File.ReadAllBytes(filePath)), "file", filePath.IndexOf("/") > 0 ? filePath.Substring(filePath.LastIndexOf("/")) : filePath);
+            setMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
+            var response = _httpClient.SendAsync(createRequest(reHandleUrl(url, data), headers, HttpMethod.Post, content)).Result;
+            var rs = response.Content.ReadAsStringAsync().Result;
+            return handleResult(response, rs);
+        }
+
+    }
+}

+ 39 - 0
Common/HttpClients/HttpPut.cs

@@ -0,0 +1,39 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System.Text;
+
+namespace AmrControl.Common.HttpClients
+{
+    public class HttpPut : BaseHttpClient
+    {
+        /// <summary>
+        /// 设置传输类型,请在请求时注意类型,
+        /// </summary>
+        private MediaType mediaType = MediaType.APPLICATION_JSON_VALUE;
+        public HttpPut(HttpClient httpClient, string baseUri) : base(httpClient, baseUri)
+        {
+        }
+
+        public void setMediaType(MediaType type)
+        {
+            this.mediaType = type;
+        }
+        protected override HttpMethod getMethod()
+        {
+            return HttpMethod.Put;
+        }
+
+        protected override HttpContent? handleData<T>(T data)
+        {
+            if (mediaType == MediaType.APPLICATION_JSON_VALUE)
+            {
+                //请求内容在请求体中时使用
+                return new StringContent(JsonConvert.SerializeObject(data, new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }), Encoding.UTF8, "application/json");
+            }
+            else
+            {
+                return new FormUrlEncodedContent(convertObj2Dict(data));
+            }
+        }
+    }
+}

+ 25 - 0
Common/HttpClients/HttpRespResult.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace AmrControl.Common.HttpClients
+{
+    /// <summary>
+    /// http响应内容
+    /// </summary>
+    public class HttpRespResult
+    {
+        /// <summary>
+        /// 处理结果
+        /// </summary>
+        public int code { set; get; }
+        /// <summary>
+        /// 结果消息
+        /// </summary>
+        public string message { set; get; }
+        /// <summary>
+        /// 响应内容
+        /// </summary>
+        public string data { set; get; }
+    }
+}

+ 25 - 0
Common/HttpClients/IHttpRequest.cs

@@ -0,0 +1,25 @@
+using AmrControl.Common.HttpClients;
+
+namespace AmrControl.Common.HttpClients
+{
+    public interface IHttpRequest
+    {
+        /// <summary>
+        /// 发送请求
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="header"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        HttpRespResult sendRequest<T>(string url, T data);
+        /// <summary>
+        /// 发送请求
+        /// </summary>
+        /// <param name="url"></param>
+        /// <param name="headers"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        HttpRespResult sendRequest<T>(string url, Dictionary<string, string> headers, T data);
+
+    }
+}

+ 26 - 0
Common/HttpClients/MediaType.cs

@@ -0,0 +1,26 @@
+namespace AmrControl.Common.HttpClients
+{
+    public class MediaType
+    {
+        /// <summary>
+        /// 文件上传或下载使用
+        /// </summary>
+        public static readonly MediaType MULTIPART_FORM_DATA_VALUE = new("multipart/form-data");
+        /// <summary>
+        /// json传输
+        /// </summary>
+        public static readonly MediaType APPLICATION_JSON_VALUE = new("application/json");
+        /// <summary>
+        /// 通用
+        /// </summary>
+        public static readonly MediaType APPLICATION_FORM_URLENCODED_VALUE = new("application/x-www-form-urlencoded");
+        /// <summary>
+        /// 类型
+        /// </summary>
+        private readonly string _type;
+        private MediaType(string type)
+        {
+            this._type = type;
+        }
+    }
+}

+ 46 - 0
Common/IntStringConverter.cs

@@ -0,0 +1,46 @@
+using Newtonsoft.Json;
+
+namespace AmrControl.Common
+{
+    public class IntStringConverter : JsonConverter
+    {
+        public override bool CanConvert(Type objectType)
+        {
+            
+            if (objectType.FullName == "System.Int32" || objectType.FullName == "int")
+            {
+                return true;
+            }
+
+            if (objectType == typeof(int) || objectType == typeof(Int32))
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        public override void WriteJson(JsonWriter writer, object? Data, JsonSerializer serializer)
+        {
+            if (Data == null)
+            {
+                writer.WriteNull();
+                return;
+            }
+            writer.WriteValue(Data.ToString());
+        }
+
+        public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+        {
+            if (reader.TokenType == JsonToken.Null)
+            {
+                return null;
+            }
+
+            if (reader.Value == null)
+                return null;
+
+            return Convert.ToInt32(reader.Value!.ToString());
+        }
+    }
+}

+ 333 - 0
Common/MockData.cs

@@ -0,0 +1,333 @@
+using AmrControl.ADS;
+using AmrControl.Dto;
+using System.Text;
+
+namespace AmrControl.Common
+{
+    /// <summary>
+    /// 测试数据
+    /// </summary>
+    public class MockData
+    {
+        /// <summary>
+        /// 车的初始化
+        /// </summary>
+        /// <returns></returns>
+        public static List<JGR_Tc_Model> getJgrs()
+        {
+            List<JGR_Tc_Model> ls = new List<JGR_Tc_Model>();
+            //todo:补上数据接口
+            JGR_Tc_Model jGR_Tc_Model = new JGR_Tc_Model();
+            jGR_Tc_Model.jgrSN = "JGR-23001";
+            jGR_Tc_Model.jgrType = (byte)JgrType.StorageCar; //仓储车
+            jGR_Tc_Model.jgrID = 001;
+            jGR_Tc_Model.isOnline = 1;
+            jGR_Tc_Model.currLocation_X = 3;
+            jGR_Tc_Model.currLocation_Y = 2;
+            jGR_Tc_Model.electricity = 80;
+            jGR_Tc_Model.displayX = 38;
+            jGR_Tc_Model.displayY = 2;
+            ls.Add(jGR_Tc_Model);
+
+            jGR_Tc_Model = new JGR_Tc_Model();
+            jGR_Tc_Model.jgrSN = "JGR-23101";
+            jGR_Tc_Model.rfid = "230105";
+            jGR_Tc_Model.jgrType = (byte)JgrType.RailCar;//轨道车
+            jGR_Tc_Model.jgrID = 002;
+            jGR_Tc_Model.isOnline = 1;
+            jGR_Tc_Model.currLocation_X = 2;
+            jGR_Tc_Model.currLocation_Y = 1;
+            jGR_Tc_Model.displayX = 2;
+            jGR_Tc_Model.displayY = 2;
+            jGR_Tc_Model.electricity = 30;
+            ls.Add(jGR_Tc_Model);
+
+            return ls;
+        }
+
+        /// <summary>
+        /// 充电桩初始化
+        /// </summary>
+        /// <returns></returns>
+        public static List<ChargingStationDataModel> getChargingStations()
+        {
+            List<ChargingStationDataModel> ls = new List<ChargingStationDataModel>();
+            //todo:补上数据接口
+            ChargingStationDataModel jGR_Tc_Model = new ChargingStationDataModel();
+            jGR_Tc_Model.csSN = "JGP-23101";
+            jGR_Tc_Model.csType = (byte)JgrType.StorageCharging; //仓储的充电器
+            jGR_Tc_Model.csID = 001;
+            jGR_Tc_Model.isOnline = 1;
+            jGR_Tc_Model.positionX = 1;
+            jGR_Tc_Model.positionX = 1;
+            jGR_Tc_Model.displayX = 36;
+            jGR_Tc_Model.displayY = 1;
+            ls.Add(jGR_Tc_Model);
+
+
+            jGR_Tc_Model = new ChargingStationDataModel();
+            jGR_Tc_Model.csSN = "JGP-23102";
+            jGR_Tc_Model.csType = (byte)JgrType.RailCharging; //轨道的充电器
+            jGR_Tc_Model.csID = 002;
+            jGR_Tc_Model.isOnline = 1;
+            jGR_Tc_Model.positionX = 33;
+            jGR_Tc_Model.positionY = 1;
+            jGR_Tc_Model.displayX = 33;
+            jGR_Tc_Model.displayY = 2;
+            ls.Add(jGR_Tc_Model);
+            return ls;
+        }
+
+        /// <summary>
+        /// 工位的初始化
+        /// </summary>
+        /// <returns></returns>
+        public static List<StationDataModel> getStations()
+        {
+            List<StationDataModel> ls = new List<StationDataModel>();
+            //todo:补上数据接口
+            StationDataModel station = new StationDataModel();
+            station.stationID = "JGS-ZP-23201";
+            station.stationType = StationType.WS_CWCS;
+            station.displayName = "常温测试";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 1;
+            station.positionY = 5;
+            station.displayX = 1;
+            station.displayY = 5;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            station = new StationDataModel();
+            station.stationID = "JGS-SPTS-23202";
+            station.stationType = StationType.WS_SPTS;
+            station.displayName = "射频调试工位1";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 4;
+            station.positionY = 5;
+            station.displayX = 4;
+            station.displayY = 5;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            station = new StationDataModel();
+            station.stationID = "JGS-SPTS-23203";
+            station.stationType = StationType.WS_SPTS;
+            station.displayName = "射频调试工位2";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 7;
+            station.positionY = 5;
+            station.displayX = 7;
+            station.displayY = 5;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            station = new StationDataModel();
+            station.stationID = "JGS-ZP-23204";
+            station.stationType = StationType.WS_ZP;
+            station.displayName = "装配工位1";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 10;
+            station.positionY = 5;
+            station.displayX = 10;
+            station.displayY = 5;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            station = new StationDataModel();
+            station.stationID = "JGS-ZP-23205";
+            station.stationType = StationType.WS_ZP;
+            station.displayName = "装配工位2";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 13;
+            station.positionY = 5;
+            station.displayX = 13;
+            station.displayY = 5;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            station = new StationDataModel();
+            station.stationID = "JGS-ZP-23206";
+            station.stationType = StationType.WS_ZP;
+            station.displayName = "装配工位3";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 16;
+            station.positionY = 5;
+            station.displayX = 16;
+            station.displayY = 5;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            station = new StationDataModel();
+            station.stationID = "JGS-ZP-23207";
+            station.stationType = StationType.WS_ZP;
+            station.displayName = "装配工位4";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 19;
+            station.positionY = 5;
+            station.displayX = 19;
+            station.displayY = 5;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            station = new StationDataModel();
+            station.stationID = "JGS-YCL-23208";
+            station.stationType = StationType.WS_YCL;
+            station.displayName = "预处理工位1";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 22;
+            station.positionY = 5;
+            station.displayX = 22;
+            station.displayY = 5;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            station = new StationDataModel();
+            station.stationID = "JGS-CC-JL-23301";
+            station.stationType = StationType.WS_CC_JL;
+            station.displayName = "捡料工位";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 26;
+            station.positionY = 8;
+            station.displayX = 26;
+            station.displayY = 8;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            station = new StationDataModel();
+            station.stationID = "JGS-CC-PF-23302";
+            station.stationType = StationType.WS_CC_PF;
+            station.displayName = "派发工位";
+            station.userOnPosition = false;
+            station.isPowerOn = true;
+            station.positionX = 27;
+            station.positionY = 8;
+            station.displayX = 27;
+            station.displayY = 8;
+            station.taskCount = 8;
+            station.finishedCount = 3;
+            ls.Add(station);
+
+            return ls;
+        }
+
+        public static List<IssueNoteDto> getIssueNodeDto()
+        {
+            List<IssueNoteDto> issueNodeDtos = new List<IssueNoteDto>();
+            IssueNoteDto node = new IssueNoteDto();
+            node.StationID = "JGS-ZP-23207";
+            node.ID = "ID230501";
+            node.MesTaskID = "mesid230501";
+            RequireRawBom bom = new RequireRawBom();
+            bom.MaterialID = "pcba-i001";
+            bom.MaterialType = "PCBA";
+            bom.MaterialName = "PCBA板";
+            bom.DemandQuantity = 2;
+            bom.ReleaseQuantity = 0;
+            node.RawBoms.Add(bom);
+
+            bom = new RequireRawBom();
+            bom.MaterialID = "j30j29k-i022";
+            bom.MaterialName = "J30J-29K接头";
+            bom.MaterialType = "J30J";
+            bom.DemandQuantity = 5;
+            bom.ReleaseQuantity = 0;
+            node.RawBoms.Add(bom);
+
+            issueNodeDtos.Add(node);
+            return issueNodeDtos;
+        }
+
+        /// <summary>
+        /// 根据物料号获取储位信息
+        /// </summary>
+        /// <param name="materialid"></param>
+        /// <returns></returns>
+        public static List<StorageBox> getStorageBox()
+        {
+            List<StorageBox> boxs = new List<StorageBox>();
+            StorageBox storageBox = new StorageBox();
+            storageBox.BoxRFID = "rfid001";
+            storageBox.PositionX = 1;
+            storageBox.PositionY = 1;
+            storageBox.PositionZ = 1;
+            RawBom bom = new RawBom();
+            bom.MaterialID = "pcba-i001";
+            bom.MaterialName = "PCBA板";
+            bom.MaterialType = "PCBA";
+            bom.Quantity = 10;
+            storageBox.RawBoms.Add(bom);
+            boxs.Add(storageBox);
+
+            storageBox = new StorageBox();
+            storageBox.BoxRFID = "rfid002";
+            storageBox.PositionX = 2;
+            storageBox.PositionY = 2;
+            storageBox.PositionZ = 1;
+            bom = new RawBom();
+            bom.MaterialID = "j30j29k-i022";
+            bom.MaterialName = "J30J-29K接头";
+            bom.MaterialType = "J30J";
+            bom.Quantity = 20;
+            storageBox.RawBoms.Add(bom);
+            boxs.Add(storageBox);
+
+            return boxs;
+        }
+
+        public static List<StorageBox> getStorageBox(int x , int y)
+        {
+            List<StorageBox> boxs = new List<StorageBox>();
+            StorageBox storageBox = new StorageBox();
+            if(x ==1 && y==1)
+            {
+                storageBox.BoxRFID = "rfid001";
+                storageBox.PositionX = 1;
+                storageBox.PositionY = 1;
+                storageBox.PositionZ = 1;
+                RawBom bom = new RawBom();
+                bom.MaterialID = "pcba-i001";
+                bom.MaterialName = "PCBA板";
+                bom.MaterialType = "PCBA";
+                bom.Quantity = 10;
+                storageBox.RawBoms.Add(bom);
+                boxs.Add(storageBox);
+            }
+            else if(x == 2 && y == 2)
+            {
+                storageBox = new StorageBox();
+                storageBox.BoxRFID = "rfid002";
+                storageBox.PositionX = 2;
+                storageBox.PositionY = 2;
+                storageBox.PositionZ = 1;
+                RawBom bom = new RawBom();
+                bom.MaterialID = "j30j29k-i022";
+                bom.MaterialName = "J30J-29K接头";
+                bom.MaterialType = "J30J";
+                bom.Quantity = 20;
+                storageBox.RawBoms.Add(bom);
+                boxs.Add(storageBox);
+            }
+
+            return boxs;
+        }
+    }
+}

+ 10 - 0
Common/MqMessage.cs

@@ -0,0 +1,10 @@
+namespace AmrControl.Common
+{
+
+    public class MqMessage
+    {
+        public string Topic { get; set; }
+        public DateTime Time { get; set; }
+        public string Message { get; set; }
+    }
+}

+ 25 - 0
Common/Proxy.cs

@@ -0,0 +1,25 @@
+namespace AmrControl.Common
+{
+    /// <summary>
+    /// 代理处理类
+    /// </summary>
+    public class Proxy
+    {
+        /// <summary>
+        /// 执行器
+        /// </summary>
+        private readonly Action _action;
+
+        public Proxy(Action action)
+        {
+            _action = action;
+        }
+        /// <summary>
+        /// 执行代理逻辑
+        /// </summary>
+        public void Invoke()
+        {
+            _action();
+        }
+    }
+}

+ 557 - 0
Common/RS485.cs

@@ -0,0 +1,557 @@
+using System.IO.Ports;
+
+namespace AmrControl.Common
+{
+    public delegate void DltReceiveCarStateCmd(CarStateCmd carState);
+
+    public class RS485 
+    {
+        SerialPort serial;
+        string portName = "COM4";
+        int baudRate = 38400;
+        int dataBits = 8;
+        StopBits stopBits = StopBits.One;
+        Parity parity = Parity.None;
+        byte[] buf;
+        int len;
+        int start = 0, end = 0;
+        bool IsStart = false;
+
+        ushort head; //两字节的帧头、帧尾
+
+        //车的定时更新状态事件
+        public event DltReceiveCarStateCmd ReceiveCarStateCmdEvent;
+        //车的到达目标地址的事件
+        public event DltReceiveCarStateCmd CarArrivedEvent;
+        //车上线的事件
+        public event DltReceiveCarStateCmd CarConnectedEvent;
+        //车离线的事件
+        public event DltReceiveCarStateCmd CarDisconnectedEvent;
+
+        public int CarCount
+        {
+            get
+            {
+                if(cars == null)
+                {
+                    return 0;
+                }
+                else
+                {
+                    return cars.Count;
+                }
+            }
+        }
+
+        private object lockobj = new object();
+        //所有的车
+        Dictionary<ushort, CarStateCmd> cars;
+
+        public RS485()
+        {
+            buf = new byte[10240];
+            len = 0;
+            start = -1;
+            end = 0;
+            cars = new Dictionary<ushort, CarStateCmd>();
+            portName = AppHelper.ReadAppSettings("zigbee", "Com");
+            baudRate = Int32.Parse(AppHelper.ReadAppSettings("zigbee", "baudRate"));
+        }
+
+        private void Serial_DataReceived(object sender, SerialDataReceivedEventArgs e)
+        {
+            //报文的首尾都是0x7e,判断思路是当发现有两个7e时候,结合长度位判断是否是一条报文
+            //如果是则提取报文并把剩余数据前移到索引0,如果不是则把第2个7e之前的数据抛弃掉
+            int size = serial.BytesToRead;
+
+            //正常情况下,不应该出现超出缓冲的报文,如果出现则可能是出错了,直接把索引置0
+            if (len + size > buf.Length)
+            {
+                //以缓存大小为主
+                size = buf.Length - len;
+            }
+
+            serial.Read(buf, len, size);
+            int olen = len;
+            len += size;
+            for (int i = olen; i < len; i++)
+            {
+                head = (ushort)(((head << 8) & 0xff00) + buf[i]);
+                //这里要求不能连续出现三个0x7e
+                if(head == 0x7e7e)
+                {
+                    start  = i - 1;
+                }
+                else if (head == 0x0d0a)
+                {
+                    end = i;
+                    if((start >=0) &&((end - start + 1 )== buf[start + 2]))
+                    {
+                        //帧长度正确
+                        byte[] datas = new byte[end - start + 1];
+                        Array.Copy(buf, start, datas, 0, datas.Length);
+                        HandleReceive(datas);
+                        //移动剩余数据到0;
+                        for (int j = end + 1; j < len; j++)
+                        {
+                            buf[j - end - 1] = buf[j];
+                        }
+                        len = len - end - 1; //重新调整长度
+                        i = -1;//下一个循环从0开始
+                        start = -1;//第2个7前移动到0
+                        end = 0;
+                    }
+                    else
+                    {
+                        start = -1;
+                        end = 0;
+                    }
+                    
+                }
+            }
+
+            //如果处理完了缓存依然是满的,则抛弃数据,重新归零
+            if (len >= buf.Length)
+            {
+                //相当于初始化缓冲
+                len = 0;
+                start = -1;
+                end = 0;
+            }
+        }
+
+        //处理接收到的报文,先转义再校验
+        bool HandleReceive(byte[] datas)
+        {
+            //长度不是正常报文的长度
+            if (datas == null || datas.Length < 8 || IsStart == false || AmrManager.ProcessExit)
+                return false;
+
+            ushort carid = (ushort)(datas[4] + ((datas[5] << 8) & 0xff00));
+            //命令码
+            switch (datas[3])
+            {
+                case 0x01:
+                    //不存在则创建
+                    CarStateCmd cmd = null;
+                    bool ok = false;
+                    //保留最新的
+                    lock (lockobj)
+                    {
+                        if (cars.ContainsKey((ushort)carid))
+                        {
+                            cmd = (CarStateCmd)cars[carid];
+                            cmd.sendTime = DateTime.Now;
+                            ok = true;
+                        }
+                        else
+                        {
+                            cmd = new CarStateCmd();
+                            cmd.iCarID = carid;
+                            cmd.carId = "0x" + carid.ToString("X2");
+                            cmd.sendTime = DateTime.Now;
+                            cars.Add(carid, cmd);
+                        }
+                    }
+
+                    //这里的写和心跳里面的读不能同时执行
+                    HandleCarStateMsg(cmd, datas);
+
+                    if (ok == false)
+                    {
+                        //车辆上线是一个事件,需要对外抛出事件
+                        if (CarConnectedEvent != null)
+                        {
+                            Task.Run(() => {
+                                cmd.msgType = MessageType.MSG_TYPE_CAR_Connected;
+                                CarConnectedEvent(cmd);
+                            });
+                        }
+                    }
+                    break;
+                case 0x04:
+                    CarStateCmd cmd2 = new CarStateCmd();
+                    cmd2.carId = "0x" + carid.ToString("X2");
+                    cmd2.iCarID = carid;
+                    HandleCarArrivedMsg(cmd2, datas);
+                    //车辆到位是一个事件,需要对外抛出事件
+                    if(CarArrivedEvent != null)
+                    {
+                        Task.Run(() => {
+                            CarArrivedEvent(cmd2);
+                        });
+                    }
+                    break;
+                default:
+                    break;
+            }
+           
+            //获取锁,移除旧信息
+
+            //如果长度值匹配得上,就是正常的包
+            return true;
+        }
+
+        //处理车的心跳消息,更新到车的对象中
+        void HandleCarStateMsg(CarStateCmd cmd , byte[] datas)
+        {
+            cmd.msgType = MessageType.MSG_TYPE_CAR_REPORT;
+            cmd.powerLevel = datas[8].ToString();
+            cmd.speed = (datas[21] * 0.1).ToString("0.00");
+            cmd.state = (datas[22] > 0) ? CarState.Moving : CarState.Stoped; //根据要求,未完整,还有是否故障,是否中断,是否到达等
+            cmd.loading = datas[23] > 0 ? true : false;
+            cmd.isEnable = datas[24] > 0? true : false;
+            int x, y, z, yaw;
+            x = (int)(datas[9] + ((datas[10] << 8) & 0xff00));
+            y = (int)(datas[11] + ((datas[12] << 8) & 0xff00));
+            z = (int)(datas[13] + ((datas[14] << 8) & 0xff00));
+            cmd.curPos = new Position(x,y,z, (z * 0.01).ToString("0.00"));
+            x = (int)(datas[15] + ((datas[16] << 8) & 0xff00));
+            y = (int)(datas[17] + ((datas[18] << 8) & 0xff00));
+            z = (int)(datas[19] + ((datas[20] << 8) & 0xff00));
+            cmd.destPos = new Position(x,y,z, (z * 0.01).ToString("0.00"));
+            if(datas[27] == 0)
+            {
+                cmd.type = CarType.AMR;
+            }
+            else
+            {
+                cmd.type = CarType.AGV;
+            }
+            //几下更新数据的时间,更新数据与发送数据到mqtt总线上的时间不一样
+            cmd.sendTime = DateTime.Now;
+            //此次z和yaw赋值一样
+            //需要z和yaw分开,要和老蔡商量
+        }
+
+        //心跳和车到达消息都存在车的属性缓存消息结构中,只是中间产生了车到达的事件
+        void HandleCarArrivedMsg(CarStateCmd cmd , byte[] datas)
+        {
+            cmd.state = CarState.Arrived; //小车到达的消息,需要和老马确认
+            cmd.msgType = MessageType.MSG_TYPE_CAR_Arrived;
+            int x, y, z, yaw;
+            x = (int)(datas[8] + ((datas[9] << 8) & 0xff00));
+            y = (int)(datas[10] + ((datas[11] << 8) & 0xff00));
+            z = (int)(datas[12] + ((datas[13] << 8) & 0xff00));
+            cmd.curPos = new Position(x, y, z, (z*0.01).ToString("0.00"));
+            x = (int)(datas[14] + ((datas[15] << 8) & 0xff00));
+            y = (int)(datas[16] + ((datas[17] << 8) & 0xff00));
+            z = (int)(datas[18] + ((datas[19] << 8) & 0xff00));
+            cmd.destPos = new Position(x, y, z, (z * 0.01).ToString("0.00"));
+            //此次z和yaw赋值一样
+            if (datas[27] == 0)
+            {
+                cmd.type = CarType.AMR;
+            }
+            else
+            {
+                cmd.type = CarType.AGV;
+            }
+            //几下更新数据的时间,更新数据与发送数据到mqtt总线上的时间不一样
+            cmd.sendTime = DateTime.Now;
+
+        }
+
+        //串口对外发送
+        public bool Send(byte[] buffer)
+        {
+            if (buffer == null)
+                return false;
+
+            if (serial == null || serial.IsOpen == false)
+                Start();
+
+            if (serial != null && serial.IsOpen)
+            {
+                lock (this)
+                {
+                    serial.Write(buffer, 0, buffer.Length);
+                }
+
+            }
+            return true;
+
+        }
+
+        //串口启动后的定期执行事件,如果有内容,则对外发送定时消息
+        private void CreateHeartBeat()
+        {
+           // return;
+
+            List<CarStateCmd> offlinecars = new List<CarStateCmd>();
+            List<CarStateCmd> onlinecars = new List<CarStateCmd>();
+
+            Task.Run(() => {
+                while (true)
+                {
+                    if (IsStart == false  ||  AmrManager.ProcessExit)
+                        break;
+
+                    Thread.Sleep(350);
+                    //保留最新的,lock内的内容要尽快结束,并且不要出错,另一个线程会把最新的车状态替换到cars里面。
+                    lock (lockobj)
+                    {
+                        offlinecars.Clear();
+                        onlinecars.Clear();
+                        List<CarStateCmd> carstates = cars.Values.ToList();
+                        foreach (var item in carstates)
+                        {
+                            //首先移除超时的消息,正常是每辆车100ms都有数据上来
+                            if ((DateTime.Now - item.sendTime).TotalMilliseconds > 2000)
+                            {
+                                //超时,认为掉线
+                                offlinecars.Add(item);
+                                //移除
+                                cars.Remove((ushort)item.iCarID);
+                            }
+                            else
+                            {
+                                onlinecars.Add(item);
+                            }
+                        }
+                    }
+
+                    //对offlinecars,产生离线消息
+                    //车辆上线是一个事件,需要对外抛出事件
+                    if (CarDisconnectedEvent != null)
+                    {
+                        foreach (var item in offlinecars)
+                        {
+                            item.msgType = MessageType.MSG_TYPE_CAR_Disconnected;
+                            CarDisconnectedEvent(item);
+                        }
+                    }
+                    //对onlinecars,产生状态变更消息
+                    if (ReceiveCarStateCmdEvent != null)
+                    {
+                        foreach (var item in onlinecars)
+                        {
+                            ReceiveCarStateCmdEvent(item);
+                        }
+                    }
+                }
+            });
+        }
+
+        public bool Start()
+        {
+            if (serial != null)
+                serial.Close();
+
+            serial = new SerialPort();
+            serial.PortName = portName;
+            serial.BaudRate = baudRate;
+            serial.DataBits = dataBits;
+            serial.StopBits = stopBits;
+            serial.Parity = parity;
+            serial.ReceivedBytesThreshold = 1;
+            len = 0;
+            start = 0;
+            end = 0;
+            serial.DataReceived += Serial_DataReceived;
+            try
+            {
+                serial.Open();
+                IsStart = true;
+                //创建定时程序
+                CreateHeartBeat();
+            }
+            catch (Exception)
+            {
+                //串口线被拔掉时会有异常
+            }
+
+            return serial.IsOpen;
+        }
+
+        public bool Stop()
+        {
+            if (serial != null)
+            {
+                serial.Close();
+                serial = null;
+                IsStart = false;
+                len = 0;
+                start = -1;
+                end = 0;
+            }
+            return true;
+        }
+
+        public void Dispose()
+        {
+            try
+            {
+                Stop();
+            }
+            catch (Exception)
+            {
+            }
+        }
+
+        //对车发送命令
+        public bool SendCarCmd(CallCarCmd cmd)
+        {
+            byte[] datas = new byte[36];
+            datas[0] = 0xfd; //zigbee帧头
+            datas[1] = 32;   //数据长度
+            ushort id = Convert.ToUInt16(cmd.carId, 16);
+            datas[2] = (byte)(id & 0xff);
+            datas[3] = (byte)((id >> 8) & 0xff);
+            datas[4] = 0x7e;
+            datas[4 + 1] = 0x7e;
+            datas[4 + 2] = 32;
+            datas[4 + 3] = 0x02; //命令
+            datas[4 + 6] = (byte)(id & 0xff);
+            datas[4 + 7] = (byte)((id >> 8) & 0xff);
+            datas[4 + 8] = (byte)(cmd.destPos.x & 0xff);
+            datas[4 + 9] = (byte)((cmd.destPos.x >> 8) & 0xff);
+            datas[4 + 10] = (byte)(cmd.destPos.y & 0xff);
+            datas[4 + 11] = (byte)((cmd.destPos.y >> 8) & 0xff);
+            datas[4 + 12] = (byte)(cmd.destPos.z & 0xff);
+            datas[4 + 13] = (byte)((cmd.destPos.z >> 8) & 0xff);
+            if(id != 0xfe04)
+            {
+                datas[4 + 27] = (byte)CarType.AMR;
+            }
+            else
+            {
+                datas[4 + 27] = (byte)CarType.AGV;
+            }
+
+            datas[4 + 30] = 0x0d;
+            datas[4 + 31] = 0x0a;
+
+            return Send(datas);
+        }
+
+        //让车停
+        public bool SendCarStop(StopCarCmd cmd)
+        {
+            byte[] datas = new byte[36];
+            datas[0] = 0xfd; //zigbee帧头
+            datas[1] = 32;   //数据长度
+            ushort id = Convert.ToUInt16(cmd.carId, 16);
+            datas[2] = (byte)(id & 0xff);
+            datas[3] = (byte)((id >> 8) & 0xff);
+            datas[4] = 0x7e;
+            datas[4 + 1] = 0x7e;
+            datas[4 + 2] = 32;
+            datas[4 + 3] = 0x03; //命令
+            datas[4 + 6] = (byte)(id & 0xff);  //目的ID
+            datas[4 + 7] = (byte)((id >> 8) & 0xff);
+            if (id != 0xfe04)
+            {
+                datas[4 + 27] = (byte)CarType.AMR;
+            }
+            else
+            {
+                datas[4 + 27] = (byte)CarType.AGV;
+            }
+
+            datas[4 + 30] = 0x0d;
+            datas[4 + 31] = 0x0a;
+
+            return Send(datas);
+        }
+
+        //对车使能
+        public bool SendCarEnable(EnableCarCmd cmd)
+        {
+            byte[] datas = new byte[36];
+            datas[0] = 0xfd; //zigbee帧头
+            datas[1] = 32;   //数据长度
+            ushort id = Convert.ToUInt16(cmd.carId, 16);
+            datas[2] = (byte)(id & 0xff);
+            datas[3] = (byte)((id >> 8) & 0xff);
+            datas[4] = 0x7e;
+            datas[4 + 1] = 0x7e;
+            datas[4 + 2] = 32;
+            datas[4 + 3] = 0x05; //命令
+            datas[4 + 6] = (byte)(id & 0xff);  //目的ID
+            datas[4 + 7] = (byte)((id >> 8) & 0xff);
+            if (id != 0xfe04)
+            {
+                datas[4 + 27] = (byte)CarType.AMR;
+            }
+            else
+            {
+                datas[4 + 27] = (byte)CarType.AGV;
+            }
+
+            datas[4 + 30] = 0x0d;
+            datas[4 + 31] = 0x0a;
+
+            return Send(datas);
+        }
+
+        //停止车使能
+        public bool SendCarDisable(DisableCarCmd cmd)
+        {
+            byte[] datas = new byte[36];
+            datas[0] = 0xfd; //zigbee帧头
+            datas[1] = 32;   //数据长度
+            ushort id = Convert.ToUInt16(cmd.carId, 16);
+            datas[2] = (byte)(id & 0xff);
+            datas[3] = (byte)((id >> 8) & 0xff);
+            datas[4] = 0x7e;
+            datas[4 + 1] = 0x7e;
+            datas[4 + 2] = 32;
+            datas[4 + 3] = 0x06; //命令
+            datas[4 + 6] = (byte)(id & 0xff); //目的ID
+            datas[4 + 7] = (byte)((id >> 8) & 0xff);
+            if (id != 0xfe04)
+            {
+                datas[4 + 27] = (byte)CarType.AMR;
+            }
+            else
+            {
+                datas[4 + 27] = (byte)CarType.AGV;
+            }
+
+            datas[4 + 30] = 0x0d;
+            datas[4 + 31] = 0x0a;
+
+            return Send(datas);
+        }
+
+        public bool SendCarShowImage(CarShowCmd cmd)
+        {
+            byte[] datas = new byte[36];
+            datas[0] = 0xfd; //zigbee帧头
+            datas[1] = 32;   //数据长度
+            ushort id = Convert.ToUInt16(cmd.carId, 16);
+            datas[2] = (byte)(id & 0xff);
+            datas[3] = (byte)((id >> 8) & 0xff);
+            datas[4] = 0x7e;
+            datas[4 + 1] = 0x7e;
+            datas[4 + 2] = 32;
+            datas[4 + 3] = 0x07; //命令
+            datas[4 + 6] = (byte)(id & 0xff); //目的ID
+            datas[4 + 7] = (byte)((id >> 8) & 0xff);
+            //为了解析简单,数组必须是2
+            if(cmd.channelIds != null && cmd.picCodes != null && cmd.picCodes.Length > 1 && cmd.channelIds.Length > 1)
+            {
+                datas[8] = cmd.picCodes[0];
+                datas[9] = cmd.picCodes[1];
+            }
+            //else
+            //{
+            //    //否则索引号为0,默认不显示
+            //}
+            if (id != 0xfe04)
+            {
+                datas[4 + 27] = (byte)CarType.AMR;
+            }
+            else
+            {
+                datas[4 + 27] = (byte)CarType.AGV;
+            }
+
+            datas[4 + 30] = 0x0d;
+            datas[4 + 31] = 0x0a;
+
+            return Send(datas);
+        }
+    }
+}

+ 10 - 0
Common/ThreadLocalContext.cs

@@ -0,0 +1,10 @@
+namespace AmrControl.Common
+{
+    public class ThreadLocalContext
+    {
+        /// <summary>
+        /// 任务id
+        /// </summary>
+        public string taskId { get; set; }
+    }
+}

+ 30 - 0
Common/ThreadLocalManager.cs

@@ -0,0 +1,30 @@
+namespace AmrControl.Common
+{
+    public class ThreadLocalManager
+    {
+        private static readonly AsyncLocal<ThreadLocalContext> _current = new();
+        /// <summary>
+        /// 设置上下文
+        /// </summary>
+        /// <param name="context"></param>
+        public static void Set(ThreadLocalContext context)
+        {
+            _current.Value = context;
+        }
+        /// <summary>
+        /// 获取上下文内容
+        /// </summary>
+        /// <returns></returns>
+        public static ThreadLocalContext Get()
+        {
+            return _current.Value;
+        }
+        /// <summary>
+        /// 移除上下文
+        /// </summary>
+        public static void Remove()
+        {
+            _current.Value = null;
+        }
+    }
+}

+ 207 - 0
Controllers/JgrController.cs

@@ -0,0 +1,207 @@
+using AmrControl.ADS;
+using AmrControl.Common;
+using AmrControl.DB.Models;
+using AmrControl.Dto;
+using AmrControl.JGR;
+using AmrControl.services;
+using AmrControl.Vo;
+using Microsoft.AspNetCore.Mvc;
+using NPOI.SS.Formula.Functions;
+using Org.BouncyCastle.Asn1.Ocsp;
+using System.Threading.Tasks;
+
+namespace AmrControl.Controllers
+{
+    [ApiController]
+    public class JgrController : ControllerBase
+    {
+        /// <summary>
+        /// 给车的命令
+        /// </summary>
+        /// <returns></returns>
+        /// 
+        [HttpPost]
+        [Route("api/v1/jgr/cmd")]
+        public HttpStatus<string> SetCmd([FromBody] JgrDebugDto debugDto)
+        {
+            string str = JGRManager.GetInstance().SetCmd(debugDto);
+            if(str == "0")
+            {
+                HttpStatus<string> status = new HttpStatus<string>();
+                status.Code = Dto.Code.Success;
+                return status;
+            }
+            else
+            {
+                HttpStatus<string> status = new HttpStatus<string>();
+                status.Code = Dto.Code.Error;
+                status.Data = str;
+                return status;
+            }
+        }
+
+        [HttpGet]
+        [Route("api/v1/jgr/call-car")]
+        public HttpStatus<string> CallCar([FromQuery] string stanCode)
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            if (!string.IsNullOrEmpty(stanCode) && !string.IsNullOrWhiteSpace(stanCode))
+            {
+                var station = JGRManager.GetInstance().getStationByNo(stanCode);
+                if (station == null || station == default)
+                {
+                    status.Code = Dto.Code.Error;
+                    status.Msg = "非法的工站编号";
+                    return status;
+                }
+                //获取空的地车
+                string taskId = TaskExecManager.getInstance().genTaskId();
+                Proxy taskProxy = new Proxy(() =>
+                {
+                    JGRManager.GetInstance().moveCarToDestAsync(JgrType.RailCar, station.positionX, station.positionY);
+                });
+                TaskExecManager.getInstance().addTask(taskProxy, taskId);
+                status.Code = Dto.Code.Success;
+                status.Data = taskId;
+                return status;
+            }
+            status.Code = Dto.Code.Error;
+            status.Msg = "非法的工站编号";
+            return status;
+        }
+        /// <summary>
+        /// 呼叫空闲轨道车到工位
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("/api/v1/jgr/call-amr-car")]
+        public HttpStatus<string> CallAmrCar([FromBody] CallAmrCarDTO dto)
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            if (!string.IsNullOrEmpty(dto.stanCode) && !string.IsNullOrWhiteSpace(dto.stanCode))
+            {
+                var station = JGRManager.GetInstance().getStationByNo(dto.stanCode);
+                if (station == null || station == default)
+                {
+                    status.Code = Dto.Code.Error;
+                    status.Msg = "非法的工站编号";
+                    return status;
+                }
+                string taskId = TaskExecManager.getInstance().genTaskId();
+                Proxy taskProxy = new Proxy(() =>
+                {
+                    JGRManager.GetInstance().moveCarToDestAsync(JgrType.RailCar, station.positionX, station.positionY);
+                });
+                TaskExecManager.getInstance().addTask(taskProxy, taskId);
+                status.Code = Dto.Code.Success;
+                status.Data = taskId;
+                return status; 
+            }
+            status.Code = Dto.Code.Error;
+            status.Msg = "非法的工站编号";
+            return status;
+        }
+        /// <summary>
+        /// 移动车辆到指定位置
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("/api/v1/jgr/call-car-to-position")]
+        public HttpStatus<string> callCarToPosition([FromBody] MoveCarToPosDTO dto)
+        {
+            HttpStatus<string> status = new();
+            if (!string.IsNullOrEmpty(dto.carId) && !string.IsNullOrWhiteSpace(dto.carId))
+            {
+                //查找车辆信息
+                List<JGR_Tc_Model> carStatus = JGRManager.GetInstance().GetJgrs();
+                var carInfo = carStatus.Where(m => m.jgrSN == dto.carId).FirstOrDefault();
+                if (carInfo == null)
+                {
+                    status.Code = Dto.Code.Error;
+                    status.Msg = "非法的车辆编号信息";
+                    return status;
+                }
+                string taskId = TaskExecManager.getInstance().genTaskId();
+                int dx = -1, dy = -1;
+                if (!string.IsNullOrEmpty(dto.stanCode) && !string.IsNullOrWhiteSpace(dto.stanCode))
+                {
+                    var station = JGRManager.GetInstance().getStationByNo(dto.stanCode);
+                    if (station == null || station == default)
+                    {
+                        status.Code = Dto.Code.Error;
+                        status.Msg = "非法的工站编号";
+                        return status;
+                    }
+                    dx = station.positionX;
+                    dy = station.positionY;
+                } else
+                {
+                    //判断新坐标数据是否存在
+                    if(dto.x>0 && dto.y>0)
+                    {
+                        dx = dto.x;
+                        dy = dto.y;
+                    } else
+                    {
+                        status.Code = Dto.Code.Error;
+                        status.Msg = "位置坐标异常";
+                        return status;
+                    }
+                }
+                if (dx > 0 && dy > 0)
+                {
+                    Proxy taskProxy = new Proxy(() =>
+                    {
+                        JGRManager.GetInstance().moveCarToDestAsync(carInfo, dx, dy);
+                    });
+                    TaskExecManager.getInstance().addTask(taskProxy, taskId);
+                    status.Code = Dto.Code.Success;
+                    status.Data = taskId;
+                    return status;
+                }
+                else
+                {
+                    status.Code = Dto.Code.Error;
+                    status.Msg = "位置坐标异常";
+                    return status;
+                }
+               
+            }
+            status.Code = Dto.Code.Error;
+            status.Msg = "非法的车辆编号信息";
+            return status;
+        }
+
+        /// <summary>
+        /// 查询指定车辆状态
+        /// </summary>
+        /// <param name="carId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/jgr/query-car-state")]
+        public HttpStatus<JGR_Tc_Model> QueryCarState([FromQuery] string carId)
+        {
+            return new() { 
+                Code = Dto.Code.Success,
+                Data = JGRManager.GetInstance().Get_Tc_Models().Where(item => item.jgrSN == carId).First()
+            };
+        }
+
+            /// <summary>
+            /// 设置车或者充电桩的当前位置
+            /// </summary>
+            /// <param name="pos"></param>
+            /// <returns></returns>
+            [HttpPost]
+        [Route("api/v1/jgr/xy")]
+        public HttpStatus<string> SetXY([FromBody] NewJgrDto pos)
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            status.Data = JGRManager.GetInstance().SetNewXY(pos.SN, pos.positionX, pos.positionY);
+            return status;
+        }
+    }
+}

+ 267 - 0
Controllers/ProductLineController.cs

@@ -0,0 +1,267 @@
+using AmrControl.ADS;
+using AmrControl.Common;
+using AmrControl.Dto;
+using AmrControl.JGR;
+using AmrControl.services;
+using Microsoft.AspNetCore.Mvc;
+using System.Text;
+
+namespace AmrControl.Controllers
+{
+    [ApiController]
+    public class ProductLineController : ControllerBase
+    {
+        [HttpPost]
+        [Route("api/v1/line/start")]
+        public HttpStatus<string> Start()
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        [HttpPost]
+        [Route("api/v1/line/shutdown")]
+        public HttpStatus<string> Shutdown()
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 获得所有的小车,不管是否在线
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/line/jars")]
+        public HttpStatus<List<JGR_Tc_Model>> GetJars()
+        {
+            HttpStatus<List<JGR_Tc_Model>> status = new HttpStatus<List<JGR_Tc_Model>>();
+            status.Code = Dto.Code.Success;
+            status.Data = JGRManager.GetInstance().GetJgrs();
+            return status;
+        }
+
+        /// <summary>
+        /// 获得所有的充电桩,不管是否在线
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/line/chargingStations")]
+        public HttpStatus<List<ChargingStationDataModel>> GetChargingStations()
+        {
+            HttpStatus<List<ChargingStationDataModel>> status = new HttpStatus<List<ChargingStationDataModel>>();
+            status.Code = Dto.Code.Success;
+            status.Data = JGRManager.GetInstance().getCharges();
+            return status;
+        }
+
+        /// <summary>
+        /// 获得所有工位
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/line/stations")]
+        public HttpStatus<List<StationDataModel>> GetStations()
+        {
+            HttpStatus<List<StationDataModel>> status = new HttpStatus<List<StationDataModel>>();
+            status.Code = Dto.Code.Success;
+            status.Data = JGRManager.GetInstance().getStations();
+            return status;
+        }
+
+        /// <summary>
+        /// 获得所有工位
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/line/get-station")]
+        public HttpStatus<StationDataModel> GetStations([FromQuery] String stanCode)
+        {
+            HttpStatus<StationDataModel> status = new HttpStatus<StationDataModel>();
+            StationDataModel? model = JGRManager.GetInstance().getStation(stanCode);
+            if(model !=null && model != default)
+            {
+                status.Code = Dto.Code.Success;
+                status.Data = model;
+            } else
+            {
+                status.Code = Dto.Code.Fail;
+            }
+            return status;
+        }
+
+        /// <summary>
+        /// 产线设置成调试模式,轨道车可以手动运动
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/line/debug")]
+        public HttpStatus<string> SetProductLineDebugModel()
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 产线设置成正常模式,轨道车全自动控制
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/line/normal")]
+        public HttpStatus<string> SetProductLineNormalModel()
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 仓储设置成调试模式,仓储车可以手动运动
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/storage/debug")]
+        public HttpStatus<string> SetStorageDebugModel()
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 仓储设置成正常模式,仓储车全自动控制
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/storage/normal")]
+        public HttpStatus<string> SetStorageNormalModel()
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 新建或者修改小车或者充电桩,匹配Type和SN
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/line/new")]
+        public HttpStatus<string> NewJGR([FromBody] NewJgrDto jgrDto)
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+
+            //目前先处理车的地址修改
+
+
+            return status;
+        }
+
+        /// <summary>
+        /// 删除小车或者充电桩,匹配Type和SN
+        /// </summary>
+        /// <returns></returns>
+        [HttpDelete]
+        [Route("api/v1/line/delete")]
+        public HttpStatus<string> DeleteJGR([FromBody] NewJgrDto jgrDto)
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 获取设备类型
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/line/deviceType")]
+        public HttpStatus<List<KeyValuePair<int, string>>> GetDeviceType()
+        {
+            HttpStatus<List<KeyValuePair<int, string>>> status = new HttpStatus<List<KeyValuePair<int, string>>>();
+            status.Data = new List<KeyValuePair<int, string>>();
+            status.Data.Add(new KeyValuePair<int, string>(0, "仓储车"));
+            status.Data.Add(new KeyValuePair<int, string>(1, "运输车"));
+            status.Data.Add(new KeyValuePair<int, string>(2, "仓储充电桩"));
+            status.Data.Add(new KeyValuePair<int, string>(3, "轨道充电桩"));
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 获取信道类型
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/line/freqChannel")]
+        public HttpStatus<List<KeyValuePair<int, string>>> GetFreqChannel()
+        {
+            HttpStatus<List<KeyValuePair<int, string>>> status = new HttpStatus<List<KeyValuePair<int, string>>>();
+            status.Data = new List<KeyValuePair<int, string>>();
+            status.Data.Add(new KeyValuePair<int, string>(50, "910MHz"));
+            status.Data.Add(new KeyValuePair<int, string>(75, "915MHz"));
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 获取信道类型
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/line/jgrErrorCode")]
+        public HttpStatus<List<KeyValuePair<int, string>>> GetJgrErrorCodes()
+        {
+            HttpStatus<List<KeyValuePair<int, string>>> status = new HttpStatus<List<KeyValuePair<int, string>>>();
+            status.Data = new List<KeyValuePair<int, string>>();
+            status.Data.Add(new KeyValuePair<int, string>(0, "正常"));
+            status.Data.Add(new KeyValuePair<int, string>(1, "通信失联"));
+            status.Data.Add(new KeyValuePair<int, string>(2, "电机掉电"));
+            status.Data.Add(new KeyValuePair<int, string>(3, "电量不足"));
+            status.Data.Add(new KeyValuePair<int, string>(4, "传感器故障"));
+            status.Data.Add(new KeyValuePair<int, string>(5, "充电异常"));
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 获取轨道和仓储的配置信息
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/line/cells")]
+        public HttpStatus<GridCellsDto> GetCells()
+        {
+            HttpStatus<GridCellsDto> status = new HttpStatus<GridCellsDto>();
+            status.Data = JGRManager.GetInstance().getCells();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 获取轨道和仓储的配置信息
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/line/cancel-task")]
+        public HttpStatus<bool> CancelTask([FromQuery] string taskId)
+        {
+            HttpStatus<bool> status = new();
+            if (TaskExecManager.getInstance().checkTaskIsCompleted(taskId))
+            {
+                status.Data = false;
+                status.Code = Dto.Code.Error;
+                status.Msg = "任务已完成,无法取消";
+                return status;
+            }
+            JGRManager.GetInstance().notifyCancelTask(taskId);
+            status.Data = true;
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+    }
+}

+ 499 - 0
Controllers/StorageController.cs

@@ -0,0 +1,499 @@
+using AmrControl.ADS;
+using AmrControl.Common;
+using AmrControl.DB;
+using AmrControl.DB.Models;
+using AmrControl.Dto;
+using AmrControl.JGR;
+using AmrControl.services;
+using AmrControl.Vo;
+using Microsoft.AspNetCore.Mvc;
+using Newtonsoft.Json;
+using NPOI.OpenXmlFormats.Dml;
+
+// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
+
+namespace AmrControl.Controllers
+{
+    
+    [ApiController]
+    public class StorageController : ControllerBase
+    {
+        private readonly MyDbHandler handler;
+        public StorageController(MyDbHandler dbhandler)
+        {
+            handler = dbhandler;
+        }
+
+        /// <summary>
+        /// 获取今日任务
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/storage/tasks")]
+        public HttpStatus<List<IssueNoteDto>> GetTodayTasks()
+        {
+            HttpStatus<List<IssueNoteDto>> status = new HttpStatus<List<IssueNoteDto>>();
+            status.Code = Dto.Code.Success;
+            //status.Data = MockData.getIssueNodeDto();
+            //return status;
+
+            status.Data = handler.getIssueNodeDto();
+            return status;
+        }
+
+        /// <summary>
+        /// 查询物料号所属的储位信息
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/storage/location")]
+        public HttpStatus<List<StorageBox>> QueryLocation(string materialID)
+        {
+            HttpStatus<List<StorageBox>> status = new HttpStatus<List<StorageBox>>();
+            status.Code = Dto.Code.Success;
+            status.Data = MockData.getStorageBox();
+            return status;
+        }
+
+        /// <summary>
+        /// 查询仓储格子的储位信息
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/storage/gridLocation")]
+        public HttpStatus<List<StorageBox>> QueryGridLocation(int X,int Y)
+        {
+            HttpStatus<List<StorageBox>> status = new HttpStatus<List<StorageBox>>();
+            status.Code = Dto.Code.Success;
+            status.Data = MockData.getStorageBox(X, Y);
+            return status;
+        }
+
+        /// <summary>
+        /// 查询物料盒的RFID来返回物料盒所含物料的内容
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/storage/storageBox")]
+        public HttpStatus<StorageBox> QueryStorageBox(string boxRFID)
+        {
+            HttpStatus<StorageBox> status = new HttpStatus<StorageBox>();
+            status.Code = Dto.Code.Success;
+            status.Data = MockData.getStorageBox().FirstOrDefault(x => x.BoxRFID == boxRFID);
+            return status;
+        }
+
+        /// <summary>
+        /// 出库并做数量扣除,返回出库的数量
+        /// </summary>
+        /// <param name="location"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/storage/retrieval")]
+        public HttpStatus<List<RequireRawBom>> Retrieval([FromBody]RetrievalDto retrievalDto)
+        {
+            HttpStatus<List<RequireRawBom>> status = new HttpStatus<List<RequireRawBom>>();
+            status.Code = Dto.Code.Success;
+            //让数量和需求相满足
+            foreach (var item in retrievalDto.RequireRawBoms)
+            {
+                item.ReleaseQuantity = item.DemandQuantity;
+            }
+            status.Data = retrievalDto.RequireRawBoms;
+            return status;
+        }
+
+        /// <summary>
+        /// 物料盒入库,从入库点搬进仓储,坐标会根据系统自动重新分配
+        /// </summary>
+        /// <param name="boxRFID"></param>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/storage/moveIn")]
+        public HttpStatus<string> StorageBoxMoveIn(string boxRFID)
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 物料盒出库,从当前仓储点搬出到捡料位
+        /// </summary>
+        /// <param name="boxRFID"></param>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/storage/moveOut")]
+        public HttpStatus<string> StorageBoxMoveOut(string boxRFID)
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 物料盒调仓位
+        /// </summary>
+        /// <param name="boxRFID"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/storage/moveTo")]
+        public HttpStatus<List<KeyValuePair<int, string>>> StorageBoxMoveTo([FromBody] BoxMovingStatusDto movingStatus)
+        {
+            HttpStatus<List<KeyValuePair<int, string>>> status = new HttpStatus<List<KeyValuePair<int, string>>>();
+            status.Data = new List<KeyValuePair<int, string>>();
+            status.Data.Add(new KeyValuePair<int, string>(0, "第1步,前往目标点X1-Y2"));
+            status.Data.Add(new KeyValuePair<int, string>(1, "第2步,抓取顶部物料盒"));
+            status.Data.Add(new KeyValuePair<int, string>(2, "第3步,移走顶部物料盒到X2-Y3"));
+            status.Data.Add(new KeyValuePair<int, string>(3, "第4步,放下物料盒子"));
+            status.Data.Add(new KeyValuePair<int, string>(4, "第5步,前往目标点X1-Y2"));
+            status.Data.Add(new KeyValuePair<int, string>(5, "第6步,抓取目标物料盒"));
+            status.Data.Add(new KeyValuePair<int, string>(6, "第7步,前往调仓点X4-Y5"));
+            status.Data.Add(new KeyValuePair<int, string>(7, "第7步,放下物料盒子,调仓完成"));
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 呼叫空闲的轨道车到检料位
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/line/freeJGR")]
+        public HttpStatus<string> CallFreeRailCar()
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        /// <summary>
+        /// 调度检料位的轨道车到工位,坐标自动感知
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/line/jgrToStation")]
+        public HttpStatus<string> MoveRailCarToStation()
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+
+            return status;
+        }
+
+        /// <summary>
+        /// 移动轨道车到目标点位
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("api/v1/line/jgrMoveTo")]
+        public HttpStatus<string> RailCarMoveTo([FromBody] JgrMovingDto movingDto)
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+
+        /// <summary>
+        /// 获取仓储的信息
+        /// </summary>
+        /// <param name="StorageID"></param>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/storage/storageStatus")]
+        public HttpStatus<StorageStatus> GetStorageStatus(string StorageID)
+        {
+            HttpStatus<StorageStatus> status = new HttpStatus<StorageStatus>();
+            status.Code = Dto.Code.Success;
+            status.Data = new StorageStatus();
+            status.Data.StorageName = "1号仓储柜";
+            status.Data.TodayMoveOutCount = 10;
+            status.Data.TodayMoveInCount = 5;
+            status.Data.TotalLocation = 300;
+            status.Data.FreeLocation = 280;
+            status.Data.MaterialCount = 105;
+            return status;
+        }
+
+        /// <summary>
+        /// 获取捡料工位的信息,包括是否有车
+        /// </summary>
+        /// <param name="StorageID"></param>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("api/v1/storage/pickingStation")]
+        public HttpStatus<PickingStationStatus> GetPickingStation(string StorageID)
+        {
+            HttpStatus<PickingStationStatus> status = new HttpStatus<PickingStationStatus>();
+            status.Code = Dto.Code.Success;
+            status.Data = new PickingStationStatus();
+            status.Data.ContainsJAR = false;
+            status.Data.ContainsBox = false;
+            return status;
+        }
+
+        /// <summary>
+        /// 入库接口
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("/api/v1/storage/in-storage")]
+        public HttpStatus<InStorageVO> inStroage([FromBody] InStorageDTO dto)
+        {
+            HttpStatus<InStorageVO> status = new();
+            if (!string.IsNullOrEmpty(dto.carId) && !string.IsNullOrWhiteSpace(dto.carId))
+            {
+                //查找车辆信息
+                List<JGR_Tc_Model> carStatus = JGRManager.GetInstance().GetJgrs();
+                var carInfo = carStatus.Where(m => m.jgrSN == dto.carId).FirstOrDefault();
+                if (carInfo == null)
+                {
+                    status.Code = Dto.Code.Error;
+                    status.Msg = "非法的车辆编号信息";
+                    return status;
+                }
+                if (string.IsNullOrEmpty(dto.storagePos) || string.IsNullOrWhiteSpace(dto.storagePos))
+                {
+                    status.Code = Dto.Code.Error;
+                    status.Msg = "非法的储位坐标信息";
+                    return status;
+                }
+                bool isOk = JGRManager.GetInstance().checkStoragePos(int.Parse(dto.storagePos.Split(',')[0]), int.Parse(dto.storagePos.Split(',')[1]));
+                if(!isOk)
+                {
+                    status.Code = Dto.Code.Error;
+                    status.Msg = "非法的储位坐标信息";
+                    return status;
+                }
+                string taskId = TaskExecManager.getInstance().genTaskId();
+                Proxy taskProxy = new(() =>
+                {
+                    //移动轨道车到入库位
+                    //JGRManager.GetInstance().moveCarToDestAsync(carInfo, JGRManager.GetInstance().inAmrPos[0], JGRManager.GetInstance().inAmrPos[1]);
+                    JGRManager.GetInstance().storageCarToInAsync(carInfo, dto);
+                });
+                TaskExecManager.getInstance().addTask(taskProxy, taskId);
+                InStorageVO vo = new()
+                {
+                    taskId = taskId
+                };
+                status.Code = Dto.Code.Success;
+                status.Data = vo;
+                return status;
+            }
+            status.Code = Dto.Code.Error;
+            status.Msg = "非法的车辆编号信息";
+            return status;
+        }
+        /// <summary>
+        /// 判断任务是否完成
+        /// </summary>
+        /// <param name="taskId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        [Route("/api/v1/storage/check-task-completed")]
+        public HttpStatus<bool> checkTaskCompleted([FromQuery] string taskId)
+        {
+            HttpStatus<bool> status = new();
+            if (string.IsNullOrEmpty(taskId) || string.IsNullOrWhiteSpace(taskId))
+            {
+                status.Code = Dto.Code.Error;
+                status.Msg = "非法的任务id信息";
+                return status;
+            }
+            status.Code = Dto.Code.Success;
+            status.Data = TaskExecManager.getInstance().checkTaskIsCompleted(taskId);
+            return status;
+        }
+        /// <summary>
+        /// 出库逻辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("/api/v1/storage/out-storage")]
+        public HttpStatus<OutStorageVO> outStorage([FromBody] OutStorageDTO dto)
+        {
+            HttpStatus<OutStorageVO> status = new();
+            //判断仓储坐标是否存在
+            if (string.IsNullOrEmpty(dto.storagePos) || string.IsNullOrWhiteSpace(dto.storagePos))
+            {
+                status.Code = Dto.Code.Error;
+                status.Msg = "非法的储位坐标信息";
+                return status;
+            }
+            ////呼叫空的轨道车到出库位置
+            //List<JGR_Tc_Model> carStatus = JGRManager.GetInstance().GetJgrs();
+            ////获取空的地车
+            //JGR_Tc_Model? free_car = null;
+            //if (carStatus != null && carStatus.Count > 0)
+            //{
+            //    foreach (var car in carStatus)
+            //    {
+            //        if (car.jgrType == 1 && car.moveStatus == 0
+            //            && (string.IsNullOrEmpty(car.rfid)
+            //            || string.IsNullOrWhiteSpace(car.rfid)))
+            //        {
+            //            free_car = car;
+            //        }
+            //    }
+            //}
+            //if (free_car is null)
+            //{
+            //    status.Code = Dto.Code.Error;
+            //    status.Msg = "没有空闲的车辆,请稍后再试";
+            //    return status;
+            //}
+
+            //JGRManager.GetInstance().moveCarToDestAsync(free_car, JGRManager.GetInstance().outAmrPos[0], JGRManager.GetInstance().outAmrPos[1]);
+            //Thread.Sleep(200);
+            //carStatus = JGRManager.GetInstance().GetJgrs();
+            //if (carStatus is null)
+            //{
+            //    status.Code = Dto.Code.Error;
+            //    status.Msg = "没有空闲的车辆,请稍后再试";
+            //    return status;
+            //}
+            ////获取空闲的仓储车信息
+            //JGR_Tc_Model? storageCarInfo = storageCarInfo = carStatus.Where(m => m.jgrType == 0
+            //    && m.moveStatus == 0
+            //    && m.basketPosition == 0
+            //    && m.basketGripperStatus == 2
+            //    ).FirstOrDefault();
+            //if(storageCarInfo == null)
+            //{
+            //    status.Code = Dto.Code.Error;
+            //    status.Msg = "没有空闲的车辆,请稍后再试";
+            //    return status;
+            //}
+            //OutStorageVO vo = new()
+            //{
+            //    taskId = Guid.NewGuid().ToString(),
+            //    carId = free_car.jgrSN
+            //};
+            bool isOk = JGRManager.GetInstance().checkStoragePos(int.Parse(dto.storagePos.Split(',')[0]), int.Parse(dto.storagePos.Split(',')[1]));
+            if (!isOk)
+            {
+                status.Code = Dto.Code.Error;
+                status.Msg = "非法的储位坐标信息";
+                return status;
+            }
+            
+            int dx = 0;
+            int dy = 0;
+            if (!string.IsNullOrEmpty(dto.stanCode) 
+                && !string.IsNullOrWhiteSpace(dto.stanCode))
+            {
+                var station = JGRManager.GetInstance().getStationByNo(dto.stanCode);
+                if (station == null || station == default)
+                {
+                    status.Code = Dto.Code.Error;
+                    status.Msg = "非法的工站编号";
+                    return status;
+                }
+                dx = station.positionX; 
+                dy = station.positionY;
+            } else if (!string.IsNullOrEmpty(dto.destPos)
+                && !string.IsNullOrWhiteSpace(dto.destPos))
+            {
+                dx = int.Parse(dto.destPos.Split(',')[0]);
+                dy = int.Parse(dto.destPos.Split(',')[1]);
+            } else
+            {
+                dx = JGRManager.GetInstance().selectStationPos[0];
+                dy = JGRManager.GetInstance().selectStationPos[1];
+            }
+            string taskId = TaskExecManager.getInstance().genTaskId();
+            Proxy taskProxy = new (() =>
+            {
+                //移动轨道车到入库位
+                //JGRManager.GetInstance().moveCarToDestAsync(carInfo, JGRManager.GetInstance().inAmrPos[0], JGRManager.GetInstance().inAmrPos[1]);
+                JGRManager.GetInstance().storageCarToOutAsync(dto.storagePos, dx, dy);
+            });
+            TaskExecManager.getInstance().addTask(taskProxy, taskId);
+            OutStorageVO vo = new()
+            {
+                taskId = taskId
+            };
+            //获取到空的仓储车后,将仓储车信息反馈给服务端
+            //JGRManager.GetInstance().storageCarToOutAsync(storageCarInfo, free_car.jgrSN,
+            //    dto.storagePos, dx, dy, vo.taskId);
+            status.Code = Dto.Code.Success;
+            status.Data = vo;
+            return status;
+        }
+        /// <summary>
+        /// 调仓处理
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("/api/v1/storage/move-storage")]
+        public HttpStatus<MoveStorageVO> moveStorage([FromBody] MoveStorageDTO dto)
+        {
+            HttpStatus<MoveStorageVO> status = new();
+            //判断仓储坐标是否存在
+            if (string.IsNullOrEmpty(dto.sourcePos) 
+                || string.IsNullOrWhiteSpace(dto.sourcePos)
+                || string.IsNullOrEmpty(dto.destPos) 
+                    || string.IsNullOrWhiteSpace(dto.destPos)
+                    || dto.sourcePos == dto.destPos)
+            {
+                status.Code = Dto.Code.Error;
+                status.Msg = "非法的储位坐标信息";
+                return status;
+            }
+            bool spBreak = JGRManager.GetInstance().checkStoragePos(int.Parse(dto.sourcePos.Split(',')[0]), int.Parse(dto.sourcePos.Split(',')[1]));
+            bool dpBreak = JGRManager.GetInstance().checkStoragePos(int.Parse(dto.destPos.Split(',')[0]), int.Parse(dto.destPos.Split(',')[1]));
+            if (!spBreak || !dpBreak)
+            {
+                status.Code = Dto.Code.Error;
+                status.Msg = "非法的储位坐标信息";
+                return status;
+            }
+            ////获取空闲的仓储车信息
+            //List<JGR_Tc_Model>? carStatus = JGRManager.GetInstance().GetJgrs();
+            //if(carStatus == null)
+            //{
+            //    status.Code = Dto.Code.Error;
+            //    status.Msg = "未找到车辆信息";
+            //    return status;
+            //}
+            //JGR_Tc_Model? storageCarInfo = carStatus.Where(m => m.jgrType == 0
+            //    && m.moveStatus == 0
+            //    && m.basketPosition == 0
+            //    && m.basketGripperStatus == 2
+            //    ).FirstOrDefault();
+            //if(storageCarInfo == null)
+            //{
+            //    status.Code = Dto.Code.Error;
+            //    status.Msg = "没有空闲车辆";
+            //    return status;
+            //}
+            //MoveStorageVO vo = new()
+            //{
+            //    taskId = Guid.NewGuid().ToString(),
+            //    carId = storageCarInfo.jgrSN
+            //};
+            ////获取到空的仓储车后,将仓储车信息反馈给服务端
+            //JGRManager.GetInstance().moveStorageAsync(storageCarInfo, dto, vo.taskId);
+            string taskId = TaskExecManager.getInstance().genTaskId();
+            Proxy taskProxy = new(() =>
+            {
+                //移动轨道车到入库位
+                //JGRManager.GetInstance().moveCarToDestAsync(carInfo, JGRManager.GetInstance().inAmrPos[0], JGRManager.GetInstance().inAmrPos[1]);
+                JGRManager.GetInstance().moveStorageAsync(dto);
+            });
+            TaskExecManager.getInstance().addTask(taskProxy, taskId);
+            MoveStorageVO vo = new()
+            {
+                taskId = taskId
+            };
+            status.Code = Dto.Code.Success;
+            status.Data = vo;
+            return status;
+        }
+    }
+}

+ 33 - 0
Controllers/UserController.cs

@@ -0,0 +1,33 @@
+using AmrControl.Dto;
+using Microsoft.AspNetCore.Mvc;
+
+// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
+
+namespace AmrControl.Controllers
+{
+
+    [ApiController]
+    public class UserController : ControllerBase
+    {
+        [HttpPost]
+        [Route("api/v1/login")]
+        public HttpStatus<UserVo> Login([FromBody] UserDto user)
+        {
+            HttpStatus<UserVo> status = new HttpStatus<UserVo>();
+            status.Data = new UserVo();
+            status.Data.UserName = user.UserName;
+            status.Data.Token = "hello world!!";
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+
+        [HttpPost]
+        [Route("api/v1/logout")]
+        public HttpStatus<string> logout([FromBody] UserVo user)
+        {
+            HttpStatus<string> status = new HttpStatus<string>();
+            status.Code = Dto.Code.Success;
+            return status;
+        }
+    }
+}

+ 39 - 0
DB/Models/AppConfig.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace AmrControl.DB.Model
+{
+    /// <summary>
+    /// 程序配置项
+    /// </summary>
+    [Table("app_configs")]
+    public class AppConfig
+    {
+        /// <summary>
+        /// ID
+        /// </summary>
+        [Key]
+        public long ID { get; set; }
+        /// <summary>
+        /// 应用名称
+        /// </summary>
+        public string App { get; set; }
+        /// <summary>
+        /// 段
+        /// </summary>
+        public string Session { get; set; }
+        /// <summary>
+        /// 键
+        /// </summary>
+        public string Key { get; set; }
+        /// <summary>
+        /// 值
+        /// </summary>
+        public string Data { get; set; }
+    }
+}

+ 58 - 0
DB/Models/Charger.cs

@@ -0,0 +1,58 @@
+using Newtonsoft.Json;
+using Org.BouncyCastle.Bcpg;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace AmrControl.DB.Models
+{
+    /// <summary>
+    /// 充电桩的数据库模型
+    /// </summary>
+    [Table("chargers")]
+    public class Charger
+    {
+        [JsonIgnore]
+        [Key]
+        public long ID { get; set; }
+        /// <summary>
+        /// 充电桩编号
+        /// </summary>
+        public string chargerSN { get; set; }
+        /// <summary>
+        /// 硬件上返回的充电桩类型,=2仓储充电桩,=3轨道充电桩
+        /// </summary>
+        public byte chargerType { get; set; }
+        /// <summary>
+        /// 充电桩编码,硬件上的编码
+        /// </summary>
+        public byte chargerID { get; set; }
+        /// <summary>
+        /// 通信地址
+        /// </summary>
+        public ushort rcAddr { get; set; }
+        /// <summary>
+        /// 通信频率码
+        /// </summary>
+        public byte rcFreqCode { get; set; }
+        /// <summary>
+        /// 是否启用
+        /// </summary>
+        public bool enable { get; set; }
+        /// <summary>
+        /// 物理位置X
+        /// </summary>
+        public int positionX { get; set; }
+        /// <summary>
+        /// 物理位置Y
+        /// </summary>
+        public int positionY { get; set; }
+        /// <summary>
+        /// 图上显示位置X
+        /// </summary>
+        public int displayX { get; set; }
+        /// <summary>
+        /// 图上显示位置Y
+        /// </summary>
+        public int displayY { get; set; }
+    }
+}

+ 25 - 0
DB/Models/IssueNote.cs

@@ -0,0 +1,25 @@
+using Newtonsoft.Json;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace AmrControl.DB.Models
+{
+    /// <summary>
+    /// 出库单的信息
+    /// </summary>
+    [Table("issuenotes")]
+    public class IssueNote
+    {
+        [JsonIgnore]
+        [Key]
+        public long ID { get; set; }
+
+        public string issueNoteID { get; set; }
+        public string? mesTaskID { get; set; }
+        public string? stationID { get; set; }
+
+        public string? jsonRawBoms { get; set; }
+        public DateTime? createTime { get; set; }
+        public DateTime? finishedTime { get; set; }
+    }
+}

+ 28 - 0
DB/Models/JGR.cs

@@ -0,0 +1,28 @@
+using Newtonsoft.Json;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace AmrControl.DB.Models
+{
+    /// <summary>
+    /// 小车的数据库模型
+    /// </summary>
+    [Table("jgrs")]
+    public class JGR
+    {
+        [JsonIgnore]
+        [Key]
+        public long ID { get; set; }
+
+        public string jgrSN { get; set; }
+        public byte jgrType { get; set; }
+        public byte jgrID { get; set; }
+        public ushort rcAddr { get; set; }
+        public byte rcFreqCode { get; set; }
+        public bool enable { get; set; }
+        public int positionX { get; set; }
+        public int positionY { get; set; }
+        public int displayX { get; set; }
+        public int displayY { get; set; }
+    }
+}

+ 60 - 0
DB/Models/Log.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text;
+
+namespace AmrControl.DB.Model
+{
+    [Table("logs")]
+    public class Log
+    {
+        /**[JsonConverter(typeof(LongToStringConverter))]*/
+        /// <summary>
+        /// ID
+        /// </summary>
+        [Key]
+        public long ID { get; set; }
+        public string Workstation { get; set; }
+        /// <summary>
+        /// Message
+        /// </summary>
+        public string Message { get; set; }
+        /// <summary>
+        /// Level
+        /// </summary>
+        public LevelEnum MsgLevel { get; set; }
+        /// <summary>
+        /// 备注
+        /// </summary>
+        public string MsgType { get; set; }
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public DateTime CreateTime { get; set; }
+    }
+
+    public enum LevelEnum : int
+    {
+        /// <summary>
+        /// 警告
+        /// </summary>
+        [Display(Name = "警告")]
+        Warn = 0,
+        /// <summary>
+        /// 正常
+        /// </summary>
+        [Display(Name = "正常")]
+        Normal = 1,
+        /// <summary>
+        /// 错误
+        /// </summary>
+        [Display(Name = "错误")]
+        Error = 2,
+        /// <summary>
+        /// 调试
+        /// </summary>
+        [Display(Name = "调试")]
+        Debug = 3,
+    }
+}

+ 24 - 0
DB/Models/Material.cs

@@ -0,0 +1,24 @@
+using Newtonsoft.Json;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace AmrControl.DB.Models
+{
+    /// <summary>
+    /// 物料的数据库模型
+    /// </summary>
+    [Table("materials")]
+    public class Material
+    {
+        [JsonIgnore]
+        [Key]
+        public long ID { get; set; }
+
+        public string locationID { get; set; }
+        public string materialID { get; set; }
+        public string materialType { get; set; }
+        public int quantity { get; set; }
+        public string jsonPars { get; set; }
+
+    }
+}

+ 25 - 0
DB/Models/Station.cs

@@ -0,0 +1,25 @@
+using Newtonsoft.Json;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace AmrControl.DB.Models
+{
+    /// <summary>
+    /// 工位的数据模型
+    /// </summary>
+    [Table("stations")]
+    public class Station
+    {
+        [JsonIgnore]
+        [Key]
+        public long ID { get; set; }
+        public string stationID { get; set; }
+        public byte stationType { get; set; }
+        public string displayName { get; set; }
+        public bool enable { get; set; }
+        public int positionX { get; set; }
+        public int positionY { get; set; }
+        public int displayX { get; set; }
+        public int displayY { get; set; }
+    }
+}

+ 22 - 0
DB/Models/Storage.cs

@@ -0,0 +1,22 @@
+using Newtonsoft.Json;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace AmrControl.DB.Models
+{
+    /// <summary>
+    /// 仓储的数据模型
+    /// 
+    /// </summary>
+    [Table("storage")]
+    public class Storage
+    {
+        [JsonIgnore]
+        [Key]
+        public long ID { get; set; }
+        public string locationID { get; set; }
+        public int positionX { get; set; }
+        public int positionY { get; set; }
+        public int positionZ { get; set; }
+    }
+}

+ 73 - 0
DB/Models/UserModel.cs

@@ -0,0 +1,73 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace AmrControl.DB.Models
+{
+    /// <summary>
+    /// 用户实体
+    /// </summary>
+    [Table("users")]
+    public class UserModel
+    {
+        /// <summary>
+        /// ID 主键id
+        /// </summary>
+        [Key]
+        [Column("ID")]
+        public long ID { set; get; }
+
+        /// <summary>
+        /// 登录名
+        /// </summary>
+        [Column("UserName")]
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+        public string UserName { get; set; }
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+        /// <summary>
+        /// 密码
+        /// </summary>
+        [Column("PassWord")]
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+        public string PassWord { set; get; }
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+
+        /// <summary>
+        /// 上次登录时间
+        /// </summary>
+        [Column("LastLoginTime")]
+        public DateTime LastLoginTime { set; get; }
+
+        /// <summary>
+        /// 登录总次数
+        /// </summary>
+        [Column("LoginCount")]
+        public int LoginCount { set; get; }
+
+        /// <summary>
+        /// 备注
+        /// </summary>
+        [Column("Remark")]
+        public string? Remark { set; get; }
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        [Column("CreateTime")]
+        public DateTime CreateTime { set; get; }
+
+
+        /// <summary>
+        /// 更新时间
+        /// </summary>
+        [Column("UpdateTime")]
+        public DateTime UpdateTime { set; get; }
+
+
+        /// <summary>
+        /// 启用标识;0-未启用;1-启用
+        /// </summary>
+        [Column("Enable")]
+        public byte Enable { set; get; }
+
+    }
+}

+ 78 - 0
DB/MyDbContext.cs

@@ -0,0 +1,78 @@
+using Microsoft.EntityFrameworkCore;
+using AmrControl.DB.Models;
+using System.Linq.Expressions;
+using AmrControl.DB.Model;
+
+namespace AmrControl.DB
+{
+    /// <summary>
+    /// 数据库上下文配置
+    /// </summary>
+    public class MyDbContext: DbContext
+    {
+        /// <summary>
+        /// 连接字符串
+        /// </summary>
+        private readonly string connStr;
+        
+        public MyDbContext(string connStr) :base()
+        //public MyDbContext() :base()
+        {
+            this.connStr = connStr;
+            //this.connStr = "server=localhost;user=mysqltest;database=ms_jgr;port=3306;password=test1234;Charset=utf8;Pooling=true";
+        }
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="options"></param>
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+        {
+        }
+        /// <summary>
+        /// 
+        /// 构造实体
+        /// </summary>
+        /// <param name="modelBuilder"></param>
+        protected override void OnModelCreating(ModelBuilder modelBuilder)
+        {
+            //这条语句表示默认从本DLL中检索定义好的模型并创建对应的表,如果不这样做,那就需要在DbContext中定义所有模型并在这个函数中声明模型结构
+            modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyDbContext).Assembly);
+            base.OnModelCreating(modelBuilder);
+        }
+
+        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+        {
+            base.OnConfiguring(optionsBuilder);
+            //如果用mysql,则开启下面的语句
+            //if (this.connStr != null)
+            //{
+            //    optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
+            //}
+
+            //如果用sqlite,则开启下面的语句
+            if (this.connStr != null)
+            {
+                optionsBuilder.UseSqlite(connStr);
+            }
+        }
+
+        /// <summary>
+        /// 用户模型
+        /// </summary>
+#pragma warning disable IDE1006 // 命名样式
+        public DbSet<UserModel> userDb { get; set; }
+        public DbSet<Log> logs { get; set; }
+        public DbSet<AppConfig> appConfigs { get; set; }
+        public DbSet<Models.JGR> jgrs { get; set; }
+        public DbSet<Models.Charger> chargers { get; set; }
+        public DbSet<Models.Station> stations { get; set; }
+        public DbSet<Models.Material> materials { get; set; }
+        public DbSet<Models.Storage> storages { get; set; }
+        public DbSet<Models.IssueNote> issuenotes { get; set; }
+
+#pragma warning restore IDE1006 // 命名样式
+    }
+}

+ 193 - 0
DB/MyDbHandler.cs

@@ -0,0 +1,193 @@
+using AmrControl.ADS;
+using AmrControl.DB.Models;
+using AmrControl.Dto;
+using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
+
+namespace AmrControl.DB
+{
+    public class MyDbHandler
+    {
+        /// <summary>
+        /// 连接字符,用于动态创建MyDbContext使用
+        /// </summary>
+        private readonly string connStr;
+
+        public MyDbHandler(string connString)
+        {
+            //mysql用这一句
+            //connStr = connString;
+            //sqlite用这一句
+           connStr = "Filename=" + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, connString);
+        }
+
+        /// <summary>
+        /// 获取今日的出库单
+        /// </summary>
+        /// <returns></returns>
+        public List<IssueNoteDto> getIssueNodeDto()
+        {
+            List<IssueNoteDto> dots = new List<IssueNoteDto>();
+            using (var context = new MyDbContext(this.connStr))
+            {
+                var notes = context.issuenotes.ToList();
+                foreach (var item in notes)
+                {
+                    IssueNoteDto dto = new IssueNoteDto();
+                    dto.ID = item.issueNoteID;
+                    dto.MesTaskID = item.mesTaskID;
+                    dto.StationID = item.stationID;
+                    dto.RawBoms = JsonConvert.DeserializeObject<List<RequireRawBom>>(item.jsonRawBoms);
+                    dots.Add(dto);
+                }
+            }
+
+            return dots;
+        }
+
+        /// <summary>
+        /// 获得车的清单
+        /// </summary>
+        /// <returns></returns>
+        public List<JGR_Tc_Model> getJGRs()
+        {
+            List<JGR_Tc_Model> jgrs = new List<JGR_Tc_Model>();
+            using (var context = new MyDbContext(this.connStr))
+            {
+                var dbJgrs = context.jgrs.Where(x=>x.enable == true).ToList();
+                foreach (var item in dbJgrs)
+                {
+                    JGR_Tc_Model jgr = new JGR_Tc_Model();
+                    jgr.jgrID = item.jgrID;
+                    jgr.jgrSN = item.jgrSN;
+                    jgr.jgrType = item.jgrType;
+                    jgr.rcAddr = item.rcAddr ;
+                    jgr.rcFreqCode = item.rcFreqCode;
+                    jgr.currLocation_X = (short)item.positionX;
+                    jgr.currLocation_Y = (short)item.positionY;
+                    jgr.displayX = item.displayX;
+                    jgr.displayY = item.displayY;
+                    jgr.isOnline = 0;  //初始化的时候,是否在线为0
+                    jgr.isCharging = 0; //初始化的时候,没在充电为0
+                    jgr.isHealthy = 0; //初始化的时候,默认没有异常0
+                    jgrs.Add(jgr);
+                }
+            }
+            return jgrs;
+        }
+
+        public bool saveJGRs(List<JGR_Tc_Model> models)
+        {
+            List<JGR_Tc_Model> jgrs = new List<JGR_Tc_Model>();
+            using (var context = new MyDbContext(this.connStr))
+            {
+                var dbJgrs = context.jgrs.Where(x => x.enable == true).ToList();
+                foreach (var item in models)
+                {
+                    var dbModel = dbJgrs.Where(x => x.jgrSN == item.jgrSN).FirstOrDefault();
+                    if(dbModel != null)
+                    {
+                        dbModel.jgrID = item.jgrID;
+                        dbModel.jgrType = item.jgrType;
+                        dbModel.displayX = item.displayX;
+                        dbModel.displayY = item.displayY;
+                        dbModel.rcAddr = item.rcAddr;
+                        dbModel.rcFreqCode = item.rcFreqCode;
+                        dbModel.positionX = item.currLocation_X;
+                        dbModel.positionY = item.currLocation_Y;
+                    }
+                }
+                context.SaveChanges();
+                return true;
+            }
+        }
+
+        /// <summary>
+        /// 充电桩
+        /// </summary>
+        /// <returns></returns>
+        public List<ChargingStationDataModel> getChargers()
+        {
+            List<ChargingStationDataModel> charger = new List<ChargingStationDataModel>();
+            using (var context = new MyDbContext(this.connStr))
+            {
+                var chargers = context.chargers.Where(x => x.enable == true).ToList();
+                foreach (var item in chargers)
+                {
+                    ChargingStationDataModel newItem = new ChargingStationDataModel();
+                    newItem.csID = item.chargerID;
+                    newItem.csSN = item.chargerSN;
+                    newItem.csType = item.chargerType;
+                    newItem.rcAddr = item.rcAddr;
+                    newItem.rcFreqCode = item.rcFreqCode;
+                    newItem.positionX = item.positionX;
+                    newItem.positionY = item.positionY;
+                    newItem.displayX = item.displayX;
+                    newItem.displayY = item.displayY;
+                    charger.Add(newItem);
+                }
+            }
+            return charger;
+        }
+
+        public List<StationDataModel> getSatations()
+        {
+            List<StationDataModel> stations = new List<StationDataModel>();
+            using (var context = new MyDbContext(this.connStr))
+            {
+                var chargers = context.stations.Where(x => x.enable == true).ToList();
+                foreach (var item in chargers)
+                {
+                    StationDataModel newItem = new StationDataModel();
+                    newItem.stationID = item.stationID;
+                    newItem.stationType = (StationType)item.stationType;
+                    newItem.displayName = item.displayName;
+                    newItem.positionX = item.positionX;
+                    newItem.positionY = item.positionY;
+                    newItem.displayX = item.displayX;
+                    newItem.displayY = item.displayY;
+                    stations.Add(newItem);
+                }
+            }
+            return stations;
+        }
+
+        public void InitSqlite()
+        {
+            try
+            {
+                using (var context = new MyDbContext(this.connStr))
+                {
+                    context.Database.Migrate();
+                    context.Database.EnsureCreated();
+                }
+            }
+            catch(Exception e)
+            {
+                Console.WriteLine("初始化sqlite数据库出现异常:" + e.Message);
+            }
+        }
+        public StationDataModel? getStationByNo(String stationNo)
+        {
+            using (var context = new MyDbContext(this.connStr))
+            {
+                var item = context.stations.Where(x => x.stationID == stationNo).FirstOrDefault();
+                if (item != null)
+                {
+                    StationDataModel newItem = new()
+                    {
+                        stationID = item.stationID,
+                        stationType = (StationType)item.stationType,
+                        displayName = item.displayName,
+                        positionX = item.positionX,
+                        positionY = item.positionY,
+                        displayX = item.displayX,
+                        displayY = item.displayY
+                    };
+                    return newItem;
+                }
+                return null;
+            }
+        }
+    }
+}

+ 21 - 0
Dto/BoxMovingStatusDto.cs

@@ -0,0 +1,21 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 调仓相关得数据交互结构
+    /// </summary>
+    public class BoxMovingStatusDto
+    {
+        /// <summary>
+        /// 要调仓的盒子
+        /// </summary>
+        public string BoxRFID { get; set; }
+        /// <summary>
+        /// 新位置X
+        /// </summary>
+        public int NewPositionX { get; set; }
+        /// <summary>
+        /// 新位置Y
+        /// </summary>
+        public int NewPositionY { get; set; }
+    }
+}

+ 13 - 0
Dto/CallAmrCarDTO.cs

@@ -0,0 +1,13 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 呼叫轨道车
+    /// </summary>
+    public class CallAmrCarDTO
+    {
+        /// <summary>
+        /// 工站编号
+        /// </summary>
+        public string stanCode { get; set; }
+    }
+}

+ 48 - 0
Dto/GridCell.cs

@@ -0,0 +1,48 @@
+using Org.BouncyCastle.Asn1.Crmf;
+
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 地图的单元格,实际上只用16位的数值来表达
+    /// </summary>
+    public class GridCell
+    {
+        /// <summary>
+        /// 车辆编程用X
+        /// </summary>
+        public int positionX { get; set; }
+        /// <summary>
+        /// 车辆编程用Y
+        /// </summary>
+        public int positionY { get; set; }
+        /// <summary>
+        /// 显示用位置X
+        /// </summary>
+        public int displayX { get; set; }
+        /// <summary>
+        /// 显示用位置Y
+        /// </summary>
+        public int displayY { get; set; }
+
+        /// <summary>
+        /// 是否有效
+        /// </summary>
+        public bool isEnable { get; set; }
+        /// <summary>
+        /// 是否被锁定,小车经过的路径和周边影响的路径会被锁定
+        /// </summary>
+        public bool isLock { get; set; }
+        /// <summary>
+        /// 可用状态
+        /// </summary>
+        public bool enableUse { get; set; }
+
+        public long GetKey()
+        {
+            long key = 0;
+            key = (positionX & 0xffff);
+            key = key + ((positionY & 0xffff) << 16);
+            return key;
+        }
+    }
+}

+ 25 - 0
Dto/GridCellsDto.cs

@@ -0,0 +1,25 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 轨道和仓储格子的DTO
+    /// </summary>
+    public class GridCellsDto
+    {
+        /// <summary>
+        /// 行数
+        /// </summary>
+        public int maxRow { get; set; }
+        /// <summary>
+        /// 列数
+        /// </summary>
+        public int maxCol { get; set; }
+        /// <summary>
+        /// 轨道的单元格
+        /// </summary>
+        public List<GridCell> railCells { get; set; }
+        /// <summary>
+        /// 仓储的单元格
+        /// </summary>
+        public List<GridCell> storageCells { get; set; }
+    }
+}

+ 31 - 0
Dto/HttpStatus.cs

@@ -0,0 +1,31 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 请求的HTTP返回的信息
+    /// </summary>
+    public class HttpStatus<T>
+    {
+        public Code Code { get; set; }
+        public string Msg { get; set; }
+        /// <summary>
+        /// 附带的返回信息
+        /// </summary>
+        public T Data { get; set; }
+    }
+
+    public enum Code
+    {
+        /// <summary>
+        /// 执行并且没有异常
+        /// </summary>
+        Success = 200,
+        /// <summary>
+        /// 输入条件不满足,没有执行
+        /// </summary>
+        Fail = 300,
+        /// <summary>
+        /// 执行,发生异常
+        /// </summary>
+        Error = 500
+    }
+}

+ 17 - 0
Dto/InStorageDTO.cs

@@ -0,0 +1,17 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 入库入参操作
+    /// </summary>
+    public class InStorageDTO
+    {
+        /// <summary>
+        /// 轨道车id
+        /// </summary>
+        public string carId {  get; set; }
+        /// <summary>
+        /// 仓储仓位坐标
+        /// </summary>
+        public string storagePos { get; set; } 
+    }
+}

+ 68 - 0
Dto/IssueNoteDto.cs

@@ -0,0 +1,68 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 出库任务单,出库到工位,派送的时候通过小车送出
+    /// </summary>
+    public class IssueNoteDto
+    {
+        /// <summary>
+        /// 出库任务单ID
+        /// </summary>
+        public string ID { get; set; }
+        /// <summary>
+        /// 绑定的上游MES的订单ID
+        /// </summary>
+        public string MesTaskID { get; set; }   
+        /// <summary>
+        /// 送去的目标工站的ID,目标工站带有XY坐标
+        /// </summary>
+        public string StationID { get; set; }
+        /// <summary>
+        /// 所需的物料
+        /// </summary>
+        public List<RequireRawBom> RawBoms { get; set; }
+
+        public IssueNoteDto()
+        {
+            RawBoms = new List<RequireRawBom>();
+        }
+    }
+
+    /// <summary>
+    /// 原材料需求清单
+    /// </summary>
+    public class RequireRawBom
+    {
+        /// <summary>
+        /// 材料ID
+        /// </summary>
+        public string MaterialID { get; set; }
+        /// <summary>
+        /// 材料类型
+        /// </summary>
+        public string? MaterialType { get; set; }
+        /// <summary>
+        /// 材料名称
+        /// </summary>
+        public string? MaterialName { get; set; }
+        /// <summary>
+        /// 需求数量
+        /// </summary>
+        public double DemandQuantity { get; set; }
+        /// <summary>
+        /// 发料数量
+        /// </summary>
+        public double ReleaseQuantity { get; set; }
+
+        /// <summary>
+        /// 物料有层级,这里预留,应该只用一层
+        /// </summary>
+        public List<RequireRawBom>? ChildBoms { get; set; }
+        
+
+        public RequireRawBom()
+        {
+            ChildBoms = new List<RequireRawBom>();
+        }
+    }
+}

+ 148 - 0
Dto/JgrDebugDto.cs

@@ -0,0 +1,148 @@
+using AmrControl.ADS;
+using System.Runtime.InteropServices;
+
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// JGR小车和充电桩的调试命令
+    /// </summary>
+    public class JgrDebugDto
+    {
+        //JGR类型  0:仓储车 , 1:运输车, 2:充电器
+        public byte Type;
+        //出厂编号,最大29个字母的长度
+        public string SN;
+        /// <summary>
+        /// 命令代码
+        /// </summary>
+        public JgrCmdCode CmdCode;
+        /// <summary>
+        /// 命令参数
+        /// </summary>
+        public Dictionary<string,string> Parameters;
+
+
+        public JgrDebugDto()
+        {
+            Parameters = new Dictionary<string, string>();
+            CmdCode = JgrCmdCode.Default;
+        }
+    }
+    /// <summary>
+    /// 查找空闲车辆
+    /// </summary>
+    public class FindFreeCarDto
+    {
+        /// <summary>
+        /// 工站编号
+        /// </summary>
+        public string stanCode { set; get; }
+    }
+
+        /// <summary>
+        /// jgr的命令代码
+        /// </summary>
+        public enum JgrCmdCode : int
+    {
+        /// <summary>
+        /// 默认,实际上这条命令什么都没干
+        /// </summary>
+        Default= 0,
+        /// <summary>
+        /// 小车系统重启
+        /// </summary>
+        Restart = 1,
+        /// <summary>
+        /// 设置电源
+        /// </summary>
+        SetPower = 2,
+        /// <summary>
+        /// 设置终点
+        /// </summary>
+        SetEndPoint = 3,
+        /// <summary>
+        /// 移动到设置的终点
+        /// </summary>
+        StartMove = 4,
+        /// <summary>
+        /// 正常中止,停在前进方向最近的一格上
+        /// </summary>
+        StopNormal = 5,
+        /// <summary>
+        /// 急停,最短距离停下
+        /// </summary>
+        StopEmergency = 6,
+        /// <summary>
+        /// 前进方向移动到下一格
+        /// </summary>
+        MoveForward = 7,
+        /// <summary>
+        /// 后退一格
+        /// </summary>
+        MoveBack = 8,
+        /// <summary>
+        /// 前进方向左侧移动一格
+        /// </summary>
+        MoveLeft = 9,
+        /// <summary>
+        /// 前进方向右侧移动一格
+        /// </summary>
+        MoveRight = 10,
+        /// <summary>
+        /// 车轮置顶
+        /// </summary>
+        SetWheelTop = 11,
+        /// <summary>
+        /// 车轮置底
+        /// </summary>
+        SetWheelBottom = 12,
+        /// <summary>
+        /// 车轮置在中间,四轮着地
+        /// </summary>
+        SetWheelMiddle= 13,
+        /// <summary>
+        /// 吊篮提起
+        /// </summary>
+        BasketUp = 14,
+        /// <summary>
+        /// 吊篮放下
+        /// </summary>
+        BasketDown = 15,
+        /// <summary>
+        /// 吊篮停止运动
+        /// </summary>
+        BasketStopMoving = 16,
+        /// <summary>
+        /// 吊篮夹子张开
+        /// </summary>
+        GripperOpen = 17,
+        /// <summary>
+        /// 吊篮夹子闭合
+        /// </summary>
+        GripperClose = 18,
+        /// <summary>
+        /// 开始充电
+        /// </summary>
+        StartCharging = 19,
+        /// <summary>
+        /// 停止充电
+        /// </summary>
+        StopCharging = 20,
+        /// <summary>
+        /// 调试模式
+        /// </summary>
+        DebugModel = 21,
+        /// <summary>
+        /// 正常模式
+        /// </summary>
+        NormalModel = 22,
+        /// <summary>
+        /// 电机通电
+        /// </summary>
+        MonitorPowerOn = 23,
+        /// <summary>
+        /// 电机断电
+        /// </summary>
+        MonitorPowerOff = 24
+    }
+}

+ 25 - 0
Dto/JgrDto.cs

@@ -0,0 +1,25 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// JGRDto
+    /// </summary>
+    public class NewJgrDto
+    {
+        // 0:仓储车 , 1:运输车, 2:仓储位充电器,3:轨道位充电器
+        public byte Type;
+        //出厂编号,30个长度带'\0',有效最多29个字节
+        public string SN;
+        //无线模块的地址,长度2字节
+        public short RcAddr;
+        //无线模块的频率码,取值0~155,对应频率为 900+RC_FreqCode*0.2,单位MHz,默认频率码为0x32(50)	
+        public byte RcFreqCode;
+        /// <summary>
+        /// 所在格子的位置X
+        /// </summary>
+        public int positionX;
+        /// <summary>
+        /// 所在格子的位置Y
+        /// </summary>
+        public int positionY;
+    }
+}

+ 21 - 0
Dto/JgrMovingDto.cs

@@ -0,0 +1,21 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 目标小车移动都新位置
+    /// </summary>
+    public class JgrMovingDto
+    {
+        /// <summary>
+        /// 车的编号
+        /// </summary>
+        public string JgrSN { get; set; }
+        /// <summary>
+        /// 新位置X
+        /// </summary>
+        public int NewPositionX { get; set; }
+        /// <summary>
+        /// 新位置Y
+        /// </summary>
+        public int NewPositionY { get; set; }
+    }
+}

+ 26 - 0
Dto/MoveCarToPosDTO.cs

@@ -0,0 +1,26 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 移动车辆到指定位置
+    /// </summary>
+    public class MoveCarToPosDTO
+    {
+        /// <summary>
+        /// 工位编号
+        /// </summary>
+        public string stanCode { get; set; }
+        /// <summary>
+        /// 车辆编号
+        /// </summary>
+        public string carId { get; set;}
+
+        /// <summary>
+        /// 新位置X
+        /// </summary>
+        public int x { get; set; }
+        /// <summary>
+        /// 新位置Y
+        /// </summary>
+        public int y { get; set; }
+    }
+}

+ 17 - 0
Dto/MoveStorageDTO.cs

@@ -0,0 +1,17 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 移库入参
+    /// </summary>
+    public class MoveStorageDTO
+    {
+        /// <summary>
+        /// 源仓位坐标
+        /// </summary>
+        public string sourcePos { set; get; }
+        /// <summary>
+        /// 目标仓位坐标
+        /// </summary>
+        public string destPos { set; get; }
+    }
+}

+ 52 - 0
Dto/MovingPath.cs

@@ -0,0 +1,52 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 移动路径设置,考虑到界面上可能有多次点击,或者移动过程中有多次点击
+    /// 如果本路径的起点与当前车的起点不一致,那么本次要求执行的路径会被抛弃,以便防错
+    /// 路径会经过校验才能被执行,小车只能执行完当前路径,才能进行新的路径运动
+    /// </summary>
+    public class MovingPath
+    {
+        /// <summary>
+        /// 车的编号
+        /// </summary>
+        public string jgrSN { get; set; }
+        /// <summary>
+        /// 车的类型,0:仓储车,1:轨道车
+        /// </summary>
+        public byte jgrType { get; set; }
+        /// <summary>
+        /// 源X
+        /// </summary>
+        public int startLocationX { get; set; }
+        /// <summary>
+        /// 源Y
+        /// </summary>
+        public int startLocationY { get; set; }
+        /// <summary>
+        /// 目标X
+        /// </summary>
+        public int destLocationX { get; set; }
+        /// <summary>
+        /// 目标Y
+        /// </summary>
+        public int destLocationY { get; set; }
+        /// <summary>
+        /// 是否正在运行,可能是还在路上,也可能已经完成
+        /// 如果已经完成,则移除路径
+        /// 如果在等待,则调度
+        /// 如果运行超时,则报警并通知
+        /// </summary>
+        public bool IsRuning { get; set; }
+        /// <summary>
+        /// 开始执行的时间,用来评估超时时间
+        /// </summary>
+        public DateTime StartTime { get; set; }
+
+        public MovingPath()
+        {
+            IsRuning = false;
+        }
+
+    }
+}

+ 21 - 0
Dto/OutStorageDTO.cs

@@ -0,0 +1,21 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 出库入参
+    /// </summary>
+    public class OutStorageDTO
+    {
+        /// <summary>
+        /// 储位坐标
+        /// </summary>
+        public string storagePos { set; get; }
+        /// <summary>
+        /// 工站编号,小车到达工位
+        /// </summary>
+        public string? stanCode { set; get; }
+        /// <summary>
+        /// 目标位置,小车到达目标位置
+        /// </summary>
+        public string? destPos { set; get; }
+    }
+}

+ 28 - 0
Dto/PickingStationStatus.cs

@@ -0,0 +1,28 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 捡料工位的信息
+    /// </summary>
+    public class PickingStationStatus
+    {
+        public string PickingStationId { get; set; }
+        /// <summary>
+        /// 是否有车
+        /// </summary>
+        public bool ContainsJAR { get; set; }
+        /// <summary>
+        /// 车的编号
+        /// </summary>
+        public string JarSN { get; set; }
+
+        /// <summary>
+        /// 是否有物料盒子,没有车也可能有物料盒子
+        /// </summary>
+        public bool ContainsBox { get; set; }
+        /// <summary>
+        /// 盒子是否有RFID,这个物料盒子隶属于仓库,可以通过BoxRFID来查询里面的物料内容,
+        /// 或者根据BoxRFID来进行出入库的动作
+        /// </summary>
+        public string BoxRFID { get; set; }
+    }
+}

+ 22 - 0
Dto/RetrievalDto.cs

@@ -0,0 +1,22 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 出库申请的数据结构
+    /// </summary>
+    public class RetrievalDto
+    {
+        /// <summary>
+        /// 预计从那个物料框中出库
+        /// </summary>
+        public string BoxRFID { get; set; }
+        /// <summary>
+        /// 需求的物料清单,允许多个
+        /// </summary>
+        public List<RequireRawBom> RequireRawBoms { get; set; }
+
+        public RetrievalDto()
+        {
+            RequireRawBoms = new List<RequireRawBom>();
+        }
+    }
+}

+ 63 - 0
Dto/StorageLocation.cs

@@ -0,0 +1,63 @@
+namespace AmrControl.Dto
+{
+    /// <summary>
+    /// 储位的位置信息
+    /// </summary>
+    public class LocationBase
+    {
+        /// <summary>
+        /// 位置信息X
+        /// </summary>
+        public int PositionX { get; set; }
+        /// <summary>
+        /// 位置信息Y
+        /// </summary>
+        public int PositionY { get; set; }
+        /// <summary>
+        /// 位置信息Z
+        /// </summary>
+        public int PositionZ { get; set; }
+    }
+
+    /// <summary>
+    /// 储位信息
+    /// </summary>
+    public class StorageBox :LocationBase
+    {
+        /// <summary>
+        /// 包含的物料盒子的RFID,也可能当前储位并不包含RFID
+        /// 相对于每个料框都有一个ID,这个可以由系统虚拟产生
+        /// 箱子位置信息是变动的,不是唯一项,但是需要有一个ID值来关联
+        /// 如果没有RFID,则需要贴一个条码进行跟踪,过程中才能方便校验,避免出错。
+        /// </summary>
+        public string BoxRFID { get; set; }
+
+        public List<RawBom> RawBoms { get; set; }
+
+        public StorageBox()
+        {
+            RawBoms = new List<RawBom>();
+        }
+    }
+
+    public class RawBom
+    {
+        /// <summary>
+        /// 材料ID
+        /// </summary>
+        public string MaterialID { get; set; }
+        /// <summary>
+        /// 材料类型
+        /// </summary>
+        public string MaterialType { get; set; }
+        /// <summary>
+        /// 材料名称
+        /// </summary>
+        public string MaterialName { get; set; }
+        /// <summary>
+        /// 数量
+        /// </summary>
+        public double Quantity { get; set; }
+
+    }
+}

+ 41 - 0
Dto/StorageStatus.cs

@@ -0,0 +1,41 @@
+namespace AmrControl.Dto
+{
+    public class StorageStatus
+    {
+        /// <summary>
+        /// 仓储ID,用于关联和唯一识别
+        /// </summary>
+        public string StorageID { get; set; }
+        /// <summary>
+        /// 仓储名称,有多个仓储系统
+        /// </summary>
+        public string StorageName { get; set; }
+        /// <summary>
+        /// 总储位
+        /// </summary>
+        public int TotalLocation { get; set; }
+        /// <summary>
+        /// 空闲储位
+        /// </summary>
+        public int FreeLocation { get; set; }
+        /// <summary>
+        /// 仓储中的物料种类
+        /// </summary>
+        public int MaterialCount { get; set; }
+        /// <summary>
+        /// 今日出库次数
+        /// </summary>
+        public int TodayMoveInCount { get; set; }
+        /// <summary>
+        /// 进入入库次数
+        /// </summary>
+        public int TodayMoveOutCount { get; set; }
+
+        /// <summary>
+        /// 仓储里面子仓储的定义
+        /// </summary>
+        public List<StorageStatus>? ChildStorages { get; set; }
+
+
+    }
+}

+ 26 - 0
Dto/UserDto.cs

@@ -0,0 +1,26 @@
+namespace AmrControl.Dto
+{
+    public class UserDto
+    {
+        /// <summary>
+        /// 登录名
+        /// </summary>
+        public string UserName { get; set; }
+        /// <summary>
+        /// 登录密码
+        /// </summary>
+        public string PassWord { get; set; }
+    }
+
+    public class UserVo
+    {
+        /// <summary>
+        /// 登录名
+        /// </summary>
+        public string UserName { get; set; }
+        /// <summary>
+        /// 登录密码
+        /// </summary>
+        public string Token { get; set; }
+    }
+}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 2644 - 0
JGR/JGRManager.cs


+ 31 - 0
Pages/Debug.cshtml

@@ -0,0 +1,31 @@
+@page
+@model AmrControl.Pages.DebugModel
+<div class="container">
+    <div class="row">&nbsp;</div>
+    <div class="row">
+        <div class="col-2">Topic</div>
+        <div class="col-4"><input type="text" id="topicInput" /></div>
+    </div>
+    <div class="row">
+        <div class="col-2">Message</div>
+        <div class="col-4"><input type="text" id="messageInput" /></div>
+    </div>
+    <div class="row">&nbsp;</div>
+    <div class="row">
+        <div class="col-6">
+            <input type="button" id="sendButton" Data="Send Message" />
+        </div>
+    </div>
+</div>
+<div class="row">
+    <div class="col-12">
+        <hr />
+    </div>
+</div>
+<div class="row">
+    <div class="col-6">
+        <ul id="messagesList"></ul>
+    </div>
+</div>
+<script src="~/js/signalr/dist/browser/signalr.js"></script>
+<script src="~/js/chat.js"></script>

+ 12 - 0
Pages/Debug.cshtml.cs

@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace AmrControl.Pages
+{
+    public class DebugModel : PageModel
+    {
+        public void OnGet()
+        {
+        }
+    }
+}

+ 33 - 0
Pages/Index.cshtml

@@ -0,0 +1,33 @@
+@page
+@{
+    Response.Redirect("/index.html");
+}
+  @*  <div class="container">
+        <div class="row">&nbsp;</div>
+        <div class="row">
+            <div class="col-2">Topic</div>
+            <div class="col-4"><input type="text" id="topicInput" /></div>
+        </div>
+        <div class="row">
+            <div class="col-2">Message</div>
+            <div class="col-4"><input type="text" id="messageInput" /></div>
+        </div>
+        <div class="row">&nbsp;</div>
+        <div class="row">
+            <div class="col-6">
+                <input type="button" id="sendButton" Data="Send Message" />
+            </div>
+        </div>
+    </div>
+    <div class="row">
+        <div class="col-12">
+            <hr />
+        </div>
+    </div>
+    <div class="row">
+        <div class="col-6">
+            <ul id="messagesList"></ul>
+        </div>
+    </div>
+<script src="~/js/signalr/dist/browser/signalr.js"></script>
+<script src="~/js/chat.js"></script>*@

+ 13 - 0
Pages/Index.cshtml.cs

@@ -0,0 +1,13 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace AmrControl.wwwroot.Pages
+{
+    public class IndexModel : PageModel
+    {
+        public void OnGet()
+        {
+           
+        }
+    }
+}

+ 167 - 0
Program.cs

@@ -0,0 +1,167 @@
+using AmrControl;
+using AmrControl.ADS;
+using AmrControl.Clients;
+using AmrControl.Common;
+using AmrControl.DB;
+using AmrControl.JGR;
+using AmrControl.mq;
+using AmrControl.services;
+using AmrControl.workstation;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Hosting;
+using Newtonsoft.Json;
+using System.Runtime.InteropServices;
+using System.Text;
+
+var builder = WebApplication.CreateBuilder(args);
+var configuration = builder.Configuration;
+// Add services to the container.
+//builder.Services.AddRazorPages();
+//添加signalR服务
+//builder.Services.AddSignalR();
+//添加控制器,并使用newtonsoftjson作为json序列化与反序列化工具
+builder.Services.AddControllers().AddNewtonsoftJson();
+
+builder.Services.AddControllers();
+var apphelper = new AppHelper(builder.Configuration);
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+//AddSingleton :每次都是相同的实例
+//builder.Services.AddSingleton<ITaskExecManager, TaskExecManager>();
+//builder.Services.AddSingleton<IRegistryService, RegistryService>();
+//var mqHost = builder.Configuration["mq:host"];
+//if (!string.IsNullOrEmpty(mqHost) && !string.IsNullOrWhiteSpace(mqHost))
+//{
+//    builder.Services.AddSingleton<IRabbitmqService, RabbitmqService>();
+//}
+//builder.Services.AddSingleton<IJGR, JGRManager>();
+//builder.Services.AddSingleton(typeof(MyDbHandler), sp => {
+//    //return new MyDbHandler(builder.Configuration.GetConnectionString("MySql"));
+//    return new MyDbHandler(builder.Configuration.GetConnectionString("Sqlite"));
+//});
+
+//builder.Services.AddSingleton(typeof(IJGR), sp => {
+//    var scope = sp.CreateScope();
+//    var hubContext = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
+//    var context = scope.ServiceProvider.GetRequiredService<MyDbHandler>();
+//    var taskExecManager = scope.ServiceProvider.GetRequiredService<ITaskExecManager>();
+//    var registrySerivce = scope.ServiceProvider.GetRequiredService<IRegistryService>();
+//    return new JGRManager(hubContext, context, builder.Configuration, taskExecManager, registrySerivce);
+//});
+
+//添加工位的采集控制
+//builder.Services.AddSingleton<IWorkstation, MsWorkstation>();
+builder.Services.AddSingleton(typeof(MsWorkstation), sp => {
+    return new MsWorkstation(configuration, sp);
+});
+builder.Services.AddSingleton(typeof(MqttClient), sp => {
+    return new MqttClient(configuration, sp);
+});
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+    app.UseSwagger();
+    app.UseSwaggerUI();
+}
+
+//app.UseHttpsRedirection();
+app.UseAuthorization();
+//自定义网页入口
+DefaultFilesOptions options = new DefaultFilesOptions();
+//options.DefaultFileNames.Clear();
+//options.DefaultFileNames.Add("/index.html");
+//app.UseDefaultFiles(options);
+//允许静态文件
+//app.UseStaticFiles();
+//app.UseStaticFiles(new StaticFileOptions
+//{
+//    FileProvider = new PhysicalFileProvider(
+//            Path.Combine(AppContext.BaseDirectory, "files")),
+//    RequestPath = ""
+//});
+
+//app.MapRazorPages();
+//app.MapHub<ChatHub>("/chatHub");
+app.MapControllers();
+//var hubContext = app.Services.GetService<IHubContext<ChatHub>>();
+////初始化小车控制
+//var jgr = builder.Services.BuildServiceProvider().GetRequiredService<IJGR>();
+//if (jgr != null)
+//{
+//    jgr.Init(app.Services);
+//    jgr.SetHub(hubContext);
+//}
+//if (!string.IsNullOrEmpty(mqHost) && !string.IsNullOrWhiteSpace(mqHost))
+//{
+//    var rabbitmqService = builder.Services.BuildServiceProvider().GetRequiredService<IRabbitmqService>();
+//    if(rabbitmqService != null)
+//    {
+//        rabbitmqService.Init();
+//    }
+//}
+
+//初始化sqlite数据库
+//var sqlite = builder.Services.BuildServiceProvider().GetRequiredService<MyDbHandler>();
+//if(sqlite != null)
+//{
+//    sqlite.InitSqlite();
+//}
+
+//int size = Marshal.SizeOf<JGR_Tc_Model>();
+
+//初始化工位的采集控制
+IServiceProvider provider = builder.Services.BuildServiceProvider();
+var mqtt = provider.GetService<MqttClient>();
+if (null != mqtt)
+{
+    mqtt.CreateClient();
+}
+
+var ws = provider.GetService<MsWorkstation>();
+if (null != ws)
+{
+    ws.Stop();
+}
+
+//程序退出事件
+AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
+//app 执行
+app.Run();
+
+
+void CurrentDomain_ProcessExit(object? sender, EventArgs e)
+{
+    AmrManager.ProcessExit = true;
+    //throw new NotImplementedException();
+}
+
+
+//var builder = Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
+//{
+//    webBuilder.UseUrls("http://*:8000")
+//                    .UseStartup<Startup>()
+//                    .UseKestrel(opt => opt.Limits.MaxRequestBodySize = null);
+//})
+//.UseDefaultServiceProvider(options =>
+//{
+//    options.ValidateScopes = false;
+//});
+
+
+////程序退出事件
+//AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
+
+
+//void CurrentDomain_ProcessExit(object? sender, EventArgs e)
+//{
+//    AmrManager.ProcessExit = true;
+//    //throw new NotImplementedException();
+//}
+
+//builder.Build().Run();

+ 22 - 0
Properties/PublishProfiles/FolderProfile.pubxml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <DeleteExistingFiles>false</DeleteExistingFiles>
+    <ExcludeApp_Data>false</ExcludeApp_Data>
+    <LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
+    <LastUsedBuildConfiguration>Debug</LastUsedBuildConfiguration>
+    <LastUsedPlatform>x86</LastUsedPlatform>
+    <PublishProvider>FileSystem</PublishProvider>
+    <PublishUrl>bin\Debug\net6.0\publish\</PublishUrl>
+    <WebPublishMethod>FileSystem</WebPublishMethod>
+    <SiteUrlToLaunchAfterPublish />
+    <TargetFramework>net6.0</TargetFramework>
+    <ProjectGuid>4b98a1ee-1680-4fe0-9c5b-657db75e5f88</ProjectGuid>
+    <SelfContained>false</SelfContained>
+    <RuntimeIdentifier>win-x86</RuntimeIdentifier>
+    <PublishReadyToRun>true</PublishReadyToRun>
+  </PropertyGroup>
+</Project>

+ 12 - 0
Properties/PublishProfiles/FolderProfile.pubxml.user

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+此文件由 Web 项目的发布/打包过程使用。可以通过编辑此 MSBuild 文件
+自定义此过程的行为。为了解与此相关的更多内容,请访问 https://go.microsoft.com/fwlink/?LinkID=208121。 
+-->
+<Project>
+  <PropertyGroup>
+    <_PublishTargetUrl>C:\Users\JGAI\Desktop\show\工位数据采集\bin\Debug\net6.0\publish\</_PublishTargetUrl>
+    <History>True|2023-11-25T01:07:43.4023630Z;True|2023-11-25T07:17:15.1087383+08:00;True|2023-11-25T07:02:44.0251393+08:00;True|2023-11-25T04:10:24.4517410+08:00;True|2023-11-25T03:22:35.7141515+08:00;True|2023-11-25T02:43:05.6038856+08:00;True|2023-11-25T02:23:21.8255330+08:00;</History>
+    <LastFailureDetails />
+  </PropertyGroup>
+</Project>

+ 31 - 0
Properties/launchSettings - 复制.json

@@ -0,0 +1,31 @@
+{
+  "$schema": "https://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:20295",
+      "sslPort": 44329
+    }
+  },
+  "profiles": {
+    "AmrControl": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "applicationUrl": "https://localhost:7089;http://localhost:5089",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 29 - 0
Properties/launchSettings.json

@@ -0,0 +1,29 @@
+{
+  "$schema": "https://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://0.0.0.0:20295",
+      "sslPort": 44329
+    }
+  },
+  "profiles": {
+    "AmrControl": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "applicationUrl": "http://0.0.0.0:5089",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 3 - 0
README.md

@@ -0,0 +1,3 @@
+这是在仓储小车控制系统基础上修改的软件,用来做工位的数据采集,临时用的。
+包括通过ADS采集IO,HTTP采集电动起子,windows采集电烙铁等。
+然后使用MQ消息上传到指定位置。

+ 17 - 0
Vo/InStorageVO.cs

@@ -0,0 +1,17 @@
+namespace AmrControl.Vo
+{
+    /// <summary>
+    /// 入库返回参数
+    /// </summary>
+    public class InStorageVO
+    {
+        /// <summary>
+        /// 任务id,可凭此获取执行结果
+        /// </summary>
+        public string taskId { set; get; }
+        /// <summary>
+        /// 仓储车编号
+        /// </summary>
+        public string carId { set; get; }
+    }
+}

+ 17 - 0
Vo/MoveStorageVO.cs

@@ -0,0 +1,17 @@
+namespace AmrControl.Vo
+{
+    /// <summary>
+    /// 移库出参
+    /// </summary>
+    public class MoveStorageVO
+    {
+        /// <summary>
+        /// 任务id
+        /// </summary>
+        public string taskId {  get; set; }
+        /// <summary>
+        /// 仓储车id
+        /// </summary>
+        public string carId { get; set; }
+    }
+}

+ 17 - 0
Vo/OutStorageVO.cs

@@ -0,0 +1,17 @@
+namespace AmrControl.Vo
+{
+    /// <summary>
+    /// 出库参数
+    /// </summary>
+    public class OutStorageVO
+    {
+        /// <summary>
+        /// 任务id
+        /// </summary>
+        public string taskId { set; get; }
+        /// <summary>
+        /// 轨道车id
+        /// </summary>
+        public string carId { set; get; }
+    }
+}

+ 49 - 0
Vo/StationStateVO.cs

@@ -0,0 +1,49 @@
+namespace AmrControl.Vo
+{
+    /// <summary>
+    /// 工站状态输出函数
+    /// </summary>
+    public class StationStateVO
+    {
+        /// <summary>
+        /// 工站编号
+        /// </summary>
+        public int stationPosNo { get; set; }
+        /// <summary>
+        /// 电源状态,true-开启;false-关闭
+        /// </summary>
+        public bool power { set; get; }
+        /// <summary>
+        /// 三色灯状态,0:关闭所有灯;1:开启绿灯;2:开启黄灯;3:开启红灯
+        /// </summary>
+        public short triColorLight { set; get; }
+        /// <summary>
+        /// 静电手环1状态;0:没有静电手环连接;1:未佩戴静电手环;2:已穿戴静电手环
+        /// </summary>
+        public short wsristStrapMonitorCh1 { set; get; }
+        /// <summary>
+        /// 静电手环2状态;0:没有静电手环连接;1:未佩戴静电手环;2:已穿戴静电手环
+        /// </summary>
+        public short wsristStrapMonitorCh2 { set; get; }
+        /// <summary>
+        /// 烟雾净化器开关 输出:TRUE:烟雾净化器开;FALSE:烟雾净化器关;
+        /// </summary>
+        public bool smokePurifier { set; get; }
+        /// <summary>
+        /// 大臂编码值
+        /// </summary>
+        public short bigArm_encoding { set; get; }
+        /// <summary>
+        /// 大臂角度
+        /// </summary>
+        public float bigArm_angle { set; get; }
+        /// <summary>
+        /// 小臂编码值
+        /// </summary>
+        public short smallArm_encoding { set; get; }
+        /// <summary>
+        /// 小臂角度
+        /// </summary>
+        public float smallArm_angle { set; get; }
+    }
+}

+ 0 - 0
ads.json


Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio