QThread + QSerialPort! Крутим в отдельном потоке работу с СOM портом ( продолжение). + чучуть протокола DCON

Продолжение предыдущего моего топика о работе с Qt
Так и не дошли руки (стыдно мне очень перед тов. Alatar ) доработать его.
Задача: Организовать стабильный обмен данными (важен сам прием) дописать немножко терминалку. Получить критику и подзатыльники(если будет за что).
Посылка с устройства идет байтами и не всегда это неразрывный поток. В большинстве случаев между байтами проскакивают паузы. По этой причине нужно немножко подзаморочится… Приведенный в предыдущем моем посте способ приема – банальная подпорка палкой.

Чучуть кода:

connect(&thisPort, SIGNAL(readyRead()),this,SLOT(ReadInPort()));
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void Port :: WriteToPort(QByteArray data){
    if(thisPort.isOpen()){
        thisPort.write(data);
    }
}
void Port :: ReadInPort(){
    QByteArray data;
    data.append(thisPort.readAll());
    outPort(data);
}


Тут все просто=))
WriteToPort Если порт открыт то отсылаем в него наши данные (эту магию я не знаю, что и как там происходит).
Прием осуществляется по сигналу readyRead где сразу считывается все принятое из порта. И можно (лучше не надо) отправлять данные парсить и росковыривать дальше. Вот здесь и лежит большая Ж.=))

Приступим: Пример буду делать для протокола DCON (чуть извращенства) команды (да и сам) модуля ввода вывода ADAM4055

1. Правильно(надеюсь) отправляем данные

bool Port :: WriteToPort(QByteArray data)
{//Непосредственно пишем в порт
    if(thisPort.isOpen())
    {//если порт открыт то пишем
        thisPort.write(data);
        return thisPort.waitForBytesWritten(TIMEOUT);//ждем пока не запишутся все данные
    } else return false;// Если совсем плохо все или порт закрыт
}


При отправке данных WriteToPort вернет нам true – если все прошло хорошо! или же false – если все плохо(произошел сбой при отправке или закончилось время на отправку)

2. Правильно(надеюсь) принимаем данные:

QByteArray Port :: ReadInPort(){
    QByteArray data;
    while (thisPort.waitForReadyRead(TIMEOUT))
    {//Пока не считается все ждем
        data +=thisPort.readAll();
        int read_num = data.indexOf(END_OF_LINE);//читаем до прихода конца посылки
        if (read_num > 0)
        {
            data = data.left(read_num + 1);//лишние данные после конца посылки убираем
            if (Veryfi_CRC(data))//проверка контрольной суммы
            {
                return data;//Если все в порядке - возвращвем нашу посылку
            }
            else
            {
                data.clear();
                return data;//Если все в плохо - отправляем "пустоту"
            }
        }
    }
    data.clear();
    return data;//Если все в плохо - отправляем "пустоту"
}


Здесь тоже ждем:

while (thisPort.waitForReadyRead(TIMEOUT))
    {//Пока не считается все ждем
        data +=thisPort.readAll();


Ждем промежуток времени TIMEOUT и считываем данные валяющиеся в приемном буфере порта – суммируя их в data. Из цикла выйдем только в двух случая: если закончится время на считывание или же если приняли конец символ конца посылки. Тут уже идет разветвление, если контрольная сумма посылки сошлась – радостно возвращаем нашу посылку, при несовпадении контрольной суммы – возвращается 0.
З.Ы. Тут Следовало бы добавить проверку начала посылки и также обрезать лишнее (все может быть, мало ли что за мусор по каналу связи повалит).

3. Склеиваем все в кучу. Небольшая «служба отправки приема»:

Ну с отправкой все понятно=)) расписывать простыню не буду.
Для приема? Тут уж вариантов несколько, можно принимать по сигналу ReadyRead (как в первом посте, только чучуть модифицировав процедуру) или же после отправки данных сразу смотреть порт.
В протоколе DCON несколько команд не требующих подтверждения о выполнении или же ответа состояния со стороны контроллера. По этой причине напишем прием данными сразу после отправки:

void Port::WriteOut(QByteArray request)
{//Запись в порт данных
    bool OK_write = WriteToPort(request);//Запись в порт посылки
    QByteArray read_data = ReadInPort();//Считывание данных
    if (!(read_data.isNull()) || OK_write)//Проверка на считывание/запипись
        outPort("Все отлично!: " + (QString)read_data);
     else   outPort("Все плохо!");
}


Ну и сама программа:



В текстовом поле:

1) первую команду (два раза) $016CRCcr отправили запрос состояния портов в ответ получили !FF0000CRCcr и !FFС000CRCcr, где 00 а потом С0 — состояние входного порта!

2) вторую команду #0100FFCRCcr записали в выходной порт значение FF. В ответ получили >3E — команда успешно выполнена.

З.Ы. Код примера (Это всего лишь маленький простенький пример) сильно упрошен и для использования нужно добавлять еще кучу всяких проверок, примочек, плюшек, и т.д.
  • +1
  • 26 марта 2014, 18:09
  • kalik
  • 1
Файлы в топике: terminalka2.zip

Комментарии (20)

RSS свернуть / развернуть
Зачем! столько восклицательных (!) знаков?!!!
0
Только что попытался сам себе ответить на этот вопрос, не знаю откуда, как и почему…
Наверное пора устроить отдых организму=))
0
void Port :: WriteToPort(QByteArray data)

так лучше не делать, ибо будет происходить копирование объекта QbyteArray (а конструктор копирования там ресурсоемкий т. к. выделяется память, копируется буфер и т. д.). Лучше использовать ссылку, в данном лучше случае const ссылку.

