Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DiplomKrutova.docx
Скачиваний:
46
Добавлен:
03.05.2015
Размер:
1.46 Mб
Скачать
    1. Абстракция процесса обмена информацией

Для качественного написания программы чрезвычайно важна абстракция данных. В основе объектно-ориентированного программирования лежит понятие класса. Класс определяет природу объекта и является основным механизмом инкапсуляции. Объявление класса определяет новый тип, связывающий код и данные между собой. Таким образом, класс является логической абстракцией, а объект (экземпляр класса) – ее физическим воплощением.

Классы создаются с помощью ключевого слова class. В качестве спецификатора доступа используется одно из трех ключевых слов: public, private, protected. По умолчанию функции и данные, объявленные внутри класса, считаются закрытыми (private) и доступны только функциям-членам этого класса. Спецификатор public открывает доступ к функциям и данным класса из других частей программы. Спецификатор доступа protected необходим только при наследовании классов. Функции, объявленные внутри класса, называются функциями-членами класса. Они имеют доступ ко всем элементам своего класса, включая закрытые члены. Переменные, объявленные внутри класса, называются переменными-членами класса.

Для обеспечения обмена сообщениями по Ethernet между стендом регистрации и РПДП, было решено создать абстрактный тип данных, класс CEthDev. Этот класс позволяет скрыть от разработчика детали осуществления передачи, используемые функции, а также значения, определенные внутри класса для выполнения функций.

Листинг 3.2.1

class CEthDev

{

public:

CEthDev(); // Конструктор

virtual ~CEthDev(); // Дестректор

int open(int dev_no = 0);

void close();

int setRecvBufSize(int size); // Установка размера приемного сокета в байтах

int getRecvBufSize(); // Получение размера приемного сокета в байтах

int read_pages(void *data, int pages_count, int tout = 500); // Чтение страницы регистрации

int read_raw_pages(void *data, int pages_count, int tout = 500);

int read_bytes(void *data, int len,int tout = 500); // Чтение заданного количества байт, с таймаутом

int send_cmd(rpdp_cmd_t *cmd); //Отправка команд на РПДП

int get_header(void *buf, unsigned int pages_count, int start_block); // Получение Оглавления РПДП

protected:

SOCKET sd;

struct sockaddr_in sa;

};

Далее, по порядку, чтобы не нарушать логику программы, будут описаны некоторые основные функции-члены класса CEthDev.

    1. Алгоритм чтения оглавления РПДП

После того, как поезд приехал в депо, и было произведено соединение между стендом и регистратором параметров движения поезда по Wi-Fi, можно начинать считывание регистраций, находящихся в памяти РПДП.

Рисунок 3.3.1 Начальное окно программы

Сразу после запуска программы выполняются следующие действия:

Листинг 3.3.1

__fastcall TForm1::TForm1(TComponent* Owner)

: TForm(Owner)

{

ProgName = L"Тестирование передачи информации на РПДП";

Form1->Caption = ProgName;

rscodec_init(); //Производится подключение динамической библиотеки rscodec.dll

netlib_init(); //Инициируется использование Winsock DLL функцией WSAStartup()

dev = new CEthDev();

dev->open();

}

В первую очередь происходит netlib_init() инициирование использования функций библиотеки Winsock, описанных выше. Далее происходит создание элемента класса CEthDev и вызывается функция, обеспечивающая сетевое взаимодействие клиента и сервера open(). Эта функция создает UDP-клиента вызовом функции udp_client(),описанной в начале главы.

Листинг 3.3.2

int CEthDev::open(int dev_no)

{

//создается сокет клиента, производится адресация и ассоциирование

sd = udp_client(&sa,SRC_IP_ADDR,0);

setRecvBufSize(1024*1024*10);

return sd;

}

Прежде чем считывать конкретную регистрацию, необходимо вывести на экран список всех существующих на РПДП регистраций. Список выводится на компоненту StrGridRPDP[6] по нажатию кнопки «Оглавление» (BtnReadHeader).

Листинг 3.3.3

void __fastcall TForm1::BtnReadHeaderClick(TObject *Sender)

{

bool bResult;

igCountRPDP = 0;

Screen->Cursor = crHourGlass;

bResult = this->ReadHeader(); //Вызывается функция чтения Оглавления

Screen->Cursor = crDefault;

}

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

