Срочный вопрос по modbus rtu

Тема в разделе "Флудилка", создана пользователем Igor68, 29 окт 2020.

  1. Igor68

    Igor68 Гуру

    Доброго времени суток!
    Сейчас я на удалёнке, потому как на удалёнке. Вдруг стало НАДО реализовать команду 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
    Вот это меня и вводит в ступор... при том, что команды вроде как идентичны.
    Сейчас реализую первую команду дистанционно. Будь я по месту проверил бы и снифером(сканером) и ноут напрямую подключил, и вопросы не задавал бы.
    Спасибо!
     
  2. Igor68

    Igor68 Гуру

    Мне бы было достаточно отчёта при обмене Master и Slave в байтах. Большего и не надо. Разумеется для инструкции №1.
    Спасибо!
     
  3. Igor68

    Igor68 Гуру

    Что-то не слышно! Ну да ладно сам разберусь.
    одним словом;
    На словах Вы Львы Толстые,
    А на деле Хрены Простые.

    Ух извините!
    Зато всякие MQTT и прочее Вам как семечки. Во дела.
    Здравия Вам большого!
    Простите!
     
  4. parovoZZ

    parovoZZ Гуру

    Это надо обратиться к @Igor68
    Он здесь единственный гуру по ModBus.
     
  5. ИгорьК

    ИгорьК Гуру

    Че позоришься? Здесь все знают, включая как жить правильно, два-три персонажа. Остальные - нормальные люди.
     
    Последнее редактирование: 29 окт 2020
    Igor68 и issaom нравится это.
  6. Igor68

    Igor68 Гуру

    вот спросишь один раз срочно... а тебе вместо ответа такое:confused:. Елки палки!!! Как спорить все!!!
    Как критиковать - ВСЕ!
    Как ответить - НИКОГО!
    Ну не делал я команды 1 - ранее не надо было... тут вдруг сказали что надо. И к железке доступ по VPN есть - надо сделать сервер сбора данных. И в этом гугле ссылки на может кривое описание этого modbus. Просто спросил уточнения - тишина. Как читаю - так толпа знатоков... перебивают друг друга...
    А ведь спросил:
    Ведь на удалёнке!!! Сделать могу... но быть на месте - НЕТ! И не принцип всего протокола, а только одной команды - казус в описании. Составить эту команду не проблема - но что с ответом от устройств??? По документации - ТУПОСТЬ!
    Вот и спросил.
     
  7. Igor68

    Igor68 Гуру

    Смутило то, что в нескольких документах один и тот же пример запросов и ответов байт в байт. И по видимому одна и та же опечатка (возможно и опечатка)
     
  8. Igor68

    Igor68 Гуру

    Серверная часть кода содержит реализацию инструкции 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. Как пример датчики температуры в одном здании предприятия, а обогреватели и прочее в другом.
     
    Последнее редактирование: 2 ноя 2020