Перейти к содержанию
    

С++ взаимодействие объектов

12 hours ago, const said:

Когда он должен его вызывать? Откуда он знает, что пришла очередная порция байт? Полинг? По моему, логичней объекту comport вызывать call-back ф-ию передавая в нее принятые данные. Так сказать, событийная модель.

Не совсем поллинг. Скорее чтение с блокировкой.

Тут возможны 2 модели обслуживания порта и протоколов - push и pull. В push модели инициатором процессов является источник данных - ком порт. Он собирает буфер и вызывает callback/виртуальный-метод/сигнал(в слот). Следующий уровень принимает данные, обрабатывает и отпалвет дальше (тем же способом). Это ваш подход.

В pull модели инициатором событий является приёмник. Он вызывает метод get нижнего уровня, который вызывает get дальше и так до самого низе (ком порта), где этот вызов блокируется до прибытия данных. В таком подходе всё это необходимо запускать в отдельной нити и результаты из неё передавать в GUI уже в push модели. Этот метод проще в реализации, т.к. каждый уровень сам определяет когда он собрал всю необходимую ему информацию и полностью управляет процессом её обработки. В push модели данные приходят на уровень так, как их собрал нижний уровень и текущему приходится к этому адаптироваться (он не может управляеть приёмом). Приходится либо делать уровни на основе машин состояния и/или применять промежуточную буферизацию.

Обе модели могут легко переходить друг в друга посредством потока и очереди (самописного кольцевого буфера или std::deque - если не хочется изобретать велосипед)

 

12 hours ago, const said:

Прием данных реализован через call-back ф-ию, которая в свою очередь передает всем «нуждающемся» объектам принятые байты через .rcv(...)

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

Либо формат у них должен быть один - но в таком случае это НЕ разные протоколы.

Либо в формате есть структура некоего контейнера, и протоколы извлекают оттуда свои данные (каждый из своего места). В таком случае у вас неправильно построе приём - у вас де факто 2х уровневая струкутра данных (контейнер + разные данные) и обрабатывать её должен 2х уровневый обработчик - разбор контейнера (1 протокол) и пачка протоколов нижнего уровня - туда протокол верхнего будет отправлять разобранные данные

12 hours ago, const said:

Для передачи используется кольцевой буфер объекта comport в который методом comport.send(…) помещаются данные и пинается поток передачи. Поток выгребает данные и передает их в физ. порт. Указатель на этот метод передан всем протокольным объектам, что бы они могли отправлять сообщения своими собственными методами. Так как протоколы находятся в одном потоке, они не перекрывают друг друга. Операции «положить-достать» с кольцевыми буферами атомарны, обеспечиваются реализацией.

Ну если вам так хочется устраивать закат солнца вручную, то можно и так. По сравненю с push моделью в отдельном потоке это КУЧА лишнего кода.

12 hours ago, const said:

Описание Comm покажите

 А где собственно поля protocol_1 и protocol_2 в которые вы пытаетесь писать из конструктора?

12 hours ago, const said:

Вся «синхронизация» обеспечивается очередями, см. выше.

Да, про 'закат солнца' я уже писал 🙂

12 hours ago, const said:

При обращении к объекту нет автоматической проверки его адреса на nullptr? Может проще обрабатывать исключение чем создавать заглушки?

