Последнее обновление:
November 20, 2017

Есть мысль... Жми, напиши!
Что имеем: Постов : 171 Авторов: 1 Категорий: 38

QEventLoop and connect/disconnect lambda function

Приветствую!

Кому некогда, можно сразу прыгнуть в конец к итогу.

Бывают ситуации, когда нужно синхронно дождаться завершения асинхронного действия, при этом не подвешивая основной поток (например, не продолжать выполнение функции, пока ответ в QTCPSocket onReadyRead от сервера не придёт).   

В нашем случае для примера давайте подождём с выполнением функции, пока таймер не досчитает до 5. 

Делаем основу:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QGuiApplication>
#include <QDebug>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз

    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";
        _timer->start(5000); //-- 5 секунд в миллисекундах


        qDebug()<<"Timer END";
    }

signals:

public slots:

private:
    QTimer * _timer;
};

#endif // APPCORE_H

Но если мы запустим так, то вывод  «Timer END» произойдёт без какой-либо задержки, т.к. таймер ведёт отсчёт асинхронно в другом потоке.

Решением в «лоб» было бы подписаться на событие срабатывания таймера, объявить флаг срабатывания и в бесконечном цикле отслеживать его, как-то так, например:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
        connect(_timer, &QTimer::timeout, this, &AppCore::onTimeOut);

    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";
        _timer->start(5000); //-- 5 секунд в миллисекундах

        while (true) {
            if (_timeOut) {
                break;
            }
            QGuiApplication::processEvents();
        }

        qDebug()<<"Timer END";
    }

public slots:
    void onTimeOut()
    {
        _timeOut = true;
    }

private:
    QTimer * _timer;
    bool _timeOut=false;
};

#endif // APPCORE_H

Кстати, если бы мы не указали «QGuiApplication::processEvents();«, то слот «onTimeOut()» не вызвался бы никогда, т.к. цикл у нас бесконечный, а так мы заставляем всё таки обработать события, а заодно и не подвешивать сильно интерфейс. 

Но решение это слишком топорное и не красивое. Что бы не использовать бесконечные циклы у Qt есть QEventLoop.  Выполнение функции приостанавливается методом «exec()» и QEventLoop ждёт, пока не будет вызван метод «exit()» и лишь потом продолжается.

Делаем:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QEventLoop>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
        connect(_timer, &QTimer::timeout, this, &AppCore::onTimeOut);

    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";

        _timer->start(5000); //-- 5 секунд в миллисекундах
        _loop.exec(); //-- Ждём, пока будет вызван exit();

        qDebug()<<"Timer END";
    }

public slots:
    void onTimeOut()
    {
        _loop.exit();
    }

private:
    QTimer * _timer;
    QEventLoop _loop;
};

#endif // APPCORE_H

Стало чуть-чуть красивее, но у нас всё ещё висит одноразовый слот «onTimeOut()» и одноразовая переменная «_loop«.  Это сейчас она одна, а если в нашем классе нужно в 5 разных местах дожидаться ответов? Как-то по 5 одноразовых слотов и переменных иметь некрасиво…

Благо в Qt начиная с 5 версии появилась возможность при соединении сигнал-слота вместо слота использовать лямбда-функцию, этой фишкой мы и воспользуемся, что бы избавиться он объявления глобального слота «onTimeOut()» и переменной «_loop»

Делаем: 

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QEventLoop>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";

        QEventLoop _loop;

        connect(_timer, &QTimer::timeout, [&](){
            _loop.exit();
        });

        _timer->start(5000); //-- 5 секунд в миллисекундах
        _loop.exec(); //-- Ждём, пока будет вызван exit();

        qDebug()<<"Timer END";
    }

private:
    QTimer * _timer;

};

#endif // APPCORE_H

Запускаем и ровно через 5 секунд после «Timer BEGIN» у нас выведется «Timer END»  и вроде бы добились чего хотели, но тут есть одна тонкость. Для наглядности я в лямбду добавлю вывод информации о срабатывании, вот так теперь она выглядит::

connect(_timer, &QTimer::timeout, [&_loop](){
    qDebug()<<"TIME OUT!";
    _loop.exit();
});

Допустим нам нужно в нескольких местах ждать ответа, и мы два раза вызываем функцию:

    ....
    waitFunction();
    waitFunction();
    ....

Вывод будет такой:

Timer BEGIN
TIME OUT!
Timer END
<br>Timer BEGIN
TIME OUT!
TIME OUT!
Timer END

Как видите, «TIME OUT» после второго вызова вывелось два раза! А если бы мы функцию «waitFunction()» вызвали 10 раз, то соответственно «TIME OUT» вывелось бы то же 10 раз подряд.  Так явно не должно быть! В чём дело?  А дело в том, что лямбда функция автоматически не отключается! Если забыть про эту фишку можно нарваться в том числе и на «SIGSEGV Segmentation fault», сегфолт короче. 

Решение — это не забывать отключать (disconnect signal lambda) сигнал от лямбды, но просто так это сделать не получится, т.к. это всё таки лямбда, нужно запоминать информацию, которую возвращает метод «QMetaObject::Connection conn = connet(….)«, а по ней отключать «disconnect(conn)«. 

Делаем:

void waitFunction()
{
    qDebug()<<"Timer BEGIN";

    QEventLoop _loop;

    QMetaObject::Connection conn = connect(_timer, &QTimer::timeout, [&_loop](){
        qDebug()<<"TIME OUT!";
        _loop.exit();
    });

    _timer->start(5000); //-- 5 секунд в миллисекундах
    _loop.exec(); //-- Ждём, пока будет вызван exit();

    disconnect(conn);

    qDebug()<<"Timer END";
}

