Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
отчет_лаба3.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
178.18 Кб
Скачать

2. Постановка задачи

Разработать программную систему, обеспечивающую в режиме реального времени:

- визуализацию среды функционирования, положения робота, целей и целевых траекторий движения робота, состояния процесса выполнения задач управления и всего моделирования в целом;

- отображение параметров, передаваемых сервером моделирования и параметров управления, передаваемых серверу моделирования;

- автоматическое управление движением робота для достижения целей задач управления роботом;

- интерфейс настройки параметров автоматического алгоритма управления роботом

3. Описание алгоритма работы программной системы

Программа написана на языке C++ с использованием .Net Framework 2.0 и двух библиотек: библиотеки GDI+ для работы с графикой и библиотеки WindowsSockets2 для сетевого взаимодействия.

В обработчике загрузки формы fmMain_Load() мы инициализируем работу с сетью и с библиотекой GDI+.

Форма нашей программной системы имеет 2 таймера. Таймер 1 изначально включен.

В обработчике тика таймера 1 timer1_Tick() происходит чтение глобальной переменной – структуры информации, полученной от сервера, и отображение значений её полей на форме. Также таймер 1 занимается визуализацией моделирования – отрисовкой на форме местоположения робота, оси координат и векторов скорости и силы тяги робота.

По нажатию пользователем на форме кнопки запуска процесса моделирования button_Start_Click() производится проверка возможности сетевого соединения с сервером, создается сокет для сетевого взаимодействия с сервером и происходит соединение. Если все действия выполнены успешно, то кнопка START становится недоступной, включается таймер 2 и создается дополнительный поток (он будет принимать от сервера данные о состоянии процесса моделирования и производить вычисление управляющего воздействия).

Функция работы дополнительного потока ThreadFunction(). В ней с использованием ранее установленного соединения и настроенного сокета в бесконечном цикле выполняются действия по приему от сервера строк специфицированного формата о текущем состоянии процесса моделирования. Прием осуществляется порциями размером до 128 байт (символов). Очередная принятая порция дописывается в буфер большого размера. После этого производится поиск символа ‘}’. Если символ не найден, то считываем дальше порции. Если найден, то создаем объект типа String и помещаем в него выделенную строку с информацией от сервера.

С помощью наших функций получаем численные значения всех параметров из объекта-строки. Вычисляем управляющее воздействие и записываем его в предназначенную для этого структуру.

В обработчике тика таймера 2 timer2_Tick() происходит отправка серверу строку со значениями управляющих воздействий – проекций вектора силы тяги на оси X и Y.

4. Листинг программы

#pragma once

ULONG_PTR gdiplusToken; // Для инициализации работы с графич. библиотекой GDI+.

Gdiplus::Bitmap* RobotBitmap; // Невидимое изображение, на к-ром все нарисуем, и к-рое потом выводим на форму.

Gdiplus::Graphics* BitmapGraphics; // Объект графики для рисования на невидимом изображении.

Gdiplus::Graphics* WindowGraphics; // Объект графики для рисования на форме.

//--

bool GlobalStop = false; // Флаг - при закрытии программы он сообщит созданному потоку, что пора умирать.

void* ThreadHandle = 0; // Хэндл созданного нами потока.

CRITICAL_SECTION Section; // Объект критическая секция.

//--

//wchar_t WideString[50];

char CharString[50];

//--

SOCKET RobotSocket = 0; // Сокет, с помощью к-рого будем общаться с сервером.

double Last_X=0, Last_Y=0; // Коор-ты предыдущей точки.

double ForceKoefficient = 2.4; // Коэффициент силы тяги.

//---------------------------------------------------------------------------

double StringToDouble(System::String^ I_String)

{

pin_ptr<const wchar_t> f_str = PtrToStringChars(I_String);

if (!f_str)

return 0.0;

wchar_t* f_end = ((wchar_t*)f_str) + I_String->Length;

return wcstod(f_str, &f_end);

}

//---------------------------------------------------------------------------

