Последнее обновление:
October 19, 2020

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

Разделение на отдельные пакеты байтового потока данных.

Потребовалось мне тут обеспечить общение двух девайсов (пусть будут, для упрощения, Ардуинки — тестировалось всё равно на них) через последовательный интерфейс, ака com-порт, он же rs232.

Так как оба девайса имеют буферы на приём и отправку, то нет никакой гарантии, что если мы отправим 5 байт и сделаем небольшую задержку, то на другой девайс так же придут эти 5 байт разом. То есть при Serial.readBytes их там может оказаться и 5 и 8 (от предыдущей отправки). То есть нужно как-то гарантированно разделять на отдельные «пакеты» поток байтов.

Сразу напрашивается некоторый «разделитель» пакетов, например три байта подряд 0xFF. НО никто не даст гарантии, что при обмене не окажется этих трёх значений среди самих данных, тем самым нарушив нашу разбивку на «пакеты».
Можно, конечно, увеличить разделитель до 20 байт, но пропускная способность существенно снизиться, к тому же память у девайсов не резиновая (Особенно Ардуинок). Либо самого себя ограничить при отправке, что не будет посылать подряд эти байты, короче, извращаться. Меня это не устраивает, т.к. лень. Хочется отправить, не думая, и получить, не парясь особо.

Для упрощения, пока что, опустим тот факт, что данные могут быть искажены при передаче. Так как контрольные суммы и прочее тема отдельного поста. В общем, считаем, что потерь/искажений нет.

Так же ограничим себя следующими хотелками и условиями:
1. Не должно быть существенного увеличения объёма передаваемых данных для служебных целей.
2. Вероятность ошибки при разбивке и сборке должна быть равной 0.
3. Обеспечить минимальную потерю производительности
4. Не выделять дополнительный объём памяти динамически.
5. Длина одного пакета максимум 254 байта.
6. Без потерь

Естественно, под это подходят разные алгоритмы сжатия данных, например LZ77 , но тогда придётся вставлять дополнительные байты сколько есть повторов/сколько без повторов, что в худшем случае, увеличит объём данных практически в 1.5 раза. И цели, как таковой, «сжать» у нас не было. Разве что бонусом получить.

Давайте для примера, возьмём разделитель пакетов из 3х байт 0x0, как самые неудобные и часто встречающиеся.

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

Алгоритм исключения последовательности из данных с последующим восстановлением:

Допустим есть массив байт (десятичные значения тут и далее):
5 6 7 0 0 0 65 12 14 88 66 0 0 88 99 104 55 19 22 29 0 0 0 18 78 89 0 0 0 252 5

Ищем первое появление 2х и более нулей подряд, запоминаем смещение первого 0 и считаем сколько их. Как посчитали, по следующему индексу после него ставим количество, а перед всеми данными вставляем смещение.
4 5 6 7 0 0 0 65 12 14 88 66 0 0 88 99 104 55 19 22 29 0 0 0 18 78 89 0 0 0 252 5

Ищем следующее включение 2х и более нулей подряд, запоминаем индекс, считаем сколько подряд.
На следующий от текущего индекса ставим сколько повторений нулей подряд.
Выясняем смещение, относительно предыдущего (так сказать получается расстояние до следующего).
По индексу смещения, найденного в предыдущий раз вставляем это расстояние. Остальное можно забить любыми значениями (у меня 255).

4 5 6 7 17 3 255 65 12 14 88 66 0 0 88 99 104 55 19 22 29 0 0 0 18 78 89 0 0 0 254 5

Можно, конечно, и вместо, тем самым бонусом немного «сжать», но это ведёт к усложнению декодирования, так что пока что не паримся.

Повторяем так для всех следующих включений 2х нулей подряд:

4 5 6 7 17 3 255 65 12 14 88 66 0 0 88 99 104 55 19 22 29 6 3 255 18 78 89 0 0 0 252 5

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

4 5 6 7 17 3 255 65 12 14 88 66 0 0 88 99 104 55 19 22 29 6 3 255 18 78 89 255 3 255 252 5

Если длина массива больше, чем 254 байта, то, соответственно, начинать заново весь алгоритм, но уже относительно смещения в 255.

В итоге
нужен всего лишь 1 байт дополнительных данных и всё =)
достаточно быстро, т.к. нет сложных мат. вычислений и всего лишь один прогон по всему массиву.

Итоговая реализация (на Qt):

#include <QCoreApplication>

#include <QByteArray>>
#include <QDebug>

/**
* @brief Выводим массив байт в виде десятичных значений
* @param title
* @param ba
*/
void showBA(QString title, const QByteArray ba)
{
    QStringList sl;
    foreach (uchar b, ba) {
        sl.append(QString::number(b, 10));
    }
    qDebug()<<"ByteArray:"<<title<<sl.join(" ");
}

/**
* @brief Кодируем байты
* @param ba
*/
int serialEncode(QByteArray &ba)
{
    int firstOffset = 255; //-- Что бы не сдвигать данные, будем возвращать, с какого места начинается первая последовательность. Пока что считаем, что таких нет.
    int lastIdx = -1; //-- С какого индекса в предыдущий раз началась последовательность
    int fromIdx = -1; //-- С какого индекса сейчас началась последовательность
    int together = 0;

    for (int i=0; i<ba.length(); ++i) {
        uchar b = ba[i];
        if ( b==0 ) { //-- Начинаем считать, сколько подряд
            together++;
            if ( fromIdx<0 ) { fromIdx = i; } //-- Запоминаем, с какого началась последовательность
            if ( together>2 ) { ba[i] = 0xFF; } //-- Всё, что дальше, забиваем сразу 255 что бы лишних нулей не было.
        } else //-- Подряд кончились
        if ( together>0 ) {
            if ( together>=2 ) { //-- Ну, как минимум 2 подряд есть
                ba[fromIdx+1] = together; //-- Укажем, сколько нас
                if (lastIdx==-1) { //-- Если первый раз нашли, то запоминаем отдельнго
                    firstOffset = fromIdx;
                } else {
                    ba[lastIdx] = fromIdx-lastIdx; //-- Расстояние до текущего
                }
                lastIdx = fromIdx;
            }
            together = 0;
            fromIdx = -1;
        }
    }

    if ( lastIdx>-1 ) { ba[lastIdx] = 255; }//-- Установим, что дальше нет подряд идущих

    return firstOffset;
}