bool __fastcall TForm1::ReadHeader()

{

int ret_code, count;

reg_info_t *farr;

try {

// Выделяем память под страницу

char *sys_page = new char[4096];

// Указатель на оглавление

ginfo = (reg_info_t *) g_sys_page;

igCountRPDP = 0;

// Цикл чтения списка регистраций

for (int block = 0; block < 10; block++)

{

// читаем очередную страницу (4096 байт)

for (int i = 0; i < 5; i++)

{

ret_code = dev->get_header(sys_page,1,block);

if (ret_code == 4096)

{

break;

}

if (i == 4)

{

Application->MessageBoxW(L"РПДП не отвечает.", L"Ошибка", MB_OK);

return false;

}

Sleep(50);

}

for (int i = 0; i < 4094; i++)

{

g_sys_page[(block*4094)+i] = sys_page[i]; //заполнение

}

// в одной странице умещается 89 регистраций

for(count=0;count<89;count++)

{

if (is_free_cell(&ginfo[igCountRPDP+count]))

//if = 0xFF goto end_found

{

igCountRPDP = igCountRPDP + count;

// igCountRPDP счетчик регистраций

goto end_found;

}

}

igCountRPDP = igCountRPDP + count;

}

end_found: //

if (count > 0)

{

this->FillStrGridRPDP(count); //Заполнение StrGrid Оглавлением

return true;

}

delete [] sys_page;

}

catch(String str)

{

}

return false;

}

Функция поиска последней регистрации в странице:

Листинг 3.3.4

int TForm1::is_free_cell(void *faddr)

{

unsigned int i;

unsigned char *b;

int free;

b = (unsigned char *)faddr;

free = 1;

for(i=0; i < sizeof(reg_info_t); i++)

{

if(b[i] != 0xFF)

{

free = 0;

break;

}

}

return free;

}

Информация от РПДП приходит в виде дейтаграмм фиксированного размера. Размер дейтаграммы равен 4096 байт. Дейтаграмма далее будет называться страницей или блоком. В функции ReadHeader() создается указатель sys_page на динамически выделенную область памяти размером 4096 байт для хранения одной дейтаграммы, а также происходит инициализация созданного глобально указателя ginfo на тип reg_info_t, описывающий регистрацию.

Листинг 3.3.5

struct reg_info_t

{

rtc_time_t rtc_time; // дополнительная временная информация

rpdp_addr_t faddr; // адрес первой страницы регистрации

unsigned int pages; // количество страниц регисирации

};

Тип reg_info_t определен в хидерном файле rpdr.h. Это созданный разработчиком составной тип данных, который характеризует одну конкретную регистрацию. Он содержит адрес первой страницы регистрации в памяти РПДП faddr, количество страниц pages, а также дополнительную информацию о регистрации, хранящуюся в rtc_time типа rtc_time_t.

Листинг 3.3.6

//структура, описывающая время создания регистрации

struct rtc_time_t

{

int sec; // секунды

int min; // минуты

int hour; // часы

int mday; // день месяца

int mon; // месяц

int year; // год

int wday; // день недели

int yday; // день года

int days_ago; // прошло дней с момента запуска

};

После инициализации производится цикл чтения 10 страниц (блоков). Функция get_header(), определенная в классе CEthDev, производит чтение и запись страницы в предварительно выделенную область памяти sys_page и возвращает её размер. На чтение страницы программе дается пять попыток с интервалом в 50 миллисекунд. Если чтение произведено успешно и функция get_header() возвращает значение 4096, попытки прекращаются, в противном случае программа выдает сообщение "РПДП не отвечает". Функция get_header() будет более подробно рассмотрена далее.

Принятая страница записывается в глобально определенный массив всех страниц оглавления g_sys_page[40960] типа unsigned char.

В одной принятой странице оглавления умещается информация о 89 регистрациях (адрес первой страницы, количество страниц, временная информация), но их может быть и меньше. Для подсчета количества регистраций в странице оглавления вводится глобальный параметр igCountRPDP типа int. Подсчет производится в цикле for() по всем возможным 89 регистрациям. Проверяется существование очередной регистрации с помощью функции is_free_cell(). is_free_cell() получает указатель на область памяти, где хранится информация об очередной регистрации, сравнивает байты памяти со значением 0xFF и если находит совпадения, то делает вывод о том, что память пуста и регистрации закончились. Функция возвращает значение true, если регистрация в памяти существует и false в противном случае.