Срабатывать будет  один раз и отключаться, вывод придёт в норму:

Timer BEGIN
TIME OUT!
Timer END

Timer BEGIN
TIME OUT!
Timer END

Но об  «disconnect(….)»  можно забыть, поэтому предлагаю использовать умные указатели, а именно QSharedPointer, но у него нужно не забыть реализовать отключение, т.к. сам по себе он это делать не умеет, а так как писанины получается многовато, поэтому предлагаю запилить макрос.

Итоговый код:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QEventLoop>
#include <QSharedPointer>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
    }

    #define AutoDisconnect(l) \
        QSharedPointer<QMetaObject::Connection> l = QSharedPointer<QMetaObject::Connection>(\
            new QMetaObject::Connection(), \
            [](QMetaObject::Connection * conn) { /*QSharedPointer сам по себе не производит отключения при удалении*/ \
                QObject::disconnect(*conn);\
            }\
        ); *l //-- Use AutoDisconnect(conn1) = connect(....);

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";

        QEventLoop _loop;

        AutoDisconnect(conn) = connect(_timer, &QTimer::timeout, [&_loop](){
            qDebug()<<"TIME OUT!";
            _loop.exit();
        });

        _timer->start(5000); //-- 5 секунд в миллисекундах
        _loop.exec(); //-- Ждём, пока будет вызван exit();

        qDebug()<<"Timer END";
    }

private:
    QTimer * _timer;

};

#endif // APPCORE_H

Вот как-то так =)

P.S. Макросы не зло, нужно просто уметь их готовить 😉

Views :

7

Layout.fillWidth: true и Layout.preferredWidth зависимость (очередная хитрость)

Сталкиваюсь иногда с некоторыми хитростями в QML, о которых, по всей видимости, приходится только догадываться, ибо то ли я проглядел это в документации, то ли этого действительно в ней не указано.

Так вот, задача: нужно три колонки одинаковой ширины.

Делаем:

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QMLTips&Tricks24")

    RowLayout {
        anchors.fill: parent
        spacing: 0

        Rectangle {
            color: "blue"
            Layout.fillHeight: true
            Layout.fillWidth: true            
        }

        Rectangle {
            color: "orange"
            Layout.fillHeight: true
            Layout.fillWidth: true
        }


        Rectangle{
            color: "green"
            Layout.fillHeight: true
            Layout.fillWidth: true            
        }

    }
}

Соответственно на выходе получаем:

Получилось как и задумывали.

Обновим задачу:  Оранжевая колонка должна иметь предпочтительную ширину 200 пикселей.
На тот случай, если мы не будем ставить Layout.fillWidth в true, ну что бы он занимал какое-то место по умолчанию.

Делаем:

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Получаем:

Что-то не то, да..? 

Вот тут начинается самое интересное.

Дело в том, что Layout.prefferedWidth управляет пропорционально шириной относительно соседей, когда задано Layout.fillWidth: true

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

Делаем:

Rectangle {
    color: "blue"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle{
    color: "green"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Получаем:

То есть как и задумывали, три одинаковые колонки.

Теперь наглядный пример насчёт пропорционально соседям изменяемости размеров. Если мы у синего прямоугольника зададим желаемую ширину в 50, а у зелёного 100, то соответственно синий будет в 4 раза меньше оранжевого (200/50=4), а зелёный буде в два раза меньше оранжевого (200/100=2). 

То есть родитель Layout (RowLayout/ColumnLayout/GridLayout) берёт от потомков наибольший желаемый размер и относительно него пропорционально выставляет ширину всем потомкам.
Повторюсь, что так QML себя ведёт, только когда задано Layout.fillWidth: true

Делаем:

Rectangle {
    color: "blue"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 50
}

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle{
    color: "green"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 100
}

Получаем:

Надеюсь, теперь больше с этим проблем не возникнет.

Кстати, такое же поведение будет если у компонента задано свойство implicitWidth или implicitHeight, потому что если мы не задали явно Layout.preferredWidth или  Layout.fillHeight, то берутся как раз они. Наверное вы спрашиваете: как быть, если это не просто прямоугольник, а какой-то компонент? То всё просто — оберните его в Item или Rectangle или задайте явно Layout.preferredWidth

Кстати, вот документация на лэйауты: тынц.

Вот как-то так =)

Views :

5

Кросскомпиляция Qt 5.7, Qt 5.8 для Raspberry Pi 1,2,3 под Windows

В общем на Ubuntu скомпилили, открываем пост и компилим теперь под Windows.

1. Качаем актуальную версию Raspbian Jessie  и с помощью WinFLASHTool  пишем её на сд карточку.

2. так же.

3.

Качаем msys2, ставим в папку C:\SysGCC\msys2\

Качаем MinGW 4.9.2, распаковываем в папку C:\SysGCC\mingw32

Качаем Python 2.7.x

Запускаем C:\SysGCC\msys2\mingw32.exe,

pacman -Syu #попросит закрыть - закрываем, запускаем вновь и прописываем далее: 
pacman -Su 
pacman -S openssh 
pacman -S rsync
pacman -S make 
pacman -S perl 
pacman -S python
pacman -S python2
pacman -S wget
pacman -S patch
pacman -S pkg-config 
pacman -S diffutils

и остальное прописываем так же

4.  Качаем тулчейн и ставим в C:\SysGCC\Raspberry

5. Запускаем C:\SysGCC\Raspberry\TOOLS\UpdateSysroot.bat, нажимаем select, подключаемся к малине (пользователь «pi» пароль «raspberry») и в список синхронизации дописываем

/opt/vc/

6. Пропускаем, за нас это сделал шаг 5.

7.

cd /c/SysGCC/Raspberry
git clone --recursive https://github.com/qt/qt5 -b 5.7 Qt57Sources
cd Qt57Sources
BASEPATH=/c/SysGCC/Raspberry
PATH=$PATH:/c/SysGCC/Raspberry/bin
PATH=$PATH:/c/SysGCC/mingw32/bin
./configure -skip wayland -c++std 11 -skip webkit -release -opengl es2 -device linux-rasp-pi-g++ -device-option CROSS_COMPILE=$BASEPATH/bin/arm-linux-gnueabihf- -sysroot $BASEPATH/arm-linux-gnueabihf/sysroot -opensource -confirm-license -platform win32-g++ -xplatform linux-arm-gnueabihf-g++ -skip qtscript -make libs -prefix $BASEPATH/qt5pi -extprefix $BASEPATH/qt5pi -hostprefix $BASEPATH/qt5 -v
make -j 4
make install

Здесь компилится для Raspberry Pi 1, для 2 и 3 в исходном посте.

Для Qt 5.7 нужно применить патчик:

wget http://pavelk.ru/wp-content/uploads/qt57raspberrypi.patch
patch -p1 < qt57raspberrypi.patch

8. так же

9. так же

10. так же

11. так же

12. так же

12.1. так же

12.2. Путь C:\SysGCC\Raspberry\bin\arm-linux-gnueabihf-g++

12.3. Путь C:\SysGCC\Raspberry\bin\arm-linux-gnueabihf-gdb
           для qmake путь C:\SysGCC\Raspberry\qt5\bin\qmake

12.4. так же

12.5. так же

14. так же

15. ????

16. PROFIT!

Views :

816

Кросскомпиляция Qt 5.9 для Raspberry Pi3

Пока в разгаре новогодние праздники захотелось попробовать в действии Raspberry Pi (модель 1, но так же подходит и для 2,3), а именно чего-нибудь для неё написать, хотя бы Hello World с помощью Qt. Ставить весь Qt на саму малинку как-то долго, да и пока на ней компилируется простейшая программа можно упиться в усмерть, поэтому будем настраивать кроссс-компиляцию, что бы всю разработку вести на своём пк (Ubuntu 16.10  x64).

Приступаем.

Обновлено для Qt 5.9.1 и Raspbian 2017-09-07 Stretch для Raspberry PI3 ModelB

1. Первым делом необходимо скачать образ ОС для малинки, это будет raspbian и залить её на SD карточку, которая уже должна быть воткнута.

mkdir ~/Projects/RaspberryPI 
cd ~/Projects/RaspberryPI 
wget https://downloads.raspberrypi.org/raspbian/images/raspbian-2017-09-08/2017-09-07-raspbian-stretch.zip
unzip 2017-09-07-raspbian-stretch.zip 
sudo dd if=2017-09-07-raspbian-stretch.img of=/dev/mmcblk0 bs=4M
sync
Что бы узнать адрес флэшки, наберите lsblk

Если так случалось, что нет картридера, то залить прошивку можно и на винде с помощью win32diskimager (запускать от рута)

2. Когда заливка завершиться, вытаскиваем СДшку и загружаем с неё малинку.

Теперь её немножко надо настроить, переходим Menu -> Preferences -> Raspberry Pi configuration

Жмём «expand filesyste», включаем «ssh», перезагружаемся.

Подключаемся к сети (можно по вафле), вбиваем в консольке (Menu->Accessories->Terminal)  ifconfig, запоминаем айпишник, на этом пока что всё.

3. Приконнектимся к малинке по ssh, раскомментим получение исходников пакетов, установим нужные пакеты и создадим нужные папки

ssh pi@192.168.2.101 (пароль по дефолту "raspberry")
sudo nano /etc/apt/sources.list  #расскомментить строчку с deb-src
sudo apt-get update
sudo apt-get install -y libfontconfig1-dev libfreetype6-dev libx11-dev libxext-dev libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libx11-xcb-dev libxcb-glx0-dev
sudo apt-get install -y libxcb1 libxcb1-dev libx11-xcb1 libx11-xcb-dev libxcb-keysyms1 libxcb-keysyms1-dev libxcb-image0 libxcb-image0-dev libxcb-shm0 libxcb-shm0-dev libxcb-icccm4 libxcb-icccm4-dev libxcb-sync1 libxcb-sync-dev libxcb-render-util0 libxcb-render-util0-dev libxcb-xfixes0-dev libxrender-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-glx0-dev libxcb-xinerama0-dev
sudo mkdir /usr/local/qt5pi
sudo chmod -R 777 /usr/local/qt5pi

4. Так, возвращаемся к хосту, качаем тулчейн:

mkdir raspi
cd raspi
git clone https://github.com/raspberrypi/tools

5. Создаём sysroot и через rsync синхронизируем его с малиновым, что бы оттуда взять заголовочники, либы и компилятор

IP=192.168.2.101 # айпишник малинки
rsync -avz pi@$IP:/lib sysroot  # опять идём пить кофе
rsync -avz pi@$IP:/usr/include sysroot/usr
rsync -avz pi@$IP:/usr/lib sysroot/usr # можно сходить просраться
rsync -avz pi@$IP:/opt/vc sysroot/opt

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

wget https://raw.githubusercontent.com/riscv/riscv-poky/master/scripts/sysroot-relativelinks.py
chmod +x sysroot-relativelinks.py
./sysroot-relativelinks.py sysroot

7. Теперь можно склонировать Qt, сконфигурировать и запустить на компиляцию

git clone git://code.qt.io/qt/qt5.git Qt59Sources
perl init-repository
git checkout 5.9.1
git submodule update --recursive

cd Qt59Sources
BASEPATH=~/Projects/RaspberryPI/raspi # базовый путь, где все наши манипуляции происходят (без слэша на конце)
./configure -skip wayland -skip webkit -skip script -qt-xcb -no-pch -no-use-gold-linker -nomake tests -nomake examples -reduce-exports -release -opengl es2 -device linux-rasp-pi3-g++ -device-option CROSS_COMPILE=$BASEPATH/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf- -sysroot $BASEPATH/sysroot -opensource -confirm-license -make libs -prefix $BASEPATH/qt5pi -extprefix $BASEPATH/qt5pi -hostprefix $BASEPATH/qt5 -v
make -j 4 # и поспим часик (4 - количество ядер проца, для ускорения)
make install
Для Qt < 5.9.1 нужен -device linux-rpi3-g++
Если возникли ошибки при сборке и нужно переконфигурировать с другими параметрами, не забудьте удалить предыдущий результат, набрав «git clean -dxf»  и «make clean» иначе будет ещё хуже =)
При ошибке с JavaScriptCore/wtf/Platform.h:370:6: error: #error «Not supported ARM architecture» добавьте к конфигурации «-skip script» или добавьте к make флаг:»make CFLAGS=»${CFLAGS}-D__ARM_ARCH_7M__»»  (взял из /3rdparty/javascriptcore/JavaScriptCore/wtf/Platform.h)
При ошибке с PCRE2: PCRE2_CODE_UNIT_WIDTH, LINK_SIZE и прочие мессаджы с ней связанные  (отвечает за регулярные выражения)  можно либо отключить ( в конфиге «-no-pcre»), но почему-то у меня не было такого параметра,
либо прописать все дефайны вручную: make CFLAGS=»${CFLAGS} -ldl -DPCRE2_CODE_UNIT_WIDTH=16 -DHAVE_INTTYPES_H=1 -DHAVE_MEMMOVE=1 -DHAVE_LIMITS_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STDINT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DLINK_SIZE=2 -DMATCH_LIMIT=10000000 -DMATCH_LIMIT_RECURSION=10000000 -DMAX_NAME_COUNT=10000 -DMAX_NAME_SIZE=32 -DNEWLINE_DEFAULT=2 -DPARENS_NEST_LIMIT=250 -DSUPPORT_UNICODE»  причина этому — почему-то не подключается файл config.h (Расположен qtbase/src/3rdparty/pcre2/src/config.h), где прописаны все эти дефайны.
При ошибке с zlib, можно её отключить -no-zlib (скорее всего каких-то либ на хосте нехватает, но т.к. мне было не важно, не стал разбираться, остальных проблем хватило)
При ошибке «ERROR: The OpenGL functionality tests failed!
You might need to modify the include and library search paths by editing QMAKE_INCDIR_OPENGL[_ES2],
QMAKE_LIBDIR_OPENGL[_ES2] and QMAKE_LIBS_OPENGL[_ES2] in the mkspec for your platform.» ИЛИ «In function `QEGLPlatformContext::getProcAddress(char const*)’:
qeglplatformcontext.cpp:(.text+0xa4): undefined reference to `dlsym'»  для Raspbian Stretch нужно подредактировать nano ./qtbase/mkspecs/devices/linux-rasp-pi3-g++/qmake.conf  и изменить названия либ «-lEGL» и «-lGLESv2» на  «-lbrcmEGL» и «-lbrcmGLESv2» так как названия в /opt/vc/lib/ отличаются.  И make запускать так: «make CFLAGS=»${CFLAGS} -ldl»» или так: «LIBS=-ldl ./configure«. Хрен знает что из этого помогло, скорее первое. 
В случае ошибки  «error: invalid use of incomplete type ‘X509 {aka struct x509_st}’«, то это баг Qt: https://bugreports.qt.io/browse/QTBUG-52905. Исправления будут в версии 5.10, так что либо отключайте ssl при конфигурации: «-no-openssl» либо даунгрейдите openssl до 1.0