/**
* @brief Декодируем обратно
* @param firstOffset - с какого индекса начинается первая последовательность
* @param ba
*/
void serialDecode(int firstOffset, QByteArray &ba)
{
    if ( firstOffset==255 ) { return; } //-- Нет у нас повторений, так что нечего декодировать

    int fromOffset = firstOffset; //-- С какого начинать заполнение
    int fillCount = 0; //-- Сколько заполнять
    for (int i=0; i<ba.length(); ++i) {
        if ( i==fromOffset ) { //-- Дошли до места, откуда нужно начинать заполнять
            fromOffset += ba[i]; //-- Узнаём, откуда в следующий раз начинать заполнять
            fillCount = ba[i+1]; //-- Узнаём, сколько сейчас надо заполнить
        }
        if ( fillCount>0 ) { //-- Если ещё не заполнили, то пора
            fillCount--;
            ba[i] = 0;
            if ( fillCount==0 && fromOffset==255 ) { break; } //-- Дальше идти нет смысла, т.к. повторения кончились.
        }
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray ba = QByteArray(std::begin<char>({0x5, 0x6, 0x7, 0x0, 0x0, 0x0, 0x41, 0xC, 0xE, 0x58, 0x42, 0x0, 0x0, 0x58, 0x63, 0x68, 0x37, 0x13, 0x16, 0x1D, 0x0, 0x0, 0x0, 0x12, 0x4E, 0x59, 0x0, 0x0, 0x0, 0x4, 0x5}), 31);

    showBA("Source  ", ba);

    int firstOffset = serialEncode(ba);
    showBA("Encoded:", ba);


    serialDecode(firstOffset, ba);
    showBA("Decoded:", ba);

    return a.exec();
}

Вот как-то так.

Views :

9

Qt QCoreApplication или EventLoop в DLL

Потребовалось мне создать динамическую dll для сторонней программы (не Qt).
Разумеется, хотелось использовать все возможности Qt, а именно сигналы/слоты/QTCPSocket и прочее.

Но для их работы нужна очередь событий (EventLoop), то есть либо QCoreApplication, либо QEventLoop. Но в dll этого нет.

Можно, конечно, сигналы-слоты использовать с Qt::DirectConnect (прямой вызов), но тогда всё равно не будут нормально работать QTCPSocket (потому что для их работы в WndProс должно приходить уведомление), поэтому придётся вызывать методы waitForBytesWriten() и т.д.) и прочее, кто работает асинхронно и ждёт событий от системы.

Была бы программа, в которую подключается dll сама написана на Qt, то таких танцев с бубном не потребовалось (т.к. там уже есть QApplication и ещё одного не нужно)

В dll основная функция, с которой всё начинается, это DllMain (при загрузке/выгрузке в процесс или подключении/отключении к потоку она автоматически вызывается системой).

extern "C" BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch(fdwReason) {
        case DLL_PROCESS_ATTACH: 
          QCoreApplication app(argc, nullptr); //-- Нельзя так!
          ...
          app.exec(); //-- Дальше загрузка остальных библиотек и хост-программа целиком зависнет, т.к. exec() запускает почти бесконечный цикл и из DllMain не выйдем.
        break;

        case DLL_PROCESS_DETACH: 
          ...
        break;
    }

    return true;
}

Кажется, что достаточно поместить QCoreApplication app(argc, nullptr); в блок DLL_PROCESS_ATTACH, но тогда загрузка всех остальных библиотек зависнет наглухо, про это чётко сказано в официальной документации: https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain

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

/**
* @brief В отдельном потоке
*/
DWORD WINAPI run(HMODULE hInst)
{
    int argC=0;
    QCoreApplication app(argC, nullptr);
    ... //-- Можем использовать все возможности Qt
    return app.exec(); //-- Бесконечный цикл запущен, пока не завершим, из этой функции не выйдем
}

/**
* @brief При загрузке библиотеки в процесс
*/
void init(HINSTANCE hInst)
{
    toLog("INIT\n");
    CreateThread(nullptr, 0,  (LPTHREAD_START_ROUTINE)run, (LPVOID)hInst, 0, nullptr);
}

/**
* @brief При выгрузке из процесса
*/
void deInit(HINSTANCE hInst)
{    
   ...
}

extern "C" BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    Q_UNUSED(lpvReserved);

    switch(fdwReason) {
        case DLL_PROCESS_ATTACH: init(hinstDLL); break;
        case DLL_PROCESS_DETACH: deInit(hinstDLL); break;
    }

    return true;
}
CONFIG += qt

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

ВАЖНО:
1. Почему используется winapi функция создания потока CreateThread()?
Потому, что при завершении программы система сама убъёт поток.
В случае, к примеру, QtConcurrent::run(..) нам придётся самостоятельно отслеживать закрытие всех окон и делать выход из QCoreApplicaton. Если всё же пойдёте этим путём, учтите, что напрямую из другого потока вызывать будет бесполезно! Придётся делать через «postMessage» (т.к. sendMessage сразу вызывает) или флаг ставить, и по таймеру обрабатывать.

2. Кстати, так же внутри классов нельзя вызывать системные функции, которые могут заблокировать поток Qt. К примеру, winapi MessageBoxW(nullptr, ….)! Как избежать?
Первым параметром идёт указатель на окно, которое будет принимать события (а следовательно и поток) (точнее, обработкой то занимается WndProc), нам всего лишь нужно поставить туда указатель на окно от QCoreApplication. Да, Вы можете возразить, что его нет! На самом деле есть, просто скрытое. Найти его можно с помощью winapi FindWindowEx по id процесса (можно из из Qt достать, но создатели вроде бы не предусмотрели таких извращений, поэтому без модификации файлов фреймворка не получится).
ИД процесса (не путить с ИД процесса приложения!) можно получить: QCoreApplication::applicationPid().
Чуть ниже выложу реализацию, запускать так:
HWND hApp = findwind(nullptr, L»», L»», 1, QCoreApplication::applicationPid())