void Port :: WriteToPort(const QByteArray & data)
+2
Кстати да=)) в основном проекте так и сделано
0
Честно говоря у Вас в программе много мелких «косяков»

Например:

void Port::WriteOut(QByteArray request)
{//Запись в порт данных
    bool OK_write = WriteToPort(request);//Запись в порт посылки
    QByteArray read_data = ReadInPort();//Считывание данных
    if (!(read_data.isNull()) || OK_write)//Проверка на считывание/запипись
        outPort("Все отлично!: " + (QString)read_data);
     else   outPort("Все плохо!");
}


Если метод WriteToPort() вернул false (OK_write), то нет смысла дальше ждать ответа от устройства (ReadInPort()), запрос «не доставлен», ответа на него можно не ожидать, можно сразу возвращать ошибку (выводить «Все плохо»).

А почему Вы не используете механизм исключений? Из-за накладных расходов или просто из-за стиля?
0
Честно говоря у Вас в программе много мелких «косяков»
Есть такое=)) Стыдно… но есть! От хорошего и правильного пинка по нужному направлению не откажусь=)) Изначально указал:
Получить критику и подзатыльники(если будет за что).
Пока учусь — заполняю те пустоты, которые после ВУЗа образовались(стыдно за систему образования).
А почему Вы не используете механизм исключений? Из-за накладных расходов или просто из-за стиля?
По многим причинам! Именно в этом случае? Попытка немного упростить код убрав навороты всякие. В основной программе она реализована (кривовато пока) жаль сроки давят… могут руки не дойти поправить
0
github.com/pixraider/featured-serial-terminal
Уверен, вам будет интересно.
Если отбросить там всякие свистелки, то реализация довольно проста, работает стабильно, можно адаптировать под свой протокол.
0
Интересненько, поковыряюсь
0
надо бы мне как-нить дооформить статейку про многопоточный обмен с последовательным портом из лазаруза + библиотечки синапс. тоже интересно было курнуть многопоточность лазаруса, а то фризы окошка достали :)
0
  • avatar
  • 21h
  • 26 марта 2014, 22:12
Как там у него по совместимости с Делфой?
0
ну некоторые программы старые импортировал нормально. некоторые канеш пришлось допилить, но с нуля ничего не переписывал. попробуй на досуге. это не тот лазарус, что был 8 лет назад когда все только начиналось.
0
вот тут я слегка разжевал про многопоточность в лазарусе blindage.org/?p=4467

а вот прога, которая использует это dev.blindage.org/home-light-controller/src/333f7bd07e95cdced5c7d8da41f059f913d934dc/multiplatform%20client-mt/?at=default
0
вот тут я слегка разжевал про многопоточность в лазарусе
Проще говоря — «как в дельфи, только дефайн UseCThreads добавить».
Я только одного не понял — зачем уничтожать объект ser (да и прочие действия выполнять) в обработке каждой ошибки, если у тебя используется try-finally? И нафига вообще использовать try-finally, если блок finally пустой?
0
опа. не замечал этого. помог ошибку найти. ну а try нужен чтоб ошибки ловить и флажки поднимать нужные. в этой проге оно просто не используется, но обрабатывать ошибки нужно в любом случае, даже если обработки внутри обработки нет ;)
0
try-finally с пустым finally бесполезен. Это не try-except, оторый с пустым except выполняет функцию подавления исключений. try-finally же только гарантирует выполнение кода finally при выходе и блока try (штатно, по исклюению или команде Exit — неважно).
Но в любом случае, классическая конструкция — это
Ser:=TSomeClass.Create;
try
  //code
  //Здесь можно смело использовать Exit - он выкинет в finally
finally
  FreeAndNil(Ser);
  //Опционально можно сюда же запихать и прочую общую деинициализацию, тогда проверка ошибок сводится к if Error then Exit;
end;

Кроме того, вылетевшая в таком коде в блоке try птичка не погасится, а полетит прямо к следующему уровню перехвата — вплоть до системной ловушки «программа выполнила недопустимую операцию и будет закрыта», если по пути не встретится блок try-except.

Впрочем, это все верно для Delphi. FPC может отличаться в плане обработки ошибок.
0
да да, я копипастил с другой старой проги это и не обратил внимание. спасиб что показал.
0
Не было проблемы, что приходят то 1 байт, то 3 байта, то 7 и т.п., иными словами вразнобой?
0
Какая же єто проблема? Так и должно быть=))
Лечится это с помощью пары строк=))
Запись в порт:
{//Запись данных в порт
    if(Port.isOpen())
    {
        Port.write(data);
        while(Port.waitForBytesWritten(Time))//ожидаем завершения записи в порт
        {
            if(Port.isDataTerminalReady())
                return true;//Все в порядке
        }
    }
    return false;//запись не завершена
}

Чтение с порта:

if (Port.isOpen())//если порт открыт
    while (Port.waitForReadyRead(Time))//пока не считаем все данные
    {
        dataRead += Port.readAll();// +
        if(dataRead.length() > 0)//если считали хоть что то
        {
            if (dataRead.indexOf(EOT) > 0 && dataRead.indexOf(STX) >= 0 && //EOT = h04, STX = h02
                               dataRead.indexOf(EOT) >dataRead.indexOf(STX))//нахождение конца посылки 
                                                                        //&&  нахождение начала посылки
            {
            //Тут ковыряем принятую посылку или еще чего...
            }
        }
    }

Примерно так сделать (на 100 % идеальность кода не гарантирую) будет у Вас все приниматься нормально.
0
Спасибо! Починил.
0
Не за что=)) надеюсь код помог
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.