Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ДипломКрутоваИТ0801.docx
Скачиваний:
80
Добавлен:
03.05.2015
Размер:
1.61 Mб
Скачать

Глава 3. Разработка пакета прикладных программ для передачи информации с рпдп

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

Создание программы передачи и обработки информации можно разделить на две части: разработку функций передачи информации по сети и разработку функций обработки переданной информации.

    1. Организация сетевого соединения с РПДП

Для обеспечения взаимодействия по сети между двумя приложениями (вычислительными процессами) было решено использовать стандартный прикладной программный интерфейс (application programming interface (API)) Winsock. Winsock не является протоколом и является основным средством для программирования сетевых приложений для операционных систем Windows. Winsock предоставляет программный интерфейс для коммуникации процессов с помощью всех известных современных протоколов: TCP/IP версий 4 и 6, IPX/SPX, NetBIOS, ATM, Bluetooth и т.д. Основная цель разработки спецификации Winsock — создать независимый от транспортного протокола программный интерфейс.

Различают транспортные протоколы, требующие и не требующие установления логического соединения. Протоколы, устанавливающие соединение, поддерживают информацию о состоянии, которая позволяет следить за последовательностью пакетов. Они самостоятельно отслеживают состояние пакетов. Сохраняемая информация о состоянии позволяет протоколу обеспечить надежную доставку. Например, отправитель запоминает, когда и какие данные послал, но они еще не подтверждены. Если подтверждение не приходит в течение определенного времени, отправитель повторяет передачу. Получатель запоминает, какие данные уже принял, и отбрасывает пакеты-дубликаты. Если пакет поступает не в порядке очередности, то получатель может «придержать» его, пока не придут логически предшествующие пакеты. Как правило, в протоколах, требующих соединения, данные передаются потоком. У типичного протокола, требующего наличия соединения, есть три фазы. Сначала устанавливается соединение между двумя приложениями. Затем происходит обмен данными. И, наконец, когда оба приложения завершили обмен данными, соединение разрывается. Обычно такой протокол сравнивают с телефонным разговором, а протокол, не требующий соединения, - с отправкой почтовой открытки.

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

В связи с многочисленными недостатками протоколов, не требующих соединения, возникает закономерный вопрос: зачем вообще нужен такой вид протоколов? Часто встречаются ситуации, когда для создания приложения использование именно такого протокола оправдано. Например, протокол без соединения может легко поддерживать связь одного хоста со многими и наоборот. Между тем протоколы, устанавливающие соединение, должны обычно организовать по одному соединению между каждой парой хостов. Впрочем, установление канала связи между двумя участниками и гарантия безошибочной с правильным порядком передачи данных влечет дополнительные издержки. С другой стороны, протокол, не требующий соединения, можно использовать для быстрой передачи коротких сообщений.

Internet Protocol (IP) не требует установления соединения и не гарантирует доставку данных, поэтому для передачи данных поверх IP используются два протокола более высокого уровня TCP (Transrmssion Control Protocol – протокол управления передачей) и UDP (User Datagram Protocol пользовательский датаграммный протокол). Каждый TCP и UDP пакет инкапсулируются в поле данных IP-пакета. В стеке TCP/IP протокол UDP работает без установки соединения, передаёт данные в виде дейтаграмм и не гарантирует их безошибочную передачу в правильном порядке. UDP может осуществлять передачу данных множеству адресатов и принимать данные от множества источников. Например, данные, отправляемые клиентом на сервер, передаются немедленно, независимо от того, готов ли сервер к приему. При получении данных от клиента, сервер не подтверждает их прием. Протокол TCP работает с установкой соединения, передаёт данные в виде потока байт и гарантирует их безошибочную передачу в правильном порядке между двумя компьютерами. Когда приложения связываются по TCP, осуществляется виртуальное соединение исходного компьютера с целевым, после чего между ними возможен одновременный двунаправленный обмен данными. То есть в случае протокола TCP имеем полнодуплексную связь, как при телефонном разговоре: два связанных абонента могут разговаривать одновременно.

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