3. Что бы библиотека загрузилась, ей для работы нужны Qt шные библиотеки (той же версии, которой компилируется текущая), это как минимум:
1. libwinpthread-1.dll
2. libgcc_s_dw2-1.dll
3. libstdc++-6.dll
4. Qt5Core.dll
5. Остальные

Порядок загрузки важен!
Положите либо рядом с загружаемой, либо в System32 (в случае Винды).
Либо при загрузке библиотеки используйте loadLibraryEx которая сама подгрузит нужные (в случае, если Вы используете injectdll (инъекцию dll)).

P.S. Функции вывода в лог (принцип такой же, как и у printf, не забудьте заголовочники подключить):

void toLog(const char *fmt, ...)
{        
    va_list arg;
    va_start(arg, fmt);
    char buffer[4096];
    int rc = vsnprintf(buffer, sizeof(buffer), fmt, arg);
    va_end(arg);

    FILE * fp = fopen("C:/DELME/Log.txt", "a");
    fprintf(fp, buffer);
    fclose(fp);
}

void toLogW(const wchar_t *fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    wchar_t buffer[4096];
    int rc = vsnwprintf(buffer, sizeof(buffer), fmt, arg);
    va_end(arg);

    FILE * fp = fopen("C:/DELME/Log.txt", "a");
    fwprintf(fp, buffer);
    fclose(fp);
}

Поиск окна по нескольким параметрам:

HWND findwind(HWND parent, std::wstring text, std::wstring className, int instance = 1, DWORD processID=0, bool fromStart=true)
{
    WCHAR bufferText[1024];
    WCHAR bufferClass[1024];
    DWORD dwPID = 0;
    HWND hWnd = parent;

    while (true) {
        hWnd = FindWindowEx(nullptr, hWnd, nullptr, nullptr);

        if ( hWnd==nullptr ) break;
        if ( !IsWindow(hWnd) ) { continue; }

        if( !text.empty() ) {
            GetWindowText(hWnd, bufferText, 1000);
            wchar_t * pos = wcsstr(bufferText, text.c_str());
            if ( (pos==nullptr) || (fromStart && pos-bufferText!=0) ) { continue; }
        }

        if ( processID>0) {
            GetWindowThreadProcessId(hWnd, &dwPID);
            if ( dwPID!=processID ) { continue; }
        }

        if ( !className.empty() ) {
            GetClassName(hWnd, bufferClass, 1000);
            if ( wcsstr(bufferClass, className.c_str())-bufferClass!=0 ) { continue; }
        }

        if ( --instance==0 ) { return hWnd; }
    }

    return nullptr;
}

P.P.S. Так же бесконечный цикл/очередь событий нужна, если ставиться SetWindowHookEx, иначе без этого он просто сброситься и хук вызываться не будет. Это же применимо и для injectDLL.
Кстати, при срабатывании хука напрямую Qt слоты вызывать не стоит, т.к. хук в другом потоке. Лучше использовать «QCoreApplication::postEvent(…)» а в обработчике переопределить функцию bool event(QEvent * event) override; Вот так примерно:

#include <QEvent>
#include "minwindef.h"
#include "winuser.h"
/**
* @brief Тип события для обработки хука
*/
const QEvent::Type WndProcHookEventType = static_cast<QEvent::Type>(QEvent::User+1000);
class WndProcHookEvent: public QEvent
{
public:
    WndProcHookEvent(WPARAM wParam, LPARAM lParam): QEvent(WndProcHookEventType) {
        _cmd = wParam;
        //-- Нужно сразу получать значения всего интересующего, иначе указатели после выхода их хука могут стать не валидными
        _cwps = *reinterpret_cast<CWPSTRUCT*>(lParam); 
    }

    uint cmd() const { return _cmd; }
    CWPSTRUCT cwps() { return _cwps; }

private:
    uint _cmd;
    CWPSTRUCT _cwps;

};

void run(HMODULE hInst)
{
   ...
   hHook = SetWindowsHookExW(WH_CALLWNDPROC, hookCB, 0, GetWindowThreadProcessId(<base program window handle>, nullptr));
   ...
}

LRESULT WINAPI hookCB(int nCode, WPARAM wParam, LPARAM lParam)
{
    if( nCode<0 ) {
        CallNextHookEx(hHook, nCode, wParam, lParam );
        return 0;
    }

    QCoreApplication::postEvent(_handler, new WndProcHookEvent(wParam, lParam));

    return CallNextHookEx(hHook, nCode, wParam, lParam);
}

bool event(QEvent* event) override 
{
   if ( event->type()!=WndProcHookEventType ) { return false; }
   CWPSTRUCT cwps = wndProc->cwps();
   if ( cwps.message==WM_COMMAND ) {
      ...
   }
   return true;
}

Вот как-то так.

Views :

39

Qt 5.12 + Android с чего начать или Hello World

Потребовалось в очередной раз обновить Qt для кросскомпиляции под Андроид. В моём случае разработка идёт под Линукс, но с Виндой, думаю, особых проблем не будет.

Качаем Android SDK, для этого нужно скачать SDKManager. (На странице ищем «Command line tools only»), пока что не распаковываем.

Рабочим каталогом у меня будет «~/Projects/ANDROID», все манипуляции будут отталкиваться от него, как от основного.

Дальше запускаем Qt Update Manager (в папке с установленным Qt, называется «MaintanceTool») и добавляем все (либо только под целевую архитектуру) компоненты, связанные с Андроидом для вашей версии Qt.

Качаем JDK версии 8!, распаковываем куда-нить.
Кстати, подойдёт и OpenJDK 8 версии (sudo apt install openjdk-8-jre)
И сразу зададим, какую использовать версия Java, а то ошибок может быть куча:

 JAVA_HOME=/home/pavelk/Projects/ANDROID/jdk1.8.0_251/

Да, и консольку не закрываем, иначе придётся перепрописывать заново.

Создаём папку, в которой будет наш SDK:

mkdir ./SDK

и распаковываем в неё наш sdkmanager, что бы он оказался в подпапке tools (а в ней были bin и lib). Ну и начинаем с помощью него ставить все необходимые пакеты и утилиты:

cd ~/Projects/ANDROID
./SDK/tools/bin/sdkmanager --sdk_root="/home/pavelk/Projects/ANDROID/SDK" "platform-tools" "platforms;android-28"
./SDK/tools/bin/sdkmanager --sdk_root="/home/pavelk/Projects/ANDROID/SDK" "build-tools;28.0.1"

Очень внимательно читаем все лицензии, консультируемся с юристами и принимает, благославясь:

./SDK/tools/bin/sdkmanager --sdk_root="/home/pavelk/Projects/ANDROID/SDK" --license

Как Вы поняли, качаем под Android 9 (API 28 уровня), SDK будет распологаться по пути, указанным в —sdk_root
Полный список команд тут: https://developer.android.com/studio/command-line/sdkmanager.

На 64х битном Лине необходимо доставить 32х битные пакеты (но это не точно):

sudo apt install libstdc++6:i386 libgcc1:i386 zlib1g:i386 libncurses5:i386 libsdl1.2debian:i386

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

Настраиваем QtCreator:
Tools > Options > Devices > Android
Выбираем пути до OpenJDK, AndroidSDK и AndroidNDK

Ну чтож, создадим первый проект.

Как обычно создаём новый проект, в моём случае QML.
Когда доёдёт до стадии выбора комплекта — выбираем архитектуру, которая на вашем устройстве для отладки (да, тестить будем на реальном девайсе). Если не вкурсе какая именно архитектуру процессора вашего девайса, то из Play Market ставим AIDA иди CPU-z.

Сразу прожимаем «запуск», и откроется окно с предложением выбрать девайс.

Включаем на телефоне режим разработчика, для этого нужно 7 раз кликнуть в настройках, подменюшка «о телефоне» по номеру сборку.
Дальше включаем «отладку по USB» в «Настройки» -> «Система» -> «Для разработчиков»

Первый раз телефон нужно подключить по USB, потом будем по WiFi.
Выясняем его ID:

lsusb

Вытыкаем девайс и снова набираем эту же команду, сравниваем вывод и выясняем, какая строка пропала. Запоминаем значение, которое сразу за «ID» идёт (к примеру «1d6b»).

Теперь пропишем правила монтирования, что бы при подключении по USB автоматически выдавались нужные права, а так же ADBAndroid Debug Bridge — Отладочный мост Android, через него мы как раз тправляем и тлаживаем приложение на устройстве) был вкурсе нашего устройства.

echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="<ID>", MODE="0666", GROUP="plugdev"' | sudo tee --append /etc/udev/rules.d/51-android.rules
echo '<ID>' | sudo tee --append ~/.android/adb_usb.ini

sudo chmod a+r /etc/udev/rules.d/51-android.rules
sudo udevadm control --reload-rules 
sudo udevadm trigger

Перезапускаем ADB

./SDK/platform-tools/adb kill-server
./SDK/platform-tools/adb start-server

Дальше приконнектимся к телефону по wifi, т.к. по проводу лень.

./SDK/platform-tools/adb tcpip 5555
./SDK/platform-tools/adb connect <IP девайса>

IP девайса можно узнать в настройках, подменюшка «о телефоне», в «общая информация».
По USB телефон теперь можно отключить и спокойно деплоить по WiFI.

Возвращаемся к Qt, жмём «Обновить список», выбираем наше устройство — справа в строке должен быть IP адрес его.

Гляньте в «консоль сборки», возможно, будут автоматически скачаны какие-либо пакеты дополнительные (возможно, при медленном соединеении, будут ошибки, поэтому запускать несколько раз придётся).

Кстати, если открыта Android Studio, то, при отладке в Qt, её придётся закрыть.

Надеюсь, сборка прошла успешно! Добро пожаловать в мир тормозов и лагов чудесный.

Пост на основе https://doc.qt.io/qt-5/android-getting-started.html

Views :

62

Qt QML OpenGL(es) application on Linux without X11/Wayland/etc (kiosk mode)

Потребовалось мне сделать управляющий софт под один девайс. То есть при загрузке он должен сразу стартовать и отображаться всё время на экране. По сути типо терминала. Зовётся это в народе «режим киоска».

Можно было бы не париться и установить обычную, десктопную систему, но очень не хотелось тащить оконный менеджер, оболочку и прочее за собой, дабы избавиться от посредников прорисовки, ну и что бы дистр с софтом весил как можно меньше. За основу был взят Ubuntu Server 19.10 (можно и ещё более облегчённый дистр).

Разумеется при попытке сразу запустить QML приложение (QML выводит всё через OpenGL) он выдал про невозможность запуска с платформой «xcb», а если указать «-platform eglfs» (то есть принудительно укажем через что выводить), выдаёт «Could not initialize egl display», то есть не может открыть дисплей (потому что eglsfs скомпилирован только для «X11»), нус, будем компилировать для работы напрямую, то есть пересобирать Qt из исходников.

Будет две машины, одна «рабочая» (на которой и будет происходить компиляция, стоит Ubuntu 19.10 Desktop), другая «тестовая» (на которой будем запускать приложение, стоит Ubuntu Server 19.10, при установке включите «OpenSSH Server» для удалённого доступа с рабочей машины).

Кстати, на тестовой машине должна быть хоть какая-нибудь видеокарта с поддержкой OpenGL 3.1. Проверить, можно поставив kmscube (заодно ставим доп. пакеты, скорее всего все они не нужны, но чтоб наверняка):

sudo apt install cmscube
sudo apt install mesa-utils
sudo aptt install libglu1-mesa freeglut3 mesa-common
cmscube

Должна запуститься демка c вращающимся кубом. Возможно, нужно будет поставить ещё драйвера (об этом в конце поста).

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

