No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MonitorUI.cs 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. //
  2. // MonitorUI.cs
  3. //
  4. // Author:
  5. // Di MERCURIO Sébastien <dimercur@insa-toulouse.fr>
  6. //
  7. // Copyright (c) 2018 INSA - DGEI
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU General Public License as published by
  11. // the Free Software Foundation, either version 3 of the License, or
  12. // (at your option) any later version.
  13. //
  14. // This program is distributed in the hope that it will be useful,
  15. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. // GNU General Public License for more details.
  18. //
  19. // You should have received a copy of the GNU General Public License
  20. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. // 15/01/2019 dimercur
  22. // Demande #41: Modifier les messages envoyés par les flèches de direction
  23. // 15/10/2019 dimercur
  24. // Demande #43: Migrer le code lié à la gestion des images dans sa propre classe widget
  25. using System;
  26. using Gtk;
  27. using Gdk;
  28. using Cairo;
  29. using monitor;
  30. using System.Timers;
  31. /// <summary>
  32. /// Main part of the program, behavior of main window
  33. /// </summary>
  34. public partial class MainWindow : Gtk.Window
  35. {
  36. /// <summary>
  37. /// Destijl command manager reference
  38. /// </summary>
  39. private DestijlCommandManager cmdManager;
  40. /// <summary>
  41. /// Position used for displaying position
  42. /// </summary>
  43. private DestijlCommandManager.Position position=new DestijlCommandManager.Position();
  44. /// <summary>
  45. /// List of availble state for the application
  46. /// </summary>
  47. enum SystemState
  48. {
  49. NotConnected,
  50. ServerConnected,
  51. RobotConnected
  52. };
  53. /// <summary>
  54. /// The state of the system. Can take a value from SystemState
  55. /// </summary>
  56. private SystemState systemState = SystemState.NotConnected;
  57. /// <summary>
  58. /// Object used for displaying image on a valid DrawingArea widget
  59. /// </summary>
  60. private ImageWidget imageWidget;
  61. /// <summary>
  62. /// Timer for battery request
  63. /// </summary>
  64. private System.Timers.Timer batteryTimer;
  65. /// <summary>
  66. /// Counter for image reception and detecting bad picture ratio
  67. /// </summary>
  68. private int imageReceivedCounter = 0;
  69. private int badImageReceivedCounter = 0;
  70. /// <summary>
  71. /// Initializes a new instance of the <see cref="MainWindow"/> class.
  72. /// </summary>
  73. public MainWindow() : base(Gtk.WindowType.Toplevel)
  74. {
  75. Build();
  76. cmdManager = new DestijlCommandManager(OnCommandReceivedEvent);
  77. // Init of image widget
  78. imageWidget = new ImageWidget(drawingAreaCamera);
  79. // create new timer for battery request, every 10s
  80. batteryTimer = new System.Timers.Timer(10000.0);
  81. batteryTimer.Elapsed += OnBatteryTimerElapsed;
  82. // Customize controls
  83. AdjustControls();
  84. }
  85. /// <summary>
  86. /// Make some adjustement to controls, like disabling some controls
  87. /// </summary>
  88. public void AdjustControls()
  89. {
  90. // Change state of system, and grey every controls not needed
  91. ChangeState(SystemState.NotConnected);
  92. // Load "no picture" image from disque
  93. imageWidget.ShowImage("monitor.ressources.missing_picture.png");
  94. // setup server controls
  95. entryServerName.Text = Client.defaultIP;
  96. entryServerPort.Text = Client.defaultPort.ToString();
  97. entryTimeout.Text = "1000";
  98. }
  99. /// <summary>
  100. /// Method used to change controls visibility (greyed or not) depending on current state
  101. /// </summary>
  102. /// <param name="newState">New state</param>
  103. private void ChangeState(SystemState newState)
  104. {
  105. switch (newState)
  106. {
  107. case SystemState.NotConnected:
  108. labelRobot.Sensitive = false;
  109. gtkAlignmentRobot.Sensitive = false;
  110. labelRobotControl.Sensitive = false;
  111. gtkAlignmentRobotControl.Sensitive = false;
  112. boxCamera.Sensitive = false;
  113. buttonServerConnection.Label = "Connect";
  114. buttonRobotActivation.Label = "Activate";
  115. labelBatteryLevel.Text = "Unknown";
  116. checkButtonCameraOn.Active = false;
  117. checkButtonRobotPosition.Active = false;
  118. checkButtonFPS.Active = false;
  119. imageWidget.ShowFPS = false;
  120. imageWidget.showPosition = false;
  121. if (cmdManager != null) cmdManager.Close();
  122. batteryTimer.Stop();
  123. break;
  124. case SystemState.ServerConnected:
  125. buttonServerConnection.Label = "Disconnect";
  126. buttonRobotActivation.Label = "Activate";
  127. labelBatteryLevel.Text = "Unknown";
  128. labelRobot.Sensitive = true;
  129. gtkAlignmentRobot.Sensitive = true;
  130. boxCamera.Sensitive = true;
  131. labelRobotControl.Sensitive = false;
  132. gtkAlignmentRobotControl.Sensitive = false;
  133. batteryTimer.Stop();
  134. break;
  135. case SystemState.RobotConnected:
  136. buttonRobotActivation.Label = "Reset";
  137. labelRobotControl.Sensitive = true;
  138. gtkAlignmentRobotControl.Sensitive = true;
  139. batteryTimer.Start();
  140. break;
  141. default:
  142. labelRobot.Sensitive = false;
  143. gtkAlignmentRobot.Sensitive = false;
  144. labelRobotControl.Sensitive = false;
  145. gtkAlignmentRobotControl.Sensitive = false;
  146. boxCamera.Sensitive = false;
  147. buttonServerConnection.Label = "Connect";
  148. buttonRobotActivation.Label = "Activate";
  149. labelBatteryLevel.Text = "Unknown";
  150. checkButtonCameraOn.Active = false;
  151. checkButtonRobotPosition.Active = false;
  152. checkButtonFPS.Active = false;
  153. imageWidget.ShowFPS = false;
  154. imageWidget.showPosition = false;
  155. systemState = SystemState.NotConnected;
  156. return;
  157. }
  158. systemState = newState;
  159. }
  160. /// <summary>
  161. /// Display a popup message window
  162. /// </summary>
  163. /// <param name="type">Type of popup window (question, error, information,...)</param>
  164. /// <param name="buttons">Buttons available on popup window</param>
  165. /// <param name="title">Title of window</param>
  166. /// <param name="message">Message</param>
  167. private void MessagePopup(MessageType type, ButtonsType buttons, string title, string message)
  168. {
  169. MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, type, buttons, message);
  170. md.Title = title;
  171. md.Run();
  172. md.Destroy();
  173. }
  174. /// <summary>
  175. /// Callback called when delete event is sent by window
  176. /// </summary>
  177. /// <param name="sender">Sender object</param>
  178. /// <param name="a">Not really sure of what it is...</param>
  179. protected void OnDeleteEvent(object sender, DeleteEventArgs a)
  180. {
  181. Console.WriteLine("Bye bye");
  182. if (cmdManager != null) cmdManager.Close();
  183. Application.Quit();
  184. a.RetVal = true;
  185. }
  186. /// <summary>
  187. /// Callback called when new message is received from server
  188. /// </summary>
  189. /// <param name="header">Header of message</param>
  190. /// <param name="data">Data of message</param>
  191. public void OnCommandReceivedEvent(string header, string data)
  192. {
  193. if (header == null)
  194. {
  195. // we have lost server
  196. ChangeState(SystemState.NotConnected);
  197. Gtk.Application.Invoke(delegate
  198. {
  199. MessagePopup(MessageType.Error,
  200. ButtonsType.Ok, "Server lost",
  201. "Server is down: disconnecting");
  202. cmdManager.Close();
  203. });
  204. }
  205. // if we have received a valid message
  206. if (header != null)
  207. {
  208. #if DEBUG
  209. // print message content
  210. if (header.Length > 4)
  211. {
  212. Console.WriteLine("Bad header(" + header.Length + ")");
  213. }
  214. #endif
  215. // Depending on message received (based on header), launch correponding action
  216. header = header.ToUpper();
  217. if (header == DestijlCommandList.ROBOT_BATTERY_LEVEL)
  218. {
  219. string batLevel = "";
  220. switch (data[0])
  221. {
  222. case '2':
  223. batLevel = "High";
  224. break;
  225. case '1':
  226. batLevel = "Low";
  227. break;
  228. case '0':
  229. batLevel = "Empty";
  230. break;
  231. default:
  232. batLevel = "Invalid value";
  233. break;
  234. }
  235. Gtk.Application.Invoke(delegate
  236. {
  237. labelBatteryLevel.Text = batLevel;
  238. });
  239. }
  240. else if (header == DestijlCommandList.CAMERA_IMAGE)
  241. {
  242. imageReceivedCounter++;
  243. byte[] image = new byte[2];
  244. try
  245. {
  246. image = Convert.FromBase64String(data);
  247. }
  248. catch (FormatException)
  249. {
  250. badImageReceivedCounter++;
  251. Console.WriteLine("Unable to convert from base64 ");
  252. }
  253. try
  254. {
  255. imageWidget.ShowImage(image);
  256. }
  257. catch (GLib.GException)
  258. {
  259. badImageReceivedCounter++;
  260. #if DEBUG
  261. Console.WriteLine("Bad Image: " + badImageReceivedCounter +
  262. " / " + imageReceivedCounter +
  263. " (" + badImageReceivedCounter * 100 / imageReceivedCounter + "%)");
  264. #endif
  265. }
  266. //}
  267. }
  268. else if (header == DestijlCommandList.CAMERA_POSITION)
  269. {
  270. imageWidget.Position = DestijlCommandManager.DecodePosition(data);
  271. }
  272. }
  273. }
  274. /// <summary>
  275. /// Callback called by "quit" menu
  276. /// </summary>
  277. /// <param name="sender">Sender object</param>
  278. /// <param name="e">Event</param>
  279. protected void OnQuitActionActivated(object sender, EventArgs e)
  280. {
  281. Console.WriteLine("Bye bye 2");
  282. if (cmdManager != null) cmdManager.Close();
  283. this.Destroy();
  284. Application.Quit();
  285. }
  286. /// <summary>
  287. /// Callback called by "show log" menu
  288. /// </summary>
  289. /// <param name="sender">Sender object</param>
  290. /// <param name="e">Event</param>
  291. protected void OnShowLogWindowActionActivated(object sender, EventArgs e)
  292. {
  293. MessagePopup(MessageType.Info,
  294. ButtonsType.Ok, "Info",
  295. "Logger not yet implemented");
  296. }
  297. /// <summary>
  298. /// Callback called by "buttonServerConnection" button
  299. /// </summary>
  300. /// <param name="sender">Sender object</param>
  301. /// <param name="e">Event</param>
  302. protected void OnButtonServerConnectionClicked(object sender, EventArgs e)
  303. {
  304. DestijlCommandManager.CommandStatus statusCmd;
  305. // if we are currently connected
  306. if (buttonServerConnection.Label == "Disconnect")
  307. {
  308. // Change state to disconnect and close connection
  309. ChangeState(SystemState.NotConnected);
  310. }
  311. else // we are not currently connected to server
  312. {
  313. // if information about hostname or port are invalid, show a popup error
  314. if ((entryServerName.Text == "") || (entryServerPort.Text == ""))
  315. {
  316. MessagePopup(MessageType.Error,
  317. ButtonsType.Ok, "Error",
  318. "Server name or port is invalid");
  319. }
  320. else
  321. {
  322. Console.WriteLine("Connecting to " + entryServerName.Text + ":" + entryServerPort.Text);
  323. bool status = false;
  324. // try to convert timout string value to double. If that failed, default to 100 ms
  325. try
  326. {
  327. cmdManager.timeout = Convert.ToDouble(entryTimeout.Text);
  328. }
  329. catch (Exception)
  330. {
  331. cmdManager.timeout = 100;
  332. entryTimeout.Text = cmdManager.timeout.ToString();
  333. }
  334. // try to connect to givn server.
  335. try
  336. {
  337. status = cmdManager.Open(entryServerName.Text, Convert.ToInt32(entryServerPort.Text));
  338. }
  339. catch (Exception)
  340. {
  341. Console.WriteLine("Something went wrong during connection");
  342. return;
  343. }
  344. //if connection status is not ok, show an error popup
  345. if (status != true)
  346. {
  347. MessagePopup(MessageType.Error,
  348. ButtonsType.Ok, "Error",
  349. "Unable to connect to server " + entryServerName.Text + ":" + Convert.ToInt32(entryServerPort.Text));
  350. }
  351. else // if we succed in connecting, open communication with robot
  352. {
  353. Console.Write("Send command RobotOpenCom: ");
  354. statusCmd = cmdManager.RobotOpenCom();
  355. Console.WriteLine(statusCmd.ToString());
  356. if (statusCmd == DestijlCommandManager.CommandStatus.Success)
  357. {
  358. ChangeState(SystemState.ServerConnected);
  359. }
  360. else // if communication with robot is not possible, show error
  361. {
  362. MessagePopup(MessageType.Error,
  363. ButtonsType.Ok, "Error",
  364. "Unable to open communication with robot.\nCheck that supervisor is accepting OPEN_COM_DMB command");
  365. cmdManager.Close();
  366. }
  367. }
  368. }
  369. }
  370. }
  371. /// <summary>
  372. /// Callback called when "buttonRobotactivation" is clicked
  373. /// </summary>
  374. /// <param name="sender">Sender object</param>
  375. /// <param name="e">Event</param>
  376. protected void OnButtonRobotActivationClicked(object sender, EventArgs e)
  377. {
  378. DestijlCommandManager.CommandStatus status;
  379. //if robot is not activated
  380. if (buttonRobotActivation.Label == "Activate")
  381. {
  382. // if a startup with watchdog is requested
  383. if (radioButtonWithWatchdog.Active)
  384. {
  385. status = cmdManager.RobotStartWithWatchdog();
  386. }
  387. else // startup without watchdog
  388. {
  389. status = cmdManager.RobotStartWithoutWatchdog();
  390. }
  391. // if status of command is ok, change state of system, enabling robot control
  392. if (status == DestijlCommandManager.CommandStatus.Success)
  393. {
  394. ChangeState(SystemState.RobotConnected);
  395. }
  396. else // if status is not ok, depending of error, show appropriate error
  397. {
  398. if (status == DestijlCommandManager.CommandStatus.CommunicationLostWithServer)
  399. {
  400. MessagePopup(MessageType.Error, ButtonsType.Ok, "Error", "Connection lost with server");
  401. ChangeState(SystemState.NotConnected);
  402. }
  403. else
  404. {
  405. MessagePopup(MessageType.Error, ButtonsType.Ok, "Error", "Command rejected\nCheck that supervisor accept \nDMB_START_WITH_WD and/or DMB_START_WITHOUT_WD");
  406. }
  407. }
  408. }
  409. else // If robot is already activated, request reset of robot
  410. {
  411. status = cmdManager.RobotReset();
  412. // if status of command is ok, change state of system, disabling robot control
  413. if (status == DestijlCommandManager.CommandStatus.Success)
  414. {
  415. ChangeState(SystemState.ServerConnected);
  416. }
  417. else // if status is not ok, depending of error, show appropriate error
  418. {
  419. if (status == DestijlCommandManager.CommandStatus.CommunicationLostWithServer)
  420. {
  421. MessagePopup(MessageType.Error, ButtonsType.Ok, "Error", "Connection lost with server");
  422. ChangeState(SystemState.NotConnected);
  423. }
  424. else
  425. {
  426. MessagePopup(MessageType.Error, ButtonsType.Ok, "Error", "Unknown error");
  427. }
  428. }
  429. }
  430. }
  431. /// <summary>
  432. /// Callback called when user click on direction button
  433. /// </summary>
  434. /// <param name="sender">Sender button</param>
  435. /// <param name="e">Event</param>
  436. protected void OnButtonMouvClicked(object sender, EventArgs e)
  437. {
  438. // depending on button clicked, launch appropriate action
  439. if (sender == buttonRight)
  440. {
  441. cmdManager.RobotGoRight();
  442. }
  443. else if (sender == buttonLeft)
  444. {
  445. cmdManager.RobotGoLeft();
  446. }
  447. else if (sender == buttonForward)
  448. {
  449. cmdManager.RobotGoForward();
  450. }
  451. else if (sender == buttonDown)
  452. {
  453. cmdManager.RobotGoBackward();
  454. }
  455. else if (sender == buttonStop)
  456. {
  457. cmdManager.RobotStop();
  458. }
  459. else
  460. {
  461. MessagePopup(MessageType.Warning, ButtonsType.Ok, "Abnormal behavior", "Callback OnButtonMouvClicked called by unknown sender");
  462. }
  463. }
  464. /// <summary>
  465. /// Callback called when battery timer expired
  466. /// </summary>
  467. /// <param name="sender">Sender object</param>
  468. /// <param name="e">Event</param>
  469. void OnBatteryTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
  470. {
  471. DestijlCommandManager.CommandStatus status;
  472. batteryTimer.Stop();
  473. // if battery checkbox is checked, a request for battery level is done
  474. if (checkButtonGetBattery.Active)
  475. {
  476. status = cmdManager.RobotGetBattery();
  477. // if status is not ok, show appropriate message and print "Unknown" for battery level
  478. switch (status)
  479. {
  480. case DestijlCommandManager.CommandStatus.Success:
  481. batteryTimer.Start();
  482. break;
  483. case DestijlCommandManager.CommandStatus.CommunicationLostWithServer:
  484. Console.WriteLine("Error: Connection lost with server");
  485. batteryTimer.Stop();
  486. labelBatteryLevel.Text = "Unknown";
  487. ChangeState(SystemState.NotConnected);
  488. break;
  489. case DestijlCommandManager.CommandStatus.CommunicationLostWithRobot:
  490. Console.WriteLine("Error: Connection lost with robot");
  491. batteryTimer.Stop();
  492. labelBatteryLevel.Text = "Unknown";
  493. ChangeState(SystemState.ServerConnected);
  494. break;
  495. default:
  496. labelBatteryLevel.Text = "Unknown";
  497. batteryTimer.Start();
  498. break;
  499. }
  500. }
  501. else batteryTimer.Start();
  502. }
  503. /// <summary>
  504. /// Callback called when checkbutton for camera is clicked
  505. /// </summary>
  506. /// <param name="sender">Sender object</param>
  507. /// <param name="e">Event</param>
  508. protected void OnCheckButtonCameraOnClicked(object sender, EventArgs e)
  509. {
  510. // if camera is already active, switch it off
  511. if (!checkButtonCameraOn.Active)
  512. {
  513. if (cmdManager.CameraClose() != DestijlCommandManager.CommandStatus.Success)
  514. {
  515. Console.WriteLine("Error when closing camera: bad answer for supervisor or timeout");
  516. }
  517. }
  518. else // camera is not active, switch it on
  519. {
  520. badImageReceivedCounter = 0;
  521. imageReceivedCounter = 0;
  522. if (cmdManager.CameraOpen() != DestijlCommandManager.CommandStatus.Success)
  523. {
  524. Console.WriteLine("Error when opening camera: bad answer for supervisor or timeout");
  525. //checkButtonCameraOn.Active = false;
  526. }
  527. }
  528. }
  529. /// <summary>
  530. /// Callback called when checkbutton robot position is clicked
  531. /// </summary>
  532. /// <param name="sender">Sender object</param>
  533. /// <param name="e">Event</param>
  534. protected void OnCheckButtonRobotPositionClicked(object sender, EventArgs e)
  535. {
  536. // if server already send robot position, stop it
  537. if (!checkButtonRobotPosition.Active)
  538. {
  539. if (cmdManager.CameraStopComputePosition() != DestijlCommandManager.CommandStatus.Success)
  540. {
  541. Console.WriteLine("Error when stopping position reception: bad answer for supervisor or timeout");
  542. }
  543. }
  544. else // start reception of robot position
  545. {
  546. if (cmdManager.CameraComputePosition() != DestijlCommandManager.CommandStatus.Success)
  547. {
  548. Console.WriteLine("Error when starting getting robot position: bad answer for supervisor or timeout");
  549. //checkButtonRobotPosition.Active = false;
  550. }
  551. }
  552. imageWidget.showPosition = checkButtonRobotPosition.Active;
  553. }
  554. /// <summary>
  555. /// Show a popup asking user to tell if arena is correct or not
  556. /// </summary>
  557. protected void DetectArena()
  558. {
  559. DestijlCommandManager.CommandStatus status;
  560. MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent,
  561. MessageType.Question, ButtonsType.YesNo, "Arena is correct ?");
  562. md.Title = "Check arena";
  563. ResponseType result = (ResponseType)md.Run();
  564. md.Destroy();
  565. if (result == ResponseType.Yes)
  566. {
  567. status = cmdManager.CameraArenaConfirm();
  568. }
  569. else
  570. {
  571. status = cmdManager.CameraArenaInfirm();
  572. }
  573. if (status != DestijlCommandManager.CommandStatus.Success)
  574. {
  575. MessagePopup(MessageType.Error,
  576. ButtonsType.Ok, "Error",
  577. "Unable to send Confirm or Infirm arena command to supervisor");
  578. }
  579. }
  580. /// <summary>
  581. /// Callback called when "Detect Arena" button is clicked
  582. /// </summary>
  583. /// <param name="sender">Sender object</param>
  584. /// <param name="e">Event</param>
  585. protected void OnButtonAskArenaClicked(object sender, EventArgs e)
  586. {
  587. // Send command to server for arean rendering
  588. if (cmdManager.CameraAskArena() != DestijlCommandManager.CommandStatus.Success)
  589. {
  590. MessagePopup(MessageType.Error,
  591. ButtonsType.Ok, "Error",
  592. "Error when asking for arena rendering");
  593. return;
  594. }
  595. // show popup and wait for user to say if arena is ok or not
  596. DetectArena();
  597. }
  598. /// <summary>
  599. /// Callback function for FPS checkbutton
  600. /// </summary>
  601. /// <param name="sender">Sender object</param>
  602. /// <param name="e">unused paramter</param>
  603. protected void OnCheckButtonFPSToggled(object sender, EventArgs e)
  604. {
  605. imageWidget.ShowFPS = checkButtonFPS.Active;
  606. }
  607. }