Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > С/С++: Кроссплатформенное программирование, Qt/Gtk+/wxWidgets > инкапсуляция данных в пакет Xmodem


Автор: daemonaz 8.1.2012, 13:44
Я новичок в Qt, у меня цель написать код, который бы отправлял прошивку флеш микроконтроллера, подключенный к COM-порту,  по протоколу Xmodem, я создал отдельный класс Xmodem, но не знаю как правильно было бы реализовать инкапсуляцию данных из файла в пакет xmodem, есть ли у кого нибудь готовая реализация? Буду благодарен если кто-нибудь посоветует куда посмотреть.. Вопрос вот в чем, я использую в качестве пакета QByteArray, куда инкапсулирую данные прочитанные из файла, но получилось у меня довольно громоздко из трех массивов:   один -для данных, которые я извлек из файла, второй - для служебной информации пакета (SOH, num, ~num), затем слиянием двух массивов делаю третий и отправляю в COM-порт. Как сделать более компактно и быстро? Есть вариант с использованием сериализации DataStream, но не знаю как его использовать в данном случае

Автор: borisbn 8.1.2012, 17:48
Цитата(daemonaz @  8.1.2012,  13:44 Найти цитируемый пост)
Как сделать более компактно и быстро?

Нормальное у тебя решение. Зачем тебе быстрее ? Передача по COM-порту всё равно на порядки медленнее, чем конкатинация двух массивов в третий.
Цитата(daemonaz @  8.1.2012,  13:44 Найти цитируемый пост)
Есть вариант с использованием сериализации DataStream, но не знаю как его использовать в данном случае

А вот это не советую, т.к. QDataStream добавлет в поток свои данные. Вот, почитай http://forum.vingrad.ru/forum/topic-344912.html

Автор: daemonaz 8.1.2012, 23:26
borisbn,  спасибо! 
Вот что получилось
Код


while (1)
{

// xbuff - буфер пакета, ybuff -  пакет данных с CRC
// затем конкатенацией их объединяем в один буфер xbuff

       xbuff.clear();

        xbuff.append(SOH);// стартовый байт
        xbuff.append(packetno);// номер пакета
        xbuff.append(~packetno);

        ybuff.clear();
        ybuff.append(src.left(128));// пишем блок из 128 байт данных
        src.remove(0, 128);

        ybuff.append(QByteArray().fill(CTRLZ, 128-ybuff.length()));
        ushort ccrc = crc16_ccitt(ybuff);
        ybuff.append((ccrc>>8) & 0xFF);
        ybuff.append(ccrc & 0xFF);

        xbuff.append(ybuff);

        qDebug("len %d\n\r", xbuff.length());

//////////////////////////////////////
        QString out;

        QTextStream str(&out);

        for (int i=0;i<xbuff.length();i++)
            str << QString().sprintf("%02X", xbuff.at(i));

        qDebug() << "Packet: " << out;

        /////////////////////////////////////////

//..................... отправка в COM-порт и ожидание подтверждения

        ++packetno;
        if (src.isEmpty())
            break;
}


Покритикуйте плиз код, это мой первый скажем так проект в QT, и заодно вопрос, хочу вывести на экран содержимое буфера побайтово, а выводится почему то в 32-хразрядном виде 0xffffffA5, как сделать чтобы массив выводился именно побайтово в виде 0xA5?
Спасибо

Автор: bsa 9.1.2012, 11:52
мне только непонятно, а почему ты не отправляешь заголовок напрямую модему, а пихаешь во временный буфер? Так, думаю, было бы значительно более эффективно. Аналогично и с данными, т.е. ты читаешь их непосредственно перед отправкой. В этом случае будет значительно проще выполнить повтор передачи при возникновении ошибки (достаточно будет в очередь пакетов добавить номер повторяемого).

Цитата(daemonaz @  9.1.2012,  00:26 Найти цитируемый пост)
Покритикуйте плиз код, это мой первый скажем так проект в QT