sudo apt install libglu1-mesa libglu1-mesa-dev build-essential libgl1-mesa-dev freeglut3 freeglut3-dev mesa-common-dev libglapi-mesa libosmesa6 mesa-utils libdrm-dev libgbm-dev libgbm1 libgegl-0.3-0 libgegl-dev mesa-utils-extra gegl libglfw3-dev libgles2-mesa-dev libglew1.5 libglew1.5-dev libgl1-mesa-glx

Качаем исходники

BASEPATH=/home/pavelk/QtOpenGL
mkdir $BASEPATH
cd $BASEPATH
git clone https://github.com/qt/qt5 Qt5Sources
cd Qt5Sources
perl init-repository #Успеем фильмак глянуть, пока делается
git checkout 5.14
git submodule update --recursive

Настраиваем сборку

export QT_QPA_EGLFS_INTEGRATION=eglfs_kms
./configure -platform linux-g++-64 -skip wayland -skip script -skip webengine -no-pch -no-xcb -no-xcb-xlib -no-gtk -nomake tests -nomake examples -reduce-exports -kms -eglfs -opengl es2 -opensource -release -confirm-license -make libs -prefix $BASEPATH/qt5 -v

Возможно, нужно будет добавить ещё «-qpa eglfs».
По завершению главное в выхлопе что бы было вот это:

QPA backends:
EGLFS ................................ yes
EGLFS details:
  EGLFS i.Mx6 ........................ no
  EGLFS i.Mx6 Wayland ................ no
  EGLFS EGLDevice .................... yes !!!
  EGLFS GBM .......................... yes
  EGLFS Mali ......................... no
  EGLFS Rasberry Pi .................. no
  EGL on X11 ......................... no

Та самая заветная платформа и интеграция.

Если нужно перенастроить/пересобрать, то перед запуском нужно обязательно очистить старые результаты командой: «git clean -dxf»

Ну а дальше компилируем, ставим.

make
make install

Собираться будет дооолго.

По завершению подготавливаем QtCreator, идём в «инструменты»->»параметры».

Что бы иметь возможность сразу запускать на тестовой машине:
Устройства -> Устройства, Добавить, «Обычное Linux устройство»
Название по вкусу, у меня «Test»
IP адрес устройства — айпишник тестовой машины (узнать можно залогинившись на ней и ввести «ifconfig»)
Имя пользователя — соответственно имя пользователя, которое вводили при установке.

Дальше устанавливаем ключ:
жмём «создать новую пару ключей» и запоминаем путь «файла открытого ключа» (что-то вроде «/home/pavelk/.ssh/qtc_id.pub»), прожимаем «создать и сохранить».
Далее открытый ключ нужно скопировать на тестовую машину, для этого в консольке рабочей прописываем:
«ssh-copy-id -i /home/pavelk/.ssh/qtc_id.pub user@192.168.0.193»
Только замените значения на свои.
Завершаем, проверка должна пройти без ошибок.

Комплекты -> Профили Qt, жмём Добавить
Путь: /home/pavelk/qt5/bin/qmake
Название по вкусу, у меня «OpenGLKMS»

Комплекты -> Комплекты, жмём Добавить
Компиляторы ставим те, которыми собирали.
В «Профиль Qt» выбираем созданный на предыдущем шаге
Название по вкусу, у меня «QOpenGLKMS»
Тип устройства выбираем «Обычное Linux устройство»
Устройство выбираем добавленное на предыдущем шаге.

Логинимся на тестовую машину, ставим доп. пакеты, создаём необходимые директории для библиотек Qt и переменные окружения:

sudo su
apt install mesa-utils
apt install libgles2-mesa
apt install libglfw3
apt install libharfbuzz0b
apt install libfontconfig1
apt install libmtdev1
apt install libinput10
apt install libts0

usermod -aG input user

mkdir /usr/local/lib/Qt/
mkdir /usr/local/lib/Qt/lib
chown -R user:root /usr/local/lib/Qt/

echo "/usr/local/lib/Qt/lib" > /etc/ld.so.conf.d/qt.conf
echo 'QML2_IMPORT_PATH="/usr/local/lib/Qt/qml"' >> /etc/environment
echo 'QT_PLUGIN_PATH="/usr/local/lib/plugins"' >> /etc/environment

reboot

Только имя пользователя «user» замените на своё, которое на тестовой машине.

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

IP=192.168.0.193
USER=user
scp -r $BASEPATH/qt5/plugins $USER@$IP:/usr/local/lib/Qt/
scp -r $BASEPATH/qt5/qml $USER@$IP:/usr/local/lib/Qt/

scp $BASEPATH/qt5/plugins/egldeviceintegrations/libqeglfs-kms-egldevice-integration.so $USER@$IP:/usr/local/lib/Qt/plugins/egldeviceintegrations
scp $BASEPATH/qt5/lib/libQt5Core.so.5 $USER@$IP:/usr/local/lib/Qt/lib 
scp $BASEPATH/qt5/lib/libQt5Gui.so.5 $USER@$IP:/usr/local/lib/Qt/lib
scp $BASEPATH/qt5/lib/libQt5Qml.so.5 $USER@$IP:/usr/local/lib/Qt/lib
scp $BASEPATH/qt5/lib/libQt5Network.so.5 $USER@$IP:/usr/local/lib/Qt/lib
scp $BASEPATH/qt5/lib/libQt5EglFSDeviceIntegration.so.5 $USER@$IP:/usr/local/lib/Qt/lib
scp $BASEPATH/qt5/lib/libQt5DBus.so.5 $USER@$IP:/usr/local/lib/Qt/lib
scp $BASEPATH/qt5/lib/libQt5EglFsKmsSupport.so.5 $USER@$IP:/usr/local/lib/Qt/lib
scp $BASEPATH/qt5/lib/libQt5Quick.so.5 $USER@$IP:/usr/local/lib/Qt/lib
scp $BASEPATH/qt5/lib/libQt5QmlModels.so.5 $USER@$IP:/usr/local/lib/Qt/lib
scp $BASEPATH/qt5/lib/libQt5QmlWorkerScript.so.5 $USER@$IP:/usr/local/lib/Qt/lib

Имя пользователя user и айпишник замените на свои, которые для тестовой машины. Либы копируются только самые необходимые для запуска «HelloWorld», в дальнейшем Вам необходимо будет скопировать остальные.

