Russian Qt Forum

Qt => Общие вопросы => Тема начата: Sphynx от Июль 25, 2016, 15:50



Название: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 15:50
Доброго дня. Проблема такая: приложение периодически по таймеру отправляет запрос и читает ответ от устройства подключенного по ком-порту используя QtSerialPort. Для приема сообщений написана отдельная функция read_msg(). Чтобы приложение на зависало при ожидании сообщения используется еще один таймер-таймаут при срабатывании которого функция сообщит что данные с порта не пришли. А чтобы работал этот таймер я вставил в цикл функции QApplication::processEvents();. Все работало нормально, но понадобилось отправлять устройству не только запросы по таймеру, но еще и по нажатию кнопок на форме. И происходит вот что: запрос по таймеру вызывает функцию read_msg(), функция запускает QApplication::processEvents(); и если в этот момент произойдет нажатие на кнопку, то произойдет еще один запрос устройству, и еще один вызов read_msg() и нарушается порядок чтения ответов. Поставил внутри функции read_msg() флаг занятости который сбрасывается после получения ответа, а в самом начале:
Код
C++ (Qt)
   while(ack==false)
   {//ERROR !!!
       Sleep(1);
       //QApplication::processEvents();
   }
 
Если остается Sleep, то приложение зависает намертво, если QApplication::processEvents(); , то перестают работать запросы по таймеру, видимо происходит какое-то переполнение в недрах самого qt.
Я так понимаю что запросы лезут с разных потоков, но не понятно как дать нормально отработать тому кто первый обратился к функции
read_msg()


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 25, 2016, 16:35
1 поток на чтение/запись, отправка приём через сигнал-слотовые соединения. И никаких проблем.
Хотя тут уже от вашего протокола зависит )


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 16:57
Не понятно как это решит проблему. Ведь функция read_msg() видоизменится, но все-равно останется. И значит она так-же может быть вызвана одновременно несколько раз. Все таки должен быть какой-то механизм принудительного переключения потоков. И пока один поток "держит" функцию, другие должны просто подождать.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 17:00
Не понятно как это решит проблему. Ведь функция read_msg() видоизменится, но все-равно останется. И значит она так-же может быть вызвана одновременно несколько раз. Все таки должен быть какой-то механизм принудительного переключения потоков. И пока один поток "держит" функцию, другие должны просто подождать.
Не нужны здесь несколько потоков. QSerialPort умеет работать асинхронно.
Организуйте очередь запросов и не посылайте новый запрос в железку, пока не получите ответ или не сработает timeout на старый. Все это будет спокойно асинхронно работать даже в GUI-потоке.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 17:07
Да, это решит проблему, но передача тогда будет по сути полудуплексной, т.е. устройство на другой стороне уже будет готово принять новую команду для обработки, но придется ждать пока не будет получен ответ от предыдущей.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 17:09
Да, это решит проблему, но передача тогда будет по сути полудуплексной, т.е. устройство на другой стороне уже будет готово принять новую команду для обработки, но придется ждать пока не будет получен ответ от предыдущей.
Ну так вы же этого и добиваетесь?

то произойдет еще один запрос устройству, и еще один вызов read_msg() и нарушается порядок чтения ответов.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 17:26
Сейчас попытаюсь объяснить. Вот допустим периодически по таймеру мы просим устройство - скажи параметры 1 2, устройство честно отвечает и скидывает их в свой буфер для передачи и потихоньку отправляет по каналу, ожидая новых запросов. В программе на ПК мы вызываем read_msg() и ждем пока не получим весь ответ. Но вот пользователь нажал на кнопку и из другого потока передал устройству новую команду - скажи параметры 3 4 и соответственно снова вызывается read_msg() в ожидании ответа. В буфере у нас очевидно окажется п1 п2 п3 п4, и необходимо придержать второй поток пока первый поток не заберет свои п1 п2. Так вот как дать первому потоку это сделать ? Как на него переключиться принудительно.


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 25, 2016, 17:50
Вот тут как раз уже нужен ваш протокол.
1) можно просто отсылать все ответы всем потокам (сигнал прицепить, а там пусть разбирается кто и что отправлял).
2) можно написать менеджер-надстройку, который будет контролировать запросы, ответы, кто что запросил и кому что отправлять.
Остальные варианты экзотичны или безумны.

Если у вас поток данных небольшой, вы можете спокойно сделать по п.1.
Если же вам нужна конкретика что/кто запросил, что/кто получил с контролем ошибок - пишите менеджер.

update: немного вам упрощу на вашем же примере.
По п.1
В слоте readyRead вы забираете данные и отправляете всем потокам пришедшие параметры (1,2,3,4)
Поток 1 смотрит в список запросов, сравнивает там параметр 1,2 и работает с ними. Параметры 3,4 игнорирует, ибо не запрашивал.
Поток 2 аналогично, только игнорирует 1,2.

По п.2
У вас имеется менеджер, куда вы передаёте информация
Поток 1 запросил параметры 1,2
Поток 2 запросил параметры 3,4.
Менеджер принимает данные, отправляет их согласно списку - 1,2 первому потоку, 3,4 второму.



Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 18:11
Так вот как дать первому потоку это сделать ? Как на него переключиться принудительно.
Вот поэтому и нужно вести работу с железкой в одном потоке.
В одной нитке идет взаимодействие с устройством, а из других ниток можно ставить задания (запросы) в очередь. А вот в задании можно хранить, кому нужно возвращать результат.


Название: Re: Как разрулить потоки
Отправлено: kuzulis от Июль 25, 2016, 18:52
А я так и не распарсил.. а в чем, собственно, проблема использовать асинхронно (в фулл-дуплексе) в одном потоке?