Нет, Нет. Исключение - очень дорогая операция, и в С++ нет портабельного метода обрабатывать такие исключения (они не пересекаются с С++ exception'ами)

 

PS. callback и витруальная функция (как вам её предлагают) по сути одно и тоже. Только виртуальная функция - это OOP сущность, а свободная функция callback - нет. Кстати, метод класса (я надеюсь, что обрабтчики протоколов у вас классы, а не отдельные функии?) нельзя передать в качестве свободной callback функции - у них разные способы вызова.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

// Создаёт объект, через который будет осуществляться работа
// с последовательным портом. Может создаваться различная
// обвязка для удобства работы: очереди, семафоры и т.п.
UART_base
( 
	const char* name 
);

То есть, объект для работы с последовательным портом будет виден только в классе UART_base? И пока UART_base "не отпустит" последовательный порт, другие объекты с ним работать не смогут?

Я же хочу иметь объект последовательного порта к которому будут иметь доступ несколько экземпляров разных классов. Отсюда вопрос, как это лучше реализовать? Или я неправильно понял вашу реализацию и в ней com порт можно "делить" между другими объектами?

On 4/28/2023 at 12:13 PM, xvr said:

В push модели инициатором процессов является источник данных - ком порт. Он собирает буфер и вызывает callback/виртуальный-метод/сигнал(в слот). Следующий уровень принимает данные, обрабатывает и отпалвет дальше (тем же способом). Это ваш подход.

Немного вас поправлю. У меня, если рассматривать пару "comport obj - protocol obj", порт является как инициатором, при приеме данных, так и подчиненным - при передаче. Как и объект протокола.

On 4/28/2023 at 12:13 PM, xvr said:

Этот метод проще в реализации, т.к. каждый уровень сам определяет когда он собрал всю необходимую ему информацию и полностью управляет процессом её обработки.

А у меня разве не сам решает когда собралось сообщение или несколько? Сам. И если такие сообщения имеются передает их в очередь на обработку, на уровень выше.

Если быть точней, это происходит в call-back, например:

void rcv_cb(u8 * data, u16 sz) // ф-ция вызывается объектом comport
{
  protocol_1.rcv(data, sz);
  ...
  protocol_x.rcv(data, sz);

  // и тут же проверяем не собралось ли сообщение (а может и не одно) из принятых байт
  
  msg_t msg;
  
  // если новые сообщения есть - ложим их в очередь и будем поток которому они предназначались
  if (protocol_1.get_msg(&msg)) { 
    queue.put(&msg);
    while(protocol_1.get_msg(&msg)) queue.put(&msg);
    thrd.resume();
  }
  ...
}

 

On 4/28/2023 at 12:13 PM, xvr said:

В push модели данные приходят на уровень так, как их собрал нижний уровень и текущему приходится к этому адаптироваться (он не может управляеть приёмом).

Что значит "адаптироватся" - разгребать поток байт и собирать сообщения? Так он для этого и нужен.

On 4/28/2023 at 12:13 PM, xvr said:

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

Либо формат у них должен быть один - но в таком случае это НЕ разные протоколы.

Мои протоколы не то что незнают о формате друг друга, они не знаю о существовании друг друга.

У меня по одному каналу связи (UART) осуществляют обмен два разных протокола:

1) SLIP-подобный; 2) кем то когда то придуманный (не мной).

Я понимаю, что правильней было бы сделать по другому. Но раз протоколы справляются со своей задачей, откидывая данные друг друга как мусор (по формату, crc, структуре сообщений), то и так сойдет 😄. Для справки: была программа для МК, не моя, выполнявшая некий функционал, который надо было расширить. Я особо не вникая и не меняя старое ПО (кроме работы с UART) добавил свой программный модуль, так же ранее написанный и использующий из ресурсов только CAN и UART (все тот же). Так же поступил в ПО для ПК, обернув в объекты все что касается протоколов. Все работает (CBuilder6), нареканий нет.

On 4/28/2023 at 12:13 PM, xvr said:

Ну если вам так хочется устраивать закат солнца вручную, то можно и так. По сравненю с push моделью в отдельном потоке это КУЧА лишнего кода.

Ну прям куча. Кольцевой буфер и все, даже не кучка.

On 4/28/2023 at 12:13 PM, xvr said:

 А где собственно поля protocol_1 и protocol_2 в которые вы пытаетесь писать из конструктора?