Дальше нужно снова подключиться к тестовой и обновить список либ:

sudo ldconfig

Переходим обратно на рабочую.

Создаём тестовый проект «Приложение Qt Quick — Пустое»,
комплект выбираем который недавно создали («QOpenGLKMS «).
В *.pro файле дописываем:

INSTALLS        = target
target.path     = /home/user
CONFIG += release

Это что бы запускалось сразу на тестовой машине и релизная версия.

В main.qml

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Rectangle {
        id: rect1
        property var colors: ["red", "blue", "orange", "gray", "green"]
        property int curColor: 0
        color: colors[curColor]
        anchors.centerIn: parent
        width: 150
        height: 150

        MouseArea {
            anchors.fill: parent
            onClicked: {
                rect1.curColor++;
                if ( rect1.curColor>=rect1.colors.length ) { 
                   rect1.curColor = 0; 
                }
                rect1.color = rect1.colors[rect1.curColor];
            }
        }
    }
}

В настройках проекта обязательно ставим «Release»(Выпуск) сборку.
Ну и запускаем =) На тестовой машине на весь экран должно запуститься приложение.

Возможно, нужно будет поставить драйвера на видюху (хз что она потащит за собой), но навсякий случай если не работает:

sudo su
apt install mesa-utils
apt install ubuntu-drivers-common
ubuntu-drivers autoinstall
apt purge gdm3
reboot

Так же, если не запускается, то на тестовой машинке попробовать прописать:

export QT_LOGGING_RULES=qt.qpa.*=true

Грузится должен qt.qpa.eglfs.kms, если грузится «emu» то проще удалить эту интеграцию: «rm /usr/local/lib/Qt/plugins/egldeviceintegrations/libqeglfs-emu-integration.so»

Так же для диагностики стоит проверить зависимости. На тестовой:

ldd ./myApp

Если все есть, то проверяем зависимости eglfs плагина:

cd /usr/local/lib/Qt/plugins/platforms/
ldd ./libqeglfs.so

Если чего-то нет, гуглим либу и как её ставить (скорее всего из разряда sudo apt isntall lib……, пару раз tab нажмите должен появится список возможных)

И запустить приложение вручную «./myApp» (оно должно быть по пути «target.path» из *.pro файла Вашего приложения при компиляции )

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

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

cd /dev/dri
ls

В списке смотрим, какие есть видеокарточки, запоминаем.
Что бы указать Qt интеграции, как и через что выводить, содаём конфигурационный файл:

nano /usr/local/lib/Qt/eglfs.json
{
 "device": "/dev/dri/card0",
 "hwcursor": true,
 "pbuffers": true,
 "outputs": [
  {
   "name": "HDMI1", "mode": "1024x768"
  }
 ]
}

Думаю, интуитивно понятно что куда и за что.
Так же полезным будет почитать документацию: https://doc.qt.io/qt-5/embedded-linux.html

Потом на тестовой машине нужно добавить переменную окружения, что бы указать, где файл настроек искать:

sudo su
echo 'QT_QPA_EGLFS_KMS_CONFIG="/usr/local/lib/Qt/eglfs.json"' >> /etc/environment
reboot

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

sudo usermod -aG input user

Ну а теперь осталось лишь сделать автозапуск приложения при включении. Для этого просто создадим свой systemd сервис:

sudo nano /etc/systemd/system/myApp.service

Description=myApp autostart
Wants=network.target
After=syslog.target network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/environment
ExecStart=/home/user/myApp
Restart=on-failure
RestartSec=10
KillMode=process

[Install]
WantedBy=multi-user.target

Включаем в автозагрузку и запускаем:

sudo systemctl daemon-reload
sudo systemctl enable myApp
sudo systemctl start myApp

Ахтунг! Сервис запускается с рутовыми правами! Как сделать от имени пользователя гугл подскажет, там просто.

Вот как-то так…

Views :

241

IPS alert() — диалоговые окна в даминке.

Нашёл тут как в админке UI IP.Board вывести диалоговое окно, пример alert():

ips.ui.alert.show({
 type: 'alert',
 message: ips.getString('myapp_dlg_alert1_title'),
 subText: ips.getString('myapp_dlg_alert1_desc'),
 icon: 'info',
 buttons: {
  ok: ips.getString('myapp_dlg_alert1_btn_ok'),
 },
 callbacks: {
  ok: function() {
   this.alertOpen = false;
  }
 }
});

Пример confirm():

ips.ui.alert.show({
 type: 'confirm',
 message: ips.getString('rebuildGalleryThumbnails'),
 subText: ips.getString('rebuildGalleryThumbnailsBlurb'),
 icon: 'question',
 buttons: {
  ok: ips.getString('rebuildGalleryThumbnailsYes'),
  cancel: ips.getString('rebuildGalleryThumbnailsNo')
 },
 callbacks: {
  ok: function(){
    $('input[name=rebuildWatermarkScreenshots]').val(1);
    this.alertOpen = false;
  },
  cancel: function(){
   $('input[name=rebuildWatermarkScreenshots]').val(0);
   this.alertOpen = false;
  }
 }
});

ips.getString() — берёт строку из файла jslang.php (для возможности перевода), формат файла:

$lang = array(
    'myapp_dlg_alert1_title' => 'Title Alert1',
    'myapp_dlg_alert1_desc' => 'Description of Alert1 dlg',
    'myapp_dlg_alert1_btn_ok' => 'BTN OK!'
);

Вот как-то так.

Views :

43

IPS скрипты приложения (application resources)

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

Чтож, оказывается, структура каталогов должна быть такая:
«<youapp>/dev/js/<location>/<type>/<group>/<script.js>
<youapp> — директория вашего приложения
<location> — где будет отображаться (admin/front/global)
<type> — кто выводит (controllers/templates/models/mixins/common)
<group> — название группы (любое, на ваше усмотрение)
<script.js> — название скрипта (любое, на ваше усмотрение)

Допустим, у нас есть контроллер и нужно подключить в выводе javascript, для этого создаём файл:
«/applications/myappname/dev/js/admin/controllers/mygroup/myscript.js»

