AmrManager.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. using Microsoft.AspNetCore.SignalR;
  2. using MQTTnet;
  3. using MQTTnet.Client;
  4. using Newtonsoft.Json;
  5. using Newtonsoft.Json.Converters;
  6. using System.Text;
  7. namespace AmrControl.Common
  8. {
  9. /// <summary>
  10. /// AMR车的控制器,监听Zigbee的消息,转发mqtt消息
  11. /// 在系统中是单例实现
  12. /// </summary>
  13. public class AmrManager : IAmrManager,IDisposable
  14. {
  15. public static bool ProcessExit = false;
  16. public bool LinkState { get; private set; }
  17. public int AmrOnlineCount { get; private set; }
  18. private readonly IHubContext<ChatHub> hubContext;
  19. MqttFactory Factory = null;
  20. IMqttClient mqttClient = null;
  21. string subscribeTopic = "device/agv-control/handle";
  22. string publicTopic = "srv/device/handle";
  23. IsoDateTimeConverter timeConverter;
  24. RS485 rs485;
  25. public AmrManager(IHubContext<ChatHub> context)
  26. {
  27. LinkState = false;
  28. AmrOnlineCount = 0;
  29. hubContext = context;
  30. timeConverter = new IsoDateTimeConverter();
  31. //这里使用自定义日期格式,如果不使用的话,默认是ISO8601格式
  32. timeConverter.DateTimeFormat = "yyyy-MM-dd HH:mm:ss";
  33. rs485 = new RS485();
  34. //四种事件使用的mqtt主题是一样的,可以改动消息类型来适应
  35. rs485.ReceiveCarStateCmdEvent += Rs485_ReceiveCarStateCmdEvent;
  36. rs485.CarConnectedEvent += Rs485_CarConnectedEvent;
  37. rs485.CarDisconnectedEvent += Rs485_CarDisconnectedEvent;
  38. rs485.CarArrivedEvent += Rs485_CarArrivedEvent;
  39. //模拟一个数据
  40. //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") });
  41. // 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") });
  42. //AmrOnlineCount = 2;
  43. }
  44. //车到达某个指定位置的消息
  45. private void Rs485_CarArrivedEvent(CarStateCmd carState)
  46. {
  47. string str = encodeCarStateCmd(publicTopic, carState);
  48. if (str != "")
  49. {
  50. ClientPublish(publicTopic, str);
  51. }
  52. }
  53. //车上线的消息
  54. private void Rs485_CarDisconnectedEvent(CarStateCmd carState)
  55. {
  56. AmrOnlineCount = rs485.CarCount;
  57. string str = encodeCarStateCmd(publicTopic, carState);
  58. if (str != "")
  59. {
  60. ClientPublish(publicTopic, str);
  61. }
  62. }
  63. //车下线的消息
  64. private void Rs485_CarConnectedEvent(CarStateCmd carState)
  65. {
  66. AmrOnlineCount = rs485.CarCount;
  67. string str = encodeCarStateCmd(publicTopic, carState);
  68. if (str != "")
  69. {
  70. ClientPublish(publicTopic, str);
  71. }
  72. }
  73. //车定时上报状态的消息
  74. private void Rs485_ReceiveCarStateCmdEvent(CarStateCmd carState)
  75. {
  76. string str = encodeCarStateCmd(publicTopic, carState);
  77. if (str != "")
  78. {
  79. ClientPublish(publicTopic, str);
  80. }
  81. }
  82. public bool ResetSystem()
  83. {
  84. bool ok = CreateMqttClient();
  85. if (rs485 != null)
  86. {
  87. rs485.Stop();
  88. }
  89. rs485.Start();
  90. if (hubContext != null)
  91. {
  92. if(ok)
  93. {
  94. ChatHubMsg("AmrManager", "mqttclient conntcted success.");
  95. //创建心跳上报的程序,定期上报设备的状态
  96. //CreateHeartBeat();
  97. // encodeCarStateCmd(publicTopic, cars[0]);
  98. }
  99. else
  100. {
  101. ChatHubMsg("AmrManager", "mqttclient conntcted fail.");
  102. }
  103. }
  104. return ok;
  105. }
  106. public IList<AmrMessage> GetAmrMessage()
  107. {
  108. List<AmrMessage> messages = new List<AmrMessage>();
  109. messages.Add(new AmrMessage() { Topic = "amrtopic", Time = DateTime.Now, Message = "amr message" });
  110. return messages;
  111. }
  112. public IList<MqMessage> GetMqMessages()
  113. {
  114. List<MqMessage> messages = new List<MqMessage>();
  115. messages.Add(new MqMessage() { Topic = "mqtopic", Time = DateTime.Now, Message = "mq message" });
  116. return messages;
  117. }
  118. public bool GetLinkState()
  119. {
  120. return LinkState;
  121. }
  122. public int GetAmrOnlineCount()
  123. {
  124. return AmrOnlineCount;
  125. }
  126. public bool SendToMqtt(string topic, string msg)
  127. {
  128. if(mqttClient.IsConnected)
  129. {
  130. ClientPublish(topic, msg);
  131. return true;
  132. }
  133. else
  134. {
  135. return false;
  136. }
  137. }
  138. bool CreateMqttClient()
  139. {
  140. try
  141. {
  142. if(mqttClient != null)
  143. {
  144. mqttClient.Dispose();
  145. }
  146. string CId = "AmrManager"; //用户标识ID
  147. string userName = "jg-admin"; //用户名
  148. string passWord = "jg-admin@2022"; //密码
  149. MqttClientOptions Option = new MqttClientOptionsBuilder().WithTcpServer("192.168.1.4", 1883) // ("192.168.1.10", 1883)//地址端口号
  150. .WithClientId(CId) //客户端标识Id要唯一。
  151. .WithCredentials(userName, passWord) //用户名,密码
  152. .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
  153. .WithCleanSession(false)
  154. .WithWillQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce)
  155. .WithTimeout(new TimeSpan(0, 0, 10))
  156. .WithKeepAlivePeriod(TimeSpan.FromSeconds(20))
  157. .Build();
  158. Factory = new MqttFactory();
  159. mqttClient = Factory.CreateMqttClient();
  160. mqttClient.ApplicationMessageReceivedAsync += MqttClient_ApplicationMessageReceivedAsync;
  161. mqttClient.ConnectedAsync += MqttClient_ConnectedAsync;
  162. mqttClient.DisconnectedAsync += MqttClient_DisconnectedAsync;
  163. //调用异步方法连接到服务端
  164. mqttClient.ConnectAsync(Option).Wait();
  165. //订阅主题
  166. ClientSubscribeTopic(subscribeTopic);
  167. return mqttClient.IsConnected;
  168. }
  169. catch (Exception ex)
  170. {
  171. ChatHubMsg("AmrManager", "mqttclient connect failed.");
  172. LinkState = false;
  173. return false;
  174. }
  175. }
  176. //失去连接
  177. private Task MqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg)
  178. {
  179. if(mqttClient != null)
  180. {
  181. mqttClient.Dispose();
  182. }
  183. mqttClient = null;
  184. LinkState = false;
  185. //新建线程定时连接,连接成功,LinkState= true
  186. Task.Run(() => {
  187. while(LinkState == false)
  188. {
  189. Thread.Sleep(10000);
  190. CreateMqttClient();
  191. }
  192. });
  193. return Task.CompletedTask;
  194. }
  195. //已经连接
  196. private Task MqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg)
  197. {
  198. ChatHubMsg("AmrManager", "mqttclient connect success.");
  199. LinkState = true;
  200. return Task.CompletedTask;
  201. }
  202. //接收到消息
  203. private Task MqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg)
  204. {
  205. ///收到消息
  206. string topic = arg.ApplicationMessage.Topic;
  207. string content = System.Text.Encoding.UTF8.GetString(arg.ApplicationMessage.Payload);
  208. //转到界面上显示
  209. if(topic == "device/agv-control/handle")
  210. {
  211. decodeMsg(topic,content);
  212. }
  213. else
  214. {
  215. ChatHubMsg(topic , "不识别的主题");
  216. }
  217. return Task.CompletedTask;
  218. }
  219. //mqtt解码为消息对象
  220. void decodeMsg(string topic,string s)
  221. {
  222. try
  223. {
  224. int index = s.IndexOf('.');
  225. string substr = s.Substring(index + 1);
  226. string headStr = s.Substring(0, index);
  227. byte[] bytes = Convert.FromBase64String(substr);
  228. byte[] headBytes = Convert.FromBase64String(headStr);
  229. short msgType = (short)((short)(headBytes[3] << 8) + headBytes[4]);
  230. MessageType type = (MessageType)Enum.Parse(typeof(MessageType), Enum.GetName(typeof(MessageType), msgType));
  231. switch(type)
  232. {
  233. case MessageType.MSG_TYPE_CALL_CAR:
  234. CallCarCmd cmdCall = JsonConvert.DeserializeObject<CallCarCmd>(Encoding.UTF8.GetString(bytes));
  235. cmdCall.msgType = MessageType.MSG_TYPE_CALL_CAR;
  236. rs485.SendCarCmd(cmdCall);
  237. break;
  238. case MessageType.MSG_TYPE_STOP_CAR:
  239. StopCarCmd cmdStop = JsonConvert.DeserializeObject<StopCarCmd>(Encoding.UTF8.GetString(bytes));
  240. cmdStop.msgType = MessageType.MSG_TYPE_STOP_CAR;
  241. rs485.SendCarStop(cmdStop);
  242. break;
  243. case MessageType.MSG_TYPE_ENABLE_CAR:
  244. EnableCarCmd cmdEnable = JsonConvert.DeserializeObject<EnableCarCmd>(Encoding.UTF8.GetString(bytes));
  245. cmdEnable.msgType = MessageType.MSG_TYPE_ENABLE_CAR;
  246. rs485.SendCarEnable(cmdEnable);
  247. break;
  248. case MessageType.MSG_TYPE_DISABLE_CAR:
  249. DisableCarCmd cmdDisable = JsonConvert.DeserializeObject<DisableCarCmd>(Encoding.UTF8.GetString(bytes));
  250. cmdDisable.msgType = MessageType.MSG_TYPE_DISABLE_CAR;
  251. rs485.SendCarDisable(cmdDisable);
  252. break;
  253. case MessageType.MSG_TYPE_CAR_SHOW:
  254. //显示图片
  255. CarShowCmd cmdCarshow = JsonConvert.DeserializeObject<CarShowCmd>(Encoding.UTF8.GetString(bytes));
  256. cmdCarshow.msgType = MessageType.MSG_TYPE_CAR_SHOW;
  257. rs485.SendCarShowImage(cmdCarshow);
  258. break;
  259. default:
  260. break;
  261. }
  262. //转换回字符串
  263. //Encoding.UTF8.GetString(bytes)
  264. }
  265. catch (Exception ex)
  266. {
  267. ChatHubMsg(topic, "解码失败!");
  268. }
  269. }
  270. //消息对象编码为mqtt消息
  271. string encodeCarStateCmd(string topic , CarStateCmd carState)
  272. {
  273. try
  274. {
  275. string msg = JsonConvert.SerializeObject(carState, Formatting.Indented, timeConverter);
  276. string strMsg = Convert.ToBase64String(Encoding.UTF8.GetBytes(msg));
  277. byte[] headBytes = new byte[7];
  278. short msgType = (short)carState.msgType;
  279. headBytes[0] = 0x09;
  280. headBytes[1] = 0x12;
  281. headBytes[2] = 1; //软件版本
  282. headBytes[3] = (byte)((msgType >> 8) & 0xff); //消息类型
  283. headBytes[4] = (byte)((msgType) & 0xff);
  284. headBytes[5] = 1;//加密类型
  285. headBytes[6] = 1;//客户端
  286. string strHead = Convert.ToBase64String(headBytes);
  287. return strHead + "." + strMsg;
  288. }
  289. catch (Exception ex)
  290. {
  291. ChatHubMsg(topic, "编码失败!");
  292. return "";
  293. }
  294. }
  295. //消息发送到界面上
  296. private bool ChatHubMsg(string topic , string msg)
  297. {
  298. if (hubContext != null)
  299. {
  300. hubContext.Clients.All.SendAsync("ReceiveMessage", topic, msg); //SendMessage("ArmMessage", "reset system");
  301. }
  302. return true;
  303. }
  304. //订阅主题
  305. private async void ClientSubscribeTopic(string topic)
  306. {
  307. topic = topic.Trim();
  308. if (string.IsNullOrEmpty(topic))
  309. {
  310. //Console.Write("订阅主题不能为空!");
  311. return;
  312. }
  313. // 判断客户端是否连接
  314. if (mqttClient != null && !mqttClient.IsConnected)
  315. {
  316. //Console.WriteLine("MQTT 客户端尚未连接!");
  317. return;
  318. }
  319. // 设置订阅参数
  320. var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
  321. .WithTopicFilter(topic)
  322. .Build();
  323. try
  324. {
  325. // 订阅
  326. await mqttClient.SubscribeAsync(
  327. subscribeOptions,
  328. System.Threading.CancellationToken.None);
  329. }
  330. catch (Exception ex)
  331. {
  332. }
  333. }
  334. private async void ClientUnsubscribeTopic(string topic)
  335. {
  336. topic = topic.Trim();
  337. if (string.IsNullOrEmpty(topic))
  338. {
  339. // Console.Write("退订主题不能为空!");
  340. return;
  341. }
  342. // 判断客户端是否连接
  343. if (!mqttClient.IsConnected)
  344. {
  345. // Console.WriteLine("MQTT 客户端尚未连接!");
  346. return;
  347. }
  348. // 设置订阅参数
  349. var subscribeOptions = new MqttClientUnsubscribeOptionsBuilder()
  350. .WithTopicFilter(topic)
  351. .Build();
  352. try
  353. {
  354. // 退订
  355. await mqttClient.UnsubscribeAsync(
  356. subscribeOptions,
  357. System.Threading.CancellationToken.None);
  358. }
  359. catch (Exception ex)
  360. {
  361. }
  362. }
  363. private async void ClientPublish(string topic, string message)
  364. {
  365. topic = topic.Trim();
  366. message = message.Trim();
  367. if (string.IsNullOrEmpty(topic))
  368. {
  369. // Console.Write("退订主题不能为空!");
  370. return ;
  371. }
  372. // 判断客户端是否连接
  373. if (mqttClient == null || !mqttClient.IsConnected)
  374. {
  375. // Console.WriteLine("MQTT 客户端尚未连接!");
  376. return ;
  377. }
  378. // 填充消息
  379. var applicationMessage = new MqttApplicationMessageBuilder()
  380. .WithTopic(topic) // 主题
  381. .WithPayload(message) // 消息
  382. .WithRetainFlag(false) // 持久化为空,不持久化
  383. .Build();
  384. try
  385. {
  386. await mqttClient.PublishAsync(applicationMessage);
  387. }
  388. catch (Exception ex)
  389. {
  390. }
  391. }
  392. public void Dispose()
  393. {
  394. if (rs485 != null)
  395. {
  396. rs485.Dispose();
  397. }
  398. ClientUnsubscribeTopic(subscribeTopic);
  399. mqttClient.DisconnectAsync();
  400. mqttClient.Dispose();
  401. }
  402. }
  403. public interface IAmrManager
  404. {
  405. bool LinkState { get; }
  406. int AmrOnlineCount { get; }
  407. bool ResetSystem();
  408. //msg和mqtt上序列化得消息内容一致
  409. // bool SendToAmr(string topic,string msg);
  410. //msg和mqtt上序列化得消息内容一致
  411. bool SendToMqtt(string topic, string msg);
  412. IList<MqMessage> GetMqMessages();
  413. IList<AmrMessage> GetAmrMessage();
  414. bool GetLinkState();
  415. int GetAmrOnlineCount();
  416. }
  417. }