Удаленное управление. QML.

И всем снова привет.

Тут вспомнил еще про один проект, который построен на базе ferro_remote клиента.
Это QML клиент. Тем, кто не знает, что это такое можно прочитать тут + много русскоязычных источников. QML это простой язык описания визуального интерфейса со встроенным JS. Была когда-то идея купить себе Jolla, интерфейс, которой построен именно на QML.

Проект, который входит в дерево ferro_remote. Пока я его подзабросил, потому как телефон Jolla я так и не купил.

Вот про этот клиент я и напишу сейчас.

gui_client, а именно так называется этот клиент (с названиями у меня туго, да) представляет собой обычный бинарник, который принимает в командную строку путь к QML файлу, экспортирует в машину некоторые интерфейсы клиента и исполняет файл. Все просто. Все очень похоже на работу lua_client, который я тут описывал не так давно. Есть, однако, пара моментов.
  1. Клиентов в одном экземпляре QML может быть несколько, каждый может быть соединен со своим агентом
  2. Соединяться нужно из скрипта, то есть вызывать методы клиента и реагировать на его события (сигналы)
Тут еще стоит отметить, что собранного бинарника для той же винды у меня нет, потому как под рукой нет самой винды с QtCreator.

И так. Запуск приложения выглядит вот так
$ ./gui_client test.qml

где test.qml это как раз само окно приложения со всем, что необходимо.
Самый простой пример:

import QtQuick 2.0

Rectangle {
    id: mainWindow
}

Теперь, если это запустить, мы получим просто белое окошко, которое можно свернуть, развернуть, или закрыть
Но нужно ж соединиться с малиной, например. Добавим.
import QtQuick 2.0

import Fr.Client 1.0 /// импорт клиента и некоторых интерфейсов

Rectangle {
    id: mainWindow
    FrClient { // клиент
        id: generalClient 
        Component.onCompleted: connect( "192.168.3.1:12345" ) // соединимся с малиной
    }
}

Все. Вызов: (Далее просто можно закрыть)
$ ./gui_client test.qml

В логах агента на стороне малины будет:
2000-Jan-11 02:38:19.472011 [INF] [listener] New connection: ep: tcp://0.0.0.0:12345 client: tcp://192.168.3.10:52849 total: 1
2000-Jan-11 02:38:26.854824 [INF] [listener] tcp://0.0.0.0:12345 Close connection: tcp://192.168.3.10:52849; count: 0

Этот код можно немного причесать и получить что-то типа такого
Сам QML файл лежит тут. Он соединяется, разъединяется, показывает ошибку, если возникла.

Можно сделать что-то поинтереснее. Например валяется у меня Grove LCD RGB.
Как известно из даташита на это устройство, устройства в этой железке 2. Первое отвечает за текст, второе за цвет экранчика. Оба рулятся по i2c. Все должно быть просто.
Кстати сам экранчик выглядит вот так
Это он подключен не к малинке, если что. Просто такой же девайсик с линуксом на борту и агентом ferro_remote.

Для того, чтоб уметь работать с устройством, добавлю 2 компонента FrClientI2C:

    Rectangle { // это будет общий компонент-девайс, который мы сможет потом, 
                // например, вынести в отдельный файл
        id: lcdDevice
        width: 0
        height: 0        
        FrClientI2c {
            id: txtDev // этот для текста
            client: generalClient // использовать клиента с id  generalClient
            busId: 1 // шина 1 
            slaveAddress: 0x3E // адрес
        }

        FrClientI2c {
            id: rgbDev // этот для цвета
            client: generalClient
            busId: 1
            slaveAddress: 0x62
        }
}

Что тут происходит:
Компоненты будут готовы к работе, как только клиент будет соединен с агентом. У каждого компонента (если я нигде не забыл это воткнуть), есть свойство ready и соответственно сигнал «onReadyChanged», в которой передается значение ready (true or false).
Поэтому прям в компонент lcdDevice можно добавить свойство, которое принесет с собой и сигналы, на которые можно реагировать:

    Rectangle { 
        id: lcdDevice
        width: 0
        height: 0    
        property bool ready: txtDev.ready && rgbDev.ready // привяжем состояние 2 устройств к 1 значению
........
        onReadyChanged: { // устройства изменили состояния
            if( ready ) {
                ..... // оба устройства готовы.
            }
        }
}

Добавим в код пару функций. Первая будет менять цвет, вторая очищать экран.

    Rectangle { 
        id: lcdDevice
        ......
        function set_color( r, g, b )
        {
            // установка цвета. за это отвечают 3 регистра.
            rgbDev.writeBytes( { 0x04: r, 0x03: g, 0x02: b } )
        }
        function clear( )
        {
            // очистка экрана - установка регистра 0x80 в 1
            txtDev.writeBytes( { 0x80: 0x01 } )
        }
    }

и теперь можно при готовности устройств очистить их и сбросить цвет на 0

    onReadyChanged: { 
            if( ready ) {
                clear( )
                set_color( 0, 0, 0 )                
            }
        }

Так при подключении программка будет убирать текст с экрана и устанавливать цвет в 0x000000 то есть он будет выключен.
Добавлю функцию для установки текста. Я сделаю функцию с 2 параметрами, так как строк у экранчика 2.

/// собственно функция формирует массив из объектов, которые выглядят так: {registry: value}
/// после этого все это пишется в устройство.
/// тут, при использовании массива будет гарантия того, 
/// что значение регистров будет установлено ровно в той последовательности, 
/// в какой они поступят в массив.
/// 0x40 - регистр, который пишет свое значение в следующее знакоместо.
        function set_text( txt, txt2 )
        {
            /// сначала очистка экрана и установка курсора с положение 1:1
            txtDev.writeBytes( [{ 0x80: 0x01 },
                                { 0x80: 0x08 | 0x4 },
                                { 0x80: 0x28 }] )
            var txt_value = [] /// значение регистров для записи текста

            for( var i = 0; i < txt.length; i++ ) {
                txt_value = txt_value.concat( [{ 0x40: txt.charCodeAt(i) }] )
            }
            txt_value = txt_value.concat({ 0x80: 0xC0 }) /// переход на 2 строку.
            for( i = 0; i < txt2.length; i++ ) {
                txt_value = txt_value.concat( [{ 0x40: txt2.charCodeAt(i) }] )
            }
            txtDev.writeBytes(txt_value) /// Пишем.
        }

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

        onReadyChanged: {
            if( ready ) {
                //clear( )
                set_color( 100, 100, 100 )
                set_text( "Hola,", "raspberrypi.ru" )
            }
        }

Запустив и подключив, мы увидим светлый экран и надпись на нем «Hola,\nraspberrypi.ru»
Дальше — больше. =)

Я добавил ColorDialog из «QtQuick.Dialogs 1.0» и повесил событие смены текущего цвета на смену цвета экрана.

Скрипт можно взять тут. Скрипты немного корявенькие, потому как это вообще первый мой проект, как с QML, так и с Qt вообще.

PS: есть еще примеры работы с удаленным исполнением консольной команды, с листингом директории.

+ Есть работа с пинами GPIO и, недавно начал прикручивать, SPI.

Так же при помощи QML можно легко и непринужденно смотреть видео с железки

        Rectangle {
            width: 600
            height: 200
            Video {
                anchors.fill: parent
                source: "rtsp://192.168.3.1:554/video"
                enabled: true
                visible: true
                focus: true
                autoPlay: true
            }

        }


Пока все. Спасибо за внимание =)
  • 0

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

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.