QT = QuickTime. Не путай с Qt.
По возможности не используй sprintf, а используй arg().
Цитата(daemonaz @  9.1.2012,  00:26 Найти цитируемый пост)
выводится почему то в 32-хразрядном виде 0xffffffA5, как сделать чтобы массив выводился именно побайтово в виде 0xA5?
все аргументы передаются в объеме кратном int. Таким образом, когда ты передаешь char, то она автоматически конвертируется в int. А это приводит к тому, что все числа большие 0x7f будут отрицательными. Поэтому, тебе необходимо вручную преобразовать к беззнаковому целому, а уже потом передавать.
В твоем случае код подготовки к выводу можно переписать так:
Код
qDebug() << "Packet:" << QString::fromLatin1(xbuff.toHex());


Автор: daemonaz 11.1.2012, 15:38
bsa, спасибо! smile 

Автор: daemonaz 12.1.2012, 20:40
Мне нужно отправить через COM-порт заготовленный пакет, но с ожиданием подтверждения, по протоколу xmodem, и чтобы не висеть в бесконечном цикле ожидания подтверждения по таймауту я хотел бы реализовать это через поток, но не знаю как это можно сделать, буду благодарен если будет примерчик такого способа реализации.. Планируется использовать при передаче файла QProgressDialog, где-то читал что можно реализовать через поток. Буду благодарен за любую помошь.

Автор: kuzulis 12.1.2012, 23:08
Цитата

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


Да не нужен тебе поток в этом случае, тем более, если не еще знаешь что это такое и как им пользоваться.
Используй сигналы/слоты для этого + QTimer для ожидания ответа.

Также рекомендуется использовать готовые библиотеки для I/O через Serial порты, т.к. не понятно как ты там реализуешь у себя I/O и т.п.

--

Также вместо crc16_ccitt() можно использовать Qt-шные функции (они вроде тоже могут считать CRC по разным там полиномам).


Автор: daemonaz 13.1.2012, 08:52
Цитата(kuzulis @  12.1.2012,  23:08 Найти цитируемый пост)
Используй сигналы/слоты для этого + QTimer для ожидания ответа.Также рекомендуется использовать готовые библиотеки для I/O через Serial порты, т.к. не понятно как ты там реализуешь у себя I/O и т.п.


Я использую библиотеку QextSerialPort, я обратил внимание на сигнал void QIODevice::readyRead () , он реализован в waitForReadyRead(), не это имели ввиду? Только хотелось бы понять механизм работы этой функции, или там сидим пока не придет сигнал, или же можем выполнять другую работу пока сигнал приема не придет? 

под Qt-шной функцией имеете ввиду qChecksum()? А где инициализируется полином?

Автор: bsa 13.1.2012, 10:03
Цитата(daemonaz @  13.1.2012,  09:52 Найти цитируемый пост)
Только хотелось бы понять механизм работы этой функции, или там сидим пока не придет сигнал, или же можем выполнять другую работу пока сигнал приема не придет
это не функция, это сигнал. соединяешь со своим слотом и он вызывается, когда придут данные. Так же делаешь таймер и соединяешь его сигнал с другим своим слотом. Если он вызовется, значит время ожидания ответа истекло. Естественно, когда данные приходят таймер необходимо перезапускать.
Цитата(daemonaz @  13.1.2012,  09:52 Найти цитируемый пост)
А где инициализируется полином?
мне тоже интересно...

Автор: daemonaz 13.1.2012, 10:41
Цитата(bsa @  13.1.2012,  10:03 Найти цитируемый пост)
это не функция, это сигнал. соединяешь со своим слотом и он вызывается, когда придут данные. Так же делаешь таймер и соединяешь его сигнал с другим своим слотом. Если он вызовется, значит время ожидания ответа истекло. Естественно, когда данные приходят таймер необходимо перезапускать.


Я так понимаю это имеет смысль если реализованы отправка пакета и ожидание приема по отдельности, а если они в одной функции

Код


void mySerial::xmodemSend(const QString &fileName)
{
....

while (1)
{
     // подготовка пакета к отправке


     send(packet);

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

    bool flag = wait_(&s, TIMEOUT);
    if (flag){
         // принято подтверждение
    }// иначе таймаут, значит повторим или отмена

}

.......

}



А вообще сушествует ли готовый класс для работы с протоколом X-modem, Ymodem?