8. Теперь закинем на малинку скомпилированные библиотеки и заголовочники Qt:

cd ../
rsync -avz qt5pi pi@$IP:/usr/local

9. Ну и можно собрать пример и закинуть на малинку:

cd Qt59Sources/qtbase/examples/opengl/qopenglwidget
$BASEPATH/qt5/bin/qmake
make
scp qopenglwidget pi@$IP:/home/pi

10. На девайсе необходимо дать знать линковщику о наших либах, а так же создать qt.conf в папке, откуда будем запускать все Qt приложения:

ssh pi@192.168.2.101 (пароль по дефолту "raspberry")
echo /usr/local/qt5pi/lib | sudo tee /etc/ld.so.conf.d/00-qt5pi.conf
echo QT_PLUGIN_PATH=/usr/local/qt5pi/plugins/ | sudo tee -a /etc/environment
echo QT_QPA_FONTDIR=/usr/share/fonts/truetype/dejavu | sudo tee -a /etc/environment
printf "[Paths]\nPlugins=/usr/local/qt5pi/plugins\nQml2Imports=/usr/local/qt5pi/qml" | sudo tee ~/qt.conf
cd /usr/local/qt5pi/lib
sudo ldconfig
При проблеме «QFontDatabase: Cannot find font directory /home/pi/lib/fonts.
Note that Qt no longer ships fonts. Deploy some (from http://dejavu-fonts.org for example) or switch to fontconfig.»  нужно указать, где лежат шрифты, для этого добавим переменную окружения (выше уже добавлена) : «echo QT_QPA_FONTDIR=/usr/share/fonts/truetype/dejavu | sudo tee -a /etc/environment» видимо какую-то опцию забыл в конфиге добавить, наверное «-fontconfig«.

Перезагружаем малинку.

11. Но запускать ещё рано, у rasbian по дефолту грузиться mesa драйвер и opengl пахать не будет по нормальному, поэтому заставим её использовать нужный:

Не факт! Сначала всё таки лучше попробовать запустить =)
sudo rm /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0
sudo ln -s /opt/vc/lib/libEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0
sudo ln -s /opt/vc/lib/libGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0
sudo ln -s /opt/vc/lib/libEGL.so /opt/vc/lib/libEGL.so.1
sudo ln -s /opt/vc/lib/libGLESv2.so /opt/vc/lib/libGLESv2.so.2