char* DoubleToString(double I_Double)

{

return _gcvt(I_Double, 15, CharString); // 15 - общее число цифр.

}

//---------------------------------------------------------------------------

unsigned long StringToULong(System::String^ I_String)

{

pin_ptr<const wchar_t> f_str = PtrToStringChars(I_String);

if (!f_str)

return 0;

wchar_t* f_end = ((wchar_t*)f_str) + I_String->Length;

return wcstoul(f_str, &f_end, 10);

}

//---------------------------------------------------------------------------

// Поиск подстроки в строке, между двух строк.

// Возвращает строку-результат. IO_Index - записывает новое смещение - откуда в след. раз начинать поиск.

System::String^ InsideString(System::String^ I_String, unsigned long& IO_Index, System::String^ I_Left, System::String^ I_Right)

{

System::String^ F_Result;

//---

pin_ptr<const wchar_t> f_string = PtrToStringChars(I_String);

pin_ptr<const wchar_t> f_left = PtrToStringChars(I_Left);

pin_ptr<const wchar_t> f_right = PtrToStringChars(I_Right);

//---

// Поиск подстроки - ЛЕВОЙ ГРАНИЦЫ. Возвращает адрес - начало этой подстроки.

const wchar_t* f_head = wcsstr(f_string + IO_Index, f_left);

if (f_head == 0)

{ // Не найдено такого.

IO_Index = -1;

return F_Result;

}

f_head += I_Left->Length; // Смещаемся вправо от начала лев. границы -> для попадания на начало искомой части.

//---

const wchar_t* f_tail = wcsstr(f_head, f_right); // Поиск правой границы.

if (f_tail == 0)

{ // Не найдено такого.

IO_Index = -1;

return F_Result;

}

//---

unsigned long f_length = f_tail - f_head; // Размер найденной строки.

wchar_t* f_buffer = new wchar_t[f_length+1]; // Выделяем память под нее.

wcsncpy(f_buffer, f_head, f_length); // Найденный кусок копируем -> в выдел. память.

f_buffer[f_length] = L'\0'; // В конце куска -> нуль-терминатор.

//---

F_Result = gcnew System::String(f_buffer); // Создаем объект-строку на основе найденной строки символов.

//---

IO_Index = I_Right->Length + (f_tail - f_string); // Для след. поиска - новый индекс.

//---

return F_Result;

}

//---------------------------------------------------------------------------

struct ServerInfo

{

double dT, dR, dM; // Текущ. время | Радиус робота | Текущ. масса робота.

double dRX, dRY; // Координаты робота.

double dVRX, dVRY; // Проекции скорости робота.

double dTR, dTX, dTY; // Для задачи 3: Радиус цели | Координаты цели.

char cTASK, cPOINT, cFAIL, cCRASH; // № задачи | Номер точки для задачи 2 | Флаг провала задачи | Флаг врезания в стену.

double dFUEL; // Текущий запас топлива.

char cDONE, cERROR, cIGNORED; // Флаг все задачи выполнены | Число неверных и проигнорированных сообщений от нас серверу.

};

struct ClientInfo

{

double dFRX, dFRY;

};

ServerInfo RobotServer;

ClientInfo RobotClient;

//---------------------------------------------------------------------------

// Функция работы потока.

// Бесконечно: Принимаем от сервера текущие параметры моделирования и Вычисляем управляющее воздействие.

unsigned long __stdcall ThreadFunction(void* I_Parameter)