После того, как определено, что регистрации в памяти закончились и произведен их подсчет с помощью параметра igCountRPDP, список регистраций выводится в StrGridRPDP с помощью функции FillStrGridRPDP(). Функция FillStrGridRPDP() заполняет столбцы таблицы, записывая туда данные о регистрации (количество страниц, размер в байтах), а также время, когда эта регистрация была записана на РПДП.

После общего описания процесса считывания оглавления РПДП, можно вернуться к функции get_header() и более подробно показать, каким же образом происходит считывание страницы регистрации в буфер sys_page.

Взаимодействие программы с РПДП происходит в режиме интерпретатора команд. Для того чтобы получить от РПДП страницу оглавления, необходимо отправить ему соответствующую команду на отправку этой страницы. Соответствие команд их идентификаторам описано в хидерном файле rpdp.h. Пример команд:

Листинг 3.3.7

// команды cmd для РПДП

#define RPDP_GET_PAGE 0 // получение страницы РПДП

#define RPDP_ERASE_BLOCK 4 // стирание блока РПДП

#define RPDP_GET_TIME 5 // получение времени РПДП

#define RPDP_SET_TIME 6 // установка времени РПДП

#define RPDP_PUT_PAGE 9 // запись без ожидания

#define RPDP_GET_RAW_PAGE 10 // получение страниц РПДП

#define RPDP_GET_VERSION 14 // получение версии РПДП

Перед отправлением команды на РПДП, необходимо заполнить структуру команды rpdp_cmd_t, где нужно задать тип команды (некоторые из них перечислены выше), адрес верхнего и нижнего слова, длину сдвига и некоторую дополнительную информацию.

Листинг 3.3.8

struct rpdp_cmd_t

{

unsigned char pack_type;

unsigned int cmd;

unsigned int addr_lo;

unsigned int addr_hi;

unsigned int len;

unsigned char reserv[100];

};

В функции get_header() производится заполнение данной структуры. cmd.pack_type задается по умолчанию, тип команды cmd.cmd задается как RPDP_GET_PAGE, адрес заполняется исходя из протокола, а длина устанавливается в 1. Далее открывается цикл по количеству страниц, где сначала производится отправка команды на получение информации, а потом вызывается функция read_pages(), которой в качестве параметра передается указатель на область памяти под страницу sys_page. Далее в цикле производится увеличение адреса команды на единицу, смещение указателя и чтение следующей страницы. Функция возвращает количество прочитанных байт.

Листинг 3.3.9

int CEthDev::get_header(void *buf, unsigned int pages_count, int start_block)

{

rpdp_cmd_t cmd;

int ret_code;

reg_info_t *farr;

rpdp_addr_t faddr;

cmd.pack_type = PKT_CMD;

cmd.cmd = RPDP_GET_PAGE; // команда на получение страницы РПДП

cmd.addr_lo = 0;

cmd.addr_hi = (7 << 20) + (start_block << 8); // адрес

cmd.len = 1;

char *tmp_buf = (char *)buf;

for(int i = 0; i< pages_count; i++)

{

send_cmd(&cmd);

ret_code = read_pages(tmp_buf,1,5000);

cmd.addr_hi++;

tmp_buf += 4096;

}

return ret_code;

}

Отправка команды на получение информации от РПДП производится с помощью функции send_cmd(). Сначала происходит инициализация структуры dest, в которой указывается IP-адрес пункта назначения DST_IP_ADDR ="192.168.1.10" и порт. Далее производится отправка команды cmd с помощью функции sendto() в пункт назначения dest.

Листинг 3.3.10

//следующая функция отправляет информацию (cmd)

int CEthDev::send_cmd(rpdp_cmd_t *cmd)

{

struct sockaddr_in dest;

int ret_code;

set_address(&dest, DST_IP_ADDR, DST_UDP_PORT);

ret_code = sendto(sd,(const char*)cmd, sizeof(rpdp_cmd_t),0, (sockaddr*)&dest, sizeof(dest));

return ret_code;

}