Вот теперь наконец-то можно запустить пример! Он на малинке /home/pi/qopenglwidget

Если не запускается, в консольке прописываем

export QT_LOGGING_RULES=qt.qpa.*=true
./qopenglwidget

И гуглим ошибки.

Если совсем не запускается, то прописываем:

ldd ./qopenglwidget

И смотрим по правильному адресу ли лежат библиотеки и всех ли хватает. Потом гуглим на тему ldconfig.

12. С этим разобрались, теперь настроим QtCreator что бы можно было компилить и запускать на малинке в один клик:

12.1. Параметры->Устройства->Добавить
Обычное Linux-устройство
название на свой вкус и цвет
вводим айпишник, логин и пароль
завершить

12.2. Параметры->Сборка и запуск->Компиляторы->Добавить
GCC -> C++
Название: Raspberry PI3 GCC
Путь: /home/pavelk/Projects/RaspberryPI/raspi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-g++

12.3. Параметры->Сборка и запуск->Отладчики->Добавить
Название: Raspberry PI3 GDB
Путь: /home/pavelk/Projects/RaspberryPI/raspi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gdb

12.4. Параметры->Сборка и запуск->Профили Qt->Добавить
Название: Qt 5.9.1 Raspberry PI3
Путь: /home/pavelk/Projects/RaspberryPI/qt5/bin/qmake

12.5. Параметры->Сборка и запуск->Комплекты->Добавить
Название: Raspberry PI3
Тип устройства: Обычное Linux-устройство
Устройство: выбираем добавленное из первого шага
Компилятор C++: Raspberry PI3 GCC
Отладчик: Raspberry PI3 GDB
Профиль Qt: Qt 5.9.1 Raspberry PI3

12.6. Нажимаем «Применить».

14. Создаём новый проект, в *.pro файл добавляем:

INSTALLS        = target
target.path     = /home/pi

Компилим и после завершения проект должен запуститься на малинке.

Вот как-то так в общем =)

P.S. Большая часть была взята с https://wiki.qt.io/RaspberryPi2EGLFS с моими небольшими правками — думал всё сложнее будет =)

 

Views :

3359

Программирование и отладка STM32F3 Discovery в QtCreator под Windows

Впринципе, алгоритм действий точно такой же, как и в предыдущем посте под Ubuntu

Обновил ссылку на новый ARM GCC

Здесь приведу лишь отличия по пунктам

  1. Качаем  драйвер, распаковываем и ставим. Вместо ST-Link поставим OpenOCD  , скачиваем, распаковываем в любую папку.
  2. так же
  3. Качаем GCC ARM с https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads (справа в списке экзешник) и ставим.  отладчик qtcreator-gdb-7-7-mingw32_nt-6-1-i686 (т.к. для Qt Creator нужно, что бы он был с поддержкой питона), распаковываем в любую папку и прописываем полный путь, имя на ваше усмотрение.
  4. Путь компилятораC:\Program Files (x86)\GNU Tools ARM Embedded\6 2017-q1-update\bin\arm-none-eabi-gcc.exe (либо в ту папку, куда поставили)
  5. При добавлении выбираем OpenOCD, запуск в режиме pipe,
    исполняемый файл: прописываете полный путь до OpenOCD.exe,
    файл конфигурации ставите <full path>\openocd-0.9.0\scripts\board\stm32f3discovery.cfg  под свою плату.
  6. Также
  7. Также

Вот как -то так =)

Если вылетает при отладке, либо при её завершении, поставьте в Qt Creator -> Инструменты -> Параметры -> Отладчик -> GDB, расширенные
галку у «Использовать асинхронный режим для работы с программой»
Если будет ошибка «Unknown remote qXfer reply: OK», то см. пункт 5.1 из статьи для Ubuntu

P.S  Вместо OpenOCD можно использовать старую добрую st-link-utility под Windows, но она старовата и, как мне кажется, тормознута.

Views :

484

Программирование и отладка STM32F3 Discovery в QtCreator под Ubuntu

Спустя три года опять решил поиграться с STM32F, но уже вплотную.

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

Ось — Ubuntu 16.04, под Windows тут недалеко.

1.  Поставим сам отладчик для STM т.е. gdb сервер

Для этого клонируем репозиторий и собираем ST-Link Utility

cd ~/Projects/ST-Link-Utility
git clone https://github.com/texane/stlink .
make release
sudo make install
sudo ldconfig
sudo udevadm control --reload-rules
sudo  udevadm trigger

Вот впринципе сервер скомпиллен, запускать его для вывода справки:

st-util -h

2. Включаем в QtCreator плагин Help -> About Plugins -> галка напротив BareMetal и перезапускаем QtCreator

3. Дальше необходимо поставить компилятор и отладчик для архитектуры ARM

sudo apt-get install gcc-arm-none-eabi
sudo apt-get install gdb-arm-none-eabi

