Russian Qt Forum
Ноябрь 24, 2024, 13:36 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 ... 55 56 [57] 58 59 ... 88   Вниз
  Печать  
Автор Тема: Создаю библиотеку для работы с последовательными портами. [УШЕЛ ИЗ ПРОЕКТА].  (Прочитано 786343 раз)
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #840 : Март 03, 2012, 19:00 »

2 BRE,

работает оно сейчас так:

во "внутреннем" платформо-зависимом классе Engine происходит обработка евентов от порта:
Код
C++ (Qt)
bool WinSerialPortEngine::event(QEvent *e)
{
   bool ret = false;
   if (e->type() == QEvent::WinEventAct) {
...
       if (EV_RXCHAR & m_currentMask & m_setMask) {
           m_parent->canReadNotification();
           ret = true;
       }
...
}
 

где m_parent - объект приватного класса SerialPortPrivate.

Где canReadNotification() ничем не отличается от аналогичного метода из сокетов.

Код
C++ (Qt)
bool SerialPortPrivate::canReadNotification()
{
   Q_Q(SerialPort);
 
#if defined (Q_OS_WINCE)
   m_engine->lockNotification(SerialPortEngine::CanReadLocker, true);
#endif
   // Prevent recursive calls.
   if (m_readSerialNotifierCalled) {
       if (!m_readSerialNotifierStateSet) {
           m_readSerialNotifierStateSet = true;
           m_readSerialNotifierState = m_engine->isReadNotificationEnabled();
           m_engine->setReadNotificationEnabled(false);
       }
   }
   m_readSerialNotifierCalled = true;
 
   //if (!m_isBuffered)
   //    this->serialEngine->setReadNotificationEnabled(false);
 
   // If buffered, read data from the serial into the read buffer.
   qint64 newBytes = 0;
   if (m_isBuffered) {
       // Return if there is no space in the buffer.
       if (m_readBufferMaxSize
               && (m_readBuffer.size() >= m_readBufferMaxSize)) {
 
           m_readSerialNotifierCalled = false;
           return false;
       }
 
       // If reading from the serial fails after getting a read
       // notification, close the serial.
       newBytes = m_readBuffer.size();
 
       if (!readFromPort()) {
           m_readSerialNotifierCalled = false;
           return false;
       }
       newBytes = m_readBuffer.size() - newBytes;
 
       // If read buffer is full, disable the read serial notifier.
       if (m_readBufferMaxSize
               && (m_readBuffer.size() == m_readBufferMaxSize)) {
 
           m_engine->setReadNotificationEnabled(false);
       }
   }
 
   // Only emit readyRead() when not recursing, and only if there is data available.
   bool hasData = (m_isBuffered) ? (newBytes > 0) : (bytesAvailable() > 0);
 
   if ((!m_emittedReadyRead) && hasData) {
       m_emittedReadyRead = true;
       emit q->readyRead();
       m_emittedReadyRead = false;
   }
 
   if ((!hasData) && m_engine->isReadNotificationEnabled())
       m_engine->setReadNotificationEnabled(true);
 
   // Reset the read serial notifier state if we reentered inside the
   // readyRead() connected slot.
   if (m_readSerialNotifierStateSet &&
           (m_readSerialNotifierState != m_engine->isReadNotificationEnabled())) {
 
       m_engine->setReadNotificationEnabled(m_readSerialNotifierState);
       m_readSerialNotifierStateSet = false;
   }
   m_readSerialNotifierCalled = false;
   return true;
}
 

в этом методе есть метод readFromPort() который также ничем практически не отличается от метода readFromSocket() из сокетов
и который, собственно и читает данные из порта в кольцевой буфер класса m_readBuffer.
Код
C++ (Qt)
bool SerialPortPrivate::readFromPort()
{
   qint64 bytesToRead = (m_policy == SerialPort::IgnorePolicy) ?
               bytesAvailable() : 1;
 
   if (bytesToRead <= 0)
       return false;
 
   if (m_readBufferMaxSize
           && (bytesToRead > (m_readBufferMaxSize - m_readBuffer.size()))) {
 
       bytesToRead = m_readBufferMaxSize - m_readBuffer.size();
   }
 
   char *ptr = m_readBuffer.reserve(bytesToRead);
   qint64 readBytes = read(ptr, bytesToRead);
 
   if (readBytes <= 0) {
       m_readBuffer.chop(bytesToRead);
       return false;
   }
   m_readBuffer.chop(int(bytesToRead - ((readBytes < 0) ? qint64(0) : readBytes)));
   return true;
}
 

ну и в нем, собственно метод read(), который делает вызов m_engine->read(data, len);
где m_engine->read - это платформо-зависимый метод чтения данных из порта,
в котором происходит обработка политик и установка ошибок паритета/фрейма
Код
C++ (Qt)
qint64 WinSerialPortEngine::read(char *data, qint64 len)
{
#if !defined (Q_OS_WINCE)
   clear_overlapped(&m_ovRead);
#endif
 
   DWORD readBytes = 0;
   bool sucessResult = false;
 
   // FIXME:
   if (m_parent->m_policy != SerialPort::IgnorePolicy)
       len = 1;
 
#if defined (Q_OS_WINCE)
   sucessResult = ::ReadFile(m_descriptor, data, len, &readBytes, 0);
#else
   if (::ReadFile(m_descriptor, data, len, &readBytes, &m_ovRead))
       sucessResult = true;
   else {
       if (::GetLastError() == ERROR_IO_PENDING) {
           // FIXME: Instead of an infinite wait I/O (not looped), we expect, for example 5 seconds.
           // Although, maybe there is a better solution.
           switch (::WaitForSingleObject(m_ovRead.hEvent, 5000)) {
           case WAIT_OBJECT_0:
               if (::GetOverlappedResult(m_descriptor, &m_ovRead, &readBytes, false))
                   sucessResult = true;
               break;
           default: ;
           }
       }
   }
#endif
 
   if(!sucessResult) {
       m_parent->setError(SerialPort::IoError);
       return -1;
   }
 
   // FIXME: Process emulate policy.
   if (m_flagErrorFromCommEvent) {
       m_flagErrorFromCommEvent = false;
 
       switch (m_parent->m_policy) {
       case SerialPort::SkipPolicy:
           return 0;
       case SerialPort::PassZeroPolicy:
           *data = '\0';
           break;
       case SerialPort::StopReceivingPolicy:
           break;
       default:;
       }
   }
   return qint64(readBytes);
}
 

----------

Так вот, это я к чему: куда сюда вставить то, что ты предложил?
Я не пойму что-то все-равно?

Если не трудно, а то что-то голова не варит  Грустный

И к слову, надо же принудительно не еммитить readyRead() - а принудительно вызывать метод canReadNotification() и
то только в случае, если в UART еще имеются непрочитанные данные,
а также учесть тот факт, что могут также и придти и еще несколько байт в UART  и следовательно,
подсистема нотификации тоже вызовет canReadNotification() - получится, что он вызовется дважды!

« Последнее редактирование: Март 03, 2012, 19:07 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
BRE
Гость
« Ответ #841 : Март 03, 2012, 19:36 »

Идея такая. Если венда посылает уведомление только один раз, то нам нужен кто-то, кто будет ее замещать и генерировать сигнал readyRead вместо нее, пока есть данные.
Для этого хорошо подходит таймер. Завязываем его сигнал timeout на наш readyRead. Теперь нам нужно его вовремя запускать и останавливать.
Запускать я думаю можно будет в canReadNotification, перед посылкой сигнала readyRead.
Останавливать наверное можно и в readFromPort, и в canReadNotification - когда поняли, что данных в порту уже нет.

Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #842 : Март 03, 2012, 19:55 »

Ааа.. ок. спасибо, обмозгуем.
Записан

ArchLinux x86_64 / Win10 64 bit
b-s-a
Гость
« Ответ #843 : Март 03, 2012, 22:57 »

Цитировать
Во-первых, должно быть два метода open. Один открывает со стандартными (или даже предустановленными - настройки менять можно до открытия) настройками, второй оставляет настройки неизменными. Если не удалось выставить настройки, то open не должна открывать порт с выставлением соответствующей ошибки. Как вариант, можно ввести функцию setup, которая за один вызов настроит все.
Нет, я против такого зоопарка. Я считаю, что пользователь сам напишет метод setup и т.п. если ему нужно.
И тот факт - что у нас куча конкретных методов для конкретных действий - это хорошо. Это юникс вей!
Эти методы - это кирпичики - минимальный и гибкий интерфейс класса.
Нагромождать/менять/добавлять еще что-то не имеет смысла.
Я не предлагаю отказаться от возможности раздельной настройки всех параметров. Я просто хочу уменьшить количество потенциальных ошибок, связанных с undefined behaviour после открытия порта. Сейчас человек каждый раз для настройки должен писать несколько вызовов функций. А теперь представь, что он забыл написать один из вызовов. У него все работает, а у заказчика нет - у него по умолчанию настройки другие и именно эту настройку он не трогал. Поэтому я и предлагаю как-то решить эту проблему, чтобы программист ВСЕГДА получал одинаковые настройки при открытии порта. Но если ему нужно открыть порт без изменения настроек, то для этого должна быть возможность.

Ну, можно только на крайняк переписать сеттеры так, чтобы они не возвращали значение,
а оно возвращалось в error(). Имхо, то что сейчас есть - это наилучший вариант и не нужно ничего сверху мудрить со всякими open(), setup() и т.п. !
Я против!  Улыбающийся

Цитировать
Как в стандартных Qt-шных классах подобные вещи сделаны? Там возвращаются коды ошибок или что? Например, у сокета.
В том то и дело, что сокет несколько отличается от порта.

Хотя, в сокетах сеттеры типа setSocketOption() возвращают только void, но нигде не устанавливается error(), хотя в приватных кишках метод engine->setOption() возвращает bool. Т.е. тут не так однозначно: почему они не возвращают нигде код ошибки в некоторых сеттерах, почему этого не сделано??

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

Также тут покопался в сокетах и вспомнил, что они имеют еще дополнительно такие сигналы как:
Код
C++ (Qt)
 
void connected();
void disconnected();
void error(QAbstractSocket::SocketError);
 

может быть и нам ввсести что-то похожее:
Код
C++ (Qt)
 
void opened();
void closed();
void error(SerialPort::SerialPortError);
 

?
Нахрена? В отличие от сокета, порт открывается в синхронном режиме. А на случай закрытия у QIODevice уже есть сигнал.

Цитировать
Во-вторых, во всех режимах, кроме StopReceivingPolicy, мы сами внутри не контролируем возникновение ошибок (это стандартные режимы работы порта, поэтому нет никакого смысла что-то там придумывать). Думаю, если юзеру надо, то пусть он выставляет StopReceivingPolicy и уже тогда разбирается.
Нифига!
Первична - это ошибка паритета/фрейма, вторично - это уже ее обработка при помощи разных политик!
Т.е. сама ошибка паритета/фрейма никуда не делась, и о ней необходимо сигнализировать во всех  политиках кроме Ignore,
т.к. только в этом режиме мы её не анализируем вообще!
Почему-то мне всю жизнь казалось, что в винде ошибки четности/фрейма можно не замечать, исключать поврежденный байт и занулять его. Причем во всех этих случаях никакие флаги нигде не сигнализируют об ошибках. И только в одном режиме, можно ловить событие об ошибке...
Если так подумать, то зануление и исключение вообще не имеют особого смысла на практике. А игнор можно реализовать через stopreading. Думаю, надо еще раз обмозговать это дело...
Цитировать
В режиме StopReceivingPolicy происходит следующее:
в порту есть данные VVVEVVVV. Пользователь по bytesAvailable() получает результат 8 (сможем победить фоновое буферизирование?). Читает. Метод read считывает только VVVE, а код ошибки выставляется Parity/Frame error. Таким образом, нет никаких проблем для продолжения чтения - ты сбрасываешь ошибку (или игнорируешь) и продолжаешь. Следующий read вернет уже VVVV.
В том то и дело, что первично - фоновое буферизирование - это режим по умолчанию! И побеждать его не надо, нужно отталкиваться именно от него.  Улыбающийся
Так в чем дело? Буферизируй с учетом ошибок (т.е. вместо одного байта - два).

В буферизированном режиме bytesAvailable() возвращает кол-во байт, доступных в кольцевом буфере , а не в UART!!
В режиме же без буферизации - кол-во доступных байт в UART!!!
Не вижу противоречий со сказанным мной. То что я говорил - это уровень пользователя, а не внутренней реализации. Так как он первичен, и реализация должна под него подстраиваться.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #844 : Март 04, 2012, 18:48 »

Вмержили изменения, связанные с первым шаблоном документации.

И, кстати, я там закоментировал все проперти (раз уж они нафик не нужны), т.к. при генерации документации и при наличии проперти - необходимо удалить
описания методов геттеров/сетерров этого проперти, а вместо них - описать именно сами проперти.

И кстати, про gerrit, объясните мне суть inline комментариев и драфтов..
Зачем там два варианта ответов: "Ответить" и "Ответить готово", и воообще, в чем соль?
« Последнее редактирование: Март 04, 2012, 18:58 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
b-s-a
Гость
« Ответ #845 : Март 04, 2012, 19:21 »

смысл inline в том, чтобы ты видел, что именно вызвало вопрос.
при выборе ответить, твой ответ становится "черновиком", а обеляется при нажатии на review (можно несколько ответов/комментариев за раз сделать). А "ответить готово", я так думаю, сразу набело.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #846 : Март 04, 2012, 20:34 »

2 b-s-a


Цитировать
смысл inline в том, чтобы ты видел, что именно вызвало вопрос.
при выборе ответить, твой ответ становится "черновиком", а обеляется при нажатии на review (можно несколько ответов/комментариев за раз сделать). А "ответить готово", я так думаю, сразу набело.
Так для чего это "набело"/"начерно" нужно то? Не пойму, что это дает?

Цитировать
Я не предлагаю отказаться от возможности раздельной настройки всех параметров. Я просто хочу уменьшить количество потенциальных ошибок, связанных с undefined behaviour после открытия порта. Сейчас человек каждый раз для настройки должен писать несколько вызовов функций. А теперь представь, что он забыл написать один из вызовов. У него все работает, а у заказчика нет - у него по умолчанию настройки другие и именно эту настройку он не трогал. Поэтому я и предлагаю как-то решить эту проблему, чтобы программист ВСЕГДА получал одинаковые настройки при открытии порта. Но если ему нужно открыть порт без изменения настроек, то для этого должна быть возможность.
Ок, если у тебя будет время - то сделай ревью для своих идей - а я буду минусовать (или плюсовать) Улыбающийся

Цитировать
Почему-то мне всю жизнь казалось, что в винде ошибки четности/фрейма можно не замечать, исключать поврежденный байт и занулять его.
Ну да, там есть "аппаратная" возможность это делать - но она работает через Ж. , и трудно ее пристроить (по крайней мере у меня не получилось).
И, кстати, я об этом уже писал.

Но я не использую эту "фичу", вместо нее я эмулирую эти политики через другую фичу - евент EV_ERROR, который возникает всегда при
ошибках паритета/фрейма (если не ошибаюсь).

Суть в том, что этот евент EV_ERROR приходит всегда(?) раньше чем евент EV_RXCHAR, поэтому мы успеваем по евенту  EV_ERROR выполнить processIOErrors(),
выяснить, что же именно за ошибка: паритета или фрейма или break, установить ее в error() и взвести флаг m_flagErrorFromCommEvent.

Код
C++ (Qt)
bool WinSerialPortEngine::processIOErrors()
{
   DWORD err = 0;
   COMSTAT cs;
   bool ret = (::ClearCommError(m_descriptor, &err, &cs) != 0);
   if (ret && err) {
       if (err & CE_FRAME)
           m_parent->setError(SerialPort::FramingError);
       else if (err & CE_RXPARITY)
           m_parent->setError(SerialPort::ParityError);
       else if (err & CE_BREAK)
           m_parent->setError(SerialPort::BreakConditionError);
       else
           m_parent->setError(SerialPort::UnknownPortError);
 
       m_flagErrorFromCommEvent = true;
   }
   return ret;
}
 

Где флаг m_flagErrorFromCommEvent будет использован в дальнейшем в методе read() при обработке политик,
после приема EV_RXCHAR (который идет строго за EV_ERROR)

Код
C++ (Qt)
qint64 WinSerialPortEngine::read(char *data, qint64 len)
{
...
...
 
   // FIXME: Process emulate policy.
   if (m_flagErrorFromCommEvent) {
       m_flagErrorFromCommEvent = false;
 
       switch (m_parent->m_policy) {
       case SerialPort::SkipPolicy:
           return 0;
       case SerialPort::PassZeroPolicy:
           *data = '\0';
           break;
       case SerialPort::StopReceivingPolicy:
           break;
       default:;
       }
   }
   return qint64(readBytes);
}
 

т.е. вот так оно работает сейчас в винде.
Это просто к сведению, для тебя, (т.к. ты скорее всего не особо разбирался с виндой - а все ближе с nix),
вдруг - предложишь что-нить получше, или свою nix-овую реализацию под это подгонишь (или я к никсовой подгоню) Улыбающийся

В общем, тут нужно придти к общему мнению - как оно должно работать и как это реализовать для всех ОС.

Цитировать
Я так подумал, на свойства действительно можно забить... Кому они нужны?
Согласен, закоментировал, потом можно избавиться от них.

Цитировать
Нахрена? В отличие от сокета, порт открывается в синхронном режиме. А на случай закрытия у QIODevice уже есть сигнал.
Ну, хотя бы сигнал error реализовать (как в сокетах), для того, чтобы можно было по нему построить подробный последовательный лог ошибок.
Если это, конечно, не загрузит Event Loop.

Цитировать
Так в чем дело? Буферизируй с учетом ошибок (т.е. вместо одного байта - два).
Ни в чем, посто нужно было определиться с поведением при политиках, придти к общему мнению.
А реализовать это, думаю не проблема.

Цитировать
Не вижу противоречий со сказанным мной. То что я говорил - это уровень пользователя, а не внутренней реализации. Так как он первичен, и реализация должна под него подстраиваться.
Не, я не спорю, просто я на всякий случай уточнил особенности работы с буферизацией и без.
« Последнее редактирование: Март 04, 2012, 20:37 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
b-s-a
Гость
« Ответ #847 : Март 04, 2012, 21:03 »

Цитировать
Так для чего это "набело"/"начерно" нужно то? Не пойму, что это дает?
Чтобы ты мог за один раз поставить комментарии к нескольким файлам. А не создавать review для каждого коммента.

Что я предлагаю для решения проблемы с UB следующие варианты:
1. Ввести еще одну константу типа OpenMode, которая будет означать, что порт открывается без настройки. Если она не указана, то порт открывается с начальными настройками. Рекомендую: 9600n8, один стоп.
2. Сделать еще один метод открытия порта - openUnconfigured. Или типа того. В этом случае, метод open всегда открывает задавая некие настройки.
3. Сделать свойство, которое влияет на начальное конфигурирование при открытии: configureOnOpen. По умолчанию, true. В этом случае, задаются текущие настройки порта (которые сделаны ДО открытия).
4. Сделать метод setup(...) для настройки всего в один вызов.

Мне наиболее нравится 3-й вариант.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #848 : Март 04, 2012, 22:04 »

Упс, китайцы нас обошли http://qt-project.org/wiki/Category:Add-ons::QextSerialPort

 Обеспокоенный

Пиар у них эффективнее.

Мож и нам на вики запостить что-нить, для пиара? А то чую сольем...  В замешательстве

Самое интересное, что этот самый dbzhang800 знает о существовании аддона QtSerialPort (даже он мне на список рассылки ответил) - но упорно продолжает проталкивать QextSerialPort как аддон..

Не пойму я этих китайцев...
« Последнее редактирование: Март 04, 2012, 22:06 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
b-s-a
Гость
« Ответ #849 : Март 05, 2012, 07:59 »

В любом случае, QtSerialPort уже в playground, а QextSerialPort нет. Хотя, wiki нужно бы написать. Для начала, на русском. А потом коряво перевести и попросить кого-нибудь подретушировать.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #850 : Март 05, 2012, 09:53 »

Почитал про ВиКи на qt-project.org, и в хелпе написано, что желательно использовать
песочницу для экспериментов http://qt-project.org/wiki/Playground

Я так понимаю, что сначала нужно нарисовать в песочнице об QtSerialPort,
примерно прикинуть о чем писать и т.п., а потом, когда получится нормально - то
создать нормальную ВиКи страницу.

Но непонятен момент с песочницей: в песочнице сказано "Use this page for your wiki markup experiments."
Вопрос: это означает, что я тупо могу жмакнуть в Wiki Menu (справа в колонке) "Edit" и редактировать её,
или же мне нужно жмакнуть "Create new page" и редактировать?

Я что-то не понял.
« Последнее редактирование: Март 05, 2012, 10:20 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
b-s-a
Гость
« Ответ #851 : Март 05, 2012, 11:36 »

делай в корне и на русском.
думаю, в вики стоит написать кратное описание, фичи, особенности и простейший туториал.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #852 : Март 05, 2012, 22:09 »

В принципе набросал ВиКи.

Тут русский вариант, для остальных языков пустые страницы.

Просьба ко всем дополнить, исправить, подставить ссылочки и т.п если нужно.
У меня что-то мыслей нет о чем писать.
« Последнее редактирование: Март 05, 2012, 22:16 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
b-s-a
Гость
« Ответ #853 : Март 05, 2012, 23:11 »

Немного подкорректировал.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #854 : Март 06, 2012, 09:49 »

Спасибо. Улыбающийся

Есть еще пара вопросов:
1. Есть ли возможность в ВиКи делать примечания? Я что-то не нашел.
Я б хотел написать про симбиан в примечании
(*)- типа что скорее всего поддержка симбиана будет удалена (или не будет развиваться).
Или убрать эти примечания (*) ?
2. Нужно как-то получше написать про Сборку и установку, а то какая то белиберда получилась.
3. Нужно ли как-то где-то указать, что QtSerialPort разрабатывается предназначен для Qt5?
4. Нужно ли в фичах для SerialPort перечислить его возможности типа: установка скорости, .... отслеживание ошибок паритета/фрейма, обработка политик и т.п.? Или это будет лишнее...
Записан

ArchLinux x86_64 / Win10 64 bit
Страниц: 1 ... 55 56 [57] 58 59 ... 88   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.224 секунд. Запросов: 23.