{

SOCKET f_socket = (unsigned long)I_Parameter;

//---

char f_buffer[10000];

unsigned long f_length = 0; // Число в буфере занятых символов.

char f_sub[128];

while (1)

{

EnterCriticalSection(&Section);

bool f_exit = GlobalStop; // Читаем глоб. пер-ую - флаг того, что поток возможно уже должен умереть.

LeaveCriticalSection(&Section);

if (f_exit)

return 0; // Завершаем работу потока.

//---

// ПРИЕМ от сервера порции Д-х.

unsigned long f_size = recv(f_socket, f_sub, 128, 0);

if(f_size==0 || f_size==INFINITE) // Если 0, то сервер 100% больше ничего никогда не передаст, но может он нас чего-нибудь принять.

// Если -1 (INFINITE), то произошел разрыв соединения (программный или аппаратный).

{

MessageBoxW(0, L"Сервер разорвал соединение!!!", L"Ошибка!!!", MB_ICONERROR);

return 0;

}

//---

memcpy(f_buffer + f_length, f_sub, f_size); // Дописать в большой буфер полученную порцию.

f_length += f_size;

f_buffer[f_length] = '\0'; // Нуль-терминатор.

char* f_test = strchr(f_buffer, '}'); // Ф-ия поиска символа. Возвращает адрес символа, если тот там нашелся.

unsigned long f_index = (unsigned long)f_test; // Приводим указатель на символ к адресу в виде числа.

// Если не был найден символ '}'

if (f_index == 0)

continue; // Переход на следующую итерацию бесконеч. цикла.

//

// Символ '}' найден.

//

f_index -= ((unsigned long)f_buffer) - 1; // Получим отступ от начала буфера.

// Создаем строку и пишем в нее текст - целиком слово параметров от символа '{' до '}'.

System::String^ f_string = gcnew System::String(f_buffer, 0, f_index);

//---

f_buffer[f_index - 1] = '\0'; // Забить символ '}' значением 0, чтобы этот мусор не был найден.

// Копируем все накопленные (но еще не обработанные) в буфере данные ..

// .. и вставляем поверх уже прочитанных - в начало буфера.

// Освободившуюся часть в конце буфера считаем свободной для записи.

memcpy(f_buffer, f_buffer + f_index, f_length - f_index);

f_length -= f_index; // Размер занятости массива не забываем уменьшить.

//---

f_index = 0;

EnterCriticalSection(&Section);

RobotServer.dT = StringToDouble(InsideString(f_string, f_index, L"T=", L")"));

RobotServer.dR = StringToDouble(InsideString(f_string, f_index, L"R=", L")"));

RobotServer.dM = StringToDouble(InsideString(f_string, f_index, L"M=", L")"));

RobotServer.dRX = StringToDouble(InsideString(f_string, f_index, L"RX=", L")"));

RobotServer.dRY = StringToDouble(InsideString(f_string, f_index, L"RY=", L")"));

RobotServer.dVRX = StringToDouble(InsideString(f_string, f_index, L"VRX=", L")"));

RobotServer.dVRY = StringToDouble(InsideString(f_string, f_index, L"VRY=", L")"));

RobotServer.dTR = StringToDouble(InsideString(f_string, f_index, L"TR=", L")"));

RobotServer.dTX = StringToDouble(InsideString(f_string, f_index, L"TX=", L")"));

RobotServer.dTY = StringToDouble(InsideString(f_string, f_index, L"TY=", L")"));

RobotServer.cTASK = StringToULong(InsideString(f_string, f_index, L"TASK=", L")"));

RobotServer.cPOINT = StringToULong(InsideString(f_string, f_index, L"POINT=", L")"));

RobotServer.cFAIL = StringToULong(InsideString(f_string, f_index, L"FAIL=", L")"));

RobotServer.cCRASH = StringToULong(InsideString(f_string, f_index, L"CHASH=", L")"));

RobotServer.dFUEL = StringToDouble(InsideString(f_string, f_index, L"FUEL=", L")"));

RobotServer.cDONE = StringToULong(InsideString(f_string, f_index, L"DONE=", L")"));

RobotServer.cERROR = StringToULong(InsideString(f_string, f_index, L"ERROR=", L")"));

RobotServer.cIGNORED = StringToULong(InsideString(f_string, f_index, L"IGNORED=", L")"));

//---

if (RobotServer.cFAIL || RobotServer.cCRASH)

{

LeaveCriticalSection(&Section);

return 0; // Провалили задание или врезались в стену. ВЫХОД.

}

//---

if(RobotServer.cTASK==2)

switch(RobotServer.cPOINT)

{

case 0: RobotServer.dTX=0.7; RobotServer.dTY=0; Last_X=0; Last_Y=0; break;

case 1: RobotServer.dTX=0; RobotServer.dTY=0.7; Last_X=0.7; Last_Y=0; break;

case 2: RobotServer.dTX=-0.3; RobotServer.dTY=0; Last_X=0; Last_Y=0.7; break;

case 3: RobotServer.dTX=-0.7; RobotServer.dTY=0; Last_X=-0.3; Last_Y=0; break;

case 4: RobotServer.dTX=0.7; RobotServer.dTY=0; Last_X=-0.7; Last_Y=0; break;

case 5: RobotServer.dTX=0; RobotServer.dTY=-0.8; Last_X=0.7; Last_Y=0; break;

case 6: RobotServer.dTX=0; RobotServer.dTY=-0.8; Last_X=0.0; Last_Y=-0.8; break;

}

//------------------------------- ФОРМУЛА --------------------------------------

double f_force_koef = ForceKoefficient; // 2.4

//

if ((fabs(RobotServer.dRX-RobotServer.dTX)<0.1) && (fabs(RobotServer.dRY-RobotServer.dTY)<0.1))

f_force_koef *= RobotServer.dM / 80.0;

//---

RobotClient.dFRX = f_force_koef * ((RobotServer.dRX - RobotServer.dTX) + RobotServer.dVRX);

RobotClient.dFRY = f_force_koef * ((RobotServer.dRY - RobotServer.dTY) + RobotServer.dVRY);

//------------------------------------------------------------------------------

LeaveCriticalSection(&Section);

}

//---

return 0;

}