Например:

Код
C++ (Qt)
void Foo::Foo()
{
   // шлем запросы по таймеру
   connect(timer, &QTimer::timeout, [this]() {
       const QByteArray request1 = ....;
       serial->write(request1);
   });
 
   // шлем запросы по кнопке 1
   connect(button1, &QPushButton::clicked, [this]() {
       const QByteArray request2 = ....;
       serial->write(request2);
   });
 
   // шлем запросы по кнопке N
   connect(buttonN, &QPushButton::clicked, [this]() {
       const QByteArray requestN = ....;
       serial->write(requestN2);
   });
 
   // парсим ответы
   connect(serial, &QSerialPort::readyRead, [this]() {
       // парсим входной непрерывный стрим из порта и разбираем его на ответы
       for (;;) {
           const qint64 available = serial->bytesAvailable();
           if (available < expected)
               return; // данных недостаточно, выходим до следующего readyRead.
 
           enum { StartByteIndex = ..., StopByteIndex = ..., };
           enum { StartByte = 0xAA, StopByte = 0x55, };
 
           const QByteArray peeked = serial->peek(expected); // считываем копию пакета и пытаемся его проверить
 
           // проверяем стартовый байт / поле заголовка (если есть)
           if (peeked.at(StartByteIndex) != StartByte) {
               dropBytes(1); // убираем навсегда первый символ из буфера порта, пытаемся снова синхронизироваться
               continue;
           }
 
           // проверяем стоповый байт / поле хвоста (если есть)
           if (peeked.at(StopByteIndex) != StopByte) {
               dropBytes(1); // убираем навсегда первый символ из буфера порта, пытаемся снова синхронизироваться
               continue;
           }
 
           // проверяем поле длины (если есть)
           if (peeked.at(LengthIndex) != Length) {
               dropBytes(1); // убираем навсегда первый символ из буфера порта, пытаемся снова синхронизироваться
               continue;
           }
 
           // проверяем CRC (если есть)
           if (peeked.at(CrcIndex) != CRC) {
               dropBytes(1); // убираем навсегда первый символ из буфера порта, пытаемся снова синхронизироваться
               continue;
           }
 
           // все OK, пакет годный..
 
          dropBytes(expected); // убираем весь пакет из буфера порта навсегда, он не нужен, т.к. у нас есть копия peeked
 
           // извлекаем тело с данными из peeked
 
           // чо-то с делаем с телом, обновляем UI и прочее вещи..
 
       }
 
   });
}
 
void Foo::dropBytes(qint64 bytesCount)
{
   serial->read(bytesCount); // dummy read
}
 


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 19:08
// шлем запросы по кнопке 1
    connect(button1, &QPushButton::clicked, [this]() {
        const QByteArray request2 = ....;
        serial->write(request2);
    });
Послать то данные не проблема, а вот как вы теперь получите ответ после нажатия на кнопку ?


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 19:17
У меня протокол очень простой, никаких маркеров чтоб узнать на какой запрос пришел ответ нет. Только в порядке очереди.
Мне так-же неясно почему такой код зависает, и ack не становится true, если вызывать функцию по нажатию на кнопку. Просто по таймеру все работает. Что вообще происходит по команде Sleep.
Код
C++ (Qt)
while(ack==false)
   {
       Sleep(1);
   }
ack=false;
//Запускаем таймер таймаута
//Тут читаем данные с порта
while(Не все прочитали)
   {
       QApplication::processEvents();
   }
//Тут мы все прочитали или сработал таймер
ack=true;
return
 


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 19:24
Что вообще происходит по команде Sleep.
Текущий поток засыпает.
А как может ack измениться, если пулинг последовательного порта не происходит?

Создайте очередь и работайте асинхронно в одном потоке. Отправили запрос - ждете сигнала readyRead или timeout - обрабатывайте результат - отправляйте следующий запрос из очереди - ...


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 19:39
Моя логика такая: если мы попали в функцию с ack=falsе, значит мы попали в нее из другого потока. Выполняя команду Sleep мы выжигаем процессорное время текущего потока, и по идее должно произойти переключение на следующий поток по очереди. И так до тех пор пока установивший ack=false поток не закончит свою работу.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 19:42
Моя логика такая: если мы попали в функцию с ack=falsе, значит мы попали в нее из другого потока. Выполняя команду Sleep мы выжигаем процессорное время текущего потока, и по идее должно произойти переключение на следующий поток по очереди. И так до тех пор пока установивший ack=false поток не закончит свою работу.
QSerialPort работает в том потоке, в котором был создан его объект, и "работа происходит" при работе цикла обработки событий этого потока. Если этот цикл не крутиться, то ничего происходить не будет.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 19:46
Ну вот собственно вы тоже пришли к такому-же выводу, что нужно дать поработать другим потокам. так вот как это сделать ?


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 19:48
Ну вот собственно вы тоже пришли к такому-же выводу, что нужно дать поработать другим потокам. так вот как это сделать ?
Стоп. :)
Все что вы хотите сделать легко делается в одном потоке с GUI. Все делается асинхронно - на сигналах, главное нигде не делать sleep, что-бы неблокировать цикл обработки событий.


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 25, 2016, 19:49
Да создайте вы поток, в нём создайте порт и вытяните из него:
1) сигнал readyRead(QByteArray). (подцепиться к сигналу порта readyRead, в приватном слоте вытаскивать QByteArray данные и отправлять данным сигналам)
2) слот send(QByteArray). (Пихать полученный массив в порт)
А потом спокойно работайте.