4. Добавляем их в QtCreator

Preferences -> Build & Run -> Compillers -> Add -> GCC

Название на ваше усмотрение, у меня: arm-none-eabi-gcc

Путь прописываем такой: /usr/bin/arm-none-eabi-gcc

Preferences -> Build & Run -> Debuggers -> Add

Название на ваше усмотрение, у меня: arm-none-eabi-gdb 

Путь прописываем такой: /usr/bin/arm-none-eabi-gdb 

5. Создадим устройство, переходим в Preferences ->BareMetal -> Add ST-Link

название на ваше усмотрение, у меня ST-Link-Utility

режим запуска: TCP/IP

исполняемый файл: st-util

хост: localhost, порт: 4242

5.1 Нужно дать отладчику дополнительное время для ожидания подключения:

Options->Debugger->GDB->Additional Startup Commands и прописать

set remotetimeout 10

6. Теперь добавляем комплект сборки:

Preferences -> Build & Run -> Kits -> Add

Название на ваше усмотрение, у меня Qt for Bare Metal

Тип устройства: Bare Metal

Устройство: Нажимаете Manager -> Add -> Bare Metal

Название на ваше усмотрение, у меня ST-Link1

Тип сервера gdb:  ST-Link-Utility (из предыдущего шага)

Компилятор:  как задали в предыдущем шаге, у меня arm-none-eabi-gcc

Отладчик: как задали в предыдущем шаге, у меня arm-none-eabi-gdb

Профиль Qt: отсутствует

7. Так, с подготовкой закончили, создаём новый проект, импортировав его шаблон из Git репозитория File-> New -> Import -> Git.

Репозиторий с шаблоном: https://github.com/Riflio/STM32F3DiscoveryQtCreatorTemplate

Путь выбираете свой.

Смените комплект на Qt for Bare Metal и можно наконец то прожать Run, в окне вывод приложения должно появиться примерно это:

Отладка запущена
st-util 1.2.0-147-g3de5cf0
Flash page at addr: 0x08000000 erased
Flash page at addr: 0x08000800 erased
Flash page at addr: 0x08001000 erased

И светодиоды должны начать зажигаться по кругу.

Если вылетает при отладке, либо при её завершении, поставьте в Qt Creator -> Инструменты -> Параметры -> Отладчик -> GDB, расширенные
галку у «Использовать асинхронный режим для работы с программой»
Если будет ошибка «Unknown remote qXfer reply: OK«, то см. пункт 5.1

P.S. Как создавать шаблон под другие контроллеры?
Сделать его достаточно просто, потребуется CMSIS — в ней содержатся описания для доступа к регистрам периферии и STM32F30x_StdPeriph_Driver (в новых версиях переименован в HAL)
Всё это ищется в недрах сайта st.com    ldscripts были найдены в каком-то демо-проекте 😀

Вот как-то так =)

Views :

841

Конвертация QVideoFrame to OpenCV Mat в Qt5.6 и OpenCV3.1

Потребовалось на днях обрабатывать кадры с камеры (Использовалась QCamera) через OpenCV.

Да,  OpenCV может сам захватывать фреймы, но в случае с Qt QCamera работает лучше и есть больше параметров (например выбо р формата YUV или MJPG).

Вот так выглядит конвертация:

 QVideoFrame copy(frame);
    if (frame.isValid() && copy.map(QAbstractVideoBuffer::ReadOnly)) {
        Mat frameYUV=Mat(copy.height() + copy.height()/2, copy.width(), CV_8UC1, (void*)copy.bits() );
        
        Mat frameRGB;
        cvtColor(frameYUV, frameRGB, CV_YUV2BGRA_I420);

        imshow("Video", frameRGB);

    }

C камеры приходит QVideoFrame frame в формате YUV.

Вот и всё =)

 

 

Views :

173

Qt настраиваем логирование в проекте (формат лога)

Приветствую!

Отладочные сообщения в QtCreator достаточно не информативные, особенно в большом проекте, а каждый раз прописывать  что-то вроде:

qDebug()<<"MyClass::functionName params"<<p1<<p2;

что бы знать в каком хоть классе и функции идёт вывод достаточно долго.

Можно перехватить вывод qDebug, а заодно qInfo, qWarning и т.д. и выводить их в файл логов (либо писать в базу, либо отправлять на лог-сервер).

Делается это так:

в main.cpp  сразу после QApplication app(…); прописываем:

 qInstallMessageHandler(myMessageHandler);

В *.pro файле добавляем:

DEFINES += QT_MESSAGELOGCONTEXT

что бы обрабатывался контекст вызова, ну и делаем свой обработчик вывода:

#include <QDateTime>
void myMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg)
{
    QString txt;   
    static long long uid=0; //-- номеруем вывод
    //-- название функции с классом, берём только класс и саму функцию
    QRegExp rx("([\\w-]+::[\\w-]+)");
    if (rx.indexIn(context.function) ==-1) return;
    QString function = rx.cap(1);

    QString msgSep = (msg.length()>0)? ">> " : "";

    switch (type) {
        case QtInfoMsg:
            txt = QString("Info: %1%2%3").arg(function).arg(msgSep).arg(msg);
        break;
        case QtDebugMsg:
            txt = QString("Debug: %1%2%3").arg(function).arg(msgSep).arg(msg);
            break;
        case QtWarningMsg:
            txt = QString("Warning: %1%2%3").arg(function).arg(msgSep).arg(msg);
        break;
        case QtCriticalMsg:
            txt = QString("Critical: %1%2%3").arg(function).arg(msgSep).arg(msg);
        break;
        case QtFatalMsg:
            txt = QString("Fatal: %1%2%3").arg(function).arg(msgSep).arg(msg);
            abort();
        break;
    }

    QDateTime dateTime = QDateTime::currentDateTime();
    uid++;
    txt=QString("%1:%2 %3").arg(dateTime.toString(Qt::ISODate)).arg(uid).arg(txt);

    QFile outFile(QString("%1/log-%2.log").arg(".").arg(QDate::currentDate().toString("dd.MM.yy")));
    outFile.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream ts(&outFile);
    ts << txt << endl;
    outFile.close();


}