Функция read_pages() вызывает функцию чтения байт read_bytes().

Листинг 3.3.11

//Функция читает страницу из РПДП, 1 страница - 4096

int CEthDev::read_pages(void *data, int pages_count, int tout)

{

unsigned char *b;

int ret_code;

b = (unsigned char *)data;

for(int i=0;i<pages_count;i++)

{

ret_code = read_bytes(&b[i*4096], 4096,tout); //читает по 4096 байт за раз

}

return ret_code;

}

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

В функции read_bytes() создается дескриптор события recv_event, который с помощью функции WSAEventSelect() ассоциируется с определенным набором сетевых событий, в нашем случае с событием чтения FD_READ. Таким образом, после отправки команды cmd на считывание регистрации, клиент в течение определенного времени ожидает сетевого события FD_READ. Если событие происходит до момента истечения таймера ожидания WAIT_TIMEOUT, вызывается функция recvfrom(). Функция recvfrom() получает дейтаграмму, записывает ее в буфер sys_page и возвращает количество принятых байт. Подробное описание функции recvfrom() дано в начале главы.

Листинг 3.3.12

int CEthDev::read_bytes(void *data, int len, int tout)

{

int fromlen;

struct sockaddr_in saddr;

int count;

count = -1;

//create an event object

HANDLE recv_event = CreateEvent(0,0,0,0);

BOOL bSend = TRUE;

int iCnt;

WSAEventSelect(sd,recv_event,FD_READ);

DWORD dwRet = WaitForSingleObject(recv_event,tout);

if(dwRet != WAIT_TIMEOUT)

{

count = recvfrom(sd, (char*)data, len, 0, 0, 0);

}

CloseHandle(recv_event);

return count;

}

    1. Алгоритм чтения регистрации РПДП

После вывода списка регистраций в StrGridRPDP, можно произвести считывание конкретной регистрации. Для выбора регистрации необходимо кликнуть на нее мышкой в StrGridRPDP. Считывание производится по нажатию кнопки «Принять» BtnReсeiveFromEthernet. При нажатии в параметр row записывается номер выделенной строки в StrGridRPDP для дальнейшего поиска информации об этой регистрации в буфере (адреса в памяти РПДП и количества страниц). Помимо этого в директории usgDirectoryN создается файл расширения .rpdp для дальнейшей записи считанной регистрации. Имя файла в программе EthFileName. Далее производится вызов функции чтения Download_fast(), параметрами которой является имя созданного файла для записи и номер регистрации для считывания.

Листинг 3.4.1

void __fastcall TForm1::BtnReceiveClick(TObject *Sender)

{

TDateTime dtTemp;

row = Form1->StrGridRPDP->Row-1;

dtTemp = FileNameReg(row);

SaveDialog1->InitialDir = usgDirectoryN;

SaveDialog1->Filter = L"(*.rpdp)|*.rpdp";

EthFileName = "Запись" + Time().FormatString("hh_mm_ss")+ ".rpdp";

SaveDialog1->FileName = EthFileName;

if (SaveDialog1->Execute())

{

if (FileExists(SaveDialog1->FileName))

{

ModalResult = Application->MessageBoxW(L"Файл с таким именем уже существует.\r\n Заменить его?", L"Сохранение", MB_OKCANCEL|MB_ICONWARNING);

if (ModalResult == 2) { return; }

}

Download_fast(EthFileName, row);

}

BtnCompare->Enabled = true;

}

Следующая функция производит считывание выбранной в оглавлении РПДП регистрации и запись этой регистрации в файл. По умолчанию количество считываемых за раз страниц pages_per_req регистрации выбирается равным 50. Как и при чтении оглавления, для считывания регистрации необходимо отправить команду РПДП. Название команды - RPDP_GET_RAW_PAGE, адрес считываемой регистрации можно получить из глобального массива всех страниц оглавления g_sys_page[40960] заполненного ранее. Далее необходимо определить количество запросов на скачивание для открытия цикла скачивания cycles. В цикле определяется количество страниц на текущий запрос, отправляется команда с указанным количеством страниц для скачивания cmd.len = page_count и производится считывание в tmp_buf. Далее производится декодирование считанного буфера с помощью библиотечной функции rscodec_decode_page4k(), вызванной из файла rscodec_dll. Декодированная информация записывается в ранее созданный файл.