Запрашивать через send, ответы получать через readyRead.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 20:03
Я не особо программист, и это моя первая программа на С++, раньше писал только на асме для мк.
Мне кажется я так и сделал как вы написали. Расскажу чуть подробней.
Создал класс MCU через который предполагается вести весь обмен с устройством.
Реализовал функции Send() и Read().
С интервалом по таймеру:
Код
C++ (Qt)
mcu->send("команда");
if (mcu->read()==m_ok)
   {
   //тут вывожу результат
   }
 
Так вот по таймеру все работает. Но сделал тоже самое по нажатию на кнопку и обнаружил что mcu->read() вызывается дважды, что портит правильный прием данных.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 20:09
Я не особо программист, и это моя первая программа на С++, раньше писал только на асме для мк.
Мне кажется я так и сделал как вы написали. Расскажу чуть подробней.
Создал класс MCU через который предполагается вести весь обмен с устройством.
Реализовал функции Send() и Read().
С интервалом по таймеру:
mcu->send("команда");
if (mcu->read()==m_ok)
    {

    }
Если вы хотите работать синхронно, то воспользуйтесь методом:
bool QSerialPort::waitForReadyRead(int msecs)
для ожидания появления данных от устройства. Этот метод ждет указанный интервал времени и возвращает true, если данные пришли (и их можно читать) или false - если время истекло.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 20:14
Ожидание данных реализовано внутри функции Read, и если данные не придут функция вернет m_timeout.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 20:20
Ожидание данных реализовано внутри функции Read, и если данные не придут функция вернет m_timeout.
При синхронном варианте нельзя нормально организовать отсылку запросов как от таймера, так и от кнопки GUI.
Отправляйт запрос и ждите сигнала readyRead, а в слоте обработчике читайте ответы.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 20:26
Тогда это портит всю картину ) Получается перед каждым вызовом mcu->Read я должен проверять какие-то флаги готовности. По идее это все должно происходить внутри самой mcu->Read, иначе какой тогда смысл от всего этого бреда с ооп и классами )


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 25, 2016, 20:28
Этот бред придуман для асинхронности и прочих полезных плюшек. А Qt добавляет отсутствие головной боли и синхронизации потоков.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 20:31
Тогда это портит всю картину ) Получается перед каждым вызовом mcu->Read я должен проверять какие-то флаги готовности. По идее это все должно происходить внутри самой mcu->Read, иначе какой тогда смысл от всего этого бреда с ооп и классами )
Да не нужно ничего проверять. Вот kuzulis привел пример кода.
Если у вас устройство может не вернуть ответ на некоторые запросы, то достаточно хранить очередь запросов и проверять пришел на него ответ или сработал таймаут. Это все хорошо прячется в классе устройства (то самое ООП). :)


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 20:47
В приведенном примере внутри принимаемого пакета должна хранится информация о запросе, иначе не разобрать на что это был ответ. У меня этого нет, т.е. на отправленную команду может прийти просто "ОК", поэтому нужно следить за последовательностью сообщений.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 20:51
В приведенном примере внутри принимаемого пакета должна хранится информация о запросе, иначе не разобрать на что это был ответ. У меня этого нет, т.е. на отправленную команду может прийти просто "ОК", поэтому нужно следить за последовательностью сообщений.
Да, очередь запросов позволит это легко отслеживать. Вместо прямой отправки запроса в устройство, он помещается в очередь. Далее запросы отправляются из очереди, после отправки запускается таймер таймаута и ждем, что придет раньше. Если сигнал readyRead читаем ответ, если таймаут - сообщаем об ошибке. Дальше удаляем из очереди текущий запрос и переходим к следующему.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 21:13
Хорошо, допустим я сделал очередь из запросов. Вот сработал таймер и отправил свой запрос в очередь внутри класса mcu. Теперь нам нужно из класса mcu достать полученный ответ, какой функцией ? Я вызываю mcu->Read(), которая сама читает порт, отслеживает таймауты и следит за ошибками. Но вот пользователь нажимает кнопку, снова отправляется запрос в очередь и нам опять нужно доставать ответ mcu->Read(). Функция все-равно может быть вызвана дважды, очередь запросов не решает проблему.


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 25, 2016, 21:18
Вы немного не догоняете механики. Он не отправляет запрос, а помещает его в очередь. А там уже менеджер, выждав таймаут ответа от текущего(или получив ответ), отправит его следующим.

Вы держитесь за свою ущербную схему с таймером. А мы предлагаем вам перейти на асинхронную работу по сигналам.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 21:29
Хорошо, допустим я сделал очередь из запросов. Вот сработал таймер и отправил свой запрос в очередь внутри класса mcu. Теперь нам нужно из класса mcu достать полученный ответ, какой функцией ? Я вызываю mcu->Read(), которая сама читает порт, отслеживает таймауты и следит за ошибками. Но вот пользователь нажимает кнопку, снова отправляется запрос в очередь и нам опять нужно доставать ответ mcu->Read(). Функция все-равно может быть вызвана дважды, очередь запросов не решает проблему.
Если завтра минутка появится набросаю я вам рабочий примерчик.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 25, 2016, 21:33
Я даже и не спорю, я действительно не понимаю механики. Можете привести пример ?
Сейчас общение с устройством происходит так, хоть по таймеру хоть по кнопке:
Код
C++ (Qt)
mcu->Send("Команда");
if(mcu->Read()=m_Ok);//Получаем ответ
{
   //Команда прошла успешно
}
 
Как будет происходить общение в вашем случае ? Подробностей не надо, просто хотя-бы структуру вместо моей текущей.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 25, 2016, 22:17
Код
C++ (Qt)
#ifndef MCU_H
#define MCU_H
 
#include <QObject>
 
class QSerialPort;
class QTimer;
 
