Пакетный обмен с расходомерами по УПИО (CRC-RB/CRCRB)

В результате нарастания дефицита энергоносителей и воды наступило время всеобщего их учёта и экономии. В Беларуси разработано немало расходомеров различного типа. Расходомеры с электрическим интерфейсом RS485 и сопутствующим ему интерфейсом MODBUS постепенно вытесняются приборами, имеющими интерфейс Ethernet, реализующий протокол TCP/IP. Объясняется это как появлением дешёвых микропреобразователей последовательных интерфейсов в Ethernet-TCP/IP и наоборот, например, EM1206, так и стремлением сбытовых организаций вести дистанционный учёт через интернет. С другой стороны, потребители ресурсов тоже не прочь включать установленные на их территории расходомеры прямо в свою компьютерную сеть без расходов на новые линии связи. Да и скорости обмена возрастают. Но Ethernet-TCP/IP реализует только три уровня: канальный, сетевой и транспортный. В связи с этим в Беларуси разработан, утверждён и реализован протокол уровня представления, сокращённо – УПИО – универсальный протокол информационного обмена. Другое название протокола – CRCRB или CRC-RB. Но как с ним работать?

Запрос данных из прибора имеет вид: байт, зависящий от назначения запроса, байт логического номера прибора или 0x00, если номер отсутствует, два байта длины всего запроса (сначала старший, затем младший), содержание запроса (прикладной уровень), два байта идентификатора клиента, два байта остатка от деления всех предыдущих байт на полином X^16+X^12+X^5+1. Ответ от прибора имеет точно такой же формат, но 1-ый байт может отличаться от 1-го байта запроса. Ответ обязательно содержит тот же идентификатор клиента, что и запрос. Подробнее с протоколом (применительно к электроэнергии) можно ознакомиться здесь: http://www.energo.by/sbyt/Protokol_CRCRV.pdf. Обычно ответ намного длиннее запроса и не передаётся единственным пакетом.

При использовании УПИО расходомер выполняет функцию сервера. Для работы с расходомером приложению пользователя достаточно иметь только клиентский сокет. Не будем отвлекаться на создание сокетов средствами API, воспользуемся готовыми компонентами из раздела Internet (среда Builder C++), чтобы уловить суть. Отмечу сразу, что следует использовать либо неблокирующий режим сокета, либо, используя блокирующий режим, создать для работы клиентского сокета свой поток.

С передачей запроса, как правило, не возникает затруднений: сунул запрос в буфер, указал, сколько байт, и в путь. Сложнее с ответом. Здесь важно при передаче запроса правильно подготовиться к приёму. Рассмотрим немного кода.

Для обмена с сервером нам потребуются следующие внешние переменные.

 

// Для передачи:

unsigned char T_buf[MAX_T_SIZE]; // буфер

int T_size; // размер запроса

// Для приёма:

unsigned char R_buf[MAX_R_SIZE]; // буфер

unsigned char *ptr; // указатель буфера

bool more_then_4; // признак, что принято >4 байт

bool IsSizeTrue ; // признак вычисленности длины ответа

int RcvdSize; // количество принятых байт ответа

int WantedSize; // требуемая длина ответа

Итак, мы задали в клиенте имя и/или IP-адрес устройства, порт, попытались соединиться с сервером функцией Client->Open() и по состоянию внутренней переменной Client->Socket->Connected убедились, что соединение достигнуто. Формируем в буфере передачи и посылаем запрос, но предварительно выполняем начальную установку (НУ) внешних переменных приёма, иначе корректно принять пакеты ответа не получится.

void ClientSend(TObject *Sender, TCustomWinSocket *Socket) {

ptr = R_buf; // НУ указателя буфера приёма

more_then_4 = false;// сброс признака, что принято >4 байт

IsSizeTrue = false; // сброс признака вычисленности длины ответа

RcvdSize = 0; // сброс кол-ва принятых байт

if(Client->Active == true)

Client->Socket->SendBuf(T_buf, T_size );

else

return;

}

Если сервер ответил, то вам предстоит обработать событие OnRead. Фишка в том, что это событие касается только одного очередного пакета, но ответ может состоять из нескольких пакетов, причём длина ответа может распределяться по пакетам так, как серверу удобнее. Чтобы не потерять какой-нибудь пакет, а вместе с ним и весь ответ, будем работать с внешними переменными приёма. К счастью, при использовании протокола TCP (в отличие от UDP) сервер просто обязан передать ответ полностью, и событие OnRead будет возникать неоднократно, пока сервер передаёт ответ. Итак, принимаем его.

void ClientRead(TObject *Sender, TCustomWinSocket *Socket) {

int PackLength, BytesRcvd;

PackLength = Socket->ReceiveLength(); // длина очередного пакета

unsigned char* PackBuf = new unsigned char[PackLength]; // память под пакет

BytesRcvd = Socket->ReceiveBuf(PackBuf, PackLength);

RcvdSize += BytesRcvd; // наращиваем счк байт ответа

for(int i=0; i<BytesRcvd; i++) // пересылаем пакет в приёмный буфер

*ptr++ = PackBuf[i];

delete[] PackBuf; // освобождаем память

if(RcvdSize<4) // если принято менее 4-х- байт, ждём

return;

else // если принято 4 и > байт,

more_then_4 = true; // то помечаем это событие

// и разбираемся с вычислением длины ответа:

if(!IsSizeTrue && more_then_4) {

WantedSize = 0x100*R_buf[2] + R_buf[3];

IsSizeTtrue = true;

}

if(IsSizeTrue && RcvdSize < WantedSize) // весь ответ принят?

return; // нет, ждём

// да, обрабатываем:

….

return;

}

При обработке ответа сначала проверяют ошибки, затем, если их нет, обрабатывают содержание по алгоритму пользователя. При проверке ошибок необходимо обращать внимание не только на корректность CRC, но и на идентификатор собственного запроса, который в протоколе УПИО находится перед байтами CRC. Об этом нередко забывают, а зря: вполне может случиться ситуация, что вы поймаете ответ на чужой запрос. :)

Я не описываю обработку ошибочных ситуаций и запусков таймаутов после отправки запросов, надеясь, что тот, кому это надо, разберётся с ними самостоятельно. Впрочем, при хорошей связи этого может и не потребоваться. Успехов!

Версия для печатиВерсия для печати

Рубрики: 

  • 1
  • 2
  • 3
  • 4
  • 5
Всего голосов: 0
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!

Читайте также