//---------------------------------------------------------------------------

namespace Robot2 {

using namespace System;

using namespace System::ComponentModel;

using namespace System::Collections;

using namespace System::Windows::Forms;

using namespace System::Data;

using namespace System::Drawing;

public ref class Form1 : public System::Windows::Forms::Form

{

public:

Form1(void)

{

InitializeComponent();

}

protected:

~Form1()

{

if (components)

{

delete components;

}

}

// Обработчик события создания формы.

private: System::Void fmMain_Load(System::Object^ sender, System::EventArgs^ e)

{

InitializeCriticalSection(&Section);

//---

WSADATA f_wsadata;

WSAStartup(MAKEWORD(2, 2), &f_wsadata); // Инициализируем сеть.

//---

Gdiplus::GdiplusStartupInput gdiplusStartupInput;

Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); // Инициализируем GDI+.

//---

RobotBitmap = new Gdiplus::Bitmap(532, 496, PixelFormat32bppRGB);

BitmapGraphics = new Gdiplus::Graphics(RobotBitmap);

WindowGraphics = new Gdiplus::Graphics((HWND)panel_Video->Handle.ToPointer(), (int)0);

}

// Обработчик нажатия кнопки "Start".

private: System::Void button_Start_Click(System::Object^ sender, System::EventArgs^ e)

{

ADDRINFOW f_hints, *f_addrinfo;

memset(&f_hints, 0, sizeof(ADDRINFOW));

f_hints.ai_family = PF_INET; // Задать, что это IP.

f_hints.ai_socktype = SOCK_STREAM; // И это TCP.

//---

pin_ptr<const wchar_t> f_host = PtrToStringChars(textBox_Host->Text); // Получаем с формы значение IP-адреса.

// Ф-ия по сети проверит наличие сервера с заданными пар-рами.

if (GetAddrInfoW(f_host, L"9999", &f_hints, &f_addrinfo)) // Получили в "f_addrinfo" - структуру для соединения по сети.

{

MessageBoxW(0, L"Произошла ошибка с сетью!!!", L"Ошибка!!!", MB_ICONERROR);

return;

}

//---

if (f_addrinfo->ai_family != f_hints.ai_family) // Проверить на валидность (совпадение того, что есть и того, что хотели)

{

FreeAddrInfoW(f_addrinfo);

MessageBoxW(0, L"Произошла ошибка с сетью!!!", L"Ошибка!!!", MB_ICONERROR);

return;

}

//---

// Создаем сокет для соединения с сервером.

RobotSocket = socket(f_addrinfo->ai_family, f_addrinfo->ai_socktype, f_addrinfo->ai_protocol); // Создать сокет (IP, TCP, Protocol)

if (RobotSocket == INVALID_SOCKET)

{

FreeAddrInfoW(f_addrinfo);

MessageBoxW(0, L"Произошла ошибка с сетью!!!", L"Ошибка!!!", MB_ICONERROR);

return;

}

//---

// Соединяемся с сервером.

if (connect(RobotSocket, f_addrinfo->ai_addr, f_addrinfo->ai_addrlen) == SOCKET_ERROR) // Подключить сокет к серверу

{

closesocket(RobotSocket);

FreeAddrInfoW(f_addrinfo);

MessageBoxW(0, L"Произошла ошибка с сетью!!!", L"Ошибка!!!", MB_ICONERROR);

return;

}

//---

// СОЕДИНИЛИСЬ С СЕРВЕРОМ.

//---

FreeAddrInfoW(f_addrinfo); // Очистить память

//---

GlobalStop = false; // Флаг для потока, работать дальше или сдохнуть.

unsigned long f_id;

// Создаем новый поток.

ThreadHandle = CreateThread(0, 0, ThreadFunction, (void*)RobotSocket, 0, &f_id); // Создать нить и получить ее ID.

if (!ThreadHandle)

{

closesocket(RobotSocket);

MessageBoxW(0, L"Произошла ошибка с нитью!!!", L"Ошибка!!!", MB_ICONERROR);

return;

}

//---

button_Start->Enabled = false;

timer2->Enabled = true; // ВКЛЮЧАЕМ таймер 2.

//---

panel_Video->Focus();

}

// Обработчик события закрытия формы.

private: System::Void fmMain_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e)