class MCU : public QObject
{
Q_OBJECT
public:
explicit MCU( QObject *parent = nullptr );
 
void addRequest( int val );
 
private slots:
void nextRequest();
void readyData();
void timeout();
 
private:
QSerialPort *m_port;
QTimer *m_timer;
 
using RequestQueue = QList<int>;
RequestQueue m_requests;
};
 
#endif // MCU_H
 

Код
C++ (Qt)
#include "mcu.h"
#include <QSerialPort>
#include <QTimer>
 
MCU::MCU( QObject *parent ) :
QObject( parent ),
m_port( new QSerialPort( this ) ),
m_timer( new QTimer( this ) )
{
// Настраиваем порт
connect( m_port, SIGNAL(readyRead()), SLOT(readyData()) );
 
// Настраиваем таймер
connect( m_timer, SIGNAL(timeout()), SLOT(timeout()) );
m_timer->setSingleShot( true );
m_timer->setInterval( 250 );
}
 
void MCU::addRequest( int val )
{
// Добавляем запрос в очередь
m_requests.append( val );
nextRequest();
}
 
void MCU::nextRequest()
{
if( m_requests.isEmpty() )
return;
 
// Получаем запрос но оставляем его в очереди
int request = m_requests.first();
 
QByteArray data;
m_port->write( data ); // Отправили запрос
m_timer->start(); // Запустили таймер ожидания ответа
// Теперь сработает один из сигналов: пришли данные (readyRead)
// или сработал таймер (timeout)
}
 
void MCU::readyData()
{
// Пришли данные от устройства
 
// Останавливем таймер
m_timer->stop();
 
// Читаем ответ из порта
QByteArray result = m_port->readAll();
 
// В очереди есть запрос
if( m_requests.isEmpty() )
{
// Получаем запрос на который пришел ответ и удаляем его из очереди
int request = m_requests.takeFirst();
 
// Обрабатываем ответ
// ...
 
// Переходим к следующему запросу
nextRequest();
}
}
 
void MCU::timeout()
{
// Ответ не пришел во время
 
// В очереди есть запрос
if( m_requests.isEmpty() )
{
// Получаем запрос на который пришел ответ и удаляем его из очереди
int request = m_requests.takeFirst();
 
// Обрабатываем ошибку по таймауту
// ...
 
// Переходим к следующему запросу
nextRequest();
}
}
 


Название: Re: Как разрулить потоки
Отправлено: kuzulis от Июль 25, 2016, 22:26
Код
C++ (Qt)
 
class Foo : public QObject
{
public:
   Foo() {
       // конфигурим сторожа
       watchdog->setSingleShot(true);
       watchdog->setInterval(100);
       connect(watchdog, &QTimer::timeout, [this]() {
           if (serial->bytesAvailable() == 0) {
               // это ошибка по таймауту, у-во вообще ничо не ответило
           } else {
               // извлекаем весь ответ, т.к. он будет полным
               const QByteArray response = serial->readAll();
 
               // что-то делаем с ответом, парсим его и пр.
          }
 
           sendNextRequest(); // шлем следующий запрос из очереди
       });
 
       // шлем запросы по таймеру
       connect(timer, &QTimer::timeout, [this]() {
           const QByteArray request1 = ....;
           appendRequest(request1);
       });
 
       // шлем запросы по кнопке 1
       connect(button1, &QPushButton::clicked, [this]() {
           const QByteArray request2 = ....;
           appendRequest(request2);
       });
 
       // шлем запросы по кнопке N
       connect(buttonN, &QPushButton::clicked, [this]() {
           const QByteArray requestN = ....;
           appendRequest(request3);
       });
 
      // перезапускаем сторожа чтобы он не срабатывал, т.к. когда он сработает это будет
      // означать или таймаут или весь пакет принят
      connect(serial, &QSerialPort::readyRead, [this]() {
          watchdog->start();
      }
 
private:
   void appendRequest(const QByteArray &request) {
       pendingRequests << request;
 
       sendNextRequest();
   }
 
   void sendNextRequest() {
       if (watchdog->isActive() || pendingRequests.isEmpty())
           return;
 
       const QByteArray request = pendingRequests.takeFirst();
       serial->write(request);
       watchdog->start();
   }
 
   QList<QByteArray> pendingRequests;
   QSerialPort *serial;
   QTimer *watchdog;
}
 
 

ооо.. я опоздал  :)

но у меня чуть по-другому  ;)


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 05:11
Спасибо, попробую разобраться. Только уже не понятно как будет происходить запрос-ответ, допустим по нажатию кнопки. В примере от Old по нажатию на кнопку я вызываю MCU::addRequest(), но обработка ответа происходит внутри MCU::readyData(), мне не ясно как "кнопка" узнает прошла команда или нет. От kuzulis тоже не понимаю, запрос идет через слот, а как реализована обратная связь ?


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 26, 2016, 05:19
Спасибо, попробую разобраться. Только уже не понятно как будет происходить запрос-ответ, допустим по нажатию кнопки. В примере от Old по нажатию на кнопку я вызываю MCU::addRequest(), но обработка ответа происходит внутри MCU::readyData(), мне не ясно как "кнопка" узнает прошла команда или нет. От kuzulis тоже не понимаю, запрос идет через слот, а как реализована обратная связь ?
Вы можете в класс MCU добавить свои сигналы и имитеть их при получении ответа или срабатывании таймаута, тогда вызывающая сторона сможет узнать, что происходит.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 05:30
Вот допустим по нажатию на кнопку1 я отправляю запрос, устройство возвращает мне число которое я отображаю на лебеле1. По нажатию кнопки2 на лабеле2, и т.д. Неужели внутри независимого класса придется создавать кучу слотов под конкретную задачу ?


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 26, 2016, 05:36
Вот допустим по нажатию на кнопку1 я отправляю запрос, устройство возвращает мне число которое я отображаю на лебеле1. По нажатию кнопки2 на лабеле2, и т.д. Неужели внутри независимого класса придется создавать кучу слотов под конкретную задачу ?
Слот может быть один, а в нем вы можете решать в каком label отображать результат.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 05:43
Каждая кнопка при нажатии должна перецеплять слот к своему обработчику ?


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 26, 2016, 06:10
Каждая кнопка при нажатии должна перецеплять слот к своему обработчику ?
Есть такая штука, как QSignalMapper.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 06:31
Ладно, сработал таймер, отправил запрос и подключил слот к себе. Тут, так случилось, что в этот же момент нажимается кнопка, отправляется запрос, и слот переподключается на кнопку. Ответ для таймера забирает кнопка, таймер остается вообще без ответа. Проблема та-же, но с другой стороны )


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 26, 2016, 06:45
Ладно, сработал таймер, отправил запрос и подключил слот к себе. Тут, так случилось, что в этот же момент нажимается кнопка, отправляется запрос, и слот переподключается на кнопку. Ответ для таймера забирает кнопка, таймер остается вообще без ответа. Проблема та-же, но с другой стороны )
Не надо ничего переподключать. Вы в самом запросе можете сохранять для кого предназначается ответ (таймеру или кнопке), а в слоте по этому флагу решать, что делать с ответом. Слот для получения результата, что для кнопки, что для таймера, будет один.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 07:12
Да, получается что если отправлять данные с неким ид, например mcu->Send("Команда",1); того кто отправляет запрос тогда в обработчике слота проверять:
if(id=1){кнопка1}
if(id=2){кнопка2}
....
Тогда функции mcu->Read(); вообще не будет, а останется только слот с сигналом что получено некое сообщение.
Так наверно будет работать, пока подвохов не вижу )


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 07:47
Хотя нет, появляется другая не очень приятная вещь.
Так например по нажатию на кнопку может выполнятся некая последовательность действий:

Отправляем команду1;
Если команда прошла успешно,то:
    Отправляем команду2;
    Отправляем команду3;
    Ждем пока команды обработаются;
    Читаем результат команды2;
    Читаем результат команды3;

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


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 26, 2016, 08:08
Хотя нет, появляется другая не очень приятная вещь.
Так например по нажатию на кнопку может выполнятся некая последовательность действий:

Отправляем команду1;
Если команда прошла успешно,то:
    Отправляем команду2;
    Отправляем команду3;
    Ждем пока команды обработаются;
    Читаем результат команды2;
    Читаем результат команды3;

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


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 08:22
Команды приходят во входной буфер, результат скидывается в выходной. Соответственно серия из команд ограничена только емкость буфера.


Название: Re: Как разрулить потоки
Отправлено: Igors от Июль 26, 2016, 08:53
Команды приходят во входной буфер, результат скидывается в выходной. Соответственно серия из команд ограничена только емкость буфера.
Это не протокол. Принципиально может ли устройство ответить с тем же уникальным кодом/ID что и посланная команда. Если да - можно посылать в любом (разумном) порядке.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 26, 2016, 08:56
Команды приходят во входной буфер, результат скидывается в выходной. Соответственно серия из команд ограничена только емкость буфера.
Вы сейчас говорите уже о слое бизнес-логики?


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 09:52
Я не знаю что такое слой бизнес-логики ) Не надо ничего усложнять все до банальности просто. Устройство читает свой входной буфер, если там что-то есть, то выполняет и результат скидывает в выходной буфер.
Затолкать в буфер можно и одну и 2 и 10 команд, все равно они будут считываться и выполнятся последовательно.
Отслеживать ID запроса в железке теоретически можно, но придется перепиливать софт железки. По моему немного отошли от темы. Все что сейчас мешает это двойной вызов функции mcu->Read(); Я не знаю, может есть какие-то стандартные средства С++ чтобы не допускать такого. Либо раз уж это происходит, почему программа вешается при моем варианте решения. Как вообще правильно делить одну функция между разными потоками.


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 26, 2016, 10:12
Ей богу, вам лучше написать тз и заказать программу под ваши нужды.

По идее вам надо начать с протокола, проанализировать, поменять или улучшить, соответственно перепрошить железяку под новый протокол(а мб и не нужно, кто знает), потом взять приведённые в теме коды и на его основе построить логику приема/передачи. А уже под эту логику сделать ui.


Название: Re: Как разрулить потоки
Отправлено: Old от Июль 26, 2016, 10:32
Я не знаю что такое слой бизнес-логики ) Не надо ничего усложнять все до банальности просто. Устройство читает свой входной буфер, если там что-то есть, то выполняет и результат скидывает в выходной буфер.
Затолкать в буфер можно и одну и 2 и 10 команд, все равно они будут считываться и выполнятся последовательно.
Отслеживать ID запроса в железке теоретически можно, но придется перепиливать софт железки. По моему немного отошли от темы. Все что сейчас мешает это двойной вызов функции mcu->Read(); Я не знаю, может есть какие-то стандартные средства С++ чтобы не допускать такого. Либо раз уж это происходит, почему программа вешается при моем варианте решения. Как вообще правильно делить одну функция между разными потоками.
Ничего в прошивке менять не нужно.
Покажите, для примера, несколько запросов к устройству с ответами и я покажу как это реализовать.
Например:
Запрос alive - ответ ok;
Запрос mode - ответ число обозначающее режим;
Запрос state - ответ bool (true - можно менять режим)
и т.д.