Дальше, если у вас Линь, то достаточно просто в консольке прописать:

tail -f  log-18.09.16.log

и весь вывод будет идти в реал тайме.

Если разбор выражения на отдельные поля не требуется, то можно сразу задать шаблон вывода:

в main.cpp перед QApplication  прописать:

qSetMessagePattern("%{type} %{if-category}%{category}: %{endif}%{function}: %{message}");

Подробнее можно найти в офф. доках: http://doc.qt.io/qt-5/qtglobal.html#qSetMessagePattern

Вот как-то так =)

Views :

522

Android MySql (QMysql) драйвер для Qt5.6 на Windows и Linux компиляция

В общем понадобилось портировать одну прогу под Андроид arm7, но она плотненько работает с базой данных, а так как я человек впринципе ленивый, то обёртку для REST API сервера было лень писать, решился на компилирование MySQL плагина, но если бы я знал тогда, что это займёт у меня 20 часов…
красноглазик

 

В общем вот вам готовый рецепт из 12 шагов для компиляции на Ubuntu и на Windows
То, что помечено windows16 -выполнять только на винде.
Установим инструменты:
1. Качаем и устанавливаем CMake https://cmake.org/download/ у меня 3.5.2
2. Разумеется Android NDK у вас уже должен быть установлен, но вдруг: https://developer.android.com/tools/sdk/ndk/index.html
windows163. Качаем и ставим MSYS https://sourceforge.net/projects/mingw/files/latest/download?source=files (отмечаем всё, что можно)
4. При установке Qt нужно было поставить и исходники (папка qt/5.6/src/qtbase), если нет её, то качаем и ставим
windows165. Открываем консольку MSYS (c:\mingw\msys\1.0\msys.bat), в дальнейшем все действия будут вестись в ней и для удобства выкачивания установим wget:

/c/mingw/bin/mingw-get install msys-wget-bin

1. Создадим отдельную директорию, где будем развлекаться и перейдём в неё:

mkdir /d/Projects/AndroidMySQL
 cd /d/Projects/AndroidMySQL

2. Выкачиваем саму MariaDB, а именно коннектор

wget https://downloads.mariadb.org/interstitial/connector-c-3.0.0/mariadb-connector-c-3.0.0-alpha-src.tar.gz
 tar -xzvf mariadb-connector-c-3.0.0-alpha-src.tar.gz

3. Выкачиваем и распаковываем исходники iconv http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz

wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz
 tar -xvzf libiconv-1.14.tar.gz

4. Выкачиваем и распаковываем OpenSSL

wget -c http://www.openssl.org/source/openssl-1.0.1h.tar.gz --no-check-certificate
 tar -xvzf openssl-1.0.1h.tar.gz

5. Задаём общие глобальные переменные
windows16

 export PATH="$PATH:/c/mingw/bin:/c/Program Files (x86)/CMake/bin"
 export ANDROID_NDK_ROOT=/d/Android/android-ndk-r10e
 export SR="$ANDROID_NDK_ROOT"/platforms/android-19/arch-arm/usr # Укажите платформу, под которую собираете ваши проекты (Лучше, 19)
 export BR="$ANDROID_NDK_ROOT"/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-
 export CPP="$BR"cpp
 export AR="$BR"ar
 export STRIP="$BR"strip
 export RANLIB="$BR"ranlib
 export LINKER="$BR"ld
 export OBJDUMP="$BR"objdump
 export CC="$BR"gcc
 export CFLAGS="--sysroot=$SR"
 export CPPFLAGS="$CFLAGS"
 export C_INCLUDE_PATH=$SR/include
 export ANDROID_DEV=$SR/usr
 export MAKEDEPPROG="$CC -M"

6. Собираем сначала iconv

cd libiconv-1.14
 ./configure --host=arm --prefix=$SR/usr --with-sysroot=$SR
 make
 make install

7. Собираем OpenSSL

cd ../openssl-1.0.1h
 CC="$CC -march=armv7-a -mfloat-abi=softfp"
 ./Configure android-armv7 --prefix=$SR/usr no-asm
 make
 make install
 make install_sw
Если заматериться error: undefined reference to ‘__ctype_get_mb_cur_max’,
то в файл libcharset/lib/localcharset.c после дефайнов добавляем

size_t __ctype_get_mb_cur_max(void){
 return 1;
 }

8. Нус, начинаем подготовку к сборке MariaDB

В файл include/my_global.h необходимо добавить определение типа ushort

#ifndef ushort
 #define ushort uint16
 #endif

9. Подготавливаем сборку

cd ../mariadb-connector-c-3.0.0-alpha-src
mkdir build
 cd build
cmake -G "Unix Makefiles"
 -DCMAKE_BUILD_TYPE=Release \
 -DCMAKE_AR="$BR"ar.exe \
 -DCMAKE_C_COMPILER="$BR"gcc.exe \
 -DCMAKE_C_FLAGS=--sysroot=$SR \
 -DCMAKE_LINKER="$BR"ld.exe \
 -DCMAKE_RANLIB="$BR"ranlib.exe \
 -DCMAKE_STRIP="$BR"strip.exe \
 -DCMAKE_SYSTEM_NAME=Linux \
 -DICONV_INCLUDE_DIR="$SR/usr/include" \
 -DICONV_LIBRARIES="$SR/usr/lib/libiconv.a" \
 -DOPENSSL_ROOT_DIR="$SR/usr/lib/" \
 ../