Сетевой программный интерфейс Winsock основан на понятии сокета. Сокет — это описатель поставщика сетевого транспорта. Создание сокета позволяет приложениям осуществлять сетевое подключение и обмен данными через него. Сокеты являются своего рода представителями или конечными точками сетевого соединения как со стороны клиента, так и со стороны сервера. В Win32, в отличие от UNIX/LINUX , сокет отличается от описателя файла, а потому представлен отдельным типом — SOCKET (переопределенное беззнаковое целое). Это означает, что в отличие от UNIX/LINUX, с сокетом нельзя обмениваться информацией с помощью файловых функций ввода и вывода типа read и write. В проектируемой системе клиент (стенд обработки информации) – это «сторона» соединения, которая запрашивает определенные данные, а сервер (регистратор параметров движения поезда) отвечает в соответствии с этими запросами. В приложении сокет клиента создается следующим образом:

s = socket(AF_INET, SOCK_DGRAM,0);

Первый параметр определяет семейство адресов протокола. Чтобы ссылаться на протокол IP версии 4 первый параметр необходимо задать как AF_INET. Второй параметр —это тип сокета для данного протокола. Он может принимать одно из следующих значений SOCK_STREAM (потоковый сокет для TCP), SOCK_DGRAM (для UDP), SOCK_RAW и т.д. Создавая UDP сокет, необходимо указать значение SOCK_DGRAM. Третий параметр указывает конкретный транспорт, если для данного семейства адресов и типа сокета существует несколько записей. Значение «0» может быть использовано для выбора протокола по умолчанию из указанного семейства адресов и типа сокета. При успешном вызове функция socket возвращает дескриптор, ссылающийся на новый сокет.

Когда сокет создается при помощи socket(), он ассоциируется с некоторым семейством адресов, но не с конкретным адресом. Такой сокет называют несвязанным.

Рис.3.1.1 Несвязанный сокет

До того как сокет сможет принять входящие соединения, он должен быть связан с адресом. Необходимо указать IP-адрес сетевого интерфейса клиента и номер порта службы. В Winsock IP-адрес и порт службы задают в структуре:

struct sockaddr_in

{

short sin_family;

unsigned short sin_port;

struct in_addr sin_addr;

char sin_zero[8];

}

Поле sin_family должно быть равно AF_ INET (IP версии 4). Поле sin_port задает, какой коммуникационный порт TCP или UDP будет использован. Очень важно осторожно относиться к выбору порта, поскольку некоторые доступные порты зарезервированы для использования популярными службами: такими, как File Transfer Protocol (FTP) и Hypertext Transfer Protocol (HTTP). Во избежание накладок с портами, уже занятыми системой или другим приложением, программа должна выбирать зарегистрированные порты в диапазоне 1024-49151.

Поле sin_addr структуры sockaddr_in имеет тип:

struct in_addr

{

unsigned long s_addr;

}

То есть sin_addr структуры sockaddr_in хранит IP-адрес в 4-байтном виде с типом unsigned long int с сетевым порядком байт. В зависимости от того, как это поле использовано, оно может представлять и локальный, и удаленный IP-адрес.

Далее представлена функция, заполняющая структуру sockaddr_in необходимыми адресами:

void set_address(struct sockaddr_in *sa, char *ipaddr, int port)

{

bzero(sa, sizeof(struct sockaddr_in));

sa->sin_family = AF_INET;

sa->sin_port = htons(port);

if(ipaddr==0)

sa->sin_addr.s_addr = htonl(INADDR_ANY);

else

sa->sin_addr.s_addr = inet_addr(ipaddr);

}

Если номер порта и IP-адрес хранятся в памяти компьютера как многобайтные числа, они представляются в системном порядке (host-byte-order). Стандарты Интернета требуют, чтобы многобайтные значения представлялись от старшего байта к младшему (в порядке big-endian), что обычно называется сетевым порядком (network-byte order). Есть целый ряд функций для преобразования многобайтных чисел из системного порядка в сетевой и обратно. Например, вспомогательная функция inet_addr() преобразует IP-адрес из точечной нотации в 32-битное длинное целое без знака с сетевым порядком следования байт (network-byte order). А функция htons() преобразует порт из четырехбайтного числа с системным порядком следования байт в число с сетевым порядком.