{

EnterCriticalSection(&Section);

GlobalStop = true; // Устанавливаем флаг для нашего потока, чтоб он умер.

LeaveCriticalSection(&Section);

//---

WaitForSingleObject(ThreadHandle, INFINITE); // Ждем, пока поток умрет.

closesocket(RobotSocket);

//---

WSACleanup(); // Выйти из сети.

//---

delete WindowGraphics;

delete BitmapGraphics;

delete RobotBitmap;

//---

Gdiplus::GdiplusShutdown(gdiplusToken); // Выйти из GDI+.

}

// Обработчик Таймера 1.

private: System::Void timer1_Tick(System::Object^ sender, System::EventArgs^ e)

{

System::String^ f_info;

EnterCriticalSection(&Section);

f_info+=gcnew System::String(L"T="); f_info+=gcnew System::String(DoubleToString(RobotServer.dT)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"R="); f_info+=gcnew System::String(DoubleToString(RobotServer.dR)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"M="); f_info+=gcnew System::String(DoubleToString(RobotServer.dM)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"RX="); f_info+=gcnew System::String(DoubleToString(RobotServer.dRX)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"RY="); f_info+=gcnew System::String(DoubleToString(RobotServer.dRY)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"VRX="); f_info+=gcnew System::String(DoubleToString(RobotServer.dVRX)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"VRY="); f_info+=gcnew System::String(DoubleToString(RobotServer.dVRY)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"TR="); f_info+=gcnew System::String(DoubleToString(RobotServer.dTR)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"TX="); f_info+=gcnew System::String(DoubleToString(RobotServer.dTX)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"TY="); f_info+=gcnew System::String(DoubleToString(RobotServer.dTY)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"TASK="); f_info+=gcnew System::String(DoubleToString(RobotServer.cTASK)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"POINT="); f_info+=gcnew System::String(DoubleToString(RobotServer.cPOINT)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"FAIL="); f_info+=gcnew System::String(DoubleToString(RobotServer.cFAIL)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"CRASH="); f_info+=gcnew System::String(DoubleToString(RobotServer.cCRASH)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"FUEL="); f_info+=gcnew System::String(DoubleToString(RobotServer.dFUEL)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"DONE="); f_info+=gcnew System::String(DoubleToString(RobotServer.cDONE)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"ERROR="); f_info+=gcnew System::String(DoubleToString(RobotServer.cERROR)); f_info+=gcnew System::String(L"\r\n");

f_info+=gcnew System::String(L"IGNORED="); f_info+=gcnew System::String(DoubleToString(RobotServer.cIGNORED)); f_info+=gcnew System::String(L"\r\n");

//---

textBox_Info->Text = f_info;

//---

Gdiplus::SolidBrush f_brush(Gdiplus::Color(255, 200, 200, 200)); // Кисть для заливки серым цветом.

BitmapGraphics->FillRectangle(&f_brush, (int)0, 0, 532, 496);

Gdiplus::Pen f_pen_black(Gdiplus::Color(255, 0, 0, 0), 2);

Gdiplus::Pen f_pen_R(Gdiplus::Color(255, 255, 0, 0), 2);

Gdiplus::Pen f_pen_G(Gdiplus::Color(255, 0, 255, 0), 2);

Gdiplus::Pen f_pen_B(Gdiplus::Color(255, 0, 0, 255), 2);

BitmapGraphics->DrawLine(&f_pen_black, (int)266, 0, 266, 496); // Линия сверху вниз (OY).

BitmapGraphics->DrawLine(&f_pen_black, (int)0, 248, 532, 248); // Линия слева направо (OX).

//---

int f_x = 266 + 266*RobotServer.dRX; // Вычисляем коор-ты робота ..

int f_y = 248 - 248*RobotServer.dRY; // .. в пикселях формы.

BitmapGraphics->DrawEllipse(&f_pen_G, 261+266*Last_X, 243-248*Last_Y, 10, 10); // Рисуем предыдущую точку

BitmapGraphics->DrawEllipse(&f_pen_B, 261+266*RobotServer.dTX, 243-248*RobotServer.dTY, 10, 10); // Рисуем текущую точку

//---

BitmapGraphics->DrawLine(&f_pen_B, (int)(266+266*Last_X), 248-248*Last_Y, 266+266*RobotServer.dTX, 248-248*RobotServer.dTY); // Рисуем линию меж пред и текущ точк.

//---

BitmapGraphics->DrawEllipse(&f_pen_black, f_x-20, f_y-20, 40, 40); // Рисуем робота

BitmapGraphics->DrawLine(&f_pen_G, f_x, f_y, f_x+266*RobotServer.dVRX, f_y-248*RobotServer.dVRY); // Рисуем вектор движения

BitmapGraphics->DrawLine(&f_pen_R, f_x, f_y, f_x+266*RobotClient.dFRX*0.5, f_y-248*RobotClient.dFRY*0.5); // Рисуем вектор тяги

//---

WindowGraphics->DrawImage(RobotBitmap, (int)0, 0, 532, 496); // Рисуем всё на экран

//---

LeaveCriticalSection(&Section);

}

// Обработчик Таймера 2.

private: System::Void timer2_Tick(System::Object^ sender, System::EventArgs^ e)

{

EnterCriticalSection(&Section);

// Если было установлено соединение с сервером.

if (RobotSocket) {

System::String^ f_string = gcnew System::String(L"{(FRX=");

f_string += gcnew System::String(DoubleToString(RobotClient.dFRX));

f_string += gcnew System::String(L")(FRY=");

f_string += gcnew System::String(DoubleToString(RobotClient.dFRY));

f_string += gcnew System::String(L")}");

//---

pin_ptr<const wchar_t> f_str = PtrToStringChars(f_string);

//---

char f_return[50];

unsigned long f_size = wcstombs(f_return, f_str, 50); // Преобразовать UNICODE в CHAR

//---

send(RobotSocket, f_return, f_size, 0); // Послать на сервер.

}

LeaveCriticalSection(&Section);

}};

}