Доброго времени суток! Сейчас я на удалёнке, потому как на удалёнке. Вдруг стало НАДО реализовать команду 1 на удалённом оборудовании из дома. Всё время применял (обычно) команду 2. По описанию они идентичны. Сделал давно удаленный сервер с реализацией этой команды(1)... но не испытал была не задействована нигде. Сейчас на новом оборудовании стало надо. Ввела в ступор документация: 01 Чтение статуса выходов ОПИСАНИЕ Читает статуса ON/OFF дискретных выходов в подчиненном. ЗАПРОС Запрос содержит адрес начального выхода и количество выходов для чтения. Выхода адресуются начиная с нуля: выхода 1-16 адресуются как 0-15. Ниже приведен пример запроса на чтение выходов 20-56 с подчиненного устройства 17. Имя поля Пример (Hex) Адрес подчиненного 11 Функция 01 Начальный адрес Hi 00 Начальный адрес Lo 13 Количество Hi 00 Количество Lo 25 Контрольная сумма (CRC или LRC) -- ОТВЕТ Статус выходов в ответном сообщении передается как один выход на бит. Если возвращаемое количество выходов не кратно восьми, то оставшиеся биты в последнем байте сообщения будут установлены в 0. Счетчик байт содержит количество байт передаваемых в поле данных. Имя поля Пример (Hex) Адрес подчиненного 11 Функция 01 Счетчик байт 4 005 Данные(Выхода 27-20) CD Данные(Выхода 35-28) 6B Данные(Выхода 43-36) B2 Данные(Выхода 51-44) 0E Данные(Выхода 56-52) 1B Контрольная сумма (CRC или LRC) -- 02 Read Input Status ОПИСАНИЕ Чтение ON/OFF состояния дискретных входов (ссылка 1Х) в пдчиненном. ЗАПРОС Запрос содержит номер начального входа и количество входов для чтения. Входа адресуются начиная с 0. Ниже приведен пример запроса на чтение входов 10197-10218 с подчиненного устройства 17. Запрос Имя поля Пример (Hex) Адрес подчиненного 11 Функция 02 Начальный адрес ст. 00 Начальный адрес мл. C4 Кол-во входов ст. 00 Кол-во входов мл. 16 Контрольная сумма -- ОТВЕТ Статус входов в ответном сообщении передается как один выход на бит. Если возвращаемое количество входов не кратно восьми, то оставшиеся биты в последнем байте сообщения будут установлены в 0. Счетчик байт содержит количество байт передаваемых в поле данных. Имя поля Пример (Hex) Адрес подчиненного 11 Функция 01 Счетчик байт 4 005 Данные(Входы 10204-10197) AC Данные(Входы 10212-10205) DB Данные(Входы 10218-10213) 35 Контрольная сумма (CRC или LRC) -- Видите ответ для каждой команды: Счетчик байт 4 005 При том, что для команды 1 в запросе: Количество Hi 00 Количество Lo 25 а для команды 2: Кол-во входов ст. 00 Кол-во входов мл. 16 Вот это меня и вводит в ступор... при том, что команды вроде как идентичны. Сейчас реализую первую команду дистанционно. Будь я по месту проверил бы и снифером(сканером) и ноут напрямую подключил, и вопросы не задавал бы. Спасибо!
Мне бы было достаточно отчёта при обмене Master и Slave в байтах. Большего и не надо. Разумеется для инструкции №1. Спасибо!
Что-то не слышно! Ну да ладно сам разберусь. одним словом; На словах Вы Львы Толстые, А на деле Хрены Простые. Ух извините! Зато всякие MQTT и прочее Вам как семечки. Во дела. Здравия Вам большого! Простите!
Че позоришься? Здесь все знают, включая как жить правильно, два-три персонажа. Остальные - нормальные люди.
вот спросишь один раз срочно... а тебе вместо ответа такое. Елки палки!!! Как спорить все!!! Как критиковать - ВСЕ! Как ответить - НИКОГО! Ну не делал я команды 1 - ранее не надо было... тут вдруг сказали что надо. И к железке доступ по VPN есть - надо сделать сервер сбора данных. И в этом гугле ссылки на может кривое описание этого modbus. Просто спросил уточнения - тишина. Как читаю - так толпа знатоков... перебивают друг друга... А ведь спросил: Ведь на удалёнке!!! Сделать могу... но быть на месте - НЕТ! И не принцип всего протокола, а только одной команды - казус в описании. Составить эту команду не проблема - но что с ответом от устройств??? По документации - ТУПОСТЬ! Вот и спросил.
Смутило то, что в нескольких документах один и тот же пример запросов и ответов байт в байт. И по видимому одна и та же опечатка (возможно и опечатка)
Серверная часть кода содержит реализацию инструкции 1. До недавнего времени не была испытана. Со стороны клиента(неопределённая инструкция): Код (C++): #if(_client == 1) int C_MRTU_CMD(BYTE * SndCmdPar, //массив запроса BYTE * RcvCmdPar, //массив ответа U8 SzSndCmdPar, //размер массива запроса U8 NumFarme, //номер кадра U32 time_dev_mrtu, //время ожидания ответа от устройства U32 tlive_mrtu_pkt, //время жизни пакета U32 TwSleep, //время сна после обмена U8 MRTUSndFlag //флаг запроса MRTU ) { BYTE snd[_sz_tcp_buf]; BYTE rcv[_sz_tcp_buf]; //MRTUSERV client; U16 sz; int res; #if(_scdeb == 1) int count = 0; #endif /* ************************ * установка параметров * ************************ */ memset(&snd[0], 0, _sz_tcp_buf); //время жизни пакета (*(U32*)(&snd[_tlive0_mrtu_pkt])) = tlive_mrtu_pkt; //время ожидания ответа от устройства (*(U32*)(&snd[_time0_dev_mrtu])) = time_dev_mrtu; //номер кадра Modbus RTU snd[_frame_num] = NumFarme; //время сна после обмена (*(U32*)(&snd[_tlive0_mrtu_sleep])) = TwSleep; //флаг запроса Modbus RTU snd[_cmd_flag] = MRTUSndFlag; //Modbus RTU memcpy(&snd[_addr_dev_MRTU], SndCmdPar, SzSndCmdPar); sz = SzSndCmdPar + _addr_dev_MRTU; (*(U16*)(&snd[0])) = sz; #if(_csdeb == 1) printf("----to send----\n"); printf("Sz Pkt:................%u\n", sz); printf("Time Live mrtu:........%u\n", (*(U32*)(&snd[_tlive0_mrtu_pkt]))); printf("Time Wait dev mrtu:....%u\n", (*(U32*)(&snd[_time0_dev_mrtu]))); printf("Time Sleep mrtu:.......%u\n", (*(U32*)(&snd[_tlive0_mrtu_sleep]))); printf("Num Frame:.............%u\n", snd[_frame_num]); printf("CMD Flag:..............%u\n\n", snd[_cmd_flag]); #endif #if(_scdeb == 1) printf("to sending %u\r\n", sz); while(count < sz) { printf("%.2X ", snd[count]); count++; } printf("\r\n"); #endif res = WriteSocket(MRTUClient.socket, &snd[0], sz); #if(_scdeb == 1) printf("==send res %i ==\r\n",res); #endif switch(res) { case -1: //таймаут сокета return _clanswr_socktimeout; case -2: //закрытие по ошибке close(MRTUClient.socket); #if(_scdeb == 1) printf("client socket error???\r\n"); #endif return _clanswr_sockerr; default: //ok break; } //usleep(100000); res = ReadSocket(MRTUClient.socket, &rcv[0]); switch(res) { //таймаут case -1: return _clanswr_socktimeout; //какая-то ошибка case -2: //закрытие по ошибке close(MRTUClient.socket); #if(_scdeb == 1) printf("client socket error???\r\n"); #endif return _clanswr_sockerr; //разрыв соединения по ошибке //норма default: #if(_scdeb == 1) count = 0; printf("to receiving %u\r\n", res); while(count < res) { printf("%.2X ", rcv[count]); count++; } printf("\r\n"); #endif /*if(rcv[_answr_flag_no_data] > 0) { #if(_scdeb == 1) printf("data not correct\r\n"); #endif return _clanswr_dataerr; } else { sz = (*(U16*)(&rcv[0])) - _addr_dev_MRTU; memcpy(RcvCmdPar, &rcv[_addr_dev_MRTU], sz); break; }*/ switch((int)(rcv[_answr_flag_no_data])) { case 0: //вс нормально sz = (*(U16*)(&rcv[0])) - _addr_dev_MRTU; memcpy(RcvCmdPar, &rcv[_addr_dev_MRTU], sz); break; case -4: //ошибка соответствия данных return _clanswr_dataerr; case -5: //данные не прчитаны return _clanswr_data_no_read; default: //неопределённая ошибка return _clanswr_unknownerr; } break; } //если всё нормально возвращаем флаг ModbusRTU res = rcv[_answr_flag_mrtu]; // return res; //0; } #endif Ранее... и сейчас применяется, в основе фиксированных инструкций вот так: Код (C++): #if(_client == 1) //команда 2 int C_MRTU_CMD_0x02(U8 AddrMRTU, //адрес устройства U16 AddrInp, //адрес входа U16 CntInp, //количество входов U8 * Rdat, //массив данных U8 Frame, //номер кадра U32 TimeWait, //время ожидания ответа от устройства U32 TimeLive, //время жизни пакета U32 TwSleep, //время сна после обмена U8 Flag //флаг запроса MRTU ) { int res; U8 cntbyte = 0; U8 rcnrbyte; //int paddr = 0; //U16 reg; U8 dsend[256] = {0}; U8 drecv[256] = {0}; // dsend[0] = AddrMRTU; //адрес устройства dsend[1] = 0x02; //команда dsend[2] = (U8)((AddrInp >> 8) & 0xFF); //адрес первого регистра dsend[3] = (U8)(AddrInp & 0xFF); // dsend[4] = (U8)((CntInp >> 8) & 0xFF); //количество регистров dsend[5] = (U8)(CntInp & 0xFF); // res = C_MRTU_CMD(&dsend[0], &drecv[0], 6, //размер запроса Frame, TimeWait, TimeLive, TwSleep, Flag ); if(res == _clanswr_ok) { rcnrbyte = drecv[0x02]; while(cntbyte < rcnrbyte) { *(Rdat + cntbyte) = drecv[0x03 + cntbyte]; cntbyte++; } } // return res; } #endif В документации наглая опечатка - заметьте что МЕЖСТРОЧНЫЕ ИНТЕРВАВЛЫ в документе верные... но вот блин сам документ от этого не стал верным. В данном случае в примере инструкция 2. Но по аналогии с инструкциями 3 и 4 по логическому содержанию идентичны. Испытания прошли успешно - вопрос снят! Отступление(пояснения): Имеющаяся так называемая библиотека разделена на серверную и клиентскую часть. Сервер по TCP соединению принимает инструкции и по RS485 осуществляет связь с устройствами в асинхронном режиме даже если клиент имеет редкое обращение к серверу. Клиент посылает необходимый запрос по TCP к серверу и получает данные. Особенность в том, что управляющие программы-клиенты могут обращаться каждая к своим устройствам на одной шине RS485, обращаясь к одному серверу. Эти клиенты могут работать на разных устройствах(ПК) в одной сети с сервером... ну или даже через PROXY(лично я использую SOCAT). Интересно тем, что датчики могут находиться в одной RS485, а исполнительные устройства в другой RS485 - соответственно 2 сервера ModbusRTUserverS даже на разных устройствах, но в одной сети TCP. Как пример датчики температуры в одном здании предприятия, а обогреватели и прочее в другом.