Листинг 3.4.2

bool _fastcall TForm1::Download_fast(UnicodeString asFileName, int row)

{

int pages_per_req = 50;

char cTemp[2];

reg_info_t *info;

info = (reg_info_t *) g_sys_page;

iFileName = FileCreate(asFileName);

// подготавливаем команду

rpdp_cmd_t cmd;

cmd.pack_type = PKT_CMD;

cmd.cmd = RPDP_GET_RAW_PAGE; //

cmd.addr_lo = 0;

cmd.addr_hi = info[row].faddr.addr_hi;

cmd.len = pages_per_req;

// определяем кол-во запросов на скачивание

int cycles = info[row].pages / pages_per_req;

int last_pages = info[row].pages % pages_per_req;

if(last_pages)

cycles++;

// выделяем буфер для хранения страниц FLASH-памяти

char *tmp_buf = (char*)malloc(pages_per_req * sizeof(nand_page4k_t));

// сам цикл скачки

int last_cycle = cycles - 1; // номер последнего запроса

for(int i=0; i<cycles; i++)

{

// опред. кол-во страниц на текущий запрос

int page_count;

if(i == last_cycle)

{

if(last_pages)

page_count = last_pages;

else

page_count = pages_per_req;

} else

{

page_count = pages_per_req;

}

cmd.len = page_count;

dev->send_cmd(&cmd);

dev->read_raw_pages(tmp_buf,page_count, 5000);

cmd.addr_hi += page_count; // увеличиваем адрес начальной страницы

nand_page4k_t *page_buf = (nand_page4k_t *)tmp_buf;

// декодируем страницы из буфера и

// записываем их в файл

for(int j=0; j < page_count; j++)

{

rscodec_decode_page4k(&page_buf[j]);

if (i == 0 && j == 0)

{

FileWrite(iFileName, page_buf[j].data, 4082);

} else

{

FileWrite(iFileName, page_buf[j].data, 4080);

}

}

}

cTemp[0] = 0xFA; // записываем версию регистрации

cTemp[1] = 0xFA;

FileSeek(iFileName, 0, 0);

FileWrite(iFileName, cTemp, 2);

StatusBar->Panels->operator [](3)->Text = "Чтение произведено ";

free(tmp_buf);

return true;

}

    1. Анализ регистрации

После записи регистрации в файл можно производить анализ содержащейся в ней информации, поступившей в РПДП от различных устройств поезда. В рамки данного дипломного проекта было решено включить отображение допустимой и фактической скорости поезда на графике. Для расшифровки набора байт, принятого от РПДП, используется протокол «Таблица обмена БКПУ (бортового компьютера поездного управления) − ВО (вагонного оборудования) вагонов модели 81-760/761»[8]. Информация о допустимой скорости поступает на устройство СБИС (измеритель скорости) от ПЦБ (процессор центральной безопасности). Информация, поступающая на СБИС, имеет идентификатор 0x1AE. Ниже приведен фрагмент протокола, позволяющий произвести расшифровку. Как видно из протокола, информация о допустимой скорости хранится в 0 байте принятого 8-и байтового сообщения.

Таблица. 3.5.1

Таблица обмена БКПУ-ВО. Прием СБИС

Измерение фактической скорости производит само устройство СБИС для дальнейшей отправки другим блокам поезда. Информация, отправляемая устройством СБИС, имеет идентификатор 0x22E. Как видно из нижеприведенного протокола, информация о фактической скорости хранится в 2 и 3 байтах принятого 8-и байтового сообщения.

Таблица.3.5.2

Таблица обмена БКПУ-ВО. Передача СБИС

Следующий фрагмент кода производит поиск в принятом наборе байт регистрации идентификаторов 0x22E(передачи) и 0x1AE(приема) устройства СБИС и построение графика. Для работы с графиками и диаграммами в C++Builder существует компонент TChart (вкладка Additional). Этот компонент обладает большим количеством встроенных возможностей по отображению графической информации. Для создания каждого графика (фактической и допустимой скоростей) необходимо инициализировать отдельную серию (Series).