В функции manage(), которая отвечает за вывод контроллера, прописываем:

\IPS\Output::i()->jsFiles = array_merge(\IPS\Output::i()->jsFiles, \IPS\Output::i()->js('admin_mygroup.js', 'myappname', 'location'));

Обратите внимание на ‘admin_mygroup.js’ — да, должно быть именно так! т.е. «<location>_<group>.js» название скрипта пропускаем — прогрузятся все в этой папке автоматически.
Вместо ‘myappname’ пишем имя приложения именно так, как называется его директория в папке applications.

Вот как-то так. Похоже, открываю новую рубрику по плагино писанию к IPS, посмотрим, может действительно окажется хорошим форумным движком.

Views :

50

Qt, QQuickPaintedItem отрисовка в отрицательных координатах (paint outside bounds).

Потребовалась мне тут на днях отрисовка сложных графиков в QML.
Увы, но возможностей существующих графиков не хватило — пришлось изобретать свои.

Проблема возникла с тем, что по умолчанию всё, что имеет отрицательные координаты x и y будет подвергнуто кастрации.
Да же если поставим:

setClip(false);
setFlag(QQuickItem::ItemClipsChildrenToShape, false);

И попробуем вывести круг:

void MyCircle::paint(QPainter * painter)
{
   QPainterPath path;
   path.addEllipse(-200, -200, 400, 400);

   painter->setPen(Qt::NoPen);
   painter->fillPath(path, QBrush(QColor("orange")));
}

Всё, что меньше (0, 0) будет обрезано:

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

Самое простое решение на данный момент — задать QPainter глобальную матрицу трансформации со смещением в противоположную сторону (что бы не высчитывать новые координаты при отрисовке примитивов). А компоненту задать соответственно в координатах X и Y это же значение смещения. Единственный минус — придётся делать обёртку над компонентом.
Вот так примерно:

painter->setWorldTransform(QTransform::fromTranslate(200, 200), true);
setPosition(QPoint(-200, -200));

И в QML соответственно обёртку сделать:

import QtQuick 2.12
import QtQuick.Window 2.12
import MyCircle 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QQuickPaintedItem outside bounds")

    Rectangle {
        anchors.fill: parent
        color: "black"
    }

    Item {
        id: myCircleWrapper
        anchors.centerIn: parent
        width: 200
        height: 200

        MyCircle {
            id: myCircle
            width: parent.width
            height: parent.height
            clip: false
        }
    }
}

И результат:

Как сделать более правильно я, к сожалению, после нескольких дней раздумий и ковырянии исходников Qt не придумал…

Views :

40

Статическая сборка Qt 5.12 из исходников под Windows

Потребовалось мне тут, что бы Qt приложение было в одном exe.  Для этого придётся перекомпилировать Qt.

Качаем: Git

Качаем: Perl, версию 32х битную.

Качаем: Python 2 (У меня 2.7).

Качаем: MinGW, y меня 730 (для распаковки нужен 7Zip, запускать «C:\Programm Files (x86)\7-Zip\7zFM.exe»).

Открываем консольку (Win+R, вводим cmd), прописываем необходимые системные переменные (пути замените на свои):

set PATH=C:\MinGW\mingw730_32\bin\;%PATH%
set PATH=C:\Strawberry\perl\bin\;%PATH%

Переходим в папку, в которой будем развлекаться, качаем исходники Qt:

cd C:/QtStatic
git clone https://github.com/qt/qt5
cd qt5
perl init-repository

Конфигурируем:

configure -opensource -confirm-license -platform win32-g++ -static -release -opengl desktop -qt-zlib -qt-libpng -qt-libjpeg -nomake examples
В конфиге не включён SSL, кому нужно:
Качаем и ставим Win32OpenSSLLite
-openssl -I «c:\Qt\3dparty\openssl-1.0.2m\include» -L «c:\Qt\3dparty\openssl-1.0.2m»
Если вдруг нужно переконфигурировать и пересобрать, то нужно сначала очистить результаты предыдущих потуг:

 git submodule foreach --recursive "git clean -dfx" 
 git clean -dfx

Собираем (Часа 4) и ставим:

mingw32-make -j4
mingw32-make install

После установки настраиваем QtCreator для использования новой версии фреймворка:

В верхней менюшке Инструменты -> Параметры -> Сборка и запуск

Компиляторы -> Добавить
MinGW -> C++
Название: Qt 5.12 Static MinGW
Путь: C:\MinGW\mingw730_32\bin\c++.exe

Отладчики -> Добавить
Название: Qt 5.12 Static DBG
Путь: C:\MinGW\mingw730_32\bin\gdb.exe

Профили Qt -> Добавить
Название: Qt 5.12 Static
Путь: C:\QtStatic\qt5\qtbase\bin\qmake.exe

Комплекты -> Добавить
Название: Qt 5.12 Static
Тип устройства: Desktop
Компилятор C++: Qt 5.12 Static MinGW
Отладчик: Qt 5.12 Static DBG
Профиль Qt: Qt 5.12 Static

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

Можно попробовать сделать простенький проект. В параметрах сборки и запуска поставьте «Release» т.к. мы только эту версию собрали. Так же в «*.pro» файл необходимо добавить указание на статическую сборку и линковку libGCC.

QMAKE_LFLAGS += -static -static-libgcc

Ну вот, как-то так =)

Views :

3123

Qt, вызов функции с ограничением по времени выполнения (QtConcurrent::run timeout).

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

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