Автор: kuzulis 13.1.2012, 12:39
Цитата(daemonaz)

Я использую библиотеку QextSerialPort

Лучше эту https://gitorious.org/qserialdevice/qserialdevice/archive-tarball/2.0

Цитата(daemonaz)

Я так понимаю это имеет смысль если реализованы отправка пакета и ожидание приема по отдельности, а если они в одной функции

Нене, так делать не нужно, т.к. твой цикл while(1), затормозит/приостановит Qt-шный Event loop и у тебя будут тормоза GUI
во время твоей транзакции.

Цитата(daemonaz)

А вообще сушествует ли готовый класс для работы с протоколом X-modem, Ymodem?

В Qt их точно нету. Да и что ты подразумеваешь под готовым классом? Что он должен делать? ;)

Цитата(bsa)

мне тоже интересно... 


Упс... Я думал что это запилили, судя по "утвердительным" высказываниям  на форумах:

http://developer.qt.nokia.com/forums/viewthread/13128
http://developer.qt.nokia.com/doc/note_revisions/136/229/view

Значит я ошибся :(

Это плохо, если нет такой возможности. 
Нужно запилить!!!  smile 

Автор: daemonaz 13.1.2012, 16:42
Цитата(kuzulis @  13.1.2012,  12:39 Найти цитируемый пост)
Цитата(daemonaz)Я так понимаю это имеет смысль если реализованы отправка пакета и ожидание приема по отдельности, а если они в одной функцииНене, так делать не нужно, т.к. твой цикл while(1), затормозит/приостановит Qt-шный Event loop и у тебя будут тормоза GUIво время твоей транзакции.


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

Автор: bsa 13.1.2012, 17:23
daemonaz, открываешь порт (объект serial типа SerialPort). Открываешь файл (объект file типа QFile). Подключаешь сигнал readyRead к слоту readData (или как ты его назвал) твоего класса. Этот слот читает данные из порта при вызове. Теперь тебе необходимо отправить признак начала передачи и проинициализировать переменную - номер текущего блока.
Внутри readData ты читаешь данные из порта, на их основании принимаешь решение о дальнейших действиях. Если все нормально, ты посылаешь текущий блок, предварительно считав его в буфер. Или еще что-нибудь. Все. функция завершилась. Но когда придут очередные данные она снова будет вызвана.

Автор: kuzulis 13.1.2012, 17:24
Цитата(daemonaz)

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


вот идея:
Цитата(bsa)

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


т.е как то так:
Код


class Xmodem : public QObject
{
    Q_OBJECT
signals:
    void oneBlockTransactionComplete();

public:

    Xmodem() : m_currentSendBlockNumber(0) {
        m_port = new SerialPort(this);
        connect(m_port, SIGNAL(readyRead(), this, SLOT(checkAvailable()));
        ...
        // открываем порт, настраиваем
        ...
        
        m_timer = new QTimer(this);
        connect(m_timer, SIGNAL(timeout()), this, SLOT(processResponse()));
        m_timer->setInterval(100); // это максимальный интервал ожидания ответа от устройства, в мсек.
        ...

        connect(this, SIGNAL(oneBlockTransactionComplete(), this, SLOT(sendRequest()));
    }

public slots:

    void sendRequest(const QByteArray &request) {

        if (m_currentSendBlockNumber < maxSendBlockNumber) { // еще не все отправили
            m_timer->start();
            m_port->write(request);
        } else {
            // всё уже отправили
        }
    }

private slots:

    void checkAvailable() {
        if (m_port->dataAvailable() < expectedResponseSize) {
           // если пришло в буфер кол-во байт меньше чем ожидалось, то ничего не делаем
        } else {
            processResponse();       
        }

    void processResponse() {
        m_timer->stop();
        
        QByteArray response = m_port->read(expectedResponseSize);

        // тут проверяем CRC, разные поля и т.п.

        // если все ОК, то инкремент номера блока
        // чтобы послать следующий блок
        if (ok) { 
            ++m_currentSendBlockNumber;
        }

        emit oneBlockTransactionComplete();
    }

private:

    SerialPort *m_port;
    QTimer *m_timer;

    int m_currentSendBlockNumber;



Идея думаю ясна smile

А вообще, такого рода задачи необходимо делать через State Machine, оно тогда будет нагляднее и меньше ошибок сделаешь, ИМХО

Автор: daemonaz 13.1.2012, 19:33
Спасибо, kuzulis, супер! smile 

Автор: daemonaz 14.1.2012, 12:48
Цитата(kuzulis @  13.1.2012,  17:24 Найти цитируемый пост)
А вообще, такого рода задачи необходимо делать через State Machine, оно тогда будет нагляднее и меньше ошибок сделаешь, ИМХО

А вот это уже интересно, я правда имел дело с микроконтроллером, там да, понятно, а в Qt есть вроде QstateMachine, не это имели ввиду?

Автор: kuzulis 14.1.2012, 13:13
Ну да. Сначала рисуешь на бумажке, какие должны быть состояния, переходы, что должно выполняться в каждом состоянии и т.п. а потом реализуеш на QStateMachine, сигналах/слотах.

Вот, раз пошла такая пьянка, советую еще это почитать: http://www.eventhelix.com/realtimemantra/patterncatalog/protocol_layer.htm
Может поможет в осмыслении глубины глубин. smile

Автор: daemonaz 25.1.2012, 08:36
Вопрос к kuzulis по поводу библиотеки, я хочу вывести принятые данные на QPlainTextEdit, делаю с помошью сигнала  readyRead()
Почему то выводится не все содержимое, а кусками, кстати с QExtSerial у меня такого не было. Хотелось бы понять где ошибка? Может не успевает обрабатывать, нужен поток?

Код

// открытие порта
..
..
connect(m_port, SIGNAL(readyRead()), this, SLOT(slot_readData()));
..

void MainWindow::slot_readData()
{
    QTextCharFormat textCharFormat = ui->teLog->currentCharFormat();
    textCharFormat.setForeground(Qt::black);
    ui->teLog->setCurrentCharFormat(textCharFormat);
    ui->teLog->appendPlainText(m_port->readAll());
}




Автор: kuzulis 25.1.2012, 10:21
Сигнал readyRead() сигнализирует о том, что имеется некоторое количество входящих данных, доступных для чтения.
Минимальное количество от 1 байта и больше.

Внутри, в классе SerialPort, при приходе данных, автоматически вызывается нативная (платформо-зависимая) функция чтения
данных, которая читает доступые в текущий момент данные в буфере UART и помещает их в кольцевой буфер класса.

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

Таким образом, если передаешь, скажем, 100 байт, то оно
будет читать это порциями, по N байт (где N - кол-во байт, успевших придти в UART).

Из этого всего следует, что метод readAll() вернет всё, что есть в внутреннем буфере класса!!!
т.е. это не значит, что вызвав readAll() ты получишь весь пакет данных целиком!

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

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








Автор: daemonaz 25.1.2012, 14:56
kuzulis, спасибо! на прием данных наконец-то вынес в поток, но проблема оставалась до сих пор, пока вместо readAll не взял read(count), все заработало.. Специфичная такая функция.. smile 

Автор: kuzulis 25.1.2012, 15:40
Цитата

kuzulis, спасибо! на прием данных наконец-то вынес в поток, но проблема оставалась до сих пор, пока вместо readAll не взял read(count), все заработало.. Специфичная такая функция.. smile  

Да и  read(count) аналогично работает: если запрашиваешь кол-во данных, превышающее их реальное текущее значение в буфере - то сразу вернет только то что в буфере, а не count, также не будет ждать пока набегут все count. 

В принципе, readAll() и read(<стопицотмильеновбайт>) эквивалентны, за исключением того, что в readAll() происходит некий оверхед и она чуть медленнее работает.

Автор: tzirechnoy 25.1.2012, 19:13
А есть ли смысл писать свой xmodem, а не использовать бинарник sx из lrzsz?

Автор: daemonaz 30.1.2012, 13:51
tzirechnoy, я рад бы использовать их, сам пишу под Линух, но юзеры хотят на windows only.. 

Автор: tzirechnoy 30.1.2012, 15:13
Вы так говорите, будто lrzsz под windows мало.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)