From e5d8a9c5078c9e69e197ca94981c27aafdbeab13 Mon Sep 17 00:00:00 2001 From: dr-i-boleet Date: Sat, 5 Jul 2025 08:27:19 +1100 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DroneClient/BaseCommHandler.cs | 478 +++++++ DroneClient/DroneClient.csproj | 24 + DroneClient/FormMain.Designer.cs | 1256 ++++++++++-------- DroneClient/FormMain.cs | 418 +++--- DroneClient/Models/MonitorContainer.cs | 126 ++ DroneClient/Models/Telemetry.cs | 340 +++++ DroneClient/Properties/Resources.Designer.cs | 63 + DroneClient/Properties/Resources.resx | 120 ++ DroneClient/TelemetryServer.cs | 258 ++++ DroneClient/images/connect.ico | Bin 0 -> 103906 bytes DroneClient/images/disconnect.ico | Bin 0 -> 103864 bytes connect.png | Bin 0 -> 1038 bytes disconnect.png | Bin 0 -> 1030 bytes 13 files changed, 2347 insertions(+), 736 deletions(-) create mode 100644 DroneClient/BaseCommHandler.cs create mode 100644 DroneClient/Models/MonitorContainer.cs create mode 100644 DroneClient/Models/Telemetry.cs create mode 100644 DroneClient/Properties/Resources.Designer.cs create mode 100644 DroneClient/Properties/Resources.resx create mode 100644 DroneClient/TelemetryServer.cs create mode 100644 DroneClient/images/connect.ico create mode 100644 DroneClient/images/disconnect.ico create mode 100644 connect.png create mode 100644 disconnect.png diff --git a/DroneClient/BaseCommHandler.cs b/DroneClient/BaseCommHandler.cs new file mode 100644 index 0000000..d5bbbf3 --- /dev/null +++ b/DroneClient/BaseCommHandler.cs @@ -0,0 +1,478 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TelemetryIO.Models; + +namespace TelemetryIO +{ + public interface iCommParams + { + + } + + internal class TCPCommParams : iCommParams + { + public string IP = ""; + public int Port = 0; + public TCPCommParams(string addr, int port) + { + this.IP = addr; + this.Port = port; + } + } + + public class SerialCommParams : iCommParams + { + public string PortName = ""; + public int BaudRate = 9600; + public SerialCommParams(string portName, int baudRate) + { + PortName = portName; + BaudRate = baudRate; + } + } + + public abstract class BaseCommHandler + { + public const int TELE_CMD_RD_ONCE = 1; + public const int TELE_CMD_RD_MON_ON = 2; + public const int TELE_CMD_RD_MON_OFF = 3; + public const int TELE_CMD_RD_MON_ADD = 4; + public const int TELE_CMD_RD_MON_REMOVE = 5; + public const int TELE_CMD_RD_MON_REMOVEALL = 6; + public const int TELE_CMD_WR = 10; + public const int TELE_CMD_MOTORS_CTRL = 100; + public const int TELE_CMD_ABORT = 999; + public const int TELE_CMD_HELLO = 9999; + public const byte CRC8_POLYNOMIAL = 0x07; + public const int CRC32_POLY = 0x04C11DB7; + public const byte ESCAPE_BEGIN = 0xBE; + public const byte ESCAPE_END = 0xED; + public const byte ESCAPE_CHAR = 0x0E; + + + private volatile bool _isReading = true; + private readonly ConcurrentQueue dataQueue = new ConcurrentQueue(); + static private List rx_buf = new List(); + private Task _readingTask; + private object lock_obj = new object(); + private object lock_obj_put = new object(); + private bool waitingForResponse = false; + private bool isTimeout = false; + private readonly int responseTimeout = 2000; + private System.Timers.Timer timeout = new System.Timers.Timer(3000); + private bool exitPending = false; + private float[] monitor = new float[32]; + public string view_str = ""; + + protected string IP = "127.0.0.1"; + protected int Port = 8888; + + protected string PortName = "COM1"; + protected int BaudRate = 9600; + + private Queue req_buffer = new Queue(); + + //Generate event when answer is received + public delegate void AnswerEventHandler(object sender, SerialEventArgs e); + public event AnswerEventHandler AnswerReceived;//событие получены данные в ответ на запрос + + //Generate event when handshake is occurred + public delegate void HandShakeHandler(object sender, EventArgs e); + public event HandShakeHandler HandShakeOccurred;//событие полетник ответил на запрос HELLO + + //Generate event when monitoring telegram is received + public delegate void MonitoringEventHandler(object sender, MonitoringEventArgs e); + public event MonitoringEventHandler MonitoringItemsReceived;//событие получены данные мониторинга + + Models.Telemetry telemetry = Models.Telemetry.Instance; + abstract public Task Open(); + abstract public void Close(); + abstract protected void sendData(byte[] data); + abstract public bool IsOpen(); + abstract public void CloseConnection(); + abstract public Task StartReadingAsync(object? client = null); + + abstract public void setCommParams(iCommParams commParams); + + abstract protected void ProcessCommand(int cmd, int slot, byte[] data, int offset, int len); + + /// + /// Обрабатывает входящий поток данных, при обнаружении ECSAPE_END байта декодирует данные, проверяет контрольную сумму и запускает обработку команды + /// + protected void data_extract() + { + byte b = new byte(); + while (dataQueue.TryDequeue(out b)) + { + if (b == ESCAPE_END) + {//END BYTE IS RECEIVED + isTimeout = false; + byte[] unscape = EscapeSeqToBytes(rx_buf.ToArray()); + uint checksum = crc32(unscape, unscape.Length - 4); + uint re_checksum = BitConverter.ToUInt32(unscape, unscape.Length - 4); + if (re_checksum == checksum) + { + //Parse telegram + int cmd = BitConverter.ToInt32(unscape, 0); + int slot = BitConverter.ToInt32(unscape, 4); + int len = BitConverter.ToInt32(unscape, 8); + int offset = BitConverter.ToInt32(unscape, 12); + byte[] data = new byte[len]; + Debug.WriteLine($"cmd = {cmd} *** slot = {slot} *** offset = {offset} *** len = {len}"); + if(cmd == TELE_CMD_WR)Array.Copy(unscape, 16, data, 0, len); + ProcessCommand(cmd, slot, data, offset, len); + + waitingForResponse = false; + timeout.Stop(); + sendNextRequest(); + } + } + else if (b == ESCAPE_BEGIN) + {//START BYTE IS RECEIVED + rx_buf.Clear(); + } + else + {//FILLING BUFFER + rx_buf.Add(b); + } + } + + } + //********************************************************************************************** + //*************************************** REQUESTS BLOCK *************************************** + //********************************************************************************************** + + public void getPIDs() + { + //отправить наборы ПИДов + putRequest(prepareTelegram(TELE_CMD_RD_ONCE, 1000, new byte[0], 0, 4 * 9)); + putRequest(prepareTelegram(TELE_CMD_RD_ONCE, 1001, new byte[0], 0, 4 * 9)); + putRequest(prepareTelegram(TELE_CMD_RD_ONCE, 1002, new byte[0], 0, 4 * 9)); + putRequest(prepareTelegram(TELE_CMD_RD_ONCE, 1003, new byte[0], 0, 4 * 9)); + } + + public void stopMonitoring() + { + //остановить мониторинг + putRequest(prepareTelegram(TELE_CMD_RD_MON_OFF, 0, new byte[0], 0, 0)); + } + + public void startMonitoring() + { + //начать мониторинг + putRequest(prepareTelegram(TELE_CMD_RD_MON_ON, 0, new byte[0], 0, 0)); + } + + public void AddMonitoringItem(int slot, int offset) + { + //добавить элемент из массива мониторинга по адресу + putRequest(prepareTelegram(TELE_CMD_RD_MON_ADD, slot, new byte[0], offset)); + } + + public void AddMonitoringItem(string name) + { + //добавить элемент из массива мониторинга по имени + putRequest(prepareTelegram(TELE_CMD_RD_MON_ADD, new byte[0], name)); + } + + public void RemoveMonitoringItem(int id) + { + //удалить элемент из массива мониторинга (len == id) + putRequest(prepareTelegram(TELE_CMD_RD_MON_REMOVE, 0, new byte[0], id)); + } + + public void RemoveMonitoringItems() + { + //удалить все элементы из массива мониторинга + putRequest(prepareTelegram(TELE_CMD_RD_MON_REMOVEALL, 0, new byte[0], 0)); + } + + public void sendFloats(int slot, float[] sp) + { + //Записать массив чисел с плавающей точкой + putRequest(prepareTelegram(TELE_CMD_WR, slot, sp, 0, sp.Length * 4)); + } + + public void sendMotorsControl() + { + //Отправляет задание на моторы в полетник. Тот в ответ посылает актуальные скорости на моторах + //В offset передается количество моторов + putRequest(prepareTelegram(TELE_CMD_MOTORS_CTRL, Telemetry.MOTORS_SP_ADDRESS, telemetry.motor_sp, 8)); + } + + /// + /// Считает контрольную сумму CRC32 + /// + public uint crc32(byte[] data, int length) + { + uint crc = 0xFFFFFFFF; // Начальное значение CRC + + for (int i = 0; i < length; i++) + { + crc ^= (uint)data[i] << 24; // XOR с текущим байтом + + for (int j = 0; j < 8; j++) + { + if ((uint)(crc & 0x80000000) != 0) + { + crc = (crc << 1) ^ CRC32_POLY; + } + else + { + crc <<= 1; + } + } + } + + return crc; + } + public byte[] prepareTelegram(int cmd, T[] load, string var_name) + { + VarAddress va = telemetry.getVarAdress(var_name); + return prepareTelegram(cmd, va.slot, load, va.offset, va.length); + } + + /// + /// Подготавливает данные для отправки, кодируя их в ESCAPE-последовательность + /// + public byte[] prepareTelegram(int cmd, int slot, T[] load, int offset, int len = 0) + { + byte[] byteload = DataArrayToBytes(load); + int total_len = 20 + byteload.Length;//cmd[4 bytes] + slot[4 bytes] + len[4 bytes] + offset[4 bytes] + load[len bytes] + byte[] data = new byte[total_len]; + + //Construct telegram + data[0] = (byte)(0xFF & cmd); + data[1] = (byte)(0xFF & (cmd >> 8)); + data[2] = (byte)(0xFF & (cmd >> 16)); + data[3] = (byte)(0xFF & (cmd >> 24)); + + data[4] = (byte)(0xFF & slot); + data[5] = (byte)(0xFF & (slot >> 8)); + data[6] = (byte)(0xFF & (slot >> 16)); + data[7] = (byte)(0xFF & (slot >> 24)); + + int l = 0; + if (cmd == TELE_CMD_WR) l = byteload.Length; + else l = len; + + data[8] = (byte)(0xFF & l); + data[9] = (byte)(0xFF & (l >> 8)); + data[10] = (byte)(0xFF & (l >> 16)); + data[11] = (byte)(0xFF & (l >> 24)); + + data[12] = (byte)(0xFF & offset); + data[13] = (byte)(0xFF & (offset >> 8)); + data[14] = (byte)(0xFF & (offset >> 16)); + data[15] = (byte)(0xFF & (offset >> 24)); + + if (byteload.Length > 0) + { + //Copy data + Array.Copy(byteload, 0, data, 16, byteload.Length); + } + //CRC32 + uint checksum = crc32(data, total_len - 4); + data[total_len - 4] = (byte)(0xFF & checksum); + data[total_len - 3] = (byte)(0xFF & (checksum >> 8)); + data[total_len - 2] = (byte)(0xFF & (checksum >> 16)); + data[total_len - 1] = (byte)(0xFF & (checksum >> 24)); + + byte[] escape = BytesToEscapeSeq(data); + byte[] ret = new byte[escape.Length + 2]; + Array.Copy(escape, 0, ret, 1, escape.Length); + ret[0] = ESCAPE_BEGIN; + ret[ret.Length - 1] = ESCAPE_END; + //Array.Copy(ret, saving_request, ret.Length); + return ret; + } + + /// + /// Конвертирует массив bool/byte/int/float в массив байт. Создает новый массив. + /// + byte[] DataArrayToBytes(T[] data) + { + if (data == null) return new byte[0]; + List ret = new List(); + for (int i = 0; i < data.Length; i++) + { + if (typeof(T) == typeof(float)) + { + ret.AddRange(BitConverter.GetBytes((float)(object)data[i])); + } + else if (typeof(T) == typeof(int)) + { + ret.AddRange(BitConverter.GetBytes((int)(object)data[i])); + } + else if (typeof(T) == typeof(byte)) + { + ret.Add((byte)(object)data[i]); + } + else if (typeof(T) == typeof(bool)) + { + bool t = (bool)(object)data[i]; + if (t) ret.Add(1); + else ret.Add(0); + } + + } + return ret.ToArray(); + } + + /// + /// Конвертирует массив байт в ESCAPE-последовательность. + /// + public byte[] BytesToEscapeSeq(byte[] data) + { + List ret = new List(); + for (int i = 0; i < data.Length; i++) + { + if ((data[i] == ESCAPE_BEGIN) || (data[i] == ESCAPE_CHAR) || (data[i] == ESCAPE_END)) + { + ret.Add(ESCAPE_CHAR); + ret.Add((byte)(data[i] - 0x0E)); + } + else + { + ret.Add(data[i]); + } + } + return ret.ToArray(); + } + + /// + /// Конвертирует ESCAPE-последовательность в массив байт. + /// + public byte[] EscapeSeqToBytes(byte[] EscSeq) + { + List ret = new List(); + for (int i = 0; i < EscSeq.Length; i++) + { + //if ((EscSeq[i] == ESCAPE_BEGIN) || (EscSeq[i] == ESCAPE_CHAR) || (EscSeq[i] == ESCAPE_END)) + if (EscSeq[i] == ESCAPE_CHAR) + { + i++; + ret.Add((byte)(EscSeq[i] + 0x0E)); + } + else + { + ret.Add(EscSeq[i]); + } + } + return ret.ToArray(); + } + + public void requestExit() + { + //запрос на закрытие приложения, отправляем команду на очистку массива мониторинга и ждем ответ, либо таймаут, + //после чего приложение закрывается + RemoveMonitoringItems(); + exitPending = true; + } + + /// + /// Добавляет данные в очередь ожидания отправки. + /// + public void putRequest(byte[] request) + { + //добавить в очередь или отправить(если очередь пуста) пакет в порт + lock (lock_obj_put) + { + if (waitingForResponse) + { + req_buffer.Enqueue(request); + return; + } + + sendRequest(request); + } + } + + /// + /// Толкает данные непосредственно в очередь отправки. + /// + protected void sendRequest(byte[] request) + { + //отправка пакета + //waitingForResponse = true; + if (IsOpen()) + { + sendData(request); + } + timeout.Stop(); + timeout.Start(); + } + + /// + /// Вытягивает данные из очереди ожидания непосредственно в очередь отправки. + /// + protected void sendNextRequest() + { + //вытащить из очереди пакет и отправить в порт + waitingForResponse = false; + if (req_buffer.Count > 0) + { + sendRequest(req_buffer.Dequeue()); + } + } + + /// + /// Копирует входящие данные в очередь. + /// + protected void EnqueueData(byte[] data, int len) + { + for (int i = 0; i < len; i++) + { + dataQueue.Enqueue(data[i]); + } + } + + public void ClearReqQueue() + { + req_buffer.Clear(); + } + + protected virtual void OnAnswerReceived(string answer, int id) + { + AnswerReceived?.Invoke(this, new FeedbackEventArgs(answer, id)); + } + + protected virtual void OnHandShakeOccurred() + { + HandShakeOccurred?.Invoke(this, new EventArgs()); + } + + protected virtual void OnMonitoringItemsReceived(float[] data) + { + MonitoringItemsReceived?.Invoke(this, new MonitoringEventArgs(data)); + } + } + + public class FeedbackEventArgs : EventArgs + { + public string Answer { get; set; } + public int Id { get; set; } + + public FeedbackEventArgs(string answer, int id) + { + Answer = answer; + Id = id; + } + } + + public class MonitoringEventArgs : EventArgs + { + public float[] Data { get; set; } + + public MonitoringEventArgs(float[] data) + { + Data = data; + } + } +} diff --git a/DroneClient/DroneClient.csproj b/DroneClient/DroneClient.csproj index c27cd77..1654505 100644 --- a/DroneClient/DroneClient.csproj +++ b/DroneClient/DroneClient.csproj @@ -8,4 +8,28 @@ enable + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + PreserveNewest + + + PreserveNewest + + + \ No newline at end of file diff --git a/DroneClient/FormMain.Designer.cs b/DroneClient/FormMain.Designer.cs index 4604a5d..9af3517 100644 --- a/DroneClient/FormMain.Designer.cs +++ b/DroneClient/FormMain.Designer.cs @@ -20,588 +20,677 @@ base.Dispose(disposing); } - #region Windows Form Designer generated code + #region Windows Form Designer generated code - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - timer_Test = new System.Windows.Forms.Timer(components); - groupBox1 = new GroupBox(); - textBox_Server_Addr = new TextBox(); - label2 = new Label(); - button_Connect = new Button(); - numericUpDown_Server_Port = new NumericUpDown(); - label3 = new Label(); - label1 = new Label(); - groupBox2 = new GroupBox(); - label_time_acc = new Label(); - label_Acc_Z = new Label(); - label7 = new Label(); - label_Acc_Y = new Label(); - label5 = new Label(); - label_Acc_X = new Label(); - groupBox3 = new GroupBox(); - label_time_gyr = new Label(); - label_Gyr_Z = new Label(); - label9 = new Label(); - label_Gyr_Y = new Label(); - label11 = new Label(); - label_Gyr_X = new Label(); - label13 = new Label(); - groupBox4 = new GroupBox(); - label_time_range = new Label(); - label_Pos_L = new Label(); - label6 = new Label(); - label_Pos_Y = new Label(); - label10 = new Label(); - label_Pos_X = new Label(); - label14 = new Label(); - trackBar_Power = new TrackBar(); - button_LL = new Button(); - button_UU = new Button(); - button_DD = new Button(); - button_RR = new Button(); - label_Pow = new Label(); - button_ML = new Button(); - button_MR = new Button(); - groupBox5 = new GroupBox(); - label_time_of = new Label(); - label_OF_Y = new Label(); - label17 = new Label(); - label_OF_X = new Label(); - label19 = new Label(); - trackBar_Value = new TrackBar(); - label4 = new Label(); - groupBox1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)numericUpDown_Server_Port).BeginInit(); - groupBox2.SuspendLayout(); - groupBox3.SuspendLayout(); - groupBox4.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)trackBar_Power).BeginInit(); - groupBox5.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)trackBar_Value).BeginInit(); - SuspendLayout(); - // - // timer_Test - // - timer_Test.Enabled = true; - timer_Test.Interval = 10; - timer_Test.Tick += timer_Test_Tick; - // - // groupBox1 - // - groupBox1.Controls.Add(textBox_Server_Addr); - groupBox1.Controls.Add(label2); - groupBox1.Controls.Add(button_Connect); - groupBox1.Controls.Add(numericUpDown_Server_Port); - groupBox1.Controls.Add(label3); - groupBox1.Dock = DockStyle.Top; - groupBox1.Location = new Point(0, 0); - groupBox1.Name = "groupBox1"; - groupBox1.Size = new Size(446, 80); - groupBox1.TabIndex = 3; - groupBox1.TabStop = false; - groupBox1.Tag = ""; - groupBox1.Text = "Server"; - // - // textBox_Server_Addr - // - textBox_Server_Addr.Location = new Point(48, 16); - textBox_Server_Addr.Name = "textBox_Server_Addr"; - textBox_Server_Addr.Size = new Size(125, 23); - textBox_Server_Addr.TabIndex = 4; - textBox_Server_Addr.Text = "127.0.0.1"; - // - // label2 - // - label2.AutoSize = true; - label2.Location = new Point(6, 19); - label2.Name = "label2"; - label2.Size = new Size(36, 15); - label2.TabIndex = 3; - label2.Tag = ""; - label2.Text = "Addr:"; - // - // button_Connect - // - button_Connect.BackColor = Color.Transparent; - button_Connect.Location = new Point(112, 46); - button_Connect.Name = "button_Connect"; - button_Connect.Size = new Size(61, 23); - button_Connect.TabIndex = 2; - button_Connect.Tag = ""; - button_Connect.Text = "Connect"; - button_Connect.UseVisualStyleBackColor = false; - button_Connect.Click += button_Connect_Click; - // - // numericUpDown_Server_Port - // - numericUpDown_Server_Port.Location = new Point(44, 48); - numericUpDown_Server_Port.Maximum = new decimal(new int[] { 65000, 0, 0, 0 }); - numericUpDown_Server_Port.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); - numericUpDown_Server_Port.Name = "numericUpDown_Server_Port"; - numericUpDown_Server_Port.Size = new Size(62, 23); - numericUpDown_Server_Port.TabIndex = 1; - numericUpDown_Server_Port.Value = new decimal(new int[] { 1001, 0, 0, 0 }); - // - // label3 - // - label3.AutoSize = true; - label3.Location = new Point(6, 50); - label3.Name = "label3"; - label3.Size = new Size(32, 15); - label3.TabIndex = 0; - label3.Tag = "#clients_port"; - label3.Text = "Port:"; - // - // label1 - // - label1.AutoSize = true; - label1.Location = new Point(6, 19); - label1.Name = "label1"; - label1.Size = new Size(17, 15); - label1.TabIndex = 4; - label1.Text = "X:"; - // - // groupBox2 - // - groupBox2.Controls.Add(label_time_acc); - groupBox2.Controls.Add(label_Acc_Z); - groupBox2.Controls.Add(label7); - groupBox2.Controls.Add(label_Acc_Y); - groupBox2.Controls.Add(label5); - groupBox2.Controls.Add(label_Acc_X); - groupBox2.Controls.Add(label1); - groupBox2.Location = new Point(6, 86); - groupBox2.Name = "groupBox2"; - groupBox2.Size = new Size(100, 118); - groupBox2.TabIndex = 5; - groupBox2.TabStop = false; - groupBox2.Text = "Acc"; - // - // label_time_acc - // - label_time_acc.AutoSize = true; - label_time_acc.Location = new Point(6, 100); - label_time_acc.Name = "label_time_acc"; - label_time_acc.Size = new Size(13, 15); - label_time_acc.TabIndex = 25; - label_time_acc.Text = "0"; - // - // label_Acc_Z - // - label_Acc_Z.AutoSize = true; - label_Acc_Z.Location = new Point(19, 70); - label_Acc_Z.Name = "label_Acc_Z"; - label_Acc_Z.Size = new Size(13, 15); - label_Acc_Z.TabIndex = 9; - label_Acc_Z.Text = "0"; - // - // label7 - // - label7.AutoSize = true; - label7.Location = new Point(6, 70); - label7.Name = "label7"; - label7.Size = new Size(17, 15); - label7.TabIndex = 8; - label7.Text = "Z:"; - // - // label_Acc_Y - // - label_Acc_Y.AutoSize = true; - label_Acc_Y.Location = new Point(19, 45); - label_Acc_Y.Name = "label_Acc_Y"; - label_Acc_Y.Size = new Size(13, 15); - label_Acc_Y.TabIndex = 7; - label_Acc_Y.Text = "0"; - // - // label5 - // - label5.AutoSize = true; - label5.Location = new Point(6, 45); - label5.Name = "label5"; - label5.Size = new Size(17, 15); - label5.TabIndex = 6; - label5.Text = "Y:"; - // - // label_Acc_X - // - label_Acc_X.AutoSize = true; - label_Acc_X.Location = new Point(19, 19); - label_Acc_X.Name = "label_Acc_X"; - label_Acc_X.Size = new Size(13, 15); - label_Acc_X.TabIndex = 5; - label_Acc_X.Text = "0"; - // - // groupBox3 - // - groupBox3.Controls.Add(label_time_gyr); - groupBox3.Controls.Add(label_Gyr_Z); - groupBox3.Controls.Add(label9); - groupBox3.Controls.Add(label_Gyr_Y); - groupBox3.Controls.Add(label11); - groupBox3.Controls.Add(label_Gyr_X); - groupBox3.Controls.Add(label13); - groupBox3.Location = new Point(112, 86); - groupBox3.Name = "groupBox3"; - groupBox3.Size = new Size(103, 118); - groupBox3.TabIndex = 6; - groupBox3.TabStop = false; - groupBox3.Text = "Gyr"; - // - // label_time_gyr - // - label_time_gyr.AutoSize = true; - label_time_gyr.Location = new Point(3, 100); - label_time_gyr.Name = "label_time_gyr"; - label_time_gyr.Size = new Size(13, 15); - label_time_gyr.TabIndex = 26; - label_time_gyr.Text = "0"; - // - // label_Gyr_Z - // - label_Gyr_Z.AutoSize = true; - label_Gyr_Z.Location = new Point(19, 70); - label_Gyr_Z.Name = "label_Gyr_Z"; - label_Gyr_Z.Size = new Size(13, 15); - label_Gyr_Z.TabIndex = 9; - label_Gyr_Z.Text = "0"; - // - // label9 - // - label9.AutoSize = true; - label9.Location = new Point(6, 70); - label9.Name = "label9"; - label9.Size = new Size(17, 15); - label9.TabIndex = 8; - label9.Text = "Z:"; - // - // label_Gyr_Y - // - label_Gyr_Y.AutoSize = true; - label_Gyr_Y.Location = new Point(19, 45); - label_Gyr_Y.Name = "label_Gyr_Y"; - label_Gyr_Y.Size = new Size(13, 15); - label_Gyr_Y.TabIndex = 7; - label_Gyr_Y.Text = "0"; - // - // label11 - // - label11.AutoSize = true; - label11.Location = new Point(6, 45); - label11.Name = "label11"; - label11.Size = new Size(17, 15); - label11.TabIndex = 6; - label11.Text = "Y:"; - // - // label_Gyr_X - // - label_Gyr_X.AutoSize = true; - label_Gyr_X.Location = new Point(19, 19); - label_Gyr_X.Name = "label_Gyr_X"; - label_Gyr_X.Size = new Size(13, 15); - label_Gyr_X.TabIndex = 5; - label_Gyr_X.Text = "0"; - // - // label13 - // - label13.AutoSize = true; - label13.Location = new Point(6, 19); - label13.Name = "label13"; - label13.Size = new Size(17, 15); - label13.TabIndex = 4; - label13.Text = "X:"; - // - // groupBox4 - // - groupBox4.Controls.Add(label_time_range); - groupBox4.Controls.Add(label_Pos_L); - groupBox4.Controls.Add(label6); - groupBox4.Controls.Add(label_Pos_Y); - groupBox4.Controls.Add(label10); - groupBox4.Controls.Add(label_Pos_X); - groupBox4.Controls.Add(label14); - groupBox4.Location = new Point(221, 86); - groupBox4.Name = "groupBox4"; - groupBox4.Size = new Size(103, 118); - groupBox4.TabIndex = 7; - groupBox4.TabStop = false; - groupBox4.Text = "Pos"; - // - // label_time_range - // - label_time_range.AutoSize = true; - label_time_range.Location = new Point(6, 100); - label_time_range.Name = "label_time_range"; - label_time_range.Size = new Size(13, 15); - label_time_range.TabIndex = 27; - label_time_range.Text = "0"; - // - // label_Pos_L - // - label_Pos_L.AutoSize = true; - label_Pos_L.Location = new Point(19, 70); - label_Pos_L.Name = "label_Pos_L"; - label_Pos_L.Size = new Size(13, 15); - label_Pos_L.TabIndex = 9; - label_Pos_L.Text = "0"; - // - // label6 - // - label6.AutoSize = true; - label6.Location = new Point(6, 70); - label6.Name = "label6"; - label6.Size = new Size(16, 15); - label6.TabIndex = 8; - label6.Text = "L:"; - // - // label_Pos_Y - // - label_Pos_Y.AutoSize = true; - label_Pos_Y.Location = new Point(19, 45); - label_Pos_Y.Name = "label_Pos_Y"; - label_Pos_Y.Size = new Size(13, 15); - label_Pos_Y.TabIndex = 7; - label_Pos_Y.Text = "0"; - // - // label10 - // - label10.AutoSize = true; - label10.Location = new Point(6, 45); - label10.Name = "label10"; - label10.Size = new Size(17, 15); - label10.TabIndex = 6; - label10.Text = "Y:"; - // - // label_Pos_X - // - label_Pos_X.AutoSize = true; - label_Pos_X.Location = new Point(19, 19); - label_Pos_X.Name = "label_Pos_X"; - label_Pos_X.Size = new Size(13, 15); - label_Pos_X.TabIndex = 5; - label_Pos_X.Text = "0"; - // - // label14 - // - label14.AutoSize = true; - label14.Location = new Point(6, 19); - label14.Name = "label14"; - label14.Size = new Size(17, 15); - label14.TabIndex = 4; - label14.Text = "X:"; - // - // trackBar_Power - // - trackBar_Power.Location = new Point(112, 240); - trackBar_Power.Maximum = 100; - trackBar_Power.Name = "trackBar_Power"; - trackBar_Power.Orientation = Orientation.Vertical; - trackBar_Power.Size = new Size(45, 141); - trackBar_Power.TabIndex = 12; - trackBar_Power.Scroll += trackBar_Power_Scroll; - // - // button_LL - // - button_LL.Location = new Point(9, 318); - button_LL.Name = "button_LL"; - button_LL.Size = new Size(75, 23); - button_LL.TabIndex = 13; - button_LL.Text = "LL"; - button_LL.UseVisualStyleBackColor = true; - button_LL.MouseDown += button_UU_MouseDown; - button_LL.MouseUp += button_UU_MouseUp; - // - // button_UU - // - button_UU.Location = new Point(98, 211); - button_UU.Name = "button_UU"; - button_UU.Size = new Size(75, 23); - button_UU.TabIndex = 14; - button_UU.Text = "UU"; - button_UU.UseVisualStyleBackColor = true; - button_UU.MouseDown += button_UU_MouseDown; - button_UU.MouseUp += button_UU_MouseUp; - // - // button_DD - // - button_DD.Location = new Point(98, 412); - button_DD.Name = "button_DD"; - button_DD.Size = new Size(75, 23); - button_DD.TabIndex = 15; - button_DD.Text = "DD"; - button_DD.UseVisualStyleBackColor = true; - button_DD.MouseDown += button_UU_MouseDown; - button_DD.MouseUp += button_UU_MouseUp; - // - // button_RR - // - button_RR.Location = new Point(188, 318); - button_RR.Name = "button_RR"; - button_RR.Size = new Size(75, 23); - button_RR.TabIndex = 16; - button_RR.Text = "RR"; - button_RR.UseVisualStyleBackColor = true; - button_RR.MouseDown += button_UU_MouseDown; - button_RR.MouseUp += button_UU_MouseUp; - // - // label_Pow - // - label_Pow.AutoSize = true; - label_Pow.Location = new Point(126, 384); - label_Pow.Name = "label_Pow"; - label_Pow.Size = new Size(13, 15); - label_Pow.TabIndex = 21; - label_Pow.Text = "0"; - // - // button_ML - // - button_ML.Location = new Point(9, 211); - button_ML.Name = "button_ML"; - button_ML.Size = new Size(75, 23); - button_ML.TabIndex = 22; - button_ML.Text = "<-"; - button_ML.UseVisualStyleBackColor = true; - button_ML.MouseDown += button_UU_MouseDown; - button_ML.MouseUp += button_UU_MouseUp; - // - // button_MR - // - button_MR.Location = new Point(188, 211); - button_MR.Name = "button_MR"; - button_MR.Size = new Size(75, 23); - button_MR.TabIndex = 23; - button_MR.Text = "->"; - button_MR.UseVisualStyleBackColor = true; - button_MR.MouseDown += button_UU_MouseDown; - button_MR.MouseUp += button_UU_MouseUp; - // - // groupBox5 - // - groupBox5.Controls.Add(label_time_of); - groupBox5.Controls.Add(label_OF_Y); - groupBox5.Controls.Add(label17); - groupBox5.Controls.Add(label_OF_X); - groupBox5.Controls.Add(label19); - groupBox5.Location = new Point(330, 86); - groupBox5.Name = "groupBox5"; - groupBox5.Size = new Size(104, 118); - groupBox5.TabIndex = 24; - groupBox5.TabStop = false; - groupBox5.Text = "OF"; - // - // label_time_of - // - label_time_of.AutoSize = true; - label_time_of.Location = new Point(6, 100); - label_time_of.Name = "label_time_of"; - label_time_of.Size = new Size(13, 15); - label_time_of.TabIndex = 27; - label_time_of.Text = "0"; - // - // label_OF_Y - // - label_OF_Y.AutoSize = true; - label_OF_Y.Location = new Point(19, 45); - label_OF_Y.Name = "label_OF_Y"; - label_OF_Y.Size = new Size(13, 15); - label_OF_Y.TabIndex = 7; - label_OF_Y.Text = "0"; - // - // label17 - // - label17.AutoSize = true; - label17.Location = new Point(6, 45); - label17.Name = "label17"; - label17.Size = new Size(17, 15); - label17.TabIndex = 6; - label17.Text = "Y:"; - // - // label_OF_X - // - label_OF_X.AutoSize = true; - label_OF_X.Location = new Point(19, 19); - label_OF_X.Name = "label_OF_X"; - label_OF_X.Size = new Size(13, 15); - label_OF_X.TabIndex = 5; - label_OF_X.Text = "0"; - // - // label19 - // - label19.AutoSize = true; - label19.Location = new Point(6, 19); - label19.Name = "label19"; - label19.Size = new Size(17, 15); - label19.TabIndex = 4; - label19.Text = "X:"; - // - // trackBar_Value - // - trackBar_Value.Location = new Point(349, 240); - trackBar_Value.Maximum = 100; - trackBar_Value.Minimum = 1; - trackBar_Value.Name = "trackBar_Value"; - trackBar_Value.Orientation = Orientation.Vertical; - trackBar_Value.Size = new Size(45, 195); - trackBar_Value.TabIndex = 25; - trackBar_Value.Value = 1; - // - // label4 - // - label4.AutoSize = true; - label4.Location = new Point(347, 219); - label4.Name = "label4"; - label4.Size = new Size(47, 15); - label4.TabIndex = 26; - label4.Text = "POWER"; - // - // Form_Main - // - AutoScaleDimensions = new SizeF(7F, 15F); - AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(446, 447); - Controls.Add(label4); - Controls.Add(trackBar_Value); - Controls.Add(groupBox5); - Controls.Add(button_MR); - Controls.Add(button_ML); - Controls.Add(label_Pow); - Controls.Add(button_RR); - Controls.Add(button_DD); - Controls.Add(button_UU); - Controls.Add(button_LL); - Controls.Add(trackBar_Power); - Controls.Add(groupBox4); - Controls.Add(groupBox3); - Controls.Add(groupBox2); - Controls.Add(groupBox1); - MinimumSize = new Size(291, 389); - Name = "Form_Main"; - Text = "Drone Client V1.0"; - FormClosing += Form_Main_FormClosing; - groupBox1.ResumeLayout(false); - groupBox1.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)numericUpDown_Server_Port).EndInit(); - groupBox2.ResumeLayout(false); - groupBox2.PerformLayout(); - groupBox3.ResumeLayout(false); - groupBox3.PerformLayout(); - groupBox4.ResumeLayout(false); - groupBox4.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)trackBar_Power).EndInit(); - groupBox5.ResumeLayout(false); - groupBox5.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)trackBar_Value).EndInit(); - ResumeLayout(false); - PerformLayout(); - } + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + timer_Test = new System.Windows.Forms.Timer(components); + groupBox1 = new GroupBox(); + TeleClientStatusPicture = new PictureBox(); + label12 = new Label(); + button1 = new Button(); + TeleServerPortCtrl = new NumericUpDown(); + label8 = new Label(); + textBox_Server_Addr = new TextBox(); + label2 = new Label(); + button_Connect = new Button(); + numericUpDown_Server_Port = new NumericUpDown(); + label3 = new Label(); + label1 = new Label(); + groupBox2 = new GroupBox(); + label_time_acc = new Label(); + label_Acc_Z = new Label(); + label7 = new Label(); + label_Acc_Y = new Label(); + label5 = new Label(); + label_Acc_X = new Label(); + groupBox3 = new GroupBox(); + label_time_gyr = new Label(); + label_Gyr_Z = new Label(); + label9 = new Label(); + label_Gyr_Y = new Label(); + label11 = new Label(); + label_Gyr_X = new Label(); + label13 = new Label(); + groupBox4 = new GroupBox(); + label_time_range = new Label(); + label_Pos_L = new Label(); + label6 = new Label(); + label_Pos_Y = new Label(); + label10 = new Label(); + label_Pos_X = new Label(); + label14 = new Label(); + trackBar_Power = new TrackBar(); + button_LL = new Button(); + button_UU = new Button(); + button_DD = new Button(); + button_RR = new Button(); + label_Pow = new Label(); + button_ML = new Button(); + button_MR = new Button(); + groupBox5 = new GroupBox(); + label_time_of = new Label(); + label_OF_Y = new Label(); + label17 = new Label(); + label_OF_X = new Label(); + label19 = new Label(); + trackBar_Value = new TrackBar(); + label4 = new Label(); + groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)TeleClientStatusPicture).BeginInit(); + ((System.ComponentModel.ISupportInitialize)TeleServerPortCtrl).BeginInit(); + ((System.ComponentModel.ISupportInitialize)numericUpDown_Server_Port).BeginInit(); + groupBox2.SuspendLayout(); + groupBox3.SuspendLayout(); + groupBox4.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)trackBar_Power).BeginInit(); + groupBox5.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)trackBar_Value).BeginInit(); + SuspendLayout(); + // + // timer_Test + // + timer_Test.Enabled = true; + timer_Test.Interval = 10; + timer_Test.Tick += timer_Test_Tick; + // + // groupBox1 + // + groupBox1.Controls.Add(TeleClientStatusPicture); + groupBox1.Controls.Add(label12); + groupBox1.Controls.Add(button1); + groupBox1.Controls.Add(TeleServerPortCtrl); + groupBox1.Controls.Add(label8); + groupBox1.Controls.Add(textBox_Server_Addr); + groupBox1.Controls.Add(label2); + groupBox1.Controls.Add(button_Connect); + groupBox1.Controls.Add(numericUpDown_Server_Port); + groupBox1.Controls.Add(label3); + groupBox1.Dock = DockStyle.Top; + groupBox1.Location = new Point(0, 0); + groupBox1.Margin = new Padding(3, 4, 3, 4); + groupBox1.Name = "groupBox1"; + groupBox1.Padding = new Padding(3, 4, 3, 4); + groupBox1.Size = new Size(510, 107); + groupBox1.TabIndex = 3; + groupBox1.TabStop = false; + groupBox1.Tag = ""; + groupBox1.Text = "Server"; + // + // TeleClientStatusPicture + // + TeleClientStatusPicture.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + TeleClientStatusPicture.Location = new Point(469, 53); + TeleClientStatusPicture.Name = "TeleClientStatusPicture"; + TeleClientStatusPicture.Size = new Size(24, 24); + TeleClientStatusPicture.TabIndex = 9; + TeleClientStatusPicture.TabStop = false; + // + // label12 + // + label12.AutoSize = true; + label12.Location = new Point(393, 51); + label12.Name = "label12"; + label12.Size = new Size(74, 20); + label12.TabIndex = 8; + label12.Tag = "#clients_port"; + label12.Text = "TeleClient"; + // + // button1 + // + button1.BackColor = Color.Transparent; + button1.Location = new Point(369, 16); + button1.Margin = new Padding(3, 4, 3, 4); + button1.Name = "button1"; + button1.Size = new Size(127, 31); + button1.TabIndex = 7; + button1.Tag = ""; + button1.Text = "Run TeleServer"; + button1.UseVisualStyleBackColor = false; + button1.Click += button1_Click; + // + // TeleServerPortCtrl + // + TeleServerPortCtrl.Location = new Point(291, 18); + TeleServerPortCtrl.Margin = new Padding(3, 4, 3, 4); + TeleServerPortCtrl.Maximum = new decimal(new int[] { 65000, 0, 0, 0 }); + TeleServerPortCtrl.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); + TeleServerPortCtrl.Name = "TeleServerPortCtrl"; + TeleServerPortCtrl.Size = new Size(71, 27); + TeleServerPortCtrl.TabIndex = 6; + TeleServerPortCtrl.Value = new decimal(new int[] { 8888, 0, 0, 0 }); + // + // label8 + // + label8.AutoSize = true; + label8.Location = new Point(248, 21); + label8.Name = "label8"; + label8.Size = new Size(38, 20); + label8.TabIndex = 5; + label8.Tag = "#clients_port"; + label8.Text = "Port:"; + // + // textBox_Server_Addr + // + textBox_Server_Addr.Location = new Point(55, 21); + textBox_Server_Addr.Margin = new Padding(3, 4, 3, 4); + textBox_Server_Addr.Name = "textBox_Server_Addr"; + textBox_Server_Addr.Size = new Size(142, 27); + textBox_Server_Addr.TabIndex = 4; + textBox_Server_Addr.Text = "127.0.0.1"; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new Point(7, 25); + label2.Name = "label2"; + label2.Size = new Size(45, 20); + label2.TabIndex = 3; + label2.Tag = ""; + label2.Text = "Addr:"; + // + // button_Connect + // + button_Connect.BackColor = Color.Transparent; + button_Connect.Location = new Point(128, 61); + button_Connect.Margin = new Padding(3, 4, 3, 4); + button_Connect.Name = "button_Connect"; + button_Connect.Size = new Size(70, 31); + button_Connect.TabIndex = 2; + button_Connect.Tag = ""; + button_Connect.Text = "Connect"; + button_Connect.UseVisualStyleBackColor = false; + button_Connect.Click += button_Connect_Click; + // + // numericUpDown_Server_Port + // + numericUpDown_Server_Port.Location = new Point(50, 64); + numericUpDown_Server_Port.Margin = new Padding(3, 4, 3, 4); + numericUpDown_Server_Port.Maximum = new decimal(new int[] { 65000, 0, 0, 0 }); + numericUpDown_Server_Port.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); + numericUpDown_Server_Port.Name = "numericUpDown_Server_Port"; + numericUpDown_Server_Port.Size = new Size(71, 27); + numericUpDown_Server_Port.TabIndex = 1; + numericUpDown_Server_Port.Value = new decimal(new int[] { 1001, 0, 0, 0 }); + // + // label3 + // + label3.AutoSize = true; + label3.Location = new Point(7, 67); + label3.Name = "label3"; + label3.Size = new Size(38, 20); + label3.TabIndex = 0; + label3.Tag = "#clients_port"; + label3.Text = "Port:"; + // + // label1 + // + label1.AutoSize = true; + label1.Location = new Point(7, 25); + label1.Name = "label1"; + label1.Size = new Size(21, 20); + label1.TabIndex = 4; + label1.Text = "X:"; + // + // groupBox2 + // + groupBox2.Controls.Add(label_time_acc); + groupBox2.Controls.Add(label_Acc_Z); + groupBox2.Controls.Add(label7); + groupBox2.Controls.Add(label_Acc_Y); + groupBox2.Controls.Add(label5); + groupBox2.Controls.Add(label_Acc_X); + groupBox2.Controls.Add(label1); + groupBox2.Location = new Point(7, 115); + groupBox2.Margin = new Padding(3, 4, 3, 4); + groupBox2.Name = "groupBox2"; + groupBox2.Padding = new Padding(3, 4, 3, 4); + groupBox2.Size = new Size(114, 157); + groupBox2.TabIndex = 5; + groupBox2.TabStop = false; + groupBox2.Text = "Acc"; + // + // label_time_acc + // + label_time_acc.AutoSize = true; + label_time_acc.Location = new Point(7, 133); + label_time_acc.Name = "label_time_acc"; + label_time_acc.Size = new Size(17, 20); + label_time_acc.TabIndex = 25; + label_time_acc.Text = "0"; + // + // label_Acc_Z + // + label_Acc_Z.AutoSize = true; + label_Acc_Z.Location = new Point(22, 93); + label_Acc_Z.Name = "label_Acc_Z"; + label_Acc_Z.Size = new Size(17, 20); + label_Acc_Z.TabIndex = 9; + label_Acc_Z.Text = "0"; + // + // label7 + // + label7.AutoSize = true; + label7.Location = new Point(7, 93); + label7.Name = "label7"; + label7.Size = new Size(21, 20); + label7.TabIndex = 8; + label7.Text = "Z:"; + // + // label_Acc_Y + // + label_Acc_Y.AutoSize = true; + label_Acc_Y.Location = new Point(22, 60); + label_Acc_Y.Name = "label_Acc_Y"; + label_Acc_Y.Size = new Size(17, 20); + label_Acc_Y.TabIndex = 7; + label_Acc_Y.Text = "0"; + // + // label5 + // + label5.AutoSize = true; + label5.Location = new Point(7, 60); + label5.Name = "label5"; + label5.Size = new Size(20, 20); + label5.TabIndex = 6; + label5.Text = "Y:"; + // + // label_Acc_X + // + label_Acc_X.AutoSize = true; + label_Acc_X.Location = new Point(22, 25); + label_Acc_X.Name = "label_Acc_X"; + label_Acc_X.Size = new Size(17, 20); + label_Acc_X.TabIndex = 5; + label_Acc_X.Text = "0"; + // + // groupBox3 + // + groupBox3.Controls.Add(label_time_gyr); + groupBox3.Controls.Add(label_Gyr_Z); + groupBox3.Controls.Add(label9); + groupBox3.Controls.Add(label_Gyr_Y); + groupBox3.Controls.Add(label11); + groupBox3.Controls.Add(label_Gyr_X); + groupBox3.Controls.Add(label13); + groupBox3.Location = new Point(128, 115); + groupBox3.Margin = new Padding(3, 4, 3, 4); + groupBox3.Name = "groupBox3"; + groupBox3.Padding = new Padding(3, 4, 3, 4); + groupBox3.Size = new Size(118, 157); + groupBox3.TabIndex = 6; + groupBox3.TabStop = false; + groupBox3.Text = "Gyr"; + // + // label_time_gyr + // + label_time_gyr.AutoSize = true; + label_time_gyr.Location = new Point(3, 133); + label_time_gyr.Name = "label_time_gyr"; + label_time_gyr.Size = new Size(17, 20); + label_time_gyr.TabIndex = 26; + label_time_gyr.Text = "0"; + // + // label_Gyr_Z + // + label_Gyr_Z.AutoSize = true; + label_Gyr_Z.Location = new Point(22, 93); + label_Gyr_Z.Name = "label_Gyr_Z"; + label_Gyr_Z.Size = new Size(17, 20); + label_Gyr_Z.TabIndex = 9; + label_Gyr_Z.Text = "0"; + // + // label9 + // + label9.AutoSize = true; + label9.Location = new Point(7, 93); + label9.Name = "label9"; + label9.Size = new Size(21, 20); + label9.TabIndex = 8; + label9.Text = "Z:"; + // + // label_Gyr_Y + // + label_Gyr_Y.AutoSize = true; + label_Gyr_Y.Location = new Point(22, 60); + label_Gyr_Y.Name = "label_Gyr_Y"; + label_Gyr_Y.Size = new Size(17, 20); + label_Gyr_Y.TabIndex = 7; + label_Gyr_Y.Text = "0"; + // + // label11 + // + label11.AutoSize = true; + label11.Location = new Point(7, 60); + label11.Name = "label11"; + label11.Size = new Size(20, 20); + label11.TabIndex = 6; + label11.Text = "Y:"; + // + // label_Gyr_X + // + label_Gyr_X.AutoSize = true; + label_Gyr_X.Location = new Point(22, 25); + label_Gyr_X.Name = "label_Gyr_X"; + label_Gyr_X.Size = new Size(17, 20); + label_Gyr_X.TabIndex = 5; + label_Gyr_X.Text = "0"; + // + // label13 + // + label13.AutoSize = true; + label13.Location = new Point(7, 25); + label13.Name = "label13"; + label13.Size = new Size(21, 20); + label13.TabIndex = 4; + label13.Text = "X:"; + // + // groupBox4 + // + groupBox4.Controls.Add(label_time_range); + groupBox4.Controls.Add(label_Pos_L); + groupBox4.Controls.Add(label6); + groupBox4.Controls.Add(label_Pos_Y); + groupBox4.Controls.Add(label10); + groupBox4.Controls.Add(label_Pos_X); + groupBox4.Controls.Add(label14); + groupBox4.Location = new Point(253, 115); + groupBox4.Margin = new Padding(3, 4, 3, 4); + groupBox4.Name = "groupBox4"; + groupBox4.Padding = new Padding(3, 4, 3, 4); + groupBox4.Size = new Size(118, 157); + groupBox4.TabIndex = 7; + groupBox4.TabStop = false; + groupBox4.Text = "Pos"; + // + // label_time_range + // + label_time_range.AutoSize = true; + label_time_range.Location = new Point(7, 133); + label_time_range.Name = "label_time_range"; + label_time_range.Size = new Size(17, 20); + label_time_range.TabIndex = 27; + label_time_range.Text = "0"; + // + // label_Pos_L + // + label_Pos_L.AutoSize = true; + label_Pos_L.Location = new Point(22, 93); + label_Pos_L.Name = "label_Pos_L"; + label_Pos_L.Size = new Size(17, 20); + label_Pos_L.TabIndex = 9; + label_Pos_L.Text = "0"; + // + // label6 + // + label6.AutoSize = true; + label6.Location = new Point(7, 93); + label6.Name = "label6"; + label6.Size = new Size(19, 20); + label6.TabIndex = 8; + label6.Text = "L:"; + // + // label_Pos_Y + // + label_Pos_Y.AutoSize = true; + label_Pos_Y.Location = new Point(22, 60); + label_Pos_Y.Name = "label_Pos_Y"; + label_Pos_Y.Size = new Size(17, 20); + label_Pos_Y.TabIndex = 7; + label_Pos_Y.Text = "0"; + // + // label10 + // + label10.AutoSize = true; + label10.Location = new Point(7, 60); + label10.Name = "label10"; + label10.Size = new Size(20, 20); + label10.TabIndex = 6; + label10.Text = "Y:"; + // + // label_Pos_X + // + label_Pos_X.AutoSize = true; + label_Pos_X.Location = new Point(22, 25); + label_Pos_X.Name = "label_Pos_X"; + label_Pos_X.Size = new Size(17, 20); + label_Pos_X.TabIndex = 5; + label_Pos_X.Text = "0"; + // + // label14 + // + label14.AutoSize = true; + label14.Location = new Point(7, 25); + label14.Name = "label14"; + label14.Size = new Size(21, 20); + label14.TabIndex = 4; + label14.Text = "X:"; + // + // trackBar_Power + // + trackBar_Power.Location = new Point(128, 320); + trackBar_Power.Margin = new Padding(3, 4, 3, 4); + trackBar_Power.Maximum = 100; + trackBar_Power.Name = "trackBar_Power"; + trackBar_Power.Orientation = Orientation.Vertical; + trackBar_Power.Size = new Size(56, 188); + trackBar_Power.TabIndex = 12; + trackBar_Power.Scroll += trackBar_Power_Scroll; + // + // button_LL + // + button_LL.Location = new Point(10, 424); + button_LL.Margin = new Padding(3, 4, 3, 4); + button_LL.Name = "button_LL"; + button_LL.Size = new Size(86, 31); + button_LL.TabIndex = 13; + button_LL.Text = "LL"; + button_LL.UseVisualStyleBackColor = true; + button_LL.MouseDown += button_UU_MouseDown; + button_LL.MouseUp += button_UU_MouseUp; + // + // button_UU + // + button_UU.Location = new Point(112, 281); + button_UU.Margin = new Padding(3, 4, 3, 4); + button_UU.Name = "button_UU"; + button_UU.Size = new Size(86, 31); + button_UU.TabIndex = 14; + button_UU.Text = "UU"; + button_UU.UseVisualStyleBackColor = true; + button_UU.MouseDown += button_UU_MouseDown; + button_UU.MouseUp += button_UU_MouseUp; + // + // button_DD + // + button_DD.Location = new Point(112, 549); + button_DD.Margin = new Padding(3, 4, 3, 4); + button_DD.Name = "button_DD"; + button_DD.Size = new Size(86, 31); + button_DD.TabIndex = 15; + button_DD.Text = "DD"; + button_DD.UseVisualStyleBackColor = true; + button_DD.MouseDown += button_UU_MouseDown; + button_DD.MouseUp += button_UU_MouseUp; + // + // button_RR + // + button_RR.Location = new Point(215, 424); + button_RR.Margin = new Padding(3, 4, 3, 4); + button_RR.Name = "button_RR"; + button_RR.Size = new Size(86, 31); + button_RR.TabIndex = 16; + button_RR.Text = "RR"; + button_RR.UseVisualStyleBackColor = true; + button_RR.MouseDown += button_UU_MouseDown; + button_RR.MouseUp += button_UU_MouseUp; + // + // label_Pow + // + label_Pow.AutoSize = true; + label_Pow.Location = new Point(144, 512); + label_Pow.Name = "label_Pow"; + label_Pow.Size = new Size(17, 20); + label_Pow.TabIndex = 21; + label_Pow.Text = "0"; + // + // button_ML + // + button_ML.Location = new Point(10, 281); + button_ML.Margin = new Padding(3, 4, 3, 4); + button_ML.Name = "button_ML"; + button_ML.Size = new Size(86, 31); + button_ML.TabIndex = 22; + button_ML.Text = "<-"; + button_ML.UseVisualStyleBackColor = true; + button_ML.MouseDown += button_UU_MouseDown; + button_ML.MouseUp += button_UU_MouseUp; + // + // button_MR + // + button_MR.Location = new Point(215, 281); + button_MR.Margin = new Padding(3, 4, 3, 4); + button_MR.Name = "button_MR"; + button_MR.Size = new Size(86, 31); + button_MR.TabIndex = 23; + button_MR.Text = "->"; + button_MR.UseVisualStyleBackColor = true; + button_MR.MouseDown += button_UU_MouseDown; + button_MR.MouseUp += button_UU_MouseUp; + // + // groupBox5 + // + groupBox5.Controls.Add(label_time_of); + groupBox5.Controls.Add(label_OF_Y); + groupBox5.Controls.Add(label17); + groupBox5.Controls.Add(label_OF_X); + groupBox5.Controls.Add(label19); + groupBox5.Location = new Point(377, 115); + groupBox5.Margin = new Padding(3, 4, 3, 4); + groupBox5.Name = "groupBox5"; + groupBox5.Padding = new Padding(3, 4, 3, 4); + groupBox5.Size = new Size(119, 157); + groupBox5.TabIndex = 24; + groupBox5.TabStop = false; + groupBox5.Text = "OF"; + // + // label_time_of + // + label_time_of.AutoSize = true; + label_time_of.Location = new Point(7, 133); + label_time_of.Name = "label_time_of"; + label_time_of.Size = new Size(17, 20); + label_time_of.TabIndex = 27; + label_time_of.Text = "0"; + // + // label_OF_Y + // + label_OF_Y.AutoSize = true; + label_OF_Y.Location = new Point(22, 60); + label_OF_Y.Name = "label_OF_Y"; + label_OF_Y.Size = new Size(17, 20); + label_OF_Y.TabIndex = 7; + label_OF_Y.Text = "0"; + // + // label17 + // + label17.AutoSize = true; + label17.Location = new Point(7, 60); + label17.Name = "label17"; + label17.Size = new Size(20, 20); + label17.TabIndex = 6; + label17.Text = "Y:"; + // + // label_OF_X + // + label_OF_X.AutoSize = true; + label_OF_X.Location = new Point(22, 25); + label_OF_X.Name = "label_OF_X"; + label_OF_X.Size = new Size(17, 20); + label_OF_X.TabIndex = 5; + label_OF_X.Text = "0"; + // + // label19 + // + label19.AutoSize = true; + label19.Location = new Point(7, 25); + label19.Name = "label19"; + label19.Size = new Size(21, 20); + label19.TabIndex = 4; + label19.Text = "X:"; + // + // trackBar_Value + // + trackBar_Value.Location = new Point(399, 320); + trackBar_Value.Margin = new Padding(3, 4, 3, 4); + trackBar_Value.Maximum = 100; + trackBar_Value.Minimum = 1; + trackBar_Value.Name = "trackBar_Value"; + trackBar_Value.Orientation = Orientation.Vertical; + trackBar_Value.Size = new Size(56, 260); + trackBar_Value.TabIndex = 25; + trackBar_Value.Value = 1; + // + // label4 + // + label4.AutoSize = true; + label4.Location = new Point(397, 292); + label4.Name = "label4"; + label4.Size = new Size(59, 20); + label4.TabIndex = 26; + label4.Text = "POWER"; + // + // Form_Main + // + AutoScaleDimensions = new SizeF(8F, 20F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(510, 596); + Controls.Add(label4); + Controls.Add(trackBar_Value); + Controls.Add(groupBox5); + Controls.Add(button_MR); + Controls.Add(button_ML); + Controls.Add(label_Pow); + Controls.Add(button_RR); + Controls.Add(button_DD); + Controls.Add(button_UU); + Controls.Add(button_LL); + Controls.Add(trackBar_Power); + Controls.Add(groupBox4); + Controls.Add(groupBox3); + Controls.Add(groupBox2); + Controls.Add(groupBox1); + Margin = new Padding(3, 4, 3, 4); + MinimumSize = new Size(330, 503); + Name = "Form_Main"; + Text = "Drone Client V1.0"; + FormClosing += Form_Main_FormClosing; + groupBox1.ResumeLayout(false); + groupBox1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)TeleClientStatusPicture).EndInit(); + ((System.ComponentModel.ISupportInitialize)TeleServerPortCtrl).EndInit(); + ((System.ComponentModel.ISupportInitialize)numericUpDown_Server_Port).EndInit(); + groupBox2.ResumeLayout(false); + groupBox2.PerformLayout(); + groupBox3.ResumeLayout(false); + groupBox3.PerformLayout(); + groupBox4.ResumeLayout(false); + groupBox4.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)trackBar_Power).EndInit(); + groupBox5.ResumeLayout(false); + groupBox5.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)trackBar_Value).EndInit(); + ResumeLayout(false); + PerformLayout(); + } - #endregion - private System.Windows.Forms.Timer timer_Test; + #endregion + private System.Windows.Forms.Timer timer_Test; private GroupBox groupBox1; private TextBox textBox_Server_Addr; private Label label2; @@ -648,5 +737,10 @@ private Label label_OF_X; private Label label19; private TrackBar trackBar_Value; - } + private Button button1; + private NumericUpDown TeleServerPortCtrl; + private Label label8; + private PictureBox TeleClientStatusPicture; + private Label label12; + } } diff --git a/DroneClient/FormMain.cs b/DroneClient/FormMain.cs index f33d230..540f602 100644 --- a/DroneClient/FormMain.cs +++ b/DroneClient/FormMain.cs @@ -3,170 +3,278 @@ using System.Numerics; using System.Windows.Forms; using static DroneSimulator.NetClient; using DroneClient; +using TelemetryIO.Models; +using TelemetryIO; +using DroneClient.Models; namespace DroneSimulator { - public partial class Form_Main : Form - { - private NetClient netClient = new NetClient(); - - public Form_Main() + public partial class Form_Main : Form { - InitializeComponent(); - } + private NetClient netClient = new NetClient(); + private Telemetry telemetry = Telemetry.Instance; + private MonitorContainer monitoring = MonitorContainer.Instance; + private System.Windows.Forms.Timer copyDataTimer = new System.Windows.Forms.Timer(); + private System.Windows.Forms.Timer MonitoringTimer = new System.Windows.Forms.Timer(); + TelemetryServer tele_server; + string connectIco = Path.Combine(Application.StartupPath, "Images", "connect.ico"); + string disconnectIco = Path.Combine(Application.StartupPath, "Images", "disconnect.ico"); - private void ConnectionCallback(object o) - { - ConnectData data = (ConnectData)o; - - if (!data.Connect) - { - try + public Form_Main() { - Invoke((MethodInvoker)delegate - { - button_Connect.Text = "Connect"; - button_Connect.BackColor = Color.Transparent; - MessageBox.Show("Connection closed"); - }); + InitializeComponent(); + tele_server = new TelemetryServer(); + if (Path.Exists(connectIco)) TeleClientStatusPicture.Image = Image.FromFile(disconnectIco); } - catch { } - return; - } - } - - Drone dataDrone = new Drone(); - - private void ReceiveCallback(object o) - { - ReceiveData data = (ReceiveData)o; - - List? send = dataDrone.DataStream(data.Buffer, data.Size); - - if (send == null) return; - try - { - foreach (byte[]? b in send) + private void Tele_server_OnTeleClientDisconnected(object? sender, EventArgs e) { - if (b != null) data.Server?.Send(b); + if (TeleClientStatusPicture.InvokeRequired) + { + TeleClientStatusPicture.Invoke(new Action(() => { if (Path.Exists(connectIco)) TeleClientStatusPicture.Image = Image.FromFile(disconnectIco); })); + } + else + { + if (Path.Exists(connectIco)) TeleClientStatusPicture.Image = Image.FromFile(disconnectIco); + } + monitoring.resetMonitorContainer(); + } + + private void Tele_server_OnTeleClientСonnected(object? sender, EventArgs e) + { + if (TeleClientStatusPicture.InvokeRequired) + { + TeleClientStatusPicture.Invoke(new Action(() => { if (Path.Exists(connectIco)) TeleClientStatusPicture.Image = Image.FromFile(connectIco); })); + } + else + { + if (Path.Exists(connectIco)) TeleClientStatusPicture.Image = Image.FromFile(connectIco); + } + } + + private void MonitoringTimer_Tick(object? sender, EventArgs e) + { + if (monitoring.isMonitor == 1) + { + byte[] load = new byte[monitoring.monitorFillLevel * sizeof(float)]; + for (int i = 0; i < monitoring.monitorFillLevel; i++) + { + int slot = monitoring.monitorItems[i].slotNo; + int offset = monitoring.monitorItems[i].offset; + int len = monitoring.monitorItems[i].len; + if(len == 0)continue; + Array.Copy(telemetry.getSlot(slot, offset, len), 0, load, i * sizeof(float), sizeof(float)); + } + if (tele_server != null) + { + tele_server.putRequest(tele_server.prepareTelegram(BaseCommHandler.TELE_CMD_RD_MON_ON, 0, load, 0, load.Length)); + } + } + } + + private void CopyDataTimer_Tick(object? sender, EventArgs e) + { + /*if (dataDrone != null) + { + float[] imu = { dataDrone.AccX, dataDrone.AccY, dataDrone.AccZ, dataDrone.GyrX, dataDrone.GyrY, dataDrone.GyrZ }; + Array.Copy(imu, telemetry.imu, 6); + }*/ + + if (monitoring.isMonitor == 1) MonitoringTimer.Start(); + else MonitoringTimer.Stop(); + } + + private void ConnectionCallback(object o) + { + ConnectData data = (ConnectData)o; + + if (!data.Connect) + { + try + { + Invoke((MethodInvoker)delegate + { + button_Connect.Text = "Connect"; + button_Connect.BackColor = Color.Transparent; + MessageBox.Show("Connection closed"); + }); + } + catch { } + + return; + } + } + + Drone dataDrone = new Drone(); + + private void ReceiveCallback(object o) + { + ReceiveData data = (ReceiveData)o; + + List? send = dataDrone.DataStream(data.Buffer, data.Size); + + if (send == null) return; + try + { + foreach (byte[]? b in send) + { + if (b != null) data.Server?.Send(b); + } + } + catch { } + } + + private void button_Connect_Click(object sender, EventArgs e) + { + var done = netClient.Connect(textBox_Server_Addr.Text, (int)numericUpDown_Server_Port.Value, ConnectionCallback, ReceiveCallback); + + switch (done) + { + case NetClient.ClientState.Error: + { + MessageBox.Show("Error connecting to server"); + break; + } + case NetClient.ClientState.Connected: + { + button_Connect.Text = "Disconnect"; + button_Connect.BackColor = Color.LimeGreen; + break; + } + case NetClient.ClientState.Stop: + { + button_Connect.Text = "Connect"; + button_Connect.BackColor = Color.Transparent; + break; + } + } + + if (done != NetClient.ClientState.Connected) return; + + + copyDataTimer.Interval = 10; + copyDataTimer.Tick += CopyDataTimer_Tick; + tele_server.setCommParams(new TCPCommParams("", (int)TeleServerPortCtrl.Value)); + var t = Task.Run(() => tele_server.Open()); + tele_server.OnTeleClientConnected += Tele_server_OnTeleClientСonnected; + tele_server.OnTeleClientDisconnected += Tele_server_OnTeleClientDisconnected; + MonitoringTimer.Interval = 25; + MonitoringTimer.Tick += MonitoringTimer_Tick; + copyDataTimer.Start(); + button1.BackColor = Color.LimeGreen; + } + + private void Form_Main_FormClosing(object sender, FormClosingEventArgs e) + { + netClient?.Close(); + netClient = null; + } + + private void timer_Test_Tick(object sender, EventArgs e) + { + label_Acc_X.Text = dataDrone.AccX.ToString(); + label_Acc_Y.Text = dataDrone.AccY.ToString(); + label_Acc_Z.Text = dataDrone.AccZ.ToString(); + + label_time_acc.Text = dataDrone.TimeAcc.ToString(); + + label_Gyr_X.Text = dataDrone.GyrX.ToString(); + label_Gyr_Y.Text = dataDrone.GyrY.ToString(); + label_Gyr_Z.Text = dataDrone.GyrZ.ToString(); + + label_time_gyr.Text = dataDrone.TimeGyr.ToString(); + + float[] imu = { dataDrone.AccX, dataDrone.AccY, dataDrone.AccZ, dataDrone.GyrX, dataDrone.GyrY, dataDrone.GyrZ }; + Array.Copy(imu, telemetry.imu, 6); + + label_Pos_X.Text = dataDrone.PosX.ToString(); + label_Pos_Y.Text = dataDrone.PosY.ToString(); + label_Pos_L.Text = dataDrone.LaserRange.ToString(); + + label_time_range.Text = dataDrone.TimeRange.ToString(); + + float[] pos = { dataDrone.PosX, dataDrone.PosY, dataDrone.LaserRange }; + Array.Copy(pos, telemetry.pos, 3); + + label_OF_X.Text = dataDrone.OF.X.ToString(); + label_OF_Y.Text = dataDrone.OF.Y.ToString(); + + label_time_of.Text = dataDrone.TimeRange.ToString(); + + float[] of = { dataDrone.OF.X, dataDrone.OF.Y }; + Array.Copy(of, telemetry.of, 2); + + if (monitoring.isMonitor == 1) MonitoringTimer.Start(); + else MonitoringTimer.Stop(); + + netClient.SendData(dataDrone.SendReqest()); + } + + private void trackBar_Power_Scroll(object sender, EventArgs e) + { + float pow = (float)trackBar_Power.Value / 100; + + label_Pow.Text = pow.ToString(); + + dataDrone.MotorUL = dataDrone.MotorUR = dataDrone.MotorDL = dataDrone.MotorDR = pow; + } + + private void button_UU_MouseDown(object sender, MouseEventArgs e) + { + float pow = ((float)trackBar_Value.Value) / 10.0f; + + if (sender == button_UU) + { + dataDrone.MotorUL -= pow; dataDrone.MotorUR -= pow; + dataDrone.MotorDL += pow; dataDrone.MotorDR += pow; + } + if (sender == button_DD) + { + dataDrone.MotorUL += pow; dataDrone.MotorUR += pow; + dataDrone.MotorDL -= pow; dataDrone.MotorDR -= pow; + } + if (sender == button_LL) + { + dataDrone.MotorUL -= pow; dataDrone.MotorUR += pow; + dataDrone.MotorDL -= pow; dataDrone.MotorDR += pow; + } + if (sender == button_RR) + { + dataDrone.MotorUL += pow; dataDrone.MotorUR -= pow; + dataDrone.MotorDL += pow; dataDrone.MotorDR -= pow; + } + + if (sender == button_ML) + { + dataDrone.MotorUL -= pow; dataDrone.MotorUR += pow; + dataDrone.MotorDL += pow; dataDrone.MotorDR -= pow; + } + + if (sender == button_MR) + { + dataDrone.MotorUL += pow; dataDrone.MotorUR -= pow; + dataDrone.MotorDL -= pow; dataDrone.MotorDR += pow; + } + } + + private void button_UU_MouseUp(object sender, MouseEventArgs e) + { + trackBar_Power_Scroll(null, null); + } + + private void button1_Click(object sender, EventArgs e) + { + + copyDataTimer.Interval = 10; + copyDataTimer.Tick += CopyDataTimer_Tick; + tele_server.setCommParams(new TCPCommParams("", (int)TeleServerPortCtrl.Value)); + var t = Task.Run(() => tele_server.Open()); + tele_server.OnTeleClientConnected += Tele_server_OnTeleClientСonnected; + tele_server.OnTeleClientDisconnected += Tele_server_OnTeleClientDisconnected; + MonitoringTimer.Interval = 25; + MonitoringTimer.Tick += MonitoringTimer_Tick; + copyDataTimer.Start(); + button1.BackColor = Color.LimeGreen; } - } - catch { } } - - private void button_Connect_Click(object sender, EventArgs e) - { - var done = netClient.Connect(textBox_Server_Addr.Text, (int)numericUpDown_Server_Port.Value, ConnectionCallback, ReceiveCallback); - - switch (done) - { - case NetClient.ClientState.Error: - { - MessageBox.Show("Error connecting to server"); - break; - } - case NetClient.ClientState.Connected: - { - button_Connect.Text = "Disconnect"; - button_Connect.BackColor = Color.LimeGreen; - break; - } - case NetClient.ClientState.Stop: - { - button_Connect.Text = "Connect"; - button_Connect.BackColor = Color.Transparent; - break; - } - } - - if (done != NetClient.ClientState.Connected) return; - } - - private void Form_Main_FormClosing(object sender, FormClosingEventArgs e) - { - netClient?.Close(); - netClient = null; - } - - private void timer_Test_Tick(object sender, EventArgs e) - { - label_Acc_X.Text = dataDrone.AccX.ToString(); - label_Acc_Y.Text = dataDrone.AccY.ToString(); - label_Acc_Z.Text = dataDrone.AccZ.ToString(); - - label_time_acc.Text = dataDrone.TimeAcc.ToString(); - - label_Gyr_X.Text = dataDrone.GyrX.ToString(); - label_Gyr_Y.Text = dataDrone.GyrY.ToString(); - label_Gyr_Z.Text = dataDrone.GyrZ.ToString(); - - label_time_gyr.Text = dataDrone.TimeGyr.ToString(); - - label_Pos_X.Text = dataDrone.PosX.ToString(); - label_Pos_Y.Text = dataDrone.PosY.ToString(); - label_Pos_L.Text = dataDrone.LaserRange.ToString(); - - label_time_range.Text = dataDrone.TimeRange.ToString(); - - label_OF_X.Text = dataDrone.OF.X.ToString(); - label_OF_Y.Text = dataDrone.OF.Y.ToString(); - - label_time_of.Text = dataDrone.TimeRange.ToString(); - - netClient.SendData(dataDrone.SendReqest()); - } - - private void trackBar_Power_Scroll(object sender, EventArgs e) - { - float pow = (float)trackBar_Power.Value / 100; - - label_Pow.Text = pow.ToString(); - - dataDrone.MotorUL = dataDrone.MotorUR = dataDrone.MotorDL = dataDrone.MotorDR = pow; - } - - private void button_UU_MouseDown(object sender, MouseEventArgs e) - { - float pow = ((float)trackBar_Value.Value) / 10.0f; - - if (sender == button_UU) - { - dataDrone.MotorUL -= pow; dataDrone.MotorUR -= pow; - dataDrone.MotorDL += pow; dataDrone.MotorDR += pow; - } - if (sender == button_DD) - { - dataDrone.MotorUL += pow; dataDrone.MotorUR += pow; - dataDrone.MotorDL -= pow; dataDrone.MotorDR -= pow; - } - if (sender == button_LL) - { - dataDrone.MotorUL -= pow; dataDrone.MotorUR += pow; - dataDrone.MotorDL -= pow; dataDrone.MotorDR += pow; - } - if (sender == button_RR) - { - dataDrone.MotorUL += pow; dataDrone.MotorUR -= pow; - dataDrone.MotorDL += pow; dataDrone.MotorDR -= pow; - } - - if (sender == button_ML) - { - dataDrone.MotorUL -= pow; dataDrone.MotorUR += pow; - dataDrone.MotorDL += pow; dataDrone.MotorDR -= pow; - } - - if (sender == button_MR) - { - dataDrone.MotorUL += pow; dataDrone.MotorUR -= pow; - dataDrone.MotorDL -= pow; dataDrone.MotorDR += pow; - } - } - - private void button_UU_MouseUp(object sender, MouseEventArgs e) - { - trackBar_Power_Scroll(null, null); - } - } } diff --git a/DroneClient/Models/MonitorContainer.cs b/DroneClient/Models/MonitorContainer.cs new file mode 100644 index 0000000..57fc853 --- /dev/null +++ b/DroneClient/Models/MonitorContainer.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TelemetryIO; + +namespace DroneClient.Models +{ + public class monitorItem + { + public int slotNo = 0; + public int offset = 0; + public int len = 0; + public int id = 0; + + public monitorItem() + { + + } + public monitorItem(int slotNo, int offset, int len, int id) + { + this.slotNo = slotNo; + this.offset = offset; + this.len = len; + this.id = id; + } + } + + public class MonitorContainer + { + public const int monitorMaxFill = 32; + public int monitorFillLevel = 0; + public monitorItem[] monitorItems = new monitorItem[monitorMaxFill]; + public int isMonitor = 0; + private static volatile MonitorContainer instance; + private static readonly object _lock = new object(); + + public static MonitorContainer Instance + { + get + { + if (instance == null) + { + lock (_lock) + { + if (instance == null) + { + instance = new MonitorContainer(); + } + } + } + return instance; + } + + } + + private MonitorContainer() + { + for (int i = 0; i < monitorMaxFill; i++) + { + monitorItems[i] = new monitorItem(); + } + } + + public void resetMonitorContainer() + { + for (int i = 0; i < monitorMaxFill; i++) + { + monitorItems[i] = new monitorItem(); + } + } + + public int monitorPut(int slot, int offset, int len) + { + int ret_id = 0; + if (monitorFillLevel < monitorMaxFill) + { + monitorItems[monitorFillLevel].slotNo = slot; + monitorItems[monitorFillLevel].offset = offset; + monitorItems[monitorFillLevel].len = len; + monitorItems[monitorFillLevel].id = (slot << 8) + offset; + ret_id = monitorItems[monitorFillLevel].id; + monitorFillLevel++; + isMonitor = 1; + } + + return ret_id; + } + + public int monitorRemove(int id) + { + int match = 0; + int ret_id = 0; + for (int i = 0; i < monitorMaxFill; i++) + { + if (monitorItems[i].id == id) + { + match = 1; + if (monitorFillLevel > 0) monitorFillLevel--; + ret_id = id; + } + + if (match > 0) + { + if (monitorItems[i].len == 0) return ret_id; + if (i < 31) + { + monitorItems[i].id = monitorItems[i + 1].id; + monitorItems[i].offset = monitorItems[i + 1].offset; + monitorItems[i].slotNo = monitorItems[i + 1].slotNo; + monitorItems[i].len = monitorItems[i + 1].len; + } + else + { + monitorItems[i].id = 0; + monitorItems[i].offset = 0; + monitorItems[i].slotNo = 0; + monitorItems[i].len = 0; + } + } + } + return ret_id; + } + } +} diff --git a/DroneClient/Models/Telemetry.cs b/DroneClient/Models/Telemetry.cs new file mode 100644 index 0000000..f172f15 --- /dev/null +++ b/DroneClient/Models/Telemetry.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelemetryIO.Models +{ + public sealed class Telemetry + { + //Класс синглтон для хранения данных телеметрии + private static volatile Telemetry instance; + private static readonly object _lock = new object(); + private Dictionary dictMonitor = new Dictionary();//Словарь адресов, для целей мониторинга + public string name = "_150"; + public float[] raw_imu = new float[6]; + public float[] imu = new float[6]; + public float[] filtered_imu = new float[6]; + public float[] g_integration = new float[3]; + public float[] euler = new float[3]; + public float[] quaternion = new float[4]; + public float[] battery = new float[4]; + public float[] motor_act = new float[8]; + public float[] motor_sp = new float[8]; + public float[] pid0 = new float[9]; + public float[] pid1 = new float[9]; + public float[] pid2 = new float[9]; + public float[] pid3 = new float[9]; + public float[] pos = new float[3]; + public float[] of = new float[2]; + + private object _lockRW = new object(); + + private Telemetry() { + dictMonitor.Add("rawAx", new VarAddress(RAW_IMU_ADDRESS, 0)); + dictMonitor.Add("rawAy", new VarAddress(RAW_IMU_ADDRESS, 1)); + dictMonitor.Add("rawAz", new VarAddress(RAW_IMU_ADDRESS, 2)); + dictMonitor.Add("rawGx", new VarAddress(RAW_IMU_ADDRESS, 3)); + dictMonitor.Add("rawGy", new VarAddress(RAW_IMU_ADDRESS, 4)); + dictMonitor.Add("rawGz", new VarAddress(RAW_IMU_ADDRESS, 5)); + + dictMonitor.Add("Ax", new VarAddress(IMU_ADDRESS, 0)); + dictMonitor.Add("Ay", new VarAddress(IMU_ADDRESS, 1)); + dictMonitor.Add("Az", new VarAddress(IMU_ADDRESS, 2)); + dictMonitor.Add("Gx", new VarAddress(IMU_ADDRESS, 3)); + dictMonitor.Add("Gy", new VarAddress(IMU_ADDRESS, 4)); + dictMonitor.Add("Gz", new VarAddress(IMU_ADDRESS, 5)); + + dictMonitor.Add("filtered_Ax", new VarAddress(FILTERED_IMU_ADDRESS, 0)); + dictMonitor.Add("filtered_Ay", new VarAddress(FILTERED_IMU_ADDRESS, 1)); + dictMonitor.Add("filtered_Az", new VarAddress(FILTERED_IMU_ADDRESS, 2)); + dictMonitor.Add("filtered_Gx", new VarAddress(FILTERED_IMU_ADDRESS, 3)); + dictMonitor.Add("filtered_Gy", new VarAddress(FILTERED_IMU_ADDRESS, 4)); + dictMonitor.Add("filtered_Gz", new VarAddress(FILTERED_IMU_ADDRESS, 5)); + + dictMonitor.Add("Gx_int", new VarAddress(G_INTEGRATION_ADDRESS, 0)); + dictMonitor.Add("Gy_int", new VarAddress(G_INTEGRATION_ADDRESS, 1)); + dictMonitor.Add("Gz_int", new VarAddress(G_INTEGRATION_ADDRESS, 2)); + + dictMonitor.Add("Euler.Phi", new VarAddress(EULER_ADDRESS, 0)); + dictMonitor.Add("Euler.Theta", new VarAddress(EULER_ADDRESS, 1)); + dictMonitor.Add("Euler.Psi", new VarAddress(EULER_ADDRESS, 2)); + + dictMonitor.Add("Qw", new VarAddress(Q_ADDRESS, 0)); + dictMonitor.Add("Qx", new VarAddress(Q_ADDRESS, 1)); + dictMonitor.Add("Qy", new VarAddress(Q_ADDRESS, 2)); + dictMonitor.Add("Qz", new VarAddress(Q_ADDRESS, 3)); + + dictMonitor.Add("Battery[V]", new VarAddress(BAT_ADDRESS, 0)); + + dictMonitor.Add("motor0_Act", new VarAddress(MOTORS_ACT_ADDRESS, 0)); + dictMonitor.Add("motor1_Act", new VarAddress(MOTORS_ACT_ADDRESS, 1)); + dictMonitor.Add("motor2_Act", new VarAddress(MOTORS_ACT_ADDRESS, 2)); + dictMonitor.Add("motor3_Act", new VarAddress(MOTORS_ACT_ADDRESS, 3)); + dictMonitor.Add("motor4_Act", new VarAddress(MOTORS_ACT_ADDRESS, 4)); + dictMonitor.Add("motor5_Act", new VarAddress(MOTORS_ACT_ADDRESS, 5)); + dictMonitor.Add("motor6_Act", new VarAddress(MOTORS_ACT_ADDRESS, 6)); + dictMonitor.Add("motor7_Act", new VarAddress(MOTORS_ACT_ADDRESS, 7)); + + dictMonitor.Add("Pos_X", new VarAddress(POS_ADDRESS, 0)); + dictMonitor.Add("Pos_Y", new VarAddress(POS_ADDRESS, 1)); + dictMonitor.Add("Pos_L", new VarAddress(POS_ADDRESS, 2)); + + dictMonitor.Add("OF_X", new VarAddress(OF_ADDRESS, 0)); + dictMonitor.Add("OF_Y", new VarAddress(OF_ADDRESS, 1)); + } + + public int getId(string name) + { + VarAddress var = dictMonitor[name]; + if (var == null) return -1; + return (var.slot << 8) | var.offset; + } + + public string getName(int id) + { + int slot = id >> 8; + int offset = id & 0xFF; + foreach(var element in dictMonitor) + { + if ((element.Value.slot == slot) && (element.Value.offset == offset)) return element.Key; + } + return null; + } + + public VarAddress getVarAdress(string name) + { + VarAddress ret; + dictMonitor.TryGetValue(name, out ret); + return ret; + } + + public string[] getKeys() + { + return dictMonitor.Keys.ToArray(); + } + + public static Telemetry Instance + { + get + { + if(instance == null) + { + lock(_lock) + { + if (instance == null) + { + instance = new Telemetry(); + } + } + } + return instance; + } + } + + public void setSlot(int slot, byte[] data, int offset, int len) + { + switch (slot) + { + case RAW_IMU_ADDRESS://RAW IMU + set_float(raw_imu, data, offset, len); + break; + + case IMU_ADDRESS://IMU + set_float(imu, data, offset, len); + break; + + case Q_ADDRESS://quaternion + set_float(quaternion, data, offset, len); + break; + + case G_INTEGRATION_ADDRESS://quaternion + set_float(g_integration, data, offset, len); + break; + + case EULER_ADDRESS://quaternion + set_float(euler, data, offset, len); + break; + + case FILTERED_IMU_ADDRESS://IMU + set_float(filtered_imu, data, offset, len); + break; + + case BAT_ADDRESS://battery + set_float(battery, data, offset, len); + break; + + case MOTORS_ACT_ADDRESS://motors act + set_float(motor_act, data, offset, len); + break; + + case MOTORS_SP_ADDRESS://motors act + set_float(motor_sp, data, offset, len); + break; + + case PID0_ADDRESS: + set_float(pid0, data, offset, len); + break; + + case PID1_ADDRESS: + set_float(pid1, data, offset, len); + break; + + case PID2_ADDRESS: + set_float(pid2, data, offset, len); + break; + + case PID3_ADDRESS: + set_float(pid3, data, offset, len); + break; + + case POS_ADDRESS: + set_float(pos, data, offset, len); + break; + + case OF_ADDRESS: + set_float(of, data, offset, len); + break; + + case NAME_ADDRESS: + set_name(data); + break; + } + } + + public byte[] getSlot(int slot, int offset, int len) + { + switch (slot) + { + case RAW_IMU_ADDRESS://RAW IMU + return get_float(raw_imu, offset, len); + + case IMU_ADDRESS://IMU + return get_float(imu, offset, len); + + case Q_ADDRESS://quaternion + return get_float(quaternion, offset, len); + + case G_INTEGRATION_ADDRESS://quaternion + return get_float(g_integration, offset, len); + + case EULER_ADDRESS://quaternion + return get_float(euler, offset, len); + + case FILTERED_IMU_ADDRESS://IMU + return get_float(filtered_imu, offset, len); + + case BAT_ADDRESS://battery + return get_float(battery, offset, len); + + case MOTORS_ACT_ADDRESS://motors act + return get_float(motor_act, offset, len); + + case MOTORS_SP_ADDRESS://motors act + return get_float(motor_sp, offset, len); + + case PID0_ADDRESS: + return get_float(pid0, offset, len); + + case PID1_ADDRESS: + return get_float(pid1, offset, len); + + case PID2_ADDRESS: + return get_float(pid2, offset, len); + + case PID3_ADDRESS: + return get_float(pid3, offset, len); + + case POS_ADDRESS: + return get_float(pos, offset, len); + break; + + case OF_ADDRESS: + return get_float(of, offset, len); + + case NAME_ADDRESS: + return Encoding.ASCII.GetBytes(name); + + default: + byte[] data = new byte[0]; + return data; + } + } + + + private void set_float(float[] dest, byte[] data, int offset, int len) + { + lock (_lockRW) + { + for (int i = 0; i < (int)(len / 4); i++) + { + byte[] bs = new byte[4]; + Array.Copy(data, i * 4, bs, 0, 4); + float f = BitConverter.ToSingle(bs, 0); + dest[offset + i] = f; + } + } + } + + private byte[] get_float(float[] source, int offset, int len) + { + lock (_lockRW) + { + byte[] bs = new byte[len]; + Buffer.BlockCopy(source, offset * sizeof(float), bs, 0, len); + return bs; + } + } + + private void set_name(byte[] data) + { + int len = data.Length; + if (len > 16) len = 16; + name = Encoding.ASCII.GetString(data, 0, len); + } + + public const int RAW_IMU_ADDRESS = 0; + public const int IMU_ADDRESS = 1; + public const int Q_ADDRESS = 2; + public const int G_INTEGRATION_ADDRESS = 3; + public const int EULER_ADDRESS = 4; + public const int FILTERED_IMU_ADDRESS = 5; + public const int POS_ADDRESS = 6; + public const int OF_ADDRESS = 7; + + public const int BAT_ADDRESS = 50; + + public const int MOTORS_ACT_ADDRESS = 100; + public const int MOTORS_SP_ADDRESS = 101; + + public const int PID0_ADDRESS = 1000; + public const int PID1_ADDRESS = 1001; + public const int PID2_ADDRESS = 1002; + public const int PID3_ADDRESS = 1003; + + public const int NAME_ADDRESS = 9999; + } + + public class VarAddress + { + public int slot = 0; + public int offset = 0; + public int length = 0; + + public VarAddress() + { + slot = offset = 0; + length = 4; + } + + public VarAddress(int slot, int offset, int length = 4) + { + this.slot = slot; + this.offset = offset; + this.length = length; + } + } +} diff --git a/DroneClient/Properties/Resources.Designer.cs b/DroneClient/Properties/Resources.Designer.cs new file mode 100644 index 0000000..2771671 --- /dev/null +++ b/DroneClient/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// Этот код создан программой. +// Исполняемая версия:4.0.30319.42000 +// +// Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае +// повторной генерации кода. +// +//------------------------------------------------------------------------------ + +namespace DroneClient.Properties { + using System; + + + /// + /// Класс ресурса со строгой типизацией для поиска локализованных строк и т.д. + /// + // Этот класс создан автоматически классом StronglyTypedResourceBuilder + // с помощью такого средства, как ResGen или Visual Studio. + // Чтобы добавить или удалить член, измените файл .ResX и снова запустите ResGen + // с параметром /str или перестройте свой проект VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Возвращает кэшированный экземпляр ResourceManager, использованный этим классом. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DroneClient.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Перезаписывает свойство CurrentUICulture текущего потока для всех + /// обращений к ресурсу с помощью этого класса ресурса со строгой типизацией. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/DroneClient/Properties/Resources.resx b/DroneClient/Properties/Resources.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/DroneClient/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DroneClient/TelemetryServer.cs b/DroneClient/TelemetryServer.cs new file mode 100644 index 0000000..f619039 --- /dev/null +++ b/DroneClient/TelemetryServer.cs @@ -0,0 +1,258 @@ +using DroneClient.Models; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TelemetryIO; +using TelemetryIO.Models; + +namespace TelemetryIO +{ + internal class TelemetryServer : TelemetryIO.BaseCommHandler + { + ConcurrentQueue sendQueue = new ConcurrentQueue(); + Telemetry telemetry = Telemetry.Instance; + MonitorContainer monitoring = MonitorContainer.Instance; + private bool isClientConnected = false; + + public event EventHandler OnTeleClientConnected = delegate { }; + public event EventHandler OnTeleClientDisconnected = delegate { }; + + public bool isClientAvailable { get { return isClientConnected; } } + + public TelemetryServer() + { + + } + public override void Close() + { + throw new NotImplementedException(); + } + + public override void CloseConnection() + { + throw new NotImplementedException(); + } + + public override bool IsOpen() + { + return true; + } + + public async override Task Open() + { + await StartServerAsync(); + //Console.ReadLine(); + } + + public async Task StartServerAsync() + { + var listener = new TcpListener(IPAddress.Any, Port); + listener.Start(); + Console.WriteLine("Server is running"); + try + { + while (true) + { + //if (isClientConnected) await Task.Delay(25); + var client = await listener.AcceptTcpClientAsync(); + StrikeOnTeleClientConnected(); + isClientConnected = true; + Debug.WriteLine("Client is connected"); + //this.client = client; + var readTask = StartReadingAsync(client); + var writeTask = SendMessageAsync(client); + await readTask; + await writeTask; + } + } + catch (IOException ex) when (IsNetworkError(ex)) + { + // Специальная обработка сетевых ошибок + Debug.WriteLine($"Ошибка при запуске сервера: {ex.Message}"); + } + catch (Exception ex) + { + Debug.WriteLine("An error occurred: " + ex.Message); + } + finally + { + listener.Stop(); + } + } + + public async Task SendMessageAsync(TcpClient c) + { + if (c.GetStream() == null || !c.Connected) + { + Console.WriteLine("Not connected to server"); + return; + } + + while (c.Connected) + { + if (sendQueue.TryDequeue(out byte[] buffer)) + { + try + { + await c.GetStream().WriteAsync(buffer, 0, buffer.Length); + Debug.WriteLine($"Sent"); + } + catch (IOException ex) when (IsNetworkError(ex)) + { + // Специальная обработка сетевых ошибок + Debug.WriteLine($"Клиент отключился: {ex.Message}"); + HandleDisconnectedClient(c); + } + catch (Exception ex) + { + Debug.WriteLine($"Error sending message: {ex.Message}"); + } + } + } + } + + public async override Task StartReadingAsync(object C) + { + TcpClient client; + if (C is TcpClient) client = (TcpClient)C; + else + { + return; + } + try + { + using NetworkStream stream = client.GetStream(); + + while (client.Connected) + { + byte[] buffer = new byte[1024]; + int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + //string response = await reader.ReadLineAsync(); + if (bytesRead > 0) + { + EnqueueData(buffer, bytesRead); + } + data_extract(); + } + } + catch (IOException ex) when (IsNetworkError(ex)) + { + // Специальная обработка сетевых ошибок + Debug.WriteLine($"Клиент отключился: {ex.Message}"); + HandleDisconnectedClient(client); + } + catch (Exception ex) + { + Debug.WriteLine($"Error sending message: {ex.Message}"); + } + } + + protected override void ProcessCommand(int cmd, int slot, byte[] data, int offset, int len) + { + Debug.WriteLine(@"server cmd = " + cmd); + + switch (cmd) + { + case TELE_CMD_HELLO: + byte[] load = telemetry.getSlot(cmd, offset, len); + putRequest(prepareTelegram(cmd, 0, load, 0, load.Length)); + break; + + case TELE_CMD_RD_ONCE: + byte[] load1 = telemetry.getSlot(slot, offset, len); + putRequest(prepareTelegram(cmd, slot, load1, offset, load1.Length)); + break; + + case TELE_CMD_WR: + setData(data, slot, offset, len); + sendVoidAnswer(TELE_CMD_WR); + break; + + case TELE_CMD_RD_MON_ON: + monitoring.isMonitor = 1; + sendVoidAnswer(TELE_CMD_RD_MON_ON); + break; + + case TELE_CMD_RD_MON_OFF: + monitoring.isMonitor = 0; + sendVoidAnswer(TELE_CMD_RD_MON_OFF); + break; + + case TELE_CMD_RD_MON_ADD: + int id = monitoring.monitorPut(slot, offset, 4); + sendIntAnswer(TELE_CMD_RD_MON_ADD, id); + monitoring.isMonitor = 1; + break; + + case TELE_CMD_RD_MON_REMOVE: + id = monitoring.monitorRemove(offset);//in this case offset == id + if(monitoring.monitorFillLevel == 0)monitoring.isMonitor = 0;//if monitor_fill_level == 0 there is no item to monitor + if (monitoring.isMonitor > 0) sendIntAnswer(TELE_CMD_RD_MON_REMOVE, id); + else sendVoidAnswer(TELE_CMD_RD_MON_REMOVEALL); + break; + } + } + + private void setData(byte[] load, int slot, int offset, int len) + { + telemetry.setSlot(slot, load, offset, len); + } + + private void sendVoidAnswer(int command) + { + putRequest(prepareTelegram(command, 0, new byte[0], 0)); + } + + private void sendIntAnswer(int command, int param) + { + putRequest(prepareTelegram(command, 0, new byte[0], param)); + } + + protected override void sendData(byte[] data) + { + sendQueue.Enqueue(data); + } + + public override void setCommParams(iCommParams commParams) + { + if (commParams.GetType() == typeof(TCPCommParams)) + { + var comm = (TCPCommParams)commParams; + IP = comm.IP; + Port = comm.Port; + } + } + + private void HandleDisconnectedClient(TcpClient client) + { + isClientConnected = false; + StrikeOnTeleClientDisconnected(); + } + + private bool IsNetworkError(Exception ex) + { + // Проверяем типичные сетевые ошибки при разрыве соединения + return ex is IOException + || ex is SocketException + || ex.InnerException is SocketException; + } + + public void StrikeOnTeleClientConnected() + { + OnTeleClientConnected?.Invoke(this, EventArgs.Empty); + } + + public void StrikeOnTeleClientDisconnected() + { + OnTeleClientDisconnected?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/DroneClient/images/connect.ico b/DroneClient/images/connect.ico new file mode 100644 index 0000000000000000000000000000000000000000..f3d3c3d4cf7ff38e71776fab4ddb8125212d3a5c GIT binary patch literal 103906 zcmeI52YeRA_V=G8bfhRE*NZ~bOA(|gC0qpYUKK=xil`_7!~#fHP&xz)MWiT#C`kMx ziiM{19w`<&f)wdk=tVjNvhVrs?n(BUXOVbOUy*(0^UJd{*=P66oHA$5%+7{FQK2j$ zK2f3AQ2V^0P!l;$OmyGnLZQyDM}_*{U%N)G9RJKAm(F!}%{%JwUyzMkCp`C&+%s>LyYINYZpZKU zZ;tPmaN_M=zwE91?)W_q*S~f~g`rn$yt@3?1Bz9Q%D1vazij_`Jht%lPgU*rcGnjh zuU&A{q>88V=F2wefyZAMnK-BIy`%nhMWL~M$L(75`RNa4?pgEpohQb&v;ME&fHMj6 z?wZy6!8SWLee&Y0k$djnQ(@b~ab0qDtnkjJpI(TYwxrsGJ0@-JHL_>95nY!4H+Nj0 zCV8gkD|9UCQ1`f$YKM0B8u`kEH@~@aYt-EZz6uS>aZ70Q{L4ZgmyM}jVrh7G?7Ur7 zCSKPi`PLE3zVDWj@I#3f0r6{rSkDLZkQ3&wS{n5#1VheJEzz@VslAY;U@%?uycl@(euH zU|YfM<9d`FF}!-m(@D!ej2g7OfAYUaH2AE*iY>W5U707bQSYzLyx2SM!>?~Iwtdro zx4qx?_$%9M_!8=&dt&^!fty|~n0(^>Z`RFN_CmdKXK$~UdG5U{K5abbU zS(~b@-a7WJGt+=N=sXh${zPj+|NbdNgPz>bjxR=Km4}IR%>hb(+MB!U*0X-w0q|_ zKC}90+cKNlb(%TwQ1=Cs@@}Xb>YCKPXVb?Hc8fXL`_S4lgXc}$eSFJ}Wpg&2_H~O{ zyUR5iU83!hIx7da9sfa&TR$jO_KGSaT6bAKd%*{74;Q*Ecd^?ZkBQoLe!dPZx~g~J11{{YkiRk>kqu#u2g*T)jJ#Bxqs$>)^B{5GV|^l>)vgb zzwOJwt^ok~33OxMt+Ob#99Ghd>u$$_A_G*))V-Gwzq;b`W*UTUH-YwbMRBLnD_^+*5 z`@R+#E6v%VzVcX~8@_ygYOD4Mz3Sgx?8kd<&wBX8+TG6|?o)Yd?g{g(pIR0A zrF^$~Z@hZ*ISm}lSTa0OY}&zIHRfb#+@eC8eXS}iUEZhUm#fE(?OXBS&j|-g*O>Ik z^efiAaAHHA!l@5$$~2$LT^({b$Rf*2Cgc&OZ3z zvvWG6>?^aX&cXP3mD;X~TDfY`q}@Lp+_hup(TP=VJ$s<~^;O5L%{8gi*38W|G<>(g z$79wtkK2EKZ5i31b&bJQ_8lH~=%bX$rBd$Q`Rxbq9XT_-%jSmVJM_Khm*^GUj{neW zhjbpCQZyy@yWH52e-{9$~k4)6Zs2Q>NfJ>u{$;lUDy2Erw2Z> z@2*~@&vZM~tKF1mO3z7cvUI_x?~FWqsM1#*zG*S5YO9=A=F8Qu+N`?^SVOzcZ1&vn z{nNM1T2Om-@}v*8%&PHl{Vk1)%?tgr^q$NcvTPnOAYbiK8+P70@ZipJkGASCyw%$6 z4=HxiZ(*%)4f`jH~)_v3qy)d%E-Sx7M%eaQU}6OU_BU z_S#R|Zf(rkX{d4BBgv5J>{?hV^`7hsj@`?Ci3r|!S zcVc|KXO})(zRPpRJB%FIB(V2!F_$Fm5*Be z!q#>pznawhn!DP`|5sQ$_Lb?J@Z{Rk2?O^|ZFhS13M*>I_o-1UI=)#os$17AQ|tD$ za(puAhhe#X|G24ej~4mk-t9l{mX(Ja zB*wpM?LIc`sn;8>JHJ1$xAL~yO)w?{v#KKk40KH@*1o;#Zr!)@{r8o!8!$ z^We{KJo8r7k6wTM+80J`nEdLjMOUrpRA|}qVdZi)N*ucXi)UIK9JHY0@yiE}n(^d- zPV)yhJD&Z%xE;mcxwiKF$#oo_IzmRp+kJ-Cy z|JUr?T}zE<_2{eXZ!UP(#)-?{dHmDXi$*QED!z2Rg}G);{%qta7(*m~^ojtzGwzIq58@OS|y6x9ak4nhZRJ3V# z%c*%!93AxEjw`dbAH460-MiK|sq#*InKh-R?#TIgzj1LlJr*51=c56Y2Q*#X`Cz%P zx_(h$)xPbg2MsQ_Z~xS~FH|Zw{A^Nm((4UBJ~ZsM(W}qj%3}L_{G6lFp|bCEOD>eM z^!*J(7q^|+tbMJ9i$*;CC^}|UwLd&lZ2+lzI)>H$G>|zYqft>`MAsJlhgLqkB*(vWLe(1 zp;A#IFMTF&sgnE2LZMPS*Ax{8q*Ui#O~;;mu;bY;cD}rBirA-fIPI$Y-_E@zxny#c zRX+|tle7M&0Zpt@vyWBo)T(3euH|;Mj;mC4R-V3BKijI)%}Z|X_-*{IS^I8}pY=?_ zFY!C?x~o^YP4C@0e#hD!ZLfIv=Pch})oN#a%yetdSrNdvukHLFt}llPE9}CU8mER&n!ArCgtTS$*Y?kf9p)U@+XS6Ej4@0wXK%i zm2Y*gC&%~sGReP7{I1KS>s`F8Jwp6739@ySbd&)oGN@!glops4YJI^Wh${&aiQ_=4>UHjn=D zp*7h%CA1lpJ=?Q$-hO_;zM`Kb7t1xxdOGez;_6%`c2NCQ zn+jDOIIPB`b#bk77j2k(=iE=KL?!iVmvuzNGgsuPcj%^sX)`Z-q+9My#oCtqd^6%~v*TAByrbPMKkxgzz_J$- zTIM^H*z~3a`A7C{JgfB=PZm1TyV>#U=54<`srQMB`wNs#Y`S~(u~8jgpPO^#sGnXr zeP{V&eb#lnvcv4RMnApnt?8F9+aLXij7#c{9TRn^Pv)$R^Pi}EV!`{>V&~P{+WXL~ z*hS|b5A3r-Q-6p#y1wWBY7$Kd-F;{6JI4R#@fVBBaUum0Y9JQje26<-6rT!0<^2Yl zOFk(AmxZ>5Ok5W_8Y&U8B2ZGwG6KKfa!vC-pE?DfjI}_(|BSWM-}S73 z|9@A`W~^QT|1;K3f7i1D{{Q_rn_H?>(g#DNEF8r{Q$m$Oi^6bEXj!P0_**7k6u-y+Ui8(C@{KQYW*XPTsFgXsf^5C%;sv zx)1Q7!%<4wxw`Pci^n8C&<9hc%)~4yn`2LS!8!gZfE3iLnFf3NN5II#a0Q-2UE#Fh<1-cYU3H4>6E z5f|Dceo{Cd3!Rh*niYm_k{{&*Zy&A4^X0sS1{O*Fs|?~xIgj(bSRi>n|6#P0o9H2B zEe#|{ohnN`OC4Xl>@cNxSQIgj(b;Cd^4utdsCAbVdJm?HU+KJbf2(^h8tg+jFH#qtUG zAMiiWf3FM#`XAW;!2U-ucgcc{+#K* z;gfCf!4@eqv0ch`82CZ*KV=X<%6Xjc1=ri^gYTux1b(L1cjOq~(ex9^6IV3xoRq~I zsA9)R!`su|F75o$BLuTC<65L zCOXPo=bIwOUJxVo##8l}Se=RWQnt~+X33jA9r@ip*ehlG4IGgCp$vj^P3)7hJwCu! zG=ZPE$pAhgKCB79nC8cF-rEMcN}J*Xn(*Vv9}_Dko*y35A#e8cwBb?Fh=nkznHOW@(TsD zmlw!`X%oo%@x+-skbnKIpMd`X{{#IuWgyW1@HkIk{~dq8Yx_CAxa>dc4_Skf4(6nZ zOPXjcbu%$q%DjCS$M}1G^QUI)j6CgS`cywl89tbSs3TF9iRfd|R?P93aGZ#-GM&tn zA;ieJCZdl=TTw^lUIrrkMEs9k0_gRESRy{G31WjL@Coq&yuc@9Txi1N2YF}xKsxUn zKK_<9rL0M<9tPHJl#u(IU|!1vbepkG{2Kgv6W$oB9`BL!O#dC<{dgpz&qP}$vYgFg zWlPN#hU}@?t;>8;E-NzSNX=n!z86_jvsy7{MJ^14P6$mV{Cq{^)-V3-C;!L;?d1hB z;TJ0?Ddi?uPh#@VS$i$T8TthL5BOhQBqbez{zvFOwje_Hu?2zt2ln3_Cj|CCvhT+i z&_3V$9rXX z8Rf(!iA{Rp=MyqUFk^+pjZL4L{74_L#@OVa^Q@T^XXq2~Kj43${~j3#^gpowf&G`h zCg}geynx8wKeu&}_5T&bBM3(^iRhJ-`DQQb$@5DSg#RX%Ntp@ebItktZBn+|2gjt$ zK$g>4tQ=>>+w&lAYF_IapXBG$RkA8sw;HLMRn7Wu22*vbx||)J{%!r+y4fdPXGNwf zQ?ImgrAl1UgY0LrTljz`%y_n)XDno{X(dkR7eAjO=9n|Rn}GFfk&n|D>B_~-EQ4w zq?%SuE8Zv7wjxt4tCpM{p6;;jFeJ*6uo6~eVnmY9NXUYBJfO$EYYLb*Fz1h06JgF1 zkpIF5Ev4*s1MEc+xhGj9?>Q&nzrp>Q!u^2%f&QDl%B?=yU&4+^1o|)b$J+-4_W!SH z|JkcD9CwRHS5NroMQ6#+^})ANW`e#7f6oizTxLvM@-Zngr)nTiY91@Uk*>9_wMzLU z`g$|bf8S@MI#wO)5ua4gicEE_x^i}S!gsjSC;9n=#2?M5Bt9YI0uvGI6q!3>{eZdt zVXKsV=L7E=0X^;^=Oq|mPb!noPCiM>!_iz~zy0L*UW}AHYdB1-lQI+V-HdAz(`Q|! z0dt<7{68hphTe3Ob(2-qCxvs#$u+PVSWg(~Y3pgLg->c}MW&`!Q#m_4J#Ia2J?xYG z#uUhe=@l|c@Ea@o&L0vFH^)lIgWp;KznJi7Irnt~>``X&`CR#c{|5J;7VZc9kI;Q= zfvNlC1N{&5Kd}E{G7#8*u?t4uPd@1XrCm-K_%r+eoZ=Ul=q{X@C^UI7L-KyU#wIB@ zLHug3fkPoH>a@f^4G`Be6Z7vCj6^i{exKCLicIaT_Eu*jb+@`(ulS^1R%A-F60L4V z>S%SeTKObDpOA5a87aUg^qW62*OQWGjd8?S3ICZffC<0&YqFd--2nU6`n|uv~m z_klOYugAnM%{85eqpX-S66-ZUT-Qvrl^N?}KKD_d)Xs`b_;0<9)X(Z?z3G#NSdnRf zHNbk+NH1G2+dcq(fHx5rG83_4<_o=v^#hT!*F`LzD>A{eW)X2==Fa`r&ziA!@(TpB z4e&K_fbXK0fGZ49Y;ySCMBr~Rek9CjL z&?k9$p~rai{f+d7^@cUvC%tP$CM4o@BOwpwn1FnEl-S7;g$`dmP}5#b6279?$z@Ls zf6k0N&mM;DJ#a+Y6MbS1&4k~Y0OE4+%ZvV!FX01|_v8cq8{B{0wgv(JMK(+u5a@q| z4G8pK4x~tzo1GtOp|OEZ&yM5AuT~4Kvb6YozswPa0=MCM3f2 z3h@aeBgMUZRu8naSCp8mN|9&3waSsZ59G4w}-!-`D!>iGNKG{PETjqyo-J|W`-Gge6K*l+!S-pvFCx$w&iV!9cf{}1>d@ITQ1Fc}E+U+l+n zALyDt?{{GTFCP1EuKgtcQYa-fNqFtWEXkYuJdxk*gPl@Ff8{~cFHyexADGX{lP@EQ z^^)F1{LF8yeQPT+u_gc?z?+!6B`#$YLM(J(JG7 zG;2V-U_H>ONSOPikjF17XC!3dUZ3=&6`7C-lUJ+_jf@oc5UxfhNl&5BcC*D zX6zE->^}hS&0VRO`}dP|^W6pHd7p*pKim5Ocx|j6o9pq&&lQ;KcV85qX7u^5fd2vi zy}HkFp#OpX2lhWg1{{kY{x`7yVlTXXKgU7;|JUsQ{q_KACeMH~2kgZN$@5&13BUD+ zJQHB9Q)NuZ-c|-Or^=dO1MCN2J}b{EQG94K`T2y56U;~);8EfX* zGr#=@%;(g|vrn11u7SO4{oY^T_f9K&{S0F$FNpnZ@WD4yW@40-4K&b7cbx z$9`)SmVXSip}S*iOKr; zZ#-9ICf089yty|qpBw&004K-3q-J7219ML+>cTs$On9G>OFL%&EOV}xIJ^0r7kOg6 zCb~)4V+QUy=l-ved%#zaogS;o@4aXudGnb-^2>d&PRceJAa-qz3CSOiM3$5+Veek$ znRwdI921Z?(z40tPJ|g7?c>V<+zzcjr z)2AZux2BHu1FRwRCiWEKxgs-}`*f0L?w0eti9M;zXZOjoSG}2de*y0>@`86-+3z*w z-KHM+`b79jrvJ2F%KYYfKbLY7kNLd+skrdee9xDYm*R-e5Gf&En2w4<>6>yd6MiuP ze8SZR@Cmp1fV~1tAP{Dj$Q_8bS=DQ2X%P1)ltaUMc zTGl_9FEceyM&aRxJGzo@-HO2KV|$A@ZZ>{HsF7t|AGFCt-
|;AuY34`oE&vR87g6vhxe)VCH_ZOti&mWR|D!e|b9`pQ@8Pmhh zHDluB{bIWKdh-m7mV9d;Tq!&;kym(S@-GyErx)@mD?c6>fto^-i9u2}*#L6zwGYg3 zB6-@yjDIr+ZmuaJuWj!AW~^YwT9Faihc-iiw}wpF*V`-QCg8ORc#RBuu~G7Veq-}@8r-{d`cllSBU{s;Q+)qRcw{SWN_#cls%r9){Hfl4w* zY~p!|is1uzflrw11Lg`$tdTO)C-hrWicjEYe~BNOej@(Cz6`?aOX%?~`7P@*OTbuCXNhk!b{;W@u+$bE#gXt5tkorvYfjM_X zezCxm3EIw2e~=3ko2AUOzsQO?rZ`s~esaDST+j4L$(wRYo^_fgua`;Lmp+&xWhPon zSuF$b-E3zk|7Y>}T=xm*Jv0UU5BMMGf4B?;`XAW;!2U>hY&?-ZTTm56>kl_={d(Yfb-=IIHQCk@wp_i*hqA%k`G{V3CxWm@8#7 z4NQ>yFdvkVG84pe&GvKre80B-E0zWP5BMMGzgGqV{SWMaVE=>u-;@Ef?ARW5BzwF3~{~}lO;dV2mPeXL_I01W}u|h$*iZ7_iGc{>hJam_#g1U zx(Kz2aNVbk0{sv4-)s9h4($KM-XF03kmpKF43V;Mu>Syi(0IX~RP0M>g1yR3@cx2c zA~Rm}mb{<;&{xV$aIT4$<(l2)UM89gO(yOZS}O|uUKF#RzYnjcQ*X09$Y1DMO}WHH z{iz_--*13@%90}B_x=LjVPVFZ$={ek`2Ch^`rXTqCiYN^q@OwkpNzFY!2gW3)8F;1 zfd7A2&StD$0sk}BPJh?40{;I=oVDb0(z#b*j`>_*K3PeRaV~d(d6yb4TG&I~@0CyG zLi&;GUuZsAuf?~tzy;<#wdaC`|LS>&3_d{%1TFB#w7_3o|4sTX)c!^M{zC2Z`F^jx zAME>G`kV9`{678R1^3TdL%VjxrJB_RiGnD7Y=nY8?o&`Mt+Aa~x4pFQ?Jjx{%W5~ss7%51b8N$Jp>*P6$`y;qjG4R2;V##wS<@8 zetAN>zVIC+2pSlmO#jch_sDO+?Q$LPZ@Ff1xjx`t+|xmR`38SxY?WAUbr;S&7cyI2 z>HwY>40NG$&CF5HaltwSbIpQxZ364dbWZzqIR-BXZg=qyxmWs}qtXFQf7a(xtbeRD zZX+9o#I_X?nFK8*ehfOxIuKpw{E6^M`Tmt0gRcckT==bHrcC$F_{Ke-->4jnljET- zy2`y;%RNC+p#c=O?X5%KAM>0b>sFk#pV!Hgd1|YN3r%2*K-blN18p-Y4PGs=w2ZUneqFOWGX_7U=pc z_@c6{{9Pwy`kd?*IR-p)sr%%yzlZsJFJ(Wt*e2hc559M?UcOa6yleJnOF4K;5bxr8 zp*7uaki!f5T#EINl@>$a%gVdO=h>((TH8k27w{aj>N<7{psQ1yYdu%?{u+S zzW4ZGmz05@1;_(XIoK@6$N=y@hpX?%`AEv!x%jWpiVQe%;MC){*+lH9W9wArskg2v z`%;e8R%@M+ccm4&wXO%`7#tKFc5zg`j~Srsh>Jt=&G~?~0<_6?7n|f;^=pM3gT;b* zE@%hU1+B+#d(Pxitbh7C2KskwJW&7ZeQ8sD9*Fj)zXHm0Y@5n2{Qkv3)Ct*dN)U50 z#>#wJ-ia#6nv&JZb|#yR>}TYC0)lKQ*(}~A0AfzXSkWhCPb)#1%peo0Gsu_9g32E{ zfeZk20ecAO7lGp+;I{!kUl|{|x()oA67pN1>n*Xd+80xA)kW2B3Zw>w0DB(`?hkV=CIiR7qEYu>Q|Z^pbu&jy?*0JDF^t2pgK=mtNyut8vE~MP7(Vi$eo%y?fo74 zZVq^>?{wq+E4+(^RM}0u|M?a-Aq&Ob#QOnBSEOE%W;0_>$ezvaK7MI77n?}G2(X3F z3qEndn2B*SaN-9}y)~v(RN@NNyrfhSja=^0pc$m9U(IPUt&P=sQ#w(rn@$p+oxw__W%^ z7xE2;3T}5n3`Og$-Y^V4UCu7sS$>cxrl_wwm;h{pA1PbAkFg>*N@qSE}>$!Q4}zefs68 zm#4imk@qv{`_ril=@Cg<*Jf5TtA(3dT25+iHBWnIJMF-`-buW(k;MC*R2Q%Z+ApN} z2iPI?mGQ~cFQDH7(*@`?AfA{$K1{NYFV*_%+Q%AV7bi;F0Q@IyH?^tTrEISQ`#o2( zM?U*z>$_88t=Ke~=UtonPF8qLYAva=n|fM3tykRC%W~37)=O45H$7)PXSH&Z$^o`g zZ6bYxzB5$)MB7(JE`fL4i0uaG05SlGE9?BirC$H5q%AbUH=>-u8)jE9l5lk%~>)!s^UQy;63^_rUoSWbG?dewT_O&zU{A^}eF z+C=(A68?eOLdFXActOTYX?yo#1BoGlRW9&hv`=S#L4BR#QVt#wOm*>$g>LIDWn}%9Lt>JEZ*K*R^*4x(WZlWDj7oK*l zBJH650eyk`%8Zu)X3s>r!u9zvkti@@ovI9-9#V%q??qF*t<8~lw>7YAGqlw%SrEB?~4RD30=S*lIRyn z_y_oiBz$G$jilpf;s@F%i#Dgn&(_E>AV#P0p#(WD>Eago29FD7xIkA_Z?OA-`v2k} z)9LhguIPLA@a^@T%gCzAtykxVSxy>ljkZ2<(>TjXw1Vma7D4+3^$+kJ)K_NApyNl4 z9}q)Hi>Xn6^;Oh=V=R#D0$vC8ciP}aX$$bEz)$|q!+#y;l(ovHjc*>a99Hd_%v$Y zz5cI`f9?{iIbO~IUkDbv&~Y@^!>$4u_v?FbH6}&OOXGXk0rgeX5278sx}bGc{{TNp zePzZBs`nZ{Ac~@~H2NHkD@V)R0$`3s_0#r!Rd4a(0X9wjd)6bF^Z*6(uIvM%o9A z7U)<($B*l!OnpJ(KnESfq{uv-Ajg>;Zlu0|-8UfvB=rfrHW7P}m}3HIV_y1Zl~3%o#w|6D`H7T0?}GJ->G4lzO=%H{iC2~Xg8>5d zg_g@PpdB<8hA*VH@VFcc@QHLRPM@IhDYc2nulfhHiT34MPsWZKKVW>LeTn*4M3yz? znK>o1JT}q2N8f?+0iGy- zh;=|O818~Kni-w{y)^5ezW(7~V*QIty8--RfbXbnN^D5|0+j>!pf(YmaQy@28FK}^ zYZWkdG~dsQyr?g0&+*Io{3dNoN9;*u4g0I(8~P=%Mu3ijxdOG{CFB^erX{26KQ7Js zr>}pyM%F(Sw$}`OAL=h{2a*Jg3jl4b@j>kq(LeYC@DK12!BGdy88L3Q$IfCSGo8*9 z4vDEUh5^hk*mF$s8}&16-s9T={E#g!;5%3$VB8MyUk1C-bzH3JO5dZ#Z~c>P1l>Q4 zu_ROYF>j_bTN`QMH&_4;e8p%0Etq;DuL?G4^^@t%BZpF2;ERW26GF+g5bK32%F z@>2T|@{|K~N#z^62EGu?bb+i7b5TOR0d`*Lb7WrW{=L30_4>z3L((rcl6`HHLik7k zUl}lF8X$ZC^uH5az&AilRdp6yp>|W_Gvp};3k94HK6f!jzR_{O`b5^K0(}n4>E9jt z{&!oKQCW$6{*AGt#t+&EjeT5$EA;D zs>?Bera$X*sn;L>k?|5>++53EKT8Y+5KAMT2#6~K<`*6j{(*XeM-5P>|7R@@>#+dq z(g5odS)&P9tE=}-r{jD+t^DK<{rErstn&-#2mJ-O@f^_qbFTg_y&oq|>BQ<-ul(2h zT$J^2g7~NJU?c3caqOkdG|6DF`zHjdG zcm5-VZd7HOO8p)8?01vNuirR{P^8~#^ls3~;fSK|9A-E-v_sDG*7j6Hz*Rm6b?32F)M zkZW;GuFXBT7xxUw(S?2Z{Y%mk14MQYi(H?OosU!H`MXq`UTDGh#kXDVVwHRYjbG`v z&fDl%2>Lxa?~`ldJ0l~s8BjVm z$T8UBf*1=R7URVak_TH|d@tWX>!tO9AKa&o;2ya*G&r_OzB2To&9m6Q%OSHg@p6p^ zU8b#UnV^(t@< zGS}B`cW9p|*I|6KSU?*g6U0PmZ)jFJi3uMN{Nm!Me0yq7Y<7klJ;w*XnA4k}M8)#!3OeGY`cY>L03PM-+V4W zUTAmdrhT{{GO|xVn+u)5UbipnWtcl>Zq55Fu0DgC?QFKRxfiW;Lb-=04 zdia>LGM6Aodp-#IftP;j0R6}k`hZSjm$0qS==5=3`spjy+Wp5HwjEBl>*&{70X(Md zX)A#~6RYc0ShvJnzOLWXb*%cWbHuuSKG)X^Sya2C+Rv7TW`>PFa5}o(w`*9^l|jl(CYY0@F3pypQ?x?+%Es0B4dJNWP@Vps`I#O5Wv9FMn z#s_2x9;;niEyn!xaUDXet_CZX!p<$?Rh0JU(Q@P zYeW7mN!N|&n)v%HC+YKstTkcnl=36LmEW#I8Xuq^z-#KD{lgb>j2_ey;PXMV`tSI$ zyf5KZ`QLoOB7xdT<}TnVwomLi`k6g7yFI5*+tZdjzr{K**7Wf_1M9$87s&Gstn=oZ zbz!UptcBJ4?ZAGw27_P*aU1Vc3JHzIzYcaNPyo8%{unL7w;`){pEiP z1WG^p1O4<-2K_u|gxo+o?FsDJb$}1ZQUv{b zv96Lf?<9#e#;iLf>GKw>!|W=FwWqAZR6bC5c*q(KY?EzY&xn1cpFsV*d^qT{(*juz8Zf6-vhtTt^>9M`$s>DEya#P zXE{Mh0rXcE{2Kkz4krEZ9Qu(1lYX8SgMOZ&;VCL=03S^Hu~E=ZANN1gf1}V|M)0`+ z-A4A|fw%oV^lN`b`@?tIpE|Ja8y*ai1P?xx#Ir!ufx19DIzii`+uASC&w1Nl{k{yh ze@5u{$dA&Gp8);XdZnK_KtIoLKtD<42z`Kl$ErH?V{f4!Uqjm;`tiMOzg+rDyYKlm z`BD9yAm>gM;B#Yx)NjB?7W>2cDuA8F24T0+VO?v>8vN%a@hl3@8Bz!MfGnX8=rp>G zZNC73I~POAQ@{rolh#fIAciA#U1*lPM+ zY$)r4sY5$S)B*jZ4)8%`iF!agwiVj3Q_zmD0sZh2A42KRdREpCi_F?SHMY}_ezl*M zNgqJ}^^w?8>Odb&AB^9zQ9%DoA0~b$xKa{67i&CuUKie@f5;F?`Jnos&pSagHWry7 z;cMV?;PcTB;(IfG&~cFM!_oK94Hz&W zOHw~s^;r2}KF0y=^l{LRuL14!+xV@}5ABSBZ2B3`h|JpKyK~X6{sTS`zL5H#*jIcw z9k;+g^aoiKp0b{het`Bu7x00&zxw*B56Ta0D~Udiz7Cr4m(U+%5Z@a=5S?W#XOBmZ z$-VI1u>Huv1_5$GobPo3@mBTU@ImoKdrDbv`5)s1rJp)LKYh4CKkbFT!g_G(03S%I z59p`zgl}la*TCMBpdG&ldq#gAK|gT~=!bTt|4TU@ARuO(?ms_hj|FZM3At1LM?Z#t zfvh2OUOyCF5Z|0GZqb7 z_pW~1YPl!22fH&vP*XtMJAM2TFJt1mvXAg_8G|`~9Cj4?0Xj?_&;yr#>X7ZMGme1| z$da>ayR6-=$cBz1>8G)e)QP(3a|`%@*kNdAEUNJUTjm+(soX<5m?J=UiF*^fPai*q zkKYH$|A)wV$k0Lo^~dj^Jpk8s<0kL{T>xsAV#T&X7jjL~cm!A~2a{skpgn+nr zSptV$oRV(=eKg3D>hMLs89M+S2id;mQTYvR0iYSIcYzd@dlSNo>*D@0H&?Ab<~31=KD!F_^XkKaN+pY-_oU1DR2TYoCQr4H}`SwbIH z3)C*Dz9~P@Z2-MqP%l6okvr-Q@C`N#kWJc!Hm9ASo%<5QXAb53@pHQZZXKNYi|jIY zKpo%%vhNNR{+@ti(Jf z>=L$>{((LYUSJ!N1^9f(ELi43>D78@y|kXxd%l47<~qc^iQO~*fSuu<7e0UL)WL85 z8eao{skh)&!2}oZ1<j#_~ICY>N&iV~}K72s@SbT9}sEp%?<&_pX z%NU@H|8WlI@*953HU9YZ8(IhbrXJzzXBfvZ7G*q*p5JJIGXBRooa@XE19|4t+y<-C{1@0)*LOrJ!E{gwJ`pJl=vF9aSa7sFld)i*ld7Vjr+`16(*Z54&+xo@d!A{qi!A^C|Xk&%J;3uhAA8 z$}2Nk*U0OT;>0U@Ol(m%xz{McTmk(9{haz1^w0Dg^f{viJp>8zo67RrYvmeT%VEc_ z-P8NOMq*DU+ig5tu7$nip7dk%U-Y}H1^5>L|Lj`K9cWToX3Oz&f-3S~$F_Lu={=5>Oe6c= z9fgmyBW+Aw@#*mGxW{h6UcmvuAr~Cud+!lIi_!tjwAsf3-VN{2&`9pXy}X~?Hjij0 z>aF*t4%7#K?Pr19BkG9EZ_D%4>?xe-q>Nnz97i8__nC&4T>{z%n&FeuKs#^`M;^TE zuS?pph91GUg#y}~dUGA>MO}}}dW>)Vy{q zkb4sAVSbK%5lF;Ph<6ezVl01U>Xr5$`8Ex-18qn}yQx+Ci%Z#u6Ee`#->4<;7Pe4WmvCR&Mh3|i-ZwH2CeaIY)7A&%8U8L1FxO9?b(0+T70eSro7?u$M`eHVvzcvPvX2ng zr!D|A>D%d(~jmRuE zh8Ry#NyKD{FT->0F-Q{gb~=v-4YY&KKO&Rph0Z%-qip+uOe2HzTLT2xf*S>W1n`9V z8~3LTi3h`bV&BB?sVnntIuA!Xzzdy!Lof9H*r;E{utROSQwa{nc zy6BwFCsR-6jiG^dfEUOEGD#buBgiW@3OkwB{@4L*A$q3mkNmwPvM@`^usxdvJLUer zID6J&@0f$5KIj+g1dst}pdH`^^Gfgqy+Aji6&r;eAkJm)zp+=YOWVTZ`2u_x}NEv#8e82;2uk<6CQ#0GKQRGnPLudzhfjrRO=mol|^B>qr;@@fW9@t=Xk3Qua z!6*T~oZ}BPm*es#G2kvo(} zeB^xM8?})CzbC*RLIdppFQ^CCls*~V#6F-`HpEubU)a2+z0n`&M&4+1?oFT9THx=0 z(FU=CM&bu;m3^O%+BDD(@B(?D4bcm(O?z-3cmp`zB|y%&?U;=GEXo^fDbcL&{=>#iBF5H{r2$}G^CF|u@BfN>?F3D{-U?sqmQ7kfMa|Z{3v`m z{7HOTN5>e$dVPJa{aZhdpQ`>EeHOgA(FMnprN?hFJpQ&4qpUNb&~1ckz_w5*PqM_s z5+!V)2>p*A{XO5wa?NdWZNPoFC-){!L0BSIkQS%NB$;3T|c{LMb_4*h~1WbZfqNdpQzVx=*Vq9 z!^?B*=$Ys|b}x5oZrjJ9FQRXz@4F&Z)*Rz+>NThf?ZNZFW8}BkM6p@9&gPO{x|F@n zh`yXY9KVUVcCKO5ft{f}Xj7gs#+C`)?6<=_JL?MYdDxGXwL$nKTmw4ny3ii9DST-l z-?X)qu^&2pC_V!|BYXL>pE=in4(dXC*lh}55(M=GUkRkWxiPYWjX@JNjk%Pu35x&nnk|4(h_XEBr=#e^hu2&Ct&?+SqaWv%MjE&o+Dk zt^pm?h4!FL;fw9>BOk~Za>_HQ6@*95p{{Wtzh>-vQ literal 0 HcmV?d00001 diff --git a/DroneClient/images/disconnect.ico b/DroneClient/images/disconnect.ico new file mode 100644 index 0000000000000000000000000000000000000000..37f053fb9cf503138f67a3740b9e753ff7c1063a GIT binary patch literal 103864 zcmeI537k(=`@rvvEnB=vdLv7(tc8?)n^v-)^2akt-05fY_Bxm~`--ei6fZ9Qs@B+QWW$ z^5nILh72EQEiJTud5!^2t@5IuOBdgF^jhnIHrZnynix^MU^}b9(7%T!PfR$M z^`5!;x(tu|=aR$LhtV%AcqMT{^1QCq8?@`wrsAaS&mZhCrb&U6x3{i8{-B)H}mD>xRkQf4z8@xbuO4btpvR6%OFC3XKJAfiZpoJt<7phbf25JYeJr#jrXKq(`rzi0V!Fk_CI}9#{#pv zPrUx^oYgy4-*at~sLu)woPS%ygB{}z+r{b)oVBs{-iTeV+?!m|?%40p(Eb$?fBwhP zq?F2ceVQw#SpKQanjao`ed_R(711RJx7~Db*@LaiPkCePmfoA%)@}N5^7wb&+`Fqq zqk?@KKHj=y@5CGcsY5^uG7)yu1&91`)Av#zwg<1=I5-nzE+R8d^0!g zJGt}vZS#*#FTMHcf6g1wz4)wy^SflbFUy;g8&{}pT{~<*`5s$Vw;5YBZgG}J1q||*Zq4bYQx1`=wr}V{jb>7wg=@wD1Y4*WaR2*Ebc86^fUmsgB z<-4xcKPt3xQQ5prKH2x`zLIM< zwLiaC^zKm)Z=P9j=H2tJ>6QDlqIJ6GsqneI;OEZ@O-#RO%2})1j-h>49T}W;(4;(P zE~**L@-2NmPtk-Eb#MA%*%bq~&FP%DW5S`t$yL|i)3fD-1!w0kXmefB(+yMa+<0@7 zgv2hdmMY$+z?glvb&I@e{@KO5E2k%QTHL(r)b5oF7wy?$;u~`l8%=C*c-+9Bx(_*( zbk&E&hMjt5!}RaZHr|pvYxeL)Iin-G4Jo~O?oV+~mG8c|%dG>`_C2|>OZooUcZ{sK zvUlGfJGc4!;=^sKeb=V;e6L;J&``Qf~OKm%Oa{8_b3;R`>eXwYeAx(RYuNJXj{-+%y zhs|qwU(;ioTW|fOZQYk+54>8j(6~|+AFjS-Y{lzZzEZJplgaOH$=Us(~YVm>aw;^MDzKN)~{A>a{nsVpSpMpiO;cM+=`Z4&-U*7 zRhDhz2Tt#~?}4ul4*Q|^*|Uksua!R?x#5KumhS5Pj~2NX&CAyR>j$-n-d>9SvhZ(nRDh(d1P3L`03G+JM*-kov^)P z!n!9n$A{OZQMqT9rBqw9pmnV#BS#M?`$WxZ9c#4PvO4ZxZ9hDGEHSamv5J37|FQbx zk9Vz=b4`cH>9dyYu3WF%nVL6GINfDc|8Ixx{U&dg9p%nGmomNcyfzh<+4Ec1`uX1* ziw%jdvAlk(*#|p5{^rCE&2w#g=5+VZXRNK&r}xo*hYxjIo)GuW&dHm{_vv%Y>agg& zRe2Zpx_(M*`?#jDdp6wq!NIw4agROmdhKsho_YO)M;aH7d~^5W`x<|KIKIx-e2oqa z$~vZ;hlaSybYRW9H_U(NxyJ_;tv&VN;)UPWXuho4tkn76^xstKd#T%=O$v3baLu!w zdwlen7~uooJdmwh-`R^((w4Njc5n0RcTK$cRP>o+Kdw8Me#_#onj~dwP%+{7TT>e! zo%~eIb?w)jsc=WXw(o3+J5aS+n=>s|ZR@hB^R}a{-ih1w%*LbVUTGAczN!ZH@pbQR zI%w0I75nhRCnx`_d7}vxy5{MJVAB+E~?fkmwkB%7} zaj_os#IEtx9!bc*{>o8t?@e!e<*r_-w{HAk%MV{pUvlh`nFC5kuYF+s@n9C0x%;a1>*ppOJGL%h0B^asBegZ1%_Q^z~j?f>*E>)x2UXSO|{=z>zi=d`T;!Imalj^7<^mlrEvY}MB# z?y49yIM3Gww?2?9edfD&Oi#+z;k^m-ziid3;KBn(C%=E-XoW%7m7Do+dg|W2g$Ae3 zdN{dP)wK0djg}4g;6S})_BEB79`1Z`!+&B;kJ5jyJ#=!PUA-%xEtNlgf9*E;@@)P1 zZ*Pz5)S>H%`t^4eX!%u>RdLs4-@0R6yG`W|b(#O()>8B5SN(SPU29J!e>|_x)30nO zTC`oy_U%(r|5f#D>XGR+i&W27cwFwI2fE)rsO5~FO=FUV#+6Rl_29zTFEeBIYd6Tg zp-sK(vM$d#;N~e^AA4zQ<;kVC*X;8AGiz^bxj3p<-3^6%w`n_S_uQ_}?R~0hMAN3n z5_^5UH}SFBwQl=4Uy;sVw_iK&_3GK{=4`(=<;9fchn_q2%8L4x&NfNS+AVU+%Ea;I zdekg@XjQh>1zWvap!F*|Z)%yNR;|P?{oY>R?)19KlXtiITFm=f>*C@DRGoEd>KiNe z#RH+TD0EwK;rdntEN>P+7!F`x3pU_V+X&>nwGo8xgteN#pP>lTl2>yj{l;^ z;tstUwp#sN!Kr91kh?;h~|rXGhPzO46rhZhb;4ryVz2S+?QdX?2+$yeis zB^J!zYV(o8tNX31x%Ayq$7;0fmu1@RU!OYCJ+WH0s8y#64C?+?S?jvxxyNqq|LCgx zIil89sq*Qm>?`YRD6-<{hF#~@>}fUXP$rb-ouM^iCOjuLzFKm0yB@VBom-HyV!`K~ zS1tK*P>J9Cgd*QMEsatxa-MMnx@{1dYd1B|ZAItd^(o%FQjR<>^)EyR1cqJy)X3 zsk{fO+}XEP$0gmxhb`GXa{A%H;?w4cN_~6plwqen&vmH4&cQ#vvwC{}-7hv-oieHJ zuv!V@o;>+z%7)LsE1Ym5Wkm0fZ!X-VapBR6O3sd)v9|n@Z#%_Js$P4>_|biyNIe?e z>%P2k9iN*2POhcJ2hZME>#5sY6&{o?bzt?grBh$-aeUy)v$Ka5iwJWATt=QQKpe@N z2jv|wCeNU(hfDS>oww1w-6LiXX?k1NFGqcwweie%3mpBp=-WB3cy`&dJ1W(>r(o*R zT}R@t*jsc?$J85l4$pUZ_q-j^LwYt{xu{gzYzgOjrL*vw(8xR-M(_r z(!$*er$3gOT+ynwGO_E?mv%o=eapWViL+fQd2ySnT~E#WtWe)UxoYQaZ`YYpYv_`^ zxoZzzabvZ#2hy`$%*0mRd|$#XwM%q)bXM~I$h!BotGD&%`~#;C{3&0x8{^wAUO&CY z#J**xoPGaL)&WKQsZPw6`MZiwS~j=i&2=|j)8%m2^#>-^tgs+w z-#qn4_87kR-mM=_UiEhUlr>MDERl9Hf8E@%-I`aLlBIs$iitgUJ$t4?)X4GG_D}fy zs}f85P3*I@(2%alg_7GunSaXU|1wttzWg&+Pk+_3eEI)XC7Zc& z`SQdJ~3D=={1?;Q#p_G-K>(dm;W$Lj++dYV_ghdNSSI#IZdvY zIx(TH%=$YY#FhX0)BmC+hcAb6^~d4ySmLacJ(w%URF=9$rk{JTL5`VxEyp$*Y?Ac1 zndBQekMrGdy{|l2E5}UGy%h%YBt6vwuXr?dW!9e+{im0DMh3~NFaHo7@a6Bff4}{Q z>ws_n!{;P@`|tPvX8-x8?f+w?uQ#ZkWLXXEP`XKafd`+-F%$eb(|^M!OY~r;95dM^ z$G$iCLDIW3$&Yd#=eyy0NgiyIV+?*#8%oem*)J`g)VTGS~U4$k7ck z($yXit25au$F>{nkhJO3k>2OQ0XcTi;E<$KG70CJ{4B@zdw{QKf}i-E0X`x=tch1l z^F29llEDC}Q+z-ZFPZ!yv0~!+Mof>i+0TKM@A?r09#22Jn@@d4c66A~9RG37xzuztWLyMP>j&6-lyq(+6p+KqB@e-q|4 zO~|)7*2S;EuQzeWSapBDoM-y)`0gjeh>T3LO|oY{XJ2tedI&jlrrUY)*kR<&YuhHd za;4jx?#-zhxh&X0@~-hw|@Ws|J?t-CbmB*lxnhe zb)v}2gtf9J`1#)&5c~3q-{LQa5)pAi{Or>qWX*ck&Xvm!qi|u{Hu=XtY`b*nP|B9I z?Ftn#NBQ!$&G~L_ywSFc77gdhD{b2(dv<)%vmr!8q)Iu7OPYB3gp3i)SRrv^)2Ajq z)dSWToAL`hYbJ*?_4DQL%Rfmp#f9Jg{q}F#e$uAxC+*vR-~Rjkzgq|X#QlF2vGWF{ z=Sf^i=9}F#ljoOaOWQVCC&x^f&o$@oljWG#T*(PJ9!mD?XT;At7efC0>2{GKb{Jkh zUA1bqUAuNDb?e&pop)xAs3_a!d^a^~+IHp2;gl$0+a_0Eoh~uOP;%x>6Mr@}3^ShX zq!|mDYg&m5dd1J@$#D~&HRBmtH-YyL7;+BC-F#1#q^rui3+l>s-L#kVVh_CXgzswR zpE8BU?w#~$IUY*Roae%P>T9mCZG3Q(>eX%AG*|rJekpcxmK_A@qK=;Gr1c#+Q_6W>@xM59%IZr@(g$G^a*zE?q ziz4iuWFE5T9AEx!*|YD<-*5lg=G|Mf?UVM~zi2|?_b_myBZ`(KD91eZG3H^7|rlCCWfNeke zXgJN9*|y0;57{>7yTNz3`|fbOd_v-nW=s;Fkhq{p_&P=Aj#)qOqd}6SzxBYqMnLxm z%Xuvfcqf%9=Ri7EjvI7JvaFHv_im<2nl&6ITjZDtayR3e#PnHL8HzbiPx`v+Y@0fC zbJI<>U8hbsZW-x*%a*qN#1o-B^^|RQ?i@~+F1BsbzP)X8zMIyqZTpc&!tokYpc7`l zz!<@d6O#^KJHT23;^F3434LG;U_uPoWSSfsYtTI@T+XK4HQhVQFt#6Hi2 zL0r#-`S&VSLLnA=|NY@~=wRC>y?WVpzkZ<%8f4o;hlVq3m~ETH#Mt)0fuZ#6YujDB zhU4WEFivpC2-wFb^qN2PT2sb4V{@G3JbT6%z{D&5njq&bG~m6pd6L30Wl!3dzb}7x z+h^Zz|9<=T?Z2r5zWq1*e$sybf9dr9#l$Z#86xkx|5)VcW|^eddtlDuIW4~ zao)5ri0hi*E19uA=5rr=ES#P_ZQBI@ZTRp|Mvk=YS6>b1wbyLhWb|m;e(9x9h77T7 zd_WW8f+oa@nJ;w1`T=w9nl#T9neePxcwCsdbFcNYX6&8xDq*Ps-!I@BRwiZSJd@#a zY`(#ANpJAL^hZhW7eeFw__SHGo(scVJ3+c?)$o{pqeiyfs#Q3qJV@iuj~W%qE3er0 z|yA;2aZXa_Vj}X zUTXq~%ONi}qa(q6G9_Z+|O z)BmT05E*&g&X(<5816Y<-Df?YIj@ROW%^m9`}YrL#E8(on|3^YylubvW;k!XW!ol` zCVBgW_?0Hi6`QbrfHeqiSW`+|(}ZWuO`QHebe`Pl|Cs|e`9_YJum&G_xw%mPf2*8l z@^3jtyvGge48Qiky+%>@@l`{gbK;#0*|W0-B9v04Y}Ab0r7{0UEcLg}|KbCh5EnGTZ)CpM4eJM3Lu#UPzV7X%KmR*Sd_eP^0>5Vc z$3FRcbN$D$u(cno2Psy}4&%1lLf^f>+&S~?P#X4@vL3BU(%L)@OYpb2xuCio3zoS%7R)6XEyv*srF zs^&Whcqf&vZ*XsUSG@^y<>vgk^E{QtSKV8$IhmF6_sGl5hzqR$%$*ee{1@Mv`cVGf z4eLR^@W6Z?oiy{(tO0SudeGBhnD5CTjbC)jEuo+bjT?v4wykZOpbw_3SQl!>iAj5{ zA3%T1b)~E^HlH&it?wLgZ;`$EPAca9y>#9Db^&R=XJPu!&iep%%5f8~HMy+SGS}}t zFEY)O6pk-{QwMzc`|aOv|8CpQzHk41`|tPvrVhCKecivb`hR`rk9*re`~>EJ-As`* z&lQ9Qs`6y675J}b`}QG93r&z#X^M zebyD5v1Xn<^Lqb)`J5VQ-cx3-YvA3rUf(b9`lglhUR1_VZixLQdhod%GkHsny=>4m zDf91t^5yT#-`)1v_uIeU{!QCY+PD8^AK=@6zyH64`hVlyue=kqzPu~+5oz0Q`bydz zPt<*{H6g?R(E&Fak*b3*up?ijJ|6Du~Kza}Q@ zWxsi@$b_|71M{6$l!b3tnV8SXIceUXVb1juV>h4kB2BE< zWS|^tZqVpL?|=1p2YeNIS9~4$dpGSQZ9Wr7dc6l*4yx#Rdtkwxz12{{skYv4L+gi zQ<3&sQ^)!N))2bkokBcUWMaOji!^h$obQHrQkl>0ljdFZCVanuZy34Zn^w+uP5HKI z2HrjqzLMK_Vt=dPHP<^=j+-?1`2JHFk*WEeuRvN3hy7lYBT1oD-EUd1%DqgyVgmSt zn+)&?lRe;BF%$G5)F)(&V8)3_d#xW}p50tmTB1aFOqMa4`R)PIv}MkBn+;g!@tFsc<(Nrzk(DXG-}?PmU;bY9$Ctm~{!Ja=dp3Ui z_w9eE4*2%p@Bc5E{=dBV4^g2MjtPHHXc@6{b3EW#QPWrXQjU4e6?yrDj14qCs9R#k zp=$@6wCQINOXhoGq3@dI-J|Bar!JJ{J!R&*O1Ylb_Y2JLSvhIex|%*M>mSURE@}LF z6JqMrmmA*c={5c?C&x{Sh^$QcS;#cAKVSaFyJ~&;7mo4UfB5%H{PyqL|8O1f?Z4mu zU()^mmEtHA3?*Mf%PR13IGx4+FnLpsEiqUjX}3S4`@{uJh!qn9bwi8{AIgNb77EXr z@eEBU$Hgx^kx7or-sK#rRrYZGLR4!hYdX|M6mS~+fl>=zjjcW>x{mrN{V^?Uz(`G?6K zT`*-&+LyoI{@rb#eZT$t_WzQ$|HY+4i4Lb)l4Y4ZBWnln0o>pdCU~%3j+uNT$4sBl zYfULWftURyerWoM_y(p=exbDb5$^3z^7pLEG{M%J;JcW<9`m_hdGLuGGnppG%z5^{ zQm56E!ts)cg{=NpzgW4E0s3J2gq>~gSh3gq;TLkQsT0(lSNlOPOm@gI)Bd6>?(xJg z?ITalcf<8epOmzzr=&LurmWY=u}?jiC&x^>$+3n8$lk2yK>B|r=L_8@HauUx{C)ZR z?LSlp{PyqLf8YLx>ww??U*i40mtPPq2aQ-EvEwr0)0mikF6o)V0t4cQ7t$5{MK{=5 z(|;t+YWifPz22Y2aWgK<^~^XO>D3-Amt%_!W=nda2j%3L3GrOB{sMl!S6%-Zk41;) z-JxSlyDOM0pY{p47CgJx2uYEo7c)@qsr z=I>o~7pTYLQlFRdkM$%`9wf;z1D-qNnPWGPNcu$&K9pl7adOOzsm_;U3qA0XDLTaY zZa$Xu91ljyF_UI;tgb zep%Un)*td*iOFkn%z*a~@D3U`ypyVf*a;KfRc*re3x-Mm>t?v5z5Is}a@>S-O@_!d z2g$umIwe__$-R=-s*-;f&@=uxkU*>ASmw)E!>92a0FaN))WHVPTU;de^r@!i1 zzWo13lC|Y0fk}SyB~It}8`BAatI0oEoXh>jbTBtuv=|rsozv-DSbuW)-E~-X}MsrKYN-?`ai!0{2KUUYT(b_{>}XTR{IzB_rKLXU+nLH@BYQr1N=Vy;rSjg z&>OmX#Q!Q?M$|M)h!G<6u@MgZalgWHX^rg+mHl8$Cy&HZppeXwma`qo%hn71f6mpv z)9YAVQ?Jjx{%pU?sr>Hu2s9Ky2nAYOR_T`^ky|`z9g0hAGWo(sLZd8yY*$U`vl#~IU5nc|WbIr_Av(^Au zhhVN*aIZ~Zjj7IQkClDsE!-aDTDjNxxi!rPdHP@dF30j0m&%QHC>m>7H^w`3k@zw6 zlP*%%yS*=RQn{~@efUgR8^miJGspDZ8J`Ew=WkRGX375eAOqxH-Q=E7D&C=3Y)1an z;LDNU?^j7K6VeK?!09|j*+H0PlgKvay4LGLnj9~3H=Y!T6 zb+IA{^{eZ1v>&-a@}&9uqu-NoS(QIl>QyKaFXmS29mWZ|J`1_1ZmWE^$g#vAJ7o(z zbE)si`%(6H8*uE0Ajz`je9&h=x5~cihkMQbQaKJ2geF05h9CB@3rFIvvJ z+{*7-$3Xv%jR*C=-j+J0Ujyo!{t8s)*f!N)Z(iF5 z(tHmJcu#AVEDn}+O0EO6584Az&)5^t{(*WxE`U8Czo7m>srZcY1;z`K4EbH|+s;kn^+srIku&K^_{4#7VHu1Y4*h2CP?+0Pb#JCv>NLeX! zfcs@DlzJ&+Z8tWbF*u-0fbOeLL*K6Nm^m%`e8c4I`ESGVO%W}(mXq(y>38b*{vh8X zg!}Gu;Ct8lZNsQ2+t%;blq+Z3K%3BSqR@9}Tgb49^bN=bkkh6hD`X4fh1-J=M>!u$ zyWGo9Tv4bl@uh!B{lE-C>rHJQwnh6oWbgFv*|MdH?9-$VJ7@DPaNs+!`aP+8@3n0} z&U|Ye_y%>GHV#ibVcWpB$@Sajd~aU$L%(Z8+dv0^wtzi^jCa)HAJ7Mb+gJWf(lATt z9fVj~AfD=q)7Fj8c-~>IoQIzSTY}KGw9V58V}p;M@6-SGxAgPhOyv7a`t4lG0*^lG zl#{Y{=wREOJ3DmgV%yNElWjlslmqp^x8C8-I~}M8Z41}~?H4lq1MHCc%J^jJ7pTv& zP>#{A;r4hbe{3k1Yx#30IqM(mi(Q-}bp!ZMT5oDocge8>0i5q#Iq%HYZ|4**o}u%6 zYg50;imahqH{0&l&*8b}Y%EHj%zTzZt52 zBK~rQEu=3{zZ}~Qv;lMgh%4twa=6^fe~r`yG?AD)?H)c6bbN~~0en5D&x?pSdA|SA zZ={thnbDWizdii0gUYd2FWZiZad`1X+y3{z9Y&9~ZFuP=+a5B+p>JQ?MhC!c6X_QL z|3GaaV+CitKtG%k1RF>U3BC@352JlL^9%SnP)3f!qr&_kYh9<86ddRWy*m=qVg9qF8s8J5%$J_Sg$qsM4VcRfil5LM2>p(qdTX5S% z^$+L^)K}Iq6Jy7W_`y%IuYHyJ0myTC5awCR1-abIUs37-S_*0(K9ha58>&a>l%ro+ zvu1p=4!sAyO?da+4)pPD+d8Nmv3IY&>JS@i+wZ*N@b0^|4R62g=m2^^TfiOy{UYEW z;3ERQGWrHOjwXJfeX`1%}<n1h4b*|Q1?++L3+f->H>j`7m_f&n z8b2V0k`YtG7gk?I{Wrz}2|3 zOYtj!_<`C#Vu?UZHN*bxll{!vKX?B(PyP-*6*PXJV}AP6LjfWqiRU`sp(<9)`6em- zv3`^Opa0D8Tkv~;Iv6p+fqKxgs!gPCQ2zj%slKv~59w1het@mjSenLEi7jWuiiuBX zd~T!c1OEK7AQ$TYFURt$|Jzu6!Z}hu;2!^Mmo#{fe>6UYAE;w~{4{g`I(BsE-rdmw zZ41~#K-U3#hn)oUh4B*5mVmxb$JFSW>Ye(b%rB^a>hyi;w|*tZ0iQ4|Qz$Y?HU^kqV2%kg z#=P{+s-M_vjaw4Kg!hGKg0MdEeEc&Q|Gx31jQ7%vmh;pXS}*%RJ!mWpUr24?N!dRs zb#VIp{3Ugu@hP>5=&$+*)QR@xT297}8b4rsqkW0`SHzVy=E+=x&b4LCx&9>iz(&Jn zLHm6j^S>&`qJpqa(~SRLw&i!tf2l9fOXiB+l)3`?4Iq9(y#jTNKL}1ch=@2E&;j&- zwtzhZ)mi)ljoIKUGhPDK588_Qw8RsLrvhGpw2ek#X105GQF+ryuv@I|X$UJ_=JRogC zeHL_DZMnt|C^K~g#1u8IoZ;UxzH@vIr|h(MP&ugHBOhWNmN$SXT(MEhXLPF>y+4#`UR>7$U$u)ZNl*nRAzi*SI10@9W{QWTXaQzS>`tNo4grg zI$}?%YuH~M-_S3?Hv(-Guz6~~%gH|67%%dV3PM@U-{oF@*ZQX-v9ex>HABKEsXK@j z7#9F_tnoqZ6KQ|Q1@I5>5#e|M=8PCOJ7ee5BI`2-r$s+f12Dhf%rRl-9Q*3Xey^lq zm!SSVa)%89<95J*85cy?aTSdff&bF_C$~OCOCq@bt$|zzx(FW!p$@bys7=J~XkCy$ z^+mKzP$z1$Qe@wWrDkk7<}NGegVrhSOUsPDYx`R(`>sr3&_VkZl-OZ6WUiH(>)%j&S2X(A5^F4 z+g0ZHqbm2MvJY^1|6*lNpboSxU=OrkSS9cJdtHd_n%po#1sY+wyC* z(H?>}fqtQk)Hl2u*+* zsqq=o9EVQ?&WE`{X2_N{4y;dPjVjz2?-1zUql0-QuYcsvj$cM58 z?4{Z&+KKib+GmrVAA~Yl0a{3fxQYdp;jtYx?E1|Jjdt z!u>&9>k4xxIqS=4AM_Vc_5$etbFThfpbw9g>vB!KKKJ^w{VvJ!2VP^4#g<=!^I{*d ztTOHkzKp=NmH*Co?~VLHFdcmDg1%ly-?E_ZvpK&FoGur**j8@xm;NOUU6i)(Ui?!mpdrzQ9PO+Wtr1*wYB zqPs^$uTRM@O|-Z*9ZApwxIDV-RiuJj)m}#KNA?^Bcs>lniGZca*uLy zFYd{`e^VLA{|LF(tCIgSPP-&-K1En2P|xb8YrZs}jLY3<4CE_04myrQJ_$msFjb)4 zmy>&OPwxF+=Rel6vb`-jB0Absu8HqVol|F^`An33*cs$|*#fZ`H$O-kl7eiLEoiy4 zJjjFlJRmfZdy|KNt&&Zqe$=_Z-{qPircJK#pj<0Xpbct$lYj0_-pPmNOY^x$j=9ko z67duu?!w=pUtEW=H6T~+^N4W2+?zZE$`H)gFP4FG&^eJC^-kXB$aNUsd?8Rr=marQ z>YF@k9*GGb5{?8pE?YN8B@KrK&ig6IF4S3!uJ6b0!5g0d72ILUXCGtidy?$Ao zHcz`j_wJLwrQXRq*P?E??v^0ro%`+;$P4)b@_0-*aXwZ}%-Sgn$5Vprm+d|QU84-D zPa9+(5(Msx93BwJ6L|~RCCO{Zk8~|5n%FJu3~luz`J1@{`a->vZ|aBpp(8&F)H!)0 z2G28;X@R+O=GNTL;_5TFS6sofx6b;ujP(_i8F^@1p&ZD9GEiUW677LDja?#-fxgIH z2KJ?I!uDb#>FcR?qMM( zL)&-fA3f6i$I3o^9C;+K0e=Y@i0_C0R7-r2+vWe`n9%$2h?q-M>G^)b38 zzG+k227TU;wI-~cQh5|C=#(Kt4%!}&HD%ELVTJ6|4jKyheB@dEcl=nsm++GO-%4S% zpmvge2bp5~oaX|0Hl;)fr|zjwp5I~}7;E}?o`H2>tPAA%2G)7AWnEa?w$8JEtOL8_ z4rg5q&;B6?^ocrg+BG%-+lpPLEdyQ+Vnrrwpud(&rZ`19Cu@!t=jh?n(Z)3z~my`8WaJ zn><^wPCTSQvaDkgy~5^WFDdJH0=mz2eh%83GlBIJthr$Q6ziqY4c64Lu97-`_E~3* zv97b%=Pg)=IbeXZ_LOy)lmR(Vc4SDK#x^8v|F9`XZ^>u*E;#9&60ojdRo>;vzC_plXqQDJ7|#e?38=`EM=fQkbmq_ zc>ZY%7s@~5IL4x-r7SlKbL3q5IrquCYtB1N4ZtML+H6E4~Q&OP`4i+9<>e z^vR4x8Bdpy{R+Y_O8;0+IGsIa?tib z{;{pv4)nP-$6xxT{8y4ZmlJLksta*~+iq%qnm3SL-S7=$Uo0;kbh7;qCJp*wX5VGdrSWDHMIW8KmL^Cmoxsk znEq({oh|3i7uE^bAoUyYk$E;XBo#zN>pFB^498{Mm2YJV~l6UMBdB@iv|Hu*_Li6veAIA3lQvTI`YX9}F z*iy6Oe+crKIYdm>g7unPP&>>JcXnWA-oyarKL7+2$uYu2j z&qqIq?~MpSEL_1Nql7U|Y#MeH?km*C6lo+xV^IpS&{$a`Ml3=CFY7UMTq%@_#3 zpRp)w-yNMnZ`5zwB>UJN?9L*gfk52*{P;!2U^!$gR#V1gQPM8v$p2$U$v@DBDFf}m z$v2nMCfY@R3&R8^b-9Fjj$a-?h zfG&Z#j+`=qxqe*jvXghsKm8T?r~PUEC(Ayu`y9!V-_3%?Ph^)EipHKHPAW^a{S}t@;B54kZ0H$WSwl+3baM^rW{-- zentLkIkCCY5~C|EvAPfB{8<8R9{G`fw;Z%RV3)w@ADlQsTF_s`=XP+$KWU=pydQ?L z9t%R=>0ih@J|p!GUkYCc*ps;eF->Bdp7Fz=42X?t{I;?D%|8Wg&y+#sKsnSdVO!}R z$OE7Q=rUze*|47yJWhTA+sNN(J4M%ld{ggBgn7b)LQ~-vKyh^p0`^z5#Ipbd!3a&Z#Hz&V7mD6U)D7{5(*A zU>T$w%o*gAxdY089MGkY1Z)zl7w8|jFZD)wfcga0XWeSMA+La*qbFYrUkTI=%oorX z>Ycp*s`(2o1KU`q49Edp`aqyQuuDMxf&2sgJW!v2F1yikp?9EqPTm3e0`*Qk-6v4* zfw`AhcgB9jKKeAm(LLHUcIj2&HGz7>eA5 zo#CFpdHyt52Cw;Rd=31i;lfM8>>$Vms8gVRVL=dd14aq_-8}+v?>}Vzn*8$%_78!1 z59#}ezvD0A^WnGRdozB}F$86y4FYuw$P0+`0r6J;j<`3m`#)^`fKvje43tCq2>c~{ zK72s@SbTBDK*S-5dnUUX~Ln_0hF?`7mN#f2#0{5q}tlKAg_Rpie)39(LWzQl3I zo|hNNLOFqBd6JxS0_XBKzvXwO{4wi>UlYBZC;q}Z%jzoEe_rl0LhJ)}Zgh}<&68t8 zahnoIQ$lzY7;FkYm8pr2FUg8rF) zgFa`PFj#0Ie^W#L_IkMn*ZNh@(?p9s`Piw~$#O01CHJHsqyM7c-6Y^&0RGuVfj*5s z7(a^sYpQ^JxRw_W4fdC(Q%(z&!%`;MPgeb99Y%gxo$6sB_BAbto5QaQ=F=6Hvc&}@Y z9O;s$b8^lp;iy1;ARqFtc|aEE13DS9{$JWyHPKc4G<2DABWvoLH3=uAoV>HaiN!JI zC$6h&5SXK3Tut1GxJCBtjOSA2Z;uG%fqFm|=q5Tzy9l&R*;rMi|B<%ZOxnwA`M-6- zW&yt*S)Y*WosoEs6I0=y#Cn*Y(|1J>Lm}Qttf)i@+rIj0)^unds0ZqhdZ1j?FKshm zALQQHs(Zy=^poSXZQ2ojIQb?%epcpSowy+Hl;E8U4I4UfO6F{vHFMrUL5%QjiRX|9 z>W4f~4~{I*2XvBlfo!o4*eJEBosyh-LErKJDLXQE=EZrwooD@ZKAD&uu}9_#m~U#| z-icZ3{IS+U#=JK=NxPs9kOBJLJxT5#>-719q;23+@_&>aT~84iI`hKFm-^QEU}DkC z*XdlU#&LE2SL;FNO`Uc@d8h+qfStrv(_a*mE&kpzIksNFzWgNj=KTn1fp{$CAznrd zlQ=T>BWBM#u!!X|2f;fqi6s-eM;7P<>akT>77?YG`?=0eB=`9c=FlLVcl-e{ZH2gfcT1KI&? zj6RF@eyi|rfw2m<4qNkcz{U{cA=XChkoYn(=N@CmIP-Qok4GM;2c3UJCutix?}(jp z><2oH4$^Op7Rn1{g%<^6Lit1QPaP5uM)prW>BR0SEAwu=laD-556D92-)I+le{9q* z-@lC9ld%qM51WUM>=*eTm%lp~XwSrTX>&TCOgWi1CJ)SCA`A2ZourPKLqK13-aDiI zu>;sb+L_ir`uBq9!V)=#?ZH;*Z1NDF`&q@L=b@ z=RdHM#J@AI6Nb%;b-GR{AVwp7@Y#sYmJ) zz57gau=o$B=55C8=tm%lH4accrWhh^^0}#Ks_J}^x+GEYatKvfi9C5 z^a{8qb;NzCFZwua8U4&WfpQ1#9qV;Nk7Ln}EU?Y!B<+H>sd?er)GK*Huh>#gYX#~W z`!ZXYD3lX^)%b(#6nFHYaE#L~Xq(svY!o^~UbqMHK~Lg^Wx^t1s?blspTws{*M9Z* zOR^C7=239`iG9FEbx3k-HT}hKxyOsb2!VZk82qSCNsd2>Py1hwKe@mAhn?so#5(;2 zeHOAQ8-#t1iw z*s|}%)}9Q^v$L)MpNIF6vNi~xglmuwr!3S5b&6bC%9gs`BYnr|Kp%?FfX^5k>%7mL zYmg7h;=G@nIz=wXwwbU-*d4ew{tbTS3okf+AlD!tl!f|m-tUWC^zV6|n!bOR1ByoXKoxk#*-06FGdgM3gH>Y6%5 zE>3?$pMZ{$Uv%z#TPQAT%ta^l8kB`|DJQbxp6CZUhMuD9=k-^11Ha;8Q)8X7aDD0& zxlm8&2ReqH2JS28dj5j?Or0VZWDDpRdP-fL&vW1c$r2Mykmzh4 zp|lDrBGjcW3UMcN(Jo!;La-pHxN#-86I@ipg|6yPXz8k}2668~6icxdT}kP~v`MCu znS7q}&7EYL8m&2;$u~2}xp(gC8%O^BnF1%JPfBuHM#Ryp2K*swQk61R^Wa=J0jJCL z5;!a45S_s4JeUJr{YL}_;0m|^zJoE5+hF-H24>`odb(1?tvNoM-vRyNEKWK zUxJ_I1&$BXVs3}{tlW^h@))a8a0u)N`@mjhUJWMX`B88j+?K0&su2y_0RuBuoSq*C z2h{sw7Xw*+Do<5-J_c^e6&M-BYB3$662h`1&#*e#;k637t}AXP_NQr@5=D`i0NM6& zp{Cpg*TF|NT-TYC#K1Rsg-4?YgKD4{8|caR$bd6|(;}SA!Xrl-KozK%iuesb&I7pv zR=|(o9h>-3&z2&q<(zt5XosVaFUYVnEXRO;AP>+$0PF&r&Zewq*P**3#+W$qp%zC8 znq`^tY@JV}swE$jo>Tl3Fbo_43IGl40TqA-GeDRPrB+F1@Gk)p5wLt-=ag= zUM*Iqv&Hv=9w*{lr|%`d|I^2_>HzQYH_St5!hIT+ zh6Xql1LPr8L-=MBRWns9#A-yZJS^CQm)PS#X}OB1}c?1s%=oP}@WROEG6_PgF}Gn!hFvm`Ho*Jfr+C;U z<9ZEGJ80+mm_C8gN`QumjD#UXBV?ixNEn1->ZUE6Ow(Q#lMEC2ui07*qo IM6N<$f}&XD7XSbN literal 0 HcmV?d00001 diff --git a/disconnect.png b/disconnect.png new file mode 100644 index 0000000000000000000000000000000000000000..b12a154c2d8952442a7406fef3d08102b362f26a GIT binary patch literal 1030 zcmV+h1o``kP)gYV2nLD@d zJLh-oGB#xcALq<{eBbYVt|x#0%>bo-k>W{_f~OZ_;HSuHRb(041z^4xf!E`D5tzFS z(rIi?18E@87b2hud;#77Zy|=|hFHOW3Oqe4a`!eilR#d{L%`P;IzU@R;Dvkxo&%pm zo?*F{Bn!I8=dOy}dVtLYa1=NKAViW=WK__B|BnIiR<=Yg!mEOY?SQ~jlGF0zz<^qx z?;(&aseVk*e)hRR76;o9%FN&%W6j$1cCU0wI}X$B#I(w#<4W;zoys) z)`9yrTvwPAC-D6RT%!d;Dv);qeSP8$z@I#>q+q9qS6VFuzbbL1BCSTlQe zrcL|=J)B{+Osdt{WH_=}@kT~OhKHdF*-XX?G?CujUGd6gNv*@?ogIncOZFRKz2NwhEaQP2`i1M64E}8V+Jbvwx*1xNQ5H1H%WCO{Sr1Xj& zZEjj}Hrd*mv~jHlqpCVG*v&7+wDKeJ&2ye(Eb5pjg82)rr(DF7y zR71{4+)#W4mh7mQ-ye48aP5gs(&Vt?=tv<%taFwkL&StK<_wD)c5WWQFS!GJ)M4kw z^(vel;yj;H4`QtZ1}Wm40cix%7KyR1UUi=bi##ki&(CX!Vinj= zzJ{QlAax>}N{%D+HI~_4C)xrJi}lqNlSJSGL|iG+Rz9{#p`_RxnuM{Y?6S2|%$ zN5xk3b7JW~5B&q5Eu4zuU7sy%qTNG$w)_%c01bkC>S|pivH$=807*qoM6N<$f_*LD A1ONa4 literal 0 HcmV?d00001