Название: Re: Как разрулить потоки
Отправлено: Igors от Июль 26, 2016, 11:07
От kuzulis тоже не понимаю, запрос идет через слот, а как реализована обратная связь ?
Это самый простой/естественный вариант, неясно чем он не устраивает. Не организуем никаких ожиданий, не вызывает processEvents. Просто по таймеру получаем упр-е и смотрим есть (пришли) ли данные. Нет - уходим в событийный цикл, UI свободно. Пришли - разбираемся с ними. Все

А определить/идентифицировать на что пришел ответ так или иначе придется. Напр истек таймаут - а устройство прислало (запоздалый ответ).

Как посылать - тоже рассказали, самое простое складывать в очередь, а реальную посылку устройству делать когда получен (и обработан) ответ  


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 11:39
Команды шлются в текстовом виде, в устройстве крутится простейший интерпретатор, например:
ADC 6 IREF 5 3 2 AVCC 7 ; Эту посылку можно рассматривать как 7 команд: запрашивает измерение с канала 6, установка опоры, измерение каналов 5 3 2 установка опоры, измерение канала 7.
Ответ будет последовательность слов каждое из которых оканчивается кодом $0D :
123 OK 234 345 456 OK 567
Соответственно послав строку ADC 6 IREF 5 3 2 AVCC 7 ; необходимо вычитать ровно 7 слов.
В моем случае это будет 7 последовательных вызовов mcu->Read();
Функция нарезает слова из буфера и возвращает признак:
m_OK - пришло сообщение "OK"
m_Num - пришло число
m_Timeout - превышено время ожидания.
и т.д.


Название: Re: Как разрулить потоки
Отправлено: kuzulis от Июль 26, 2016, 12:29
Цитировать
ADC 6 IREF 5 3 2 AVCC 7 ... как 7 команд

Моя парсилка видит только три:

* запрос ADC 6 -> ответ 123 OK (+CR)
* запрос  IREF 5 3 2 -> ответ 234 345 456 OK (+CR)
* запрос  AVCC 7 -> ответ 567 (ОК?) (+CR)

И в чем проблема?

1) суем в очередь три запроса
2) берем первый запрос ADC 6 из очереди и отсылаем его
3) приходит ответ 123 OK
4) мы определяем, соответствует ли данный ответ текущему запросу (запрос у нас еще в очереди)
5) если соответствует, то удаляем из очереди первый запрос, а ответ парсим и выводим в нужные контролы в UI
6) берем следующий запрос  IREF 5 3 2 из очереди и отсылаем его
7) приходит ответ 234 345 456 OK
8 ) мы определяем, соответствует ли данный ответ текущему запросу (запрос у нас еще в очереди)
9) если соответствует, то удаляем из очереди первый запрос, а ответ парсим и выводим в нужные контролы в UI

и т.п.

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

Цитировать
оканчивается кодом $0D :

Это же ЕМНИП, CR, тогда вообще нет проблем с парсингом ответа: просто используем serial->canReadLine() && serial->readLine(),
все еще проще становится.

ЗЫ: Ну и в чем сложность то?


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 26, 2016, 13:49
У него почти отсутствует протокол, в том и проблема.
ADC 6 IREF 5 3 2 AVCC 7
Разбор на мой взгляд
ADC [номер канала] - запрос измерения с 6 канала (ответ цифра)
IREF - установка опоры (ответ OK/noOK)
(5 3 2 7 )[\d] - запрос данных с канала (ответ цифра)
AVCC - установка опоры (ответ OK/noOK)

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

Хотя и в таком виде можно спокойно написать свой менеджер.
PS и ещё 1 сообщение будет ниже. Для разделения мух и котлет.


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 26, 2016, 14:10
Вам нужен менеджер с ожиданием ответа.
Пихаем в менеджер команды
ADC 6
IREF
5
3
2
AVCC 7

Он шлёт устройству, ждёт ответа.
Приходит ответ 123 OK 234 345 456 OK 567
Менеджер его парсит соответственно
123
OK
234
345
456
OK
567

И по кончанию парсинга испускает 7 (семь) сигналов о получении данных аля
Запрос   Ответ
ADC 6     123
IREF       OK
5            234
3            345
2            456
AVCC      OK
7            567

В результате вам и думать не надо о том, что где то чёто вызывать, менеджер сам разрулит всё, вам нужно будет лишь их обработать.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 16:18
С разбором ответа проблем никаких и не возникает. Проблема возникает когда от от таймера ушла команда например ADC 0 1 2 ; и вызывается mcu->Read() для получения первого результата. И в этот момент от другого потока по нажатию на кнопку отправляется например PORT $55 ; и снова вызывается mcu->Read() для получения ответа.
Вариант с менеджером мы тут уже разобрали. Менеджер испустит 7 сигналов, только кому ? Запросы могут идти от различных кнопок.
Допустим получит менеджер число 123 и что ему с ним делать ? Кому его отправлять ?
Синтаксис запросов подразумевает что все что между ADC и ; принадлежит к группе команд измерений.
ADC 6 IREF 5 3 2 AVCC 7 ; эквивалентно ADC 6 ; ADC IREF ; ADC 5 ; ADC 3 ; ADC 2 ; ADC AVCC ; ADC 7 ;
Поэтому если на запрос 3 пришел ответ 345, это абсолютно ни о чем не говорит.
Мне надо как-то заблокировать mcu->Read() для другого потока, пока первый поток не считает все данные.