Можно было бы вообще всё использование вынести в отдельный поток, но городить ради одного места неохото, зато родились вот такие извращения:

  1. В Qt есть QtConcurrent, с помощью которого можно запустить функцию в отдельном потоке,
  2. Так как нам желательно сразу получить результат не выходя из функции (в случае ошибки вызываемая функция особо не задерживается), то можно прописать future.result(), но тогда мы будем ждать до победного, а нам нужно ограничить время выполнения, поэтому
  3. Используем QTimer, что бы по истечению заданного времени завершить выполнение, но и тут облом — мы не можем принудительно завершить выполнение «QtConcurrent::run», (т.к. вызываемая функция может не освободить память/не закрыть доступ к файлу да и вообще хз что). Поэтому завершать выполнение не будем, а тупо забьём на него — пусть там себе в параллельном потоке выполняется, а мы тем временем пойдём дальше, но тогда будет необходимо использовать сигналы, что бы дать понять, когда пора выходить из функции по истечению времени, а когда функция вернула результат, поэтому
  4. Используем QFutureWatcher, что бы подписаться на сигнал «finished» если функция выполнилась.
  5. Ну а что бы ждать сигнала от таймера, либо сигнала о завершении вызываемой функции и не выходить до этого из текущей функции используем  QEventLoop.  Как только тот или другой сигнал будет вызван и обработан, то завершаем цикл.

Из преимуществ: выполнение происходит в отдельном потоке, поэтому не затрагиваем основной процесс, в котором происходит вызов функции.

Из недостатков: мы не управляем параллельным процессом в полной мере, т.е. не можем принудительно завершить процесс и он будет болтаться в памяти до исключения, завершения или посинения (если функция «зациклится»).

В общем, вот так:

#include <QtConcurrent/QtConcurrentRun>
#include <QFuture>
#include <QFutureWatcher>
#include <QEventLoop>
#include <QTimer>
#include <QDebug>

....

void AppCore::runAsync()
{
    qDebug()<<"Run async";

    QString r;
    QFuture<QString> f;

    try {
        f = QtConcurrent::run(this, &AppCore::asyncFunction);
    } catch(...) {
        qDebug()<<"ERROR";
    }

    QFutureWatcher<QString> w;
    QEventLoop l;
    QTimer t;

    connect(&w, &QFutureWatcher<QString>::finished, [&l, &r, &w](){
        r = w.result();
        qDebug()<<"From connection 1.";
        l.quit();
    });

    connect(&t, &QTimer::timeout, [&l, &r](){
        r = "TIME OUT!";
        qDebug()<<"From connection 2.";
        l.quit();
    });

    w.setFuture(f);
    t.start(2000);
    l.exec();

    qDebug()<<"RESULT:"<<r;

}

/**
* @brief Функция, которая будет выполнятся в фоновом потоке
* @return
*/
QString AppCore::asyncFunction()
{
    qDebug()<<"In async function.";
    QThread::sleep(5);

    qDebug()<<"ASYNC FUNCTION COMPLETED!";

    return QString("COMPLETED NORMAL!");
}

Вывод:

Run async
In async function.
From connection 2.
RESULT: "TIME OUT!"
ASYNC FUNCTION COMPLETED!

Т.е. вышло время, мы получили результат, что время вышла, но сама вызываемая функция в параллельном потоке всё равно выполнилась до конца.

Ну и упростим это до макроса, что бы иногда использовать:

/**
* Выполняем функцию в параллельном потоке и отдаём результат,
* если не выполнится за время timeOut_ms, то отдаём timeOut_result
* QtConcurrentRunTimeOut(int, myIntRes, this, &MyClass::myFunction, 2000, -100);
* QtConcurrentRunTimeOut(int, myIntRes, this, &MyClass::myFunctionWithParams, 2000, -100,  14, 88);
* qDebug()<<"RESULT:"<<myIntRes;
* Need include
* #include <QtConcurrent/QtConcurrentRun>
* #include <QFuture>
* #include <QFutureWatcher>
* #include <QEventLoop>
* #include <QTimer>
*/
#define QtConcurrentRunTimeOut(resultType, res, object, functionPointer, timeOut_ms, timeOut_result, ...)\
    resultType _r##res;\
    QFuture<resultType> _f##res;\
    _f##res = QtConcurrent::run(object, functionPointer,##__VA_ARGS__);\
    QFutureWatcher<resultType> _w##res;\
    QEventLoop _l##res;\
    QTimer _t##res;\
    connect(&_w##res, &QFutureWatcher<resultType>::finished, [&_l##res, &_r##res, &_w##res](){\
        _r##res = _w##res.result();\
        _l##res.quit();\
    });\
    connect(&_t##res, &QTimer::timeout, [&_l##res, &_r##res](){\
        _r##res = timeOut_result;\
        _l##res.quit();\
    });\
    _w##res.setFuture(_f##res);\
    _t##res.start(timeOut_ms);\
    _l##res.exec();\
    resultType res=_r##res;

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

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

Views :

297

Linux, USB модем ZTE MF667 от Beeline, разблокировка и подключение через wvdial

В общем, потребовалось заставить это чудо выходить в интернет через МТСовскую симку.

Для начала нужно заставить его определяться как модем, а не как cd-rom и отвязать от оператора (убрать NODOWNLOAD.FLG).

sudo apt-get update 
sudo apt-get install minicom

dmesg | grep attached  # Появится 3 устройства, нужное дальше методом перебора

sudo minicom --device=/dev/ttyUSB1  
ATE  # Должно выдать OK
AT+ZCDRUN=E   # Так же должно выдать OK
ctrl+c # Выходим и перевтыкаем модем, ждём секунд 30, пока не загорит зелёный светодиод, сигнализируя, что мы в сети

Возможно, придётся повторить для всех устройств, которые выдал «dmesg | grep attached».

Ставим wvdial и настраиваем:

sudo apt-get wvdial
sudo nano /etc/wvdial.conf

Сам конфиг wvdial для mts у меня такой:

[Dialer Defaults]
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Modem Type = Analog Modem
Phone = "*99#"
ISDN = 0
Password = mts
New PPPD = yes
Username = mts
Modem = /dev/ttyUSB1
Baud = 9600
Country = Russia
Init3 = AT^SYSCFG=14,2,3fffffff,0,1  # Приоритет 3G
Init4 = AT +CGDCONT=1,"IP","internet.mts.ru"

Idle Seconds = 0
Stupid Mode = 1
Carrier Check = no
Auto Reconnect = on

Ну а дальше остаётся только подключиться:

sudo wvdial

Что бы потом не париться с sudo можно wvdial добавить в группу.

Надеюсь, всё удалось =)

 

Views :

266