10. Зажмуриваемся и собираем

make
windows16 ага, фиг то там, failed to create symbolic link ‘libmysqlclient.a’: No error
внезапно… тут, похоже, баг самого CMake, поэтому в файле
\AndroidMySQL\mariadb-connector-c-3.0.0-alpha-src\build\libmariadb\CMakeFiles\SYM_libmysqlclient.a.dir\build.make
находим строки (их по две) c -E create_symlink libmariadbclient.a libmysqlclient.a, удаляем её полностью
а так же -E create_symlink libmariadb.so libmysqlclient_r.so аналогично
и вручную копируем и продолжаем сборку

cp libmariadb/libmariadbclient.a libmariadb/libmysqlclient.a
 cp libmariadb/libmariadbclient.a libmariadb/libmysqlclient_r.a
 cp libmariadb/libmariadb.so libmariadb/libmysqlclient.so
 cp libmariadb/libmariadb.so libmariadb/libmysqlclient_r.so
 make
и ещё раз фиг error: unknown type name ‘pthread_mutex_t’
в файле ../unittest/libmariadb/thread.c
находим pthread_t threads[THREAD_NUM]; заменяем на void * threads[THREAD_NUM];
и там же заменяем pthread_mutex_t LOCK_test; на void * LOCK_test;
и опять
make

На этот раз должно всё ок пройти, ну и устанавливаем и копируем к нашему ndk

make install
mkdir "$SR/usr/lib/mariadb"
 mkdir "$SR/usr/include/mariadb/"
 cp libmariadb/*.{a,so} "$SR/usr/lib/mariadb/"
 cp ../include/* "$SR/usr/include/mariadb/"

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

cd /d/Qt/5.6/Src/qtbase/src/plugins/sqldrivers/mysql/
 /d/Qt/5.6/android_armv7/bin/qmake.exe \
 "INCLUDEPATH+=$SR/usr/include/mariadb" \
 "LIBS+=-L$SR/usr/lib/mariadb -lmariadbclient -lssl -lcrypto -liconv" \
 -o Makefile mysql.pro
make
 make install

12. Разрабатываем тестовый проект
Так как зависит от libmariadb.so, то его необходимо таскать с проектом, для этого в *.pro файле проекта добавить

contains(ANDROID_TARGET_ARCH,armeabi-v7a) {
 ANDROID_EXTRA_LIBS = \
 d:/Projects/AndroidMySQL/mariadb-connector-c-3.0.0-alpha-src/build/libmariadb/libmariadb.so
 }

Пересобираем проект, запускаем, и хуй то там =)
QMYSQL driver not loaded
яоминь
Посмотрим зависимости libqsqlmysql.so, которая в каталоге /home/pavelk/Qt/5.6/android_armv7/plugins/sqldrivers

ldd libqsqlmysql.so

и опять облом, нужна ldd для arm, ладно и с этим разберёмся, например так, тынц.

ldd-avr libqsqlmysql.so

Дело в том, что в зависимостях у libmysql.so есть libmariadb.so.3, а не libmariadb.so
почуяли разницу? =))

В общем, что бы побыстроляну пофиксить
в файле libmariadb/CMakeLists.txt
находим

SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR})

и заменяем на

SOVERSION "so")

ну а дальше удалить полностью build и заново, начиная с 9 шага
troll

!пересобираем! проект, не забыть исправить в pro файле на libmariadb.so.so запускаем и радуемся =)

Итого: сэкономлено куча времени и нервов. Хоть спасибо скажите =))

Views :

618

Qt Android JNI преобразование QByteArray в jbytearray и обратно, а так же получение и передача в jni функцию

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

public int send(byte[] data)
{     
....
}

Ну а что бы её вызвать из C++ нужно было преобразовать QByteArray в jbytearray
делается это так:

    jbyteArray QByteArray2jbyteArray(QByteArray buf)
    {
     QAndroidJniEnvironment env;
     jbyteArray array = env->NewByteArray(buf.length());
     env->SetByteArrayRegion (array, 0, buf.length(), reinterpret_cast<jbyte*>(buf.data()));
     return array;
    }

если нужно обратное преобразование, то делается так:

QByteArray jbyteArray2QByteArray(jbyteArray buf)
    {
       QAndroidJniEnvironment env;
       int len = env->GetArrayLength(buf);
       QByteArray array;
       array.resize(len);
       env->GetByteArrayRegion (buf, 0, len, reinterpret_cast<jbyte*>(array.data()));
       return array;
    }

ну а саму функцию из Java класса с помощью JNI можно вызвать так:

QAndroidJniObject myActivity=  QtAndroid::androidActivity();
myActivity.callMethod<int>("send", "([B)I", QByteArray2jbytearray(myByteArray));

Если нужно наоборот вернуть из функции jbytearray, то тут немного по сложнее:

QAndroidJniObject myActivity=  QtAndroid::androidActivity();
QAndroidJniObject readData = myActivity.callObjectMethod("read", "(V)[B");
jbyteArray array =readData.object<jbyteArray>();

Кстати, не забываем подключать заголовочники:

#include <QtAndroid>
#include <QAndroidJniEnvironment>
#include <QAndroidJniObject>

И в *.pro файле добавлять

QT +=  androidextras

Вот как то так =)

Views :

377