В нижеприведенном коде открывается цикл по всем строкам (17 байт) регистрации, в которых производится поиск интересующих идентификаторов. После нахождения идентификатора программа записывает нужные байты информации (для 0x22E-2 и 3 байт, для 0x1AE-0 байт) в буфер, который расшифровывается по приведенным ниже протоколам. После расшифровки производится отрисовка очередной точки графика. По оси X располагаются по порядку все прочитанные кадры регистрации, по оси Y-значения фактической и допустимых скоростей в этих кадрах.

Листинг 3.5.1

void __fastcall TForm1::GraphClick(TObject *Sender)

{

int iseek=2;

unsigned char buf[17];

int kadrkol=0;

float fVdop, fVfact;

unsigned short *Vfact;

unsigned char *Vdop;

Vfact = new unsigned short[1]; //по протоколу занимает 2 байта

Vdop = new unsigned char[1]; //по протоколу занимает 1 байт

do

{

FileSeek(igFileData, iseek , 0);

FileRead(igFileData, buf, 17);

if (buf[5]==0x11 && buf[6]==0x00)

{

kadrkol++;

}

if (buf[5]==0x2E && buf[6]==0x02)

{

Vfact[0]= (buf[12]<<8)+buf[11];

fVfact= Vfact[0]*0.1; //расшифровка по протоколу

Series1->AddXY(kadrkol,fVfact) ;

}

if (buf[5]==0xAE && buf[6]==0x01)

{

Vdop[0]= buf[9];

fVdop= Vdop[0]*1; //расшифровка по протоколу

Series2->AddXY(kadrkol,fVdop) ;

}

iseek = iseek + 17;

} while (FileSeek(igFileData, iseek , 0) != FileSeek(igFileData, 0 , 2));

delete(Vfact);

delete(Vdop);

}

После выполнения функции на экране появляется график. Красным цветом изображается график допустимой скорости, синим – фактической. Сразу можно отметить, что в течение движения поезда возникло как минимум пять промежутков времени, когда фактическая скорость превышала допустимую.

Рисунок 3.5.1 График фактической и допустимой скорости поезда

Для того чтобы четко определить, в каких кадрах произошло недопустимое превышение фактической скорости, можно увеличить график и поставить маркер в интересующую точку. На оси X будет выведено значение кадра, в котором произошло нарушение. В дальнейшем, когда будет осуществлен анализ информации от всех устройств, а не только от СБИС и ПЦБ, можно будет выявить причины нарушения скоростного режима и устранить неполадки.

Рисунок 3.5.2 Определение моментов нарушения скоростного режима

Список самых необходимых для анализа параметров составили инженеры московского метрополитена. На данный момент их около 53. Отображение и анализ остальных параметров поезда будет производиться позднее, так как это сложная работа, требующая больших временных затрат, поэтому она не входит в рамки данного дипломного проекта.

    1. Вывод

В данной главе было произведено считывание регистрации из регистратора параметров движения поезда на стационарный комплекс обработки. Для обеспечения взаимодействия по сети между РПДП и стационарным комплексом обработки было решено использовать стандартный прикладной программный интерфейс Winsock, так как он предоставляет программный интерфейс для коммуникации процессов с помощью всех известных современных протоколов, в том числе и используемым в проекте протоколом TCP/IP. В связи с небольшим объемом передаваемой информации и необходимостью поддержки связи с большим количеством хостов (поездов), было решено использовать транспортный протокол UDP, протокол не устанавливающий логическое соединение между двумя узлами.

Для обеспечения обмена сообщениями по Ethernet между ПЭВМ и РПДП, был создан абстрактный тип данных, класс CEthDev, позволяющий скрыть от разработчика детали осуществления передачи, используемые функции, а также значения, определенные внутри класса.

После этого, для написания корректной программы, была тщательно изучена структура регистрации, хранящейся в РПДП, произведено чтение оглавления РПДП, выбор интересующей регистрации, и ее считывание в созданный файл. Далее был произведен вывод на график фактической и допустимой скоростей поезда для дальнейшего анализа. Было выявлено, что в течение следования поезда, когда производилась регистрация, существовали недопустимые моменты времени, когда фактическая скорость превышала допустимую на блок-участке. Данная информация очень важна для инженеров московского метрополитена, так как она является доказательством нарушения безопасности движения пассажиров.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]