Название: Re: Как разрулить потоки
Отправлено: kuzulis от Июль 26, 2016, 16:34
Блин, все же уже разжевали.  >:(

И опять-25, какие-то потоки, какие-то кнопки и пр. причем здесь кнопки, потоки вообще... каша-в-голове.

Ты понимаешь что такое очередь? Например, за колбасой:

1. Человеки в очереди - это таски/реквесты
2. Продавец - удаленный девайс
3. Первый чел протягивает 100р и грит: "Дайте краковской 100 гр" (это эквивалент запроса)
4. Продавец дает ему (это ответ)..
5. Чел покидает очередь
6. След чел дает 1рубь.. дайте московской 10 кг...
7. У продавца ступор и когнитивный диссонанс (это таймаут)
8. Продавец отвечает: "да пшел ты с рублем" - это ошибка.
9. Чел или уходит из очереди или пытается состав 1000р повторить попытку..

Я умываю руки.


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 26, 2016, 16:35
Вы опять не догоняете.
Выбросьте из головы своей свою mcu->read. Забудьте о вызове этого метода, его нет, не будет и никогда не было.

У вас 1(один) объект порта. У вас 1(один) менеджер. У вас 2(два) получателя/просителя/работника/потока.

3 сущности:
Поток 1
Поток 2
Менеджер

Поток 1 пишет в менеджер  ADC 0 1 2.
Список запросов менеджера:
0 Поток 1 запросил "ADC 0 1 2"

Поток 2 пишет в менеджер PORT $55.
Список запросов менеджера:
0 Поток 1 запросил "ADC 0 1 2"
1 Поток 2 запросил "PORT $55"

Менеджер начинает работу. Посылает устройству запрос 0 и запускает таймер на ответ
Если пришли данные - вынимает из запрос 0 отправителя и шлёт ему отвёт "122 155 166"
Если данных нет и таймер вышел, то - вынимает из запрос0 отправителя и шлёт ему ошибку "Запрос ADC 0 1 2 нет ответа"
В результате удаляет первый элемент списка запросов.

Список запросов менеджера:
0 Поток 2 запросил "PORT $55"
Дальше аналогично.

У вас в результате оба потока получили данные(или ошибку). Мало того, потоков может быть неограниченное число, менеджеру без разницы сколько их.

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

PPS на правах рекламы - за энную сумму могу вам написать программу, проконсультировать, разъяснить.


Название: Re: Как разрулить потоки
Отправлено: kuzulis от Июль 26, 2016, 16:43
Млин, да не нужны тут потоки.. ну вообще не нужны!


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 16:49
Не обижайтесь, я правда не понимаю что вы мне пытаетесь разжевать.
Можно конкретней, пусть будут 3 кнопки A B C. По нажатию на кнопки необходимо выполнять определенный сценарий из команд.
Опишите пожалуйста не нижние функции обработчика парсера менеджера и тд. а верхние.
То есть прям конкретно пусть по нажатии кнопки А необходимо:
Установить порт PORT DDR $FF 0 ;
Измерить канал ADC 5 ;
если значение меньше 100 то установить порт PORT $FF ;
Как будет выглядеть эта процедура прям вот конкретно внутри MainWindow::on_pushButton_clicked() 


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 26, 2016, 18:46
В общем вы не поймёте. Тут нужно логику строить на откликах асинхронных, а вы асинхронности не понимаете от слова вообще.
Это будет как минимум 1 слот MainWindow::on_pushButton_clicked()  и ещё 1 слот на чтение значения и дальнейшей логики.

PS разговор глухонемого со слепым получается :D
PPS а то, что вы хотите - это написание собственного скриптового движка. Вам проще заплатить и вам напишут.


Название: Re: Как разрулить потоки
Отправлено: Sphynx от Июль 26, 2016, 19:23
Да что-то мы друг друга не понимаем. Вообще-то никакие скриптовые движки не нужны.
В моей реализации внутри MainWindow::on_pushButton_clicked()  будет
Код
C++ (Qt)
mcu->Send("PORT DDR $FF 0 ;");
//Wait 2 Ok
if(mcu->Read()!=m_Ok){//Error}
if(mcu->Read()!=m_Ok){//Error}
mcu->Send("ADC 5 ;");
//Wait Num
if(mcu->Read()!=m_Num){//Error}
if(mcu->ReadNum<100)
{
   mcu->Send("PORT $FF ;");
   //Wait OK
   if(mcu->Read()!=m_Ok){//Error}
}
 
Этот код будет работать, при условии что другой поток не вмешается.
Вы утверждаете что функция mcu->Read() не нужна в принципе, так вот объясните пожалуйста как будет выглядеть код в вашем варианте, без такой функции.


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 26, 2016, 20:57
Этот код НЕ будет НОРМАЛЬНО работать. Ибо он тормозит главный поток приложения. Соответственно тот кто пользуется программой врядли будет рад тормозам в 5-7 секунд при каждом нажатии кнопки, вы не находите? :P

Мало того, данный код, как вы верно заметили в этой теме, не масштабируется. Т.е. единовременно может исполняться только 1(один) запрос.

То, что вы написали не годится для работы с интерфейсом. Разве только для "программ для себя", чтобы что то протестировать и забыть.

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

PS в любом случае вам менеджер писать. Простой уже или сложный решать вам.

PPS в моём случае c скриптовым движком будет
Код:
manager_->setScript(" 
send PORT DDR $FF 0  ;
send ADC 5 ;
if result < 100
PORT $FF ;")
Ибо у движка будет голова болеть всё ждать и тому подобное.


Название: Re: Как разрулить потоки
Отправлено: Igors от Июль 27, 2016, 10:06
В моей реализации внутри MainWindow::on_pushButton_clicked()  будет
Код
C++ (Qt)
mcu->Send("PORT DDR $FF 0 ;");
//Wait 2 Ok
if(mcu->Read()!=m_Ok){//Error}
...
 
Этот код будет работать, при условии что другой поток не вмешается.
Вы утверждаете что функция mcu->Read() не нужна в принципе, так вот объясните пожалуйста как будет выглядеть код в вашем варианте, без такой функции.
Вы находитесь под впечатлением что "почти все уже работает" (нужно только "разрулить потоки"). Но поверьте, "домучить" эту схему не удастся. Хоть с потоками (которые здесь совершенно не нужны), хоть без - в ветках  "Error" возникнут неразрешимые проблемы. Потому что нет соответствия запрос-ответ. Вы посылаете команду и ожидаете ответа(ов) полагая что ответ придет "точно на нее", но в случае таймаута это уже может быть не так.

Если у Вас там все в тексте, то нет ли возможности пристроить ID в запрос и ответ? Это было бы лучше всего. Если такой возможности нет, то остается организовывать очередь, примерно как уже показывали. Если интересно - могу расписать подробнее, но пока у меня впечатление что Вам это не очень нужно, хотите "идти своим путем". Ну этим "нужно переболеть", это нормально  :)



Название: Re: Как разрулить потоки
Отправлено: Racheengel от Июль 27, 2016, 12:10
1. Вынесите ВСЮ работу с устройством в отдельный поток. В GUI не должно быть никакого обмена данными.
2. Сделайте очередь запросов. Т.е. при нажатии на кнопку устройство не должно опрашиваться непосредственно. Вместо этого должен создаваться запрос и паковаться в очередь. Когда до него "дойдут руки" - тогда уже он должнен быть обработан устройством.



Название: Re: Как разрулить потоки
Отправлено: Racheengel от Июль 27, 2016, 12:13
Не обижайтесь, я правда не понимаю что вы мне пытаетесь разжевать.
Можно конкретней, пусть будут 3 кнопки A B C. По нажатию на кнопки необходимо выполнять определенный сценарий из команд.
Опишите пожалуйста не нижние функции обработчика парсера менеджера и тд. а верхние.
То есть прям конкретно пусть по нажатии кнопки А необходимо:
Установить порт PORT DDR $FF 0 ;
Измерить канал ADC 5 ;
если значение меньше 100 то установить порт PORT $FF ;
Как будет выглядеть эта процедура прям вот конкретно внутри MainWindow::on_pushButton_clicked() 

Прям конкретно:
manager->startComand("MyCommandA");

а менеджер уже пусть разбирает, что такое MyCommandA, и реализует соответвующую логику работы с портами (когда до этого запроса очередь дойдет).


Название: Re: Как разрулить потоки
Отправлено: Igors от Июль 27, 2016, 12:56
Прям конкретно:
manager->startComand("MyCommandA");
Ой как конкретно :) Какая-то одна строчка хз к чему относится. Правда так или иначе что-то подобное придется городить. Только вот что делать если устройство отвалилось по таймауту - хз. Например
Цитировать
Установить порт PORT DDR $FF 0 ;
Измерить канал ADC 5 ;
И тут ответа не дождались. Ну как-то продолжать работу надо - напр пробуем еще раз то же самое PORT DDR. И тут приходит ответ - но на что?  Может это запоздалый ответ на ADC 5 ?


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 27, 2016, 13:02
Это зависит уже от конкретной реализации и требований к менеджеру :)


Название: Re: Как разрулить потоки
Отправлено: Racheengel от Июль 27, 2016, 13:13
И тут ответа не дождались. Ну как-то продолжать работу надо - напр пробуем еще раз то же самое PORT DDR. И тут приходит ответ - но на что?  Может это запоздалый ответ на ADC 5 ?

Ну, как-то мало смысла слать что-либо еще в девайс, пока ответа не пришло.
А по таймауту можно какой-нибудь ресет послать, если девайс это поддерживает.


Название: Re: Как разрулить потоки
Отправлено: Igors от Июль 27, 2016, 13:39
Это зависит уже от конкретной реализации и требований к менеджеру :)
А тут, стало быть, все "еще не конкретно"?  :) Тогда в чем смысл базара на 5 страницах?

Ну, как-то мало смысла слать что-либо еще в девайс, пока ответа не пришло.
А что, есть какой-то выбор? Я его не вижу
А по таймауту можно какой-нибудь ресет послать, если девайс это поддерживает.
Ну вот, говорили "мало смысла", а сами посылаете  :) Ну и как опознать ответ пришедший на reset?


Название: Re: Как разрулить потоки
Отправлено: Racheengel от Июль 27, 2016, 14:02
Никак, ресет послали - все, что пришло после него - в мусор.


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 27, 2016, 15:06
Igors, на 5 страницах тут аргументы в сторону "вам нужен асинхронный менеджер".
А то, что спрашиваете вы про "а что он будет делать [конкретная ситуация, которую вы взяли у себя из головы]" - это незначительные подробности, рождённые вашей фантазией :)

PS вот если б вы был ТСсом - да. А так вы выдумываете хрень всякую, не имея обоснований :)


Название: Re: Как разрулить потоки
Отправлено: Igors от Июль 27, 2016, 15:25
Igors, на 5 страницах тут аргументы в сторону "вам нужен асинхронный менеджер".
А то, что спрашиваете вы про "а что он будет делать [конкретная ситуация, которую вы взяли у себя из головы]" - это незначительные подробности, рождённые вашей фантазией :)

PS вот если б вы был ТСсом - да. А так вы выдумываете хрень всякую, не имея обоснований :)
А Вашей корове лучше помолчать - и в этой теме Вы писали много, но не сказали ничего по существу  :)


Название: Re: Как разрулить потоки
Отправлено: Bepec от Июль 27, 2016, 16:32
Что ж это ваше мнение. Однако оно неверно, несправедливо и лицемерно :)