Если конкретный IP-адрес не указан (ipaddr==0), существует специальный IP-адрес INADDR_ANY, позволяющий приложению вести передачу через любой сетевой интерфейс на несущем компьютере. Если на компьютере несколько сетевых адаптеров, то этот адрес позволит отдельному приложению получать отклики от нескольких интерфейсов.

После создания сокета и инициализации структуры адресов sockaddr_in, можно произвести ассоциирование сокета с оконечной точкой. В приложении для ассоциирования вызывается функция bind(). Этот вызов принимает параметры, в которых задаются дескриптор созданного сокета и адрес оконечной точки, записанный в структуру sockaddr_in, который включает IP- адрес и номер порта точки.

ret = bind(s, (struct sockaddr *)sa, sizeof(struct sockaddr_in));

Функция bind() принимает три аргумента: s — дескриптор, представляющий сокет при привязке;  (struct sockaddr *)sa — указатель на структуру sockaddr, представляющую адрес, к которому привязываем; sizeof(struct sockaddr_in) — поле, представляющее длину структуры sockaddr.

Таким образом, следующая функция создает UDP-клиента:

SOCKET udp_client(struct sockaddr_in *sa, char *ipaddr, int port)

{

SOCKET s;

int ret;

set_address(sa, ipaddr, port);

s = socket(AF_INET, SOCK_DGRAM,0);

bind(s,(struct sockaddr*)sa, sizeof(struct sockaddr_in));

if(!sock_valid(s))

{

return -1;

}

return s;

}

Рис.3.1.2 Связанный сокет

Таким образом, так как в разрабатываемом приложении используется UDP протокол, для клиента связывание осуществляется явным вызовом функции bind(), а для сервера привязка сокета к локальному адресу осуществляется неявно с помощью функции sendto().

В программах с установлением логического соединения после создания TCP сокета и связывания его со своим адресом и портом вызывают функцию listen(), чтобы подготовить его для приема входящих запросов на установление соединения. Большая часть серверных программ состоит из бесконечного цикла, в котором принимается очередной входящий запрос на установление соединения, выполняется его обработка, а затем происходит возврат к выполнению операции приема очередного входящего соединения. Для обеспечения того, чтобы не был потерян ни один запрос на установление соединения, сервер должен передать функции listen() параметр, который указывает операционной системе, что запросы на установление соединения, поступающие в сокет, нужно ставить в очередь. Поэтому один параметр вызова функции listen() указывает сокет, который должен быть переведен в пассивный режим, а другой — размер очереди, предназначенной для этого сокета. При работе с сокетами TCP после вызова в серверной программе функций socket() (для создания сокета), bind() (для указания адреса локальной оконечной точки) и listen() (для перевода сокета в пассивный режим прослушивания) вызывается функция accept() для извлечения из очереди следующего входящего запроса на установление соединения. Параметр функции accept() указывает сокет, из очереди которого должен быть принят запрос на соединение. Функция accept() создает новый сокет для каждого нового запроса на соединение и возвращает дескриптор нового сокета в вызывающую программу. В сервере новый сокет используется только для нового соединения, а первоначальный сокет служит для приема следующих запросов на соединение. Сразу после приема запроса на соединение сервер может передавать данные через новый сокет с помощью функций send() и recv().

Для формирования запроса на установление соединения сокета клиента TCP с сокетом сервера используется функция connect(), в которой указывается IP-адрес и номер порта протокола сервера, на котором должен существовать пассивный сокет, привязанный к адресу и порту.

Сокеты UDP позволяют не производить соединение (connect()), а сразу передавать и принимать данные от произвольных абонентов с помощью функций sendto() и recvfrom(). Если же функция connect() используется, то она просто определяет адрес назначения по умолчанию для дальнейшего использования для обмена данными функций send() и recv() вместо sendto() и recfrom(). Любая дейтаграмма, полученная от адреса отличного от адреса назначения, будет отброшена.

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

int recvfrom(SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* fromlen);

Параметр s задаёт связанный сокет, buf - буфер для входящих данных, len - длину буфера в байтах, from – необязательный указатель на структуру sockaddr в которой будет возвращен адрес источника, fromlen необязательный указатель на размер в байтах адреса источника. Если не произошла ошибка, то функция возвращает число принятых байт, которых может быть меньше len, иначе может быть получен код ошибки SOCKET_ERROR (с помощью функции WSAGetLastError). Данные извлекаются из первого сообщения в очереди вплоть до размера указанного буфера. Если сообщение в очереди длиннее, чем буфер, то буфер заполняется начальной частью сообщения, recvfrom() генерирует сообщение WSAEMSGSIZE. Для протокола UDP оставшиеся в сообщении данные теряются. Если сообщение в очереди короче, чем буфер, то ошибок не возникает, recvfrom() забирает всё сообщение в буфер и возвращает длину сообщения. Если параметр from не равен нулю, сетевой адрес отправителя данных копируется в структуру sockaddr, на которую указывает from. Величина на которую указывает fromlen устанавливается в размер этой структуры и при возврате изменяется, показывая истинный размер адреса, сохранённого в структуре sockaddr.

Функция sendto() отсылает данные регистратору параметров движения поезда. Прототип определён в файле Winsock2.h. Библиотека импорта Ws2_32.lib. Код расположен в Ws2_32.dll. Функция имеет вид:

int sendto(SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen);

Парметр s определяет сокет, который может быть уже и соединён, buf – буфер, содержащий данные для передачи, len – длина буфера в байтах (для UDP сокетов параметр len не может превосходить максимального значения, которое можно получить вызвав getsockopt с парамертом SO_MAX_MSG_SIZE, flags в большинсве случаях устанавливают в 0, to – необязательный указатель на структуру, которая содержит адрес целевого сокета, tolen – размер адреса в байтах. Параметр to может быть любым действительным адресом, включая широковещательные. Если сокет не связан, то при вызове sendto() WinSock назначает ему локальный адрес и уникальный порт, осуществляя тем самым неявную привязку сокета. Успешный вызов sendto() не означает, что данные были успешно доставлены. Обычно sendto() используется на сокетах, не ориентированных на соединение для пересылки дейтаграмм к указанному сокету, определённому в параметре to.

Рис.3.1.3 Временная диаграмма установления Wi-Fi соединения

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

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

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

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.4.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(),описанной в начале главы.

int CEthDev::open(int dev_no)

{

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

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

setRecvBufSize(1024*1024*10);

return sd;

}

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

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;

}

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

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, описывающий регистрацию.

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.

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

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() заполняет столбцы таблицы, записывая туда данные о регистрации (количество страниц, размер в байтах), а также время, когда эта регистрация была записана на РПДП.

Рис. 3.4.1 Выведенное в Grid оглавление

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

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

// команды 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, где нужно задать тип команды (некоторые из них перечислены выше), адрес верхнего и нижнего слова, длину сдвига и некоторую дополнительную информацию.

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. Далее в цикле производится увеличение адреса команды на единицу, смещение указателя и чтение следующей страницы. Функция возвращает количество прочитанных байт.

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.

//следующая функция отправляет информацию (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().

//Функция читает страницу из РПДП, 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() дано в начале главы.

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(), параметрами которой является имя созданного файла для записи и номер регистрации для считывания.

void __fastcall TForm1::BtnReceiveClick(TObject *Sender)

{

TDateTime dtTemp;

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

dtTemp = this->FileNameReg(row);

this->SaveDialog1->InitialDir = usgDirectoryN;

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

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

this->SaveDialog1->FileName = EthFileName;

if (this->SaveDialog1->Execute())

{

if (FileExists(this->SaveDialog1->FileName))

{

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

if (ModalResult == 2) { return; }

}

this->Download_fast(EthFileName, row);

}

this->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. Декодированная информация записывается в ранее созданный файл.

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);

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

free(tmp_buf);

return true;

}

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

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

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

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

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

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

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

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; //расшифровка по протоколу

this->Series1->AddXY(kadrkol,fVfact) ;

}

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

{

Vdop[0]= buf[9];

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

this->Series2->AddXY(kadrkol,fVdop) ;

}

iseek = iseek + 17;

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

delete(Vfact);

delete(Vdop);

}

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

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

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

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

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

    1. Вывод

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

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

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