protocol_1 == Slip. Второй еще не добавил. Вот мучаюсь вопросом как это лучше реализовать. По приему пошел бы по старому пути, как в приложенном примере - call-back, а вот по передаче вопрос. Так же хотелось бы по старому - передать указатель на метод передачи comport.send(...) объекту Slip, но не выходит. Напомню, в старом ПО (CBuilder6) объекты протоколов и порта объявлены вне методов comm и там такой проблемы не возникает.

On 4/28/2023 at 12:13 PM, xvr said:

Да, про 'закат солнца' я уже писал 🙂

если "закат солнца" так легко делать вручную, то почему бы и нет - по будильнику только, что б не забыть 🙃

On 4/28/2023 at 12:13 PM, xvr said:

Нет, Нет. Исключение - очень дорогая операция, и в С++ нет портабельного метода обрабатывать такие исключения (они не пересекаются с С++ exception'ами)

значит "заглушки"

Пока для себя нашел только одно красивое решение (в QT): все кто отправляет данные посылают сигналы слоту порта, а по приему, что позволит уйти от call-back, порт сигнализирует всем слотам принимающих протоколов. Хотелось бы еще найти решение на чистом С++ 😶

Изменено пользователем const

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, const said:

Я же хочу иметь объект последовательного порта к которому будут иметь доступ несколько экземпляров разных классов.

Для чего это нужно? По одному порту идёт обмен по нескольким протоколам? Извращение форменное. Или каждый протокол должен иметь свой идентификатор а-ля семейство протоколов Эзернет-стека? Тогда возьмите SLIP и не мучайтесь

 

В методе консольного ввода-вывода есть метод peekChar() как раз для возможности подсматривать что пришло, но не читать, а вызывать нужный метод чтения.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, tonyk_av сказал:

По одному порту идёт обмен по нескольким протоколам?

Обычное дело. Не одновременно конечно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

57 minutes ago, jcxz said:

Обычное дело. Не одновременно конечно.

Убивать за такое надо.

Характерно для российских погромистов забубенить в свой девайс свой протокол, а потом приходится под каждый протокол линию связи кидать, иначе невозможно вести статистику работы устройств в части ошибок обмена, ибо не понятно, что стало причиной ошибки, то ли приход чужого запроса, то ли реальный сбой работы сети.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

38 minutes ago, x893 said:

Кажадая кухарка может быть гуру программирования.

Ключевое слово "может".

Я без мата говорить об этом не могу, но бля"ть, на XYя для передачи ТРЁХ байт на фиксированных 9600 городить свой протокол?! И это сплошь и рядом.

О! Знаю какая! Та, которая сделала это:

 

image.png

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

25 минут назад, tonyk_av сказал:

О! Знаю какая! Та, которая сделала это:

курит - Создать мем - Meme-arsenal.com


Производители ВЧ-трансиверов с АТ и бинарным режимом обмена, например.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

8 hours ago, const said:

А у меня разве не сам решает когда собралось сообщение или несколько? Сам.

Сам, собирает из кусочков. А мог бы получать сразу полное сообщение.

8 hours ago, const said:

Но раз протоколы справляются со своей задачей, откидывая данные друг друга как мусор (по формату, crc, структуре сообщений), то и так сойдет 😄.

Это черевато. 'Мусор' от соседнего протокола может поломать другой протокол. Не стоит насиловать средства обнаружения и коррекции ошибок протоколов заливая им широким потоком мусор, в надежде что они его отфильтруют 🙂

8 hours ago, const said:

Кольцевой буфер и все, даже не кучка.

У Qt эти буфера уже есть и встроенны в connect систему. Если вам очень хочется добавить ещё один - свой, то вам этого конечно никто запретить не может.

8 hours ago, const said:

Пока для себя нашел только одно красивое решение (в QT): все кто отправляет данные посылают сигналы слоту порта, а по приему, что позволит уйти от call-back, порт сигнализирует всем слотам принимающих протоколов.

Вполне для Qt решение.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 часов назад, tonyk_av сказал:

Я без мата говорить об этом не могу, но бля"ть, на XYя для передачи ТРЁХ байт на фиксированных 9600 городить свой протокол?! И это сплошь и рядом.

Может стоить попить водички и хоть немного прийти в себя?  

 

А когда придёте в себя, советую почитать что такое "УСПД" и зачем они умеют работать по разным протоколам. Кроме них есть некоторые интеллектуальные счётчики. Кроме них есть различные модули (BT, WiFi и др.) умеющие менять протокол обмена. Например хотя-бы BT-модули Silicon Labs позволяют менять протокол обмена по своему UART.

7 часов назад, tonyk_av сказал:

Характерно для российских погромистов

Надеюсь - Silicon Labs для вас достаточно нероссийский?

 

И наверняка есть ещё куча других устройств, умеющих мультипротокольность.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

6 hours ago, jcxz said:

Может стоить попить водички и хоть немного прийти в себя?  

А я и не выходил.

6 hours ago, jcxz said:

И наверняка есть ещё куча других устройств, умеющих мультипротокольность.

Есть, и что с того? Речь шла о

Quote

Программа на ПК обменивается через COM-PORT с МК и отображает его

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 4/29/2023 at 5:14 PM, tonyk_av said:

Для чего это нужно?

для кого. Для форменных извращенцев 😶

On 4/29/2023 at 7:06 PM, jcxz said:

Не одновременно конечно.

бывает и одновременно 🤪

On 4/29/2023 at 8:07 PM, tonyk_av said:

Убивать за такое надо.

не надо

22 hours ago, tonyk_av said:

на XYя для передачи ТРЁХ байт на фиксированных 9600 городить свой протокол?

посоветуйте, пожалуйста, протокол для UART, какието распространенные, кроме SLIP и COBS. Только без фиксации 9600.

20 hours ago, xvr said:

Это черевато. 'Мусор' от соседнего протокола может поломать другой протокол. Не стоит насиловать средства обнаружения и коррекции ошибок протоколов заливая им широким потоком мусор, в надежде что они его отфильтруют 🙂

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

 

Первый вариант решения моей задачи приводил ранее:

On 4/29/2023 at 3:07 PM, const said:

одно красивое решение (в QT): все кто отправляет данные посылают сигналы слоту порта, а по приему, что позволит уйти от call-back, порт сигнализирует всем слотам принимающих протоколов. Хотелось бы еще найти решение на чистом С++ 😶

А теперь второй, на "чистом" с++:

class Communication : public QObject
{
public:
    Communication(QObject *parent = nullptr) : QObject(parent) { ; }
    virtual size_t send(uint8_t * data, size_t sz);
    virtual size_t recv(uint8_t * data, size_t sz);
};

class ComPort : public Communication
{
public:
    ComPort(Communication * p = nullptr) : Communication() {
        comm = p;
        sp = new QSerialPort(this);
    }

    bool open(const QString & name) {
        sp->setPortName(name);
        auto isOpen = sp->open(QIODevice::ReadWrite);
        if (isOpen) QObject::connect(sp, &QSerialPort::readyRead, this, &ComPort::sp_readyRead);
        return isOpen;
    }

    size_t send(uint8_t * data, size_t sz) override { return sp->write((char*)data, sz); } // ф-ия передачи данных класса Communication

private:
    QSerialPort   * sp;
    Communication * comm;

private slots:
    void sp_readyRead() {
        auto a = sp->readAll();
        if (comm != nullptr) comm->recv((uint8_t*)a.data(), a.size()); // тут вызывается аналог call-back, который я использовал в старой программе, по событию приема comport
    }
};

class Protocol
{
public:
    typedef struct { uint8_t type; uint8_t cmd; uint16_t crc; uint8_t data[8]; } msg_t;
    Protocol(Communication * p = nullptr) { comm = p; }
    void rcv(uint8_t * data, size_t sz);
    bool getMsg(msg_t * msg);
    bool putMsg(msg_t * msg);

private:
    Communication * comm;
    void send(uint8_t * data, size_t sz) { if (comm != nullptr) comm->send(data, sz); } // ф-ия используется для отправки данных в comport в формате protocol (используется в недрах класса)
};

class Comm : public Communication // класс взаимодействия UI с внешним миром
{
public:
    Comm() : Communication() {
        cp = new ComPort(this);
        pr = new Protocol(cp);
    }

    size_t recv(uint8_t * data, size_t sz) override { // ф-ия (call-back) вызывается объектом *cp при приеме данных
        pr->rcv(data, sz);
        //...
        return 0;
    }

    bool sendMsg(Protocol::msg_t * msg) { return pr->putMsg(msg); } // отправка сообщений в формете нужного протокола (UI ПО работает только с объектом Comm)

private:
    ComPort  * cp;
    Protocol * pr;
};

Какой вариант буду использовать покажет время. С изучением С++, думаю, их количество увеличится.

Всем спасибо за участие.

Изменено пользователем const

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

41 минуту назад, const сказал:
typedef struct {...

typedef писать в этом случае не нужно, поскольку здесь уже и так будет пользовательский тип, если записать так:

struct Msg {
  uinnt8_t type;
  ...
  ...
  }

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, const said:

А теперь второй, на "чистом" с++:

Ну он как бы не совсем 'чистый' - там используется Qt класс Comm порта вместе с его особенностями подключения - connect и слот. И без Qt это работать не будет. 

Поэтому ценность С++ версии под вопросом - её 'чистота' была нарушена 🙂

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

14 minutes ago, xvr said:

Поэтому ценность С++ версии под вопросом - её 'чистота' была нарушена 🙂

а куда от этого деться если объекты работы с comport в разных фраемворках отличаются?

вот CBuilder6, участок кода потока объекта comport:

...
while(1) {

    WaitCommEvent(cp->com_hndl, &mask, &cp->com_rd_overlppd);
    signal = WaitForSingleObject(cp->com_rd_overlppd.hEvent, INFINITE);

    if(signal == WAIT_OBJECT_0
    && GetOverlappedResult(cp->com_hndl, &cp->com_rd_overlppd, &tmp, true)) {
      if((mask & EV_RXCHAR) != 0) {
        ClearCommError(cp->com_hndl, &tmp, &comstat);
        len = comstat.cbInQue; 
        if(len) {
          ReadFile(cp->com_hndl, cp->buf_rd_thr, len, &tmp, &cp->com_rd_overlppd);
          if(cp->rd_cb_ptrf) cp->rd_cb_ptrf(cp->buf_rd_thr, len); // в старой версии ПО (на CBuilder6) - вызов call-back
        }
      }
    }
  }

в новом подходе это выглядело бы так:

...
while(1) {

    WaitCommEvent(cp->com_hndl, &mask, &cp->com_rd_overlppd);
    signal = WaitForSingleObject(cp->com_rd_overlppd.hEvent, INFINITE);

    if(signal == WAIT_OBJECT_0
    && GetOverlappedResult(cp->com_hndl, &cp->com_rd_overlppd, &tmp, true)) {
      if((mask & EV_RXCHAR) != 0) {
        ClearCommError(cp->com_hndl, &tmp, &comstat);
        len = comstat.cbInQue; 
        if(len) {
          ReadFile(cp->com_hndl, cp->buf_rd_thr, len, &tmp, &cp->com_rd_overlppd);
          if(comm != nullptr) comm->recv(cp->buf_rd_thr, len); // << изменения тут
        }
      }
    }
  }

Всякого рода "прерывания" нестандартны. Не вижу ничего страшного. Одна и та же строчка среди фраемворк-зависимого кода.

Так что "чистота" максимально возможная. Для меня во всяком случае.

Вся моя проблема заключалась в непонимании того, что int AnyClass::foo(u8 * data, u16 sz) != int foo(u8 * data, u16 sz) и метод одного объекта не передать по указателю ф-ии в другой объект.

Ну не вся, еще какието пробелы. Разобрался с вопросом.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...