Управляем чем угодно откуда угодно

Образ со сборкой ferro-remote

Обсуждение клиентского ПО для доступа к виртурилке, управления и мониторинга

Образ со сборкой ferro-remote

Сообщение nwnclv » 12 сен 2016, 01:13

И снова всем здравствуйте. Пропал в связи с отпуском и отлетом далеко.

История:
тут поступило предложение собрать образ виртуалки с собранным софтом.

Образ я собрал и сегодня пересобрал, обновил исходники и пересобрал бинарники (правда с багом, лол, но об этом ниже).

Образ.

Тут можно скачать этот образ. Образ в формате OVA, который поддерживается VirtualBox и VmWare. Образ 32 битный. Сначала, было, собирал 64-битный, но подумал, что смысла от него не много, а некоторые не смогут использовать.

Система
Ubuntu 32 бита с минимальный набором софта. Только чтоб собрать все, что нужно. Нет даже браузера, кроме links. Для входа
user: builder pass: builder

В машине собраны варианты для host (то есть линукс 32 битный) и 32-битный арм (то есть virt2real, raspbery-pi и похожие)..На рабочем столе валяется файл builder-setup.txt, в котором собраны все команды, которые вводились от старта до сборки. Там можно увидеть, что ставилось на машину.

FS:

Немного бардак. Потому что изначально начал немного не в ту сторону. Но всеж.

Домашняя директория пользователя /home/builder.
Директория для исходников ~/sources там дерево исходников vtrc (библиотека, на которой основан ferro-remote)
и сам ferro-remote

Но первый (vtrc) в настоящей момент отдельно не нужен, поскольку сейчас библиотека вытягивается как submodule со вторым.
Сборка бинарников происходила в директорию ~/host/fr/. Там собраны agent, lua-client, fuse-client для хостовой машины. То есть прям там их можно позапускать и проверить.
И тут ВНИМАНИЕ в машине исходники, в которых завалялась бага, поэтому перед сборкой нужно обновить исходники (причем для арма ситуация такая же, но о нем ниже) и пересобрать. записал видюшку как это сделать тут
Если коротко, то примерно так
Код: Выделить всё
$ cd /home/builder/sources/ferro-remote/
$ git pull
.....
$ cd /home/builder/host/fr
$ make

обновит дерево исходников и пересоберет все для хостовой машине. Далее можно запустить и проверить агента. (в конце видео есть)

GUI client.
На рабочем столе есть значек QtCreator, в котором "последний проект" gui-client. в видео показано открытие и запуск простого скрипта (client-skel) и соединение с удаленной железкой (у меня на адресе 192.168.1.11:12345). Далее есть пример [youtube=https://youtu.be/rQjXgdSOYSU]https://youtu.be/rQjXgdSOYSU[/youtube] запуска агента локально и запуска клиента, который умеет выполнить удаленный вызов. Примеры для gui клиент лежат тут: /home/builder/sources/ferro-remote/gui-client/qml/examples

Еще пример снятия показания с удаленной железки и дальномера HC-SR04. тык. Пример, который использовался: тут

Так за сим я закончу описание в первом топике. Во втором опишу где находится сборка для arm (на самом деле в ~/arm; да, так тоже исходники с багами, которые нужно обновить).
nwnclv
 
Сообщения: 67
Зарегистрирован: 22 авг 2014, 19:04

Re: Образ со сборкой ferro-remote

Сообщение nwnclv » 12 сен 2016, 23:39

И так сборка под ARM 32 бита. Собиралось все искоробочным (а точнее изрепозиторным) arm-linux-gnueabi-gcc/g++ версии 5.4.0

Путь всей необходимой требухи для сборки это ~/arm. Софт "третьей стороны" находится по пути ~/arm/sdk. Там собраны для армов libboost 1.61 (весь) и libprotobuf 2.6.1. Как все это собиралось есть в builder-setup.txt, что валяется на раб.столе.

Итак, первое, что нужно сделать это зайти в ~/arm/ferro-remote/ и сделать
Код: Выделить всё
git pull
, который обновит все дерево исходников.
Второе - это собрать бинарники.
Заходим в ~/arm/build/fr/ и просто делаем
Код: Выделить всё
make
. ждем минуту и получаем собранные бинарники.
Тут собираются
~/arm/build/fr/agent/ferro_remote_agent - агент для работы на железке
~/arm/build/fr/lua-client/lua_client - lua_client для работы на железке.
И, к сожалению, совершенно забыл про fuse_client. Ну если комму-то понадобится, расскажу как собрать. Для армов нужно просто собрать libfuse по аналогии с libprotobuf и скормить правильный путь в cmake.
Кстати в хостовой сборке я так же забыл его собрать. НО с ней проще. нужно просто сделать sudo apt-get install libfuse-dev и в директории сборки ~/host/fr сделать make rebuild_cache && make

При завершении сборки бинарей strip не делается и можно его сделать вручную, чтоб убрать лишнее из бинарников.
Код: Выделить всё
$ pwd
/home/builder/arm/fr
$ arm-linux-gnueabi-strip agent/ferro_remote_agent
$ arm-linux-gnueabi-strip lua-client/lua_client


Теперь можно скопировать получившийся бинарник агента на виртурилку (например командой scp), запустить ее там, например
Код: Выделить всё
# ./ferro_remote_agent -s 0.0.0.0:12345 -l logfile.txt -D


# агент станет демоном и будет писать лог в logfile.txt. лог можно например в syslog, например указав -l syslog. логов, как и серверных точек можно указывать множество. Можно упустить параметр -D и тогда агент запустить просто в консоли. Для этого случая можно добавить параметр -l stdout, чтоб лог писался на консоль. в аттаче пример с малины.

и пробовать цепляться lua или gui клиентами. (см первый пост).

СБОРКА СО СТАТИЧЕСКОЙ GLIBC. Может быть такая ситуация, что при запуске агента на девайсе, появитс такая ошиька:
Код: Выделить всё
./ferro_remote_agent: /usr/lib/arm-linux-gnueabihf/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./ferro_remote_agent)

Вариатнта решения тут 2:
1: обновить систему на устройстве
2: всобраьт glibc в агента. Для этого есть ключик у cmake скрипта, который называется STATIC_GLIBC.
сценарий простой. Зайти в директорию сборки агента для девайса и сделать
Код: Выделить всё
$ cmake -DSTATIC_GLIBC=1 ../../ferro-remote
$ make

все это пересоберет агент уже со статической glibc. Можно опять скопировать и запускать.
Так. Со сборкой армов, вроде бы все понятно. В следующий раз расскажу больше как запускать агента.
Вложения
rpirun.png
Последний раз редактировалось nwnclv 18 сен 2016, 23:43, всего редактировалось 1 раз.
nwnclv
 
Сообщения: 67
Зарегистрирован: 22 авг 2014, 19:04

Re: Образ со сборкой ferro-remote

Сообщение nwnclv » 15 сен 2016, 01:36

Агент. Небольшой гайд. Где-то я уже его описывал, ну да ладно.

В задаче агента сидеть и слушаться команд, выдавая данные, передавая файлы и дергая пины.
Называется бинарник ./ferro_remote_agent. Подразумевается, что он работает на стороне виртурилки (либо на другой железке с линуксам). Пока он - исключительно сервер.

Для начала есть ключ --help, который покажет все доступные опции командной строки.
Код: Выделить всё
> ./ferro_remote_agent --help
Usage: ferro_remote_agent [options]
Allowed options:
  -? [ --help ]              help message
  -D [ --daemon ]            run process as daemon
  -n [ --name ] arg          agent name; whatever you want
  -l [ --log ] arg           files for log output; use '-' for stdout
  -s [ --server ] arg        endpoint name; <ip address>:<port> or <file name>
  -m [ --mcast ] arg         multicast receiver name; <ip address>:<port>
  -i [ --io-pool-size ] arg  threads for io operations; default = 1
  -r [ --rpc-pool-size ] arg threads for rpc calls; default = 1
  -o [ --only-pool ]         use io pool for io operations and rpc calls
  -c [ --config ] arg        lua script for configure server
  -k [ --key ] arg           format is: key=id:key; key will use for client
                             with this id; or key=key for key for any
                             connections


Подробнее об опциях

-D [ --daemon ]: запустить агента как демона. Все, думаю, понятно. Агент становится демоном и начинает слушать порт/порты, которые указаны в других опциях

-n [ --name ] arg: имя агента. Просто строка с каким-то произвольным именем. На случай, если в сети их несколько, то при мультикаст-разведке (о ней ниже) можно будет один отличить от другого по этому имени.

-l [ --log ] arg: приемник логов. Тут передается имя файла, в который пишется лог. Формат параметра: /path/to/log[min-max]. Где /path/to/log - путь к файлу лога, min - минимальный уровень лога, который писать в этот файл. max - максимальный уровень. Уровни: zer, err, wrn, inf, dbg. Кроме пути к файлу есть 3 специальных имени. stdout, stderr, syslog, которые пишут лог в соответствующие специальные файлы, то есть на консоль и сислог. Таким образом можно сказать агенту писать все логи в один файл, а ошибки в сислог, например.
Код: Выделить всё
./ferro_remote_agent  -l /var/logs/fr.log -l syslog[zer-wrn] ...
.
Формат лога для файлов/консоли:
Код: Выделить всё
2016-Sep-14 23:47:41.654017 M (inf) [log] Started.
"дата" "время с микросекундами" "отметка потока" "(уровень)" "[имя подсистемы]" "Текст"

Для сислога почти такой же, за исключением даты (его пишет сам сислог):
Код: Выделить всё
Sep 14 23:52:28 test-build ferro_remote: 23:52:28.256100 M <err> [listener] tcp://111.111.111.111:45575 failed to start; bind: Cannot assign requested address


-s [ --server ] arg. Точка входа. Сервер или сервера, которые необходимо открыть и слушать. Формат параметра address[:port]. В том случае, если указан порт - это TCP(поддерживаются v4 и v6), если порт не указан - локальный юникс-сокет.
Пример:
Код: Выделить всё
./ferro_remote_agent -s 0.0.0.0:12345 -s :::0 -s /home/data/fr.sock ...

Будут открыты 3 сервра: TCPv4 на все интерфейсы с портом 12345, TCPv6 на все интерфейсы с портом 0 (который скажет системе назначить порт автоматически) и локальный сокет по адресу /home/data/fr.sock. О чем лог сообщит в дальнейшем
Код: Выделить всё
2016-Sep-15 00:04:32.825110 M (dbg) [listener] Got: 0.0.0.0:12345
2016-Sep-15 00:04:32.825169 M (dbg) [listener] Got: :::0
2016-Sep-15 00:04:32.825208 M (dbg) [listener] Got: /home/data/fr.sock
2016-Sep-15 00:04:32.825369 M (inf) [listener] tcp://0.0.0.0:12345 started
2016-Sep-15 00:04:32.825394 M (inf) [listener] tcp://:::39369 started
2016-Sep-15 00:04:32.825439 M (inf) [listener] unix:///home/data/fr.sock started


-m [ --mcast ] arg: мультикаст приемники, которые будут отвечать на запросы. формат параметра ровно такой же, как у tcp точек сервера. Единственное отличие в том, что адрес должен быть валиден для мультикаста.
Пример:
Код: Выделить всё
/ferro_remote_agent -m 239.194.1.3:18852 -m ff08::13:18853 ...
Для мультикаста UDPv4 и UDPv6
в логе:
Код: Выделить всё
2016-Sep-15 00:18:48.448967 M (inf) [mcast] Adding endpoint '239.194.1.3:18852'; ipv4; port: 18852
2016-Sep-15 00:18:48.449034 M (dbg) [mcast] Store endpoint 239.194.1.3:18852. Ok
2016-Sep-15 00:18:48.449057 M (inf) [mcast] Adding endpoint 'ff08::13:18853'; ipv6; port: 18853
2016-Sep-15 00:18:48.449104 M (dbg) [mcast] Store endpoint ff08::13:18853. Ok
2016-Sep-15 00:18:48.449119 M (inf) [mcast] Starting point: 239.194.1.3:18852
2016-Sep-15 00:18:48.449145 M (inf) [mcast] Starting point: ff08::13:18853


-i [ --io-pool-size ] arg threads for io operations; default = 1
-r [ --rpc-pool-size ] arg threads for rpc calls; default = 1
-o [ --only-pool ] use io pool for io operations and rpc calls

Три параметра, отвечающие за количество потоков работы агента. IO - потоки для работы с IO-операциями, RPC - потоки для исполнения вызовов. -o - игнорировать параметр -r и использовать только один пул потоков. По-умолчанию используется 1 io поток и 1 rpc. Параметры сделаны, скорее, для тестов.

-k [ --key ] arg параметры для базовой авторизации. формат --key=[id:]key клиент должен знать id и ключ этого id для входа на агента. поле id можно упустить, тогда агенту достаточно будет знать просто ключ. Параметров может быть несколько.

И наконец -c [ --config ] arg . Это конфиг файл, который может заменить все остальные опции командной строки. Файл - простой lua-скрипт, которым можно настроить агента. При старте сначала читается этот файл, а потом считываются параметры командной строки. То есть любой параметр, заданный в строке перекроет параметр из файла. Пример файла можно увидеть тут, либо локально в исходниках.

О параметрах все.
nwnclv
 
Сообщения: 67
Зарегистрирован: 22 авг 2014, 19:04

Re: Образ со сборкой ferro-remote

Сообщение nwnclv » 19 сен 2016, 22:47

QtQuick интерфейс.

Пока выдалось время, напишу про клиента, который идет в gui-client.
Напомню, что это QML клиент. То есть это бинарник, который загружает QML файл и выполняет сценарии, в нем прописанные. Кто не в курсе, что такое QML, то советую почитать на эту тему, например тут. Русскоязычных ресурсов тоже есть. На ютубе есть видеоуроки. Грубо говоря все классы, который используются в клиенте это обычные Qt классы с сигналами и слотами. По ходу написания я, скорее всего, буду дописывать клиенту недостающие детали, если такие обнаружатся.

Для начала ...
Запустим агента на стороне железки (Virt2Real). В дополнение я запущу агента еще и локально. То есть будет 2 агента 192.168.3.1:12345 и локально по /home/data/fr.sock (локальный сокет).
Удаленный: на виртурилку зашел с root
Код: Выделить всё
# ./ferro_remote_agent -s 0.0.0.0:12345 -lstdout -n "remote"

Локальный
Код: Выделить всё
> ./ferro_remote_agent -s /home/data/fr.sock -l stdout -n "local


Далее просто можно открыть QtCreator и загрузить gui-client из последних проектов.

Теперь создаем файл, который назовем, например, test.qml (это обычный текстовый файл). И запишем его в параметры запуска gui_client. Например так: Изображение
Все, можно запустить клиент и увидеть серое окно. Так как кода пока нет.

Fr.Client
Основной компонент, который соединяется с удаленной системой. Его используют другие компоненты.
Для использования клиента для агента нужно подключить Fr.Client версии 1.0
Код:
Код: Выделить всё
import QtQuick 2.0
import Fr.Client 1.0
Rectangle { /// root компонент
    color: "gray" // пусть будет серым
    FrClient { // компонент
        id: remoteClient // id. через id компонент смогут использовать другие
        Component.onCompleted: connect( "192.168.3.1:12345" ) // когда компонент создался, соединимся с виртурилкой.
        /// метод connect принимает строку с IP:port либо именем локального сокета. 
    }
}


Запускаем и получаем следующее: агент показывает, что пришло соединение
Код: Выделить всё
2000-Jan-01 03:14:54.904461 M (inf) [listener] New connection: ep: tcp://0.0.0.0:12345 client: tcp://192.168.3.10:50626 total: 1

и окно, которое просто будет серым.
Изображение
Можно добавить второго клиента (их может быть множество в одном приложении), который будет соединен с локальным агентом.
Код: Выделить всё
import QtQuick 2.0
import Fr.Client 1.0

Rectangle {
    color: "gray"
    FrClient {
        id: remoteClient
        Component.onCompleted: connect( "192.168.3.1:12345" ) // соединен с виртурилкой
    }
    FrClient {
        id: localClient
        Component.onCompleted: connect( "/home/data/fr.sock" )  // соединен локально
    }
}

Думаю, все понятно.

Свойства клиента
Пока их всего 2: sessionId и sessionKey. отвечают за ID и ключ сессии для базовой авторизации клиента.

События/сигналы клинета
Клиент сообщает о своем состоянии сигналами.
Код: Выделить всё
       
        void tryConnect( ); /// клиент начал попытку соединения
        void connected( );  /// клиент успешно соединился !Факт соединения не обозначает готовность к работе!
        void disconnected( ); /// клиент рассоединился
        void channelReady( ); /// клиент готов к работе
        void initError( const QString &message ); // ошибка при инициализации соединения.

Порядок вызова примерно такой:
сначала клиент начинает попытку соединения - tryConnect
потом он соединяется - connected
потом у него готов канал и его могут использовать клиенты - channelReady
В любой момент между tryConnect и channelReady может возникнуть сигнал initError с текстом ошибки, который скажет, что что-то пошло не так.

Пример: немного измененный
Код: Выделить всё
import QtQuick 2.0
import Fr.Client 1.0

Rectangle {
    color: "gray"
    FrClient {
        id: remoteClient
        Component.onCompleted: connect( "192.168.3.1:12345" )
    }
    FrClient {
        id: localClient
        Component.onCompleted: connect( "/home/data/fr.sock" )
    }
    Connections {
        target: localClient
        onTryConnect:   console.log( "local: I'm trying!!!" )
        onConnected:    console.log( "local connected" )
        onChannelReady: console.log( "local channel ready" )
        onDisconnected: console.log( "local disconnected" )
        onInitError:    console.log( "local init error " + message )
    }
}

тут корневой компонент подписан на события клиента, который соединен локально. При нормальной работе, при соединении, будет примерно такая картина на консоль:
Код: Выделить всё
qml: local: I'm trying!!!
qml: local connected
qml: local channel ready

Теперь я "сломаю соединение", например погасив локального агента:
Код: Выделить всё
qml: local: I'm trying!!!
qml: local init error Connection refused

Сломаю по-другому. Например задав на локальном агенте ключ доступа:
Код: Выделить всё
$ ./ferro_remote_agent -s /home/data/fr.sock -l stdout -n "local" --key=111

Запустив клиента, получим
Код: Выделить всё
qml: local: I'm trying!!!
qml: local connected
qml: local init error Session key required
qml: local disconnected


С основным клиентом все. Такой вот он небольшой.
nwnclv
 
Сообщения: 67
Зарегистрирован: 22 авг 2014, 19:04

GPIO

Сообщение nwnclv » 24 сен 2016, 21:57

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

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

Ахтунг! По какой-то странной причине пример не хотел изначально заводиться на виртурилке (на малине все было норм). Поправил путем захода на панель управления и подергал там пины туда-сюда, все заработало.

И так для начала расскажу, что схема светофора довольно проста. Есть 3 диода, и 3 GPIO (у меня это 10, 11, 12; зеленый, желтый, красный соответственно) Второй стороной диоды воткнуты в землю.

В качестве основы я возьму предыдущий пример с соединением клинета с удаленной железкой и буду доделывать его.
Для начала добавлю кнопку:
Код: Выделить всё
import QtQuick 2.0
import QtQuick.Controls 1.1 // для кнопки
import Fr.Client 1.0

Rectangle {
    color: "gray"
    FrClient {
        id: remoteClient
        Component.onCompleted: connect( "192.168.3.1:12345" )
    }

    Button {
        text: "Next State"
        property int state: 0
    }

    Connections {
        target: remoteClient
        onTryConnect:   console.log( "local: I'm trying!!!" )
        onConnected:    console.log( "local connected" )
        onChannelReady: console.log( "local channel ready" )
        onDisconnected: console.log( "local disconnected" )
        onInitError:    console.log( "local init error " + message )
    }
}

тут все просто. Старый-добрый клиент с выводом его состояния в консоль. Плюс кнопка с надписью "Next State".
Как-то так Изображение

Светофор имеет 4 состояния: зеленый, желтый, красный, красный-желтый. То есть в определенном состоянии нужно зажигать нужные диоды и гасить другие.
Для работы с GPIO есть класс, который называется FrClientGpio. Нужно добавить 3 таких клиента и привязать из к определенным пинам. + рассказать пинам, что они находятся в состоянии "out"
Пример такого класса, добавленного в QML файл
Код: Выделить всё
    FrClientGpio {
        client: remoteClient
        direction: FrClientGpio.DirectOut
        id: red
        index: 12
    }

Это объект, привязанный к GPIO #12 (index) на виртурилке c установленным направлением out (FrClientGpio.DirectOut). Кроме того класс использует канал клиента remoteClient, который соединен с виртурилкой. id - имя, по которому этот объект будет доступен внутри QML. Это красный диод, осалось добавить еще 2 таких же. для желтого и зеленого.
Код: Выделить всё
    FrClientGpio { // красный
        client: remoteClient
        direction: FrClientGpio.DirectOut
        id: red
        index: 12
    }
    FrClientGpio { // желтый
        client: remoteClient
        direction: FrClientGpio.DirectOut
        id: yellow
        index: 11
    }
    FrClientGpio { // зеленый
        client: remoteClient
        direction: FrClientGpio.DirectOut
        id: green
        index: 10
    }

Вот и все. Осталось создать обработчик кнопки.
Он будет:
В зависимости от текущего состояния включать или гасить нужные диоды, увеличивать состояние на 1 и брать модуль 4.
итого:
Код: Выделить всё
    Button {

        text: "Next State"
        property int state: 0

        onClicked: {
            switch ( state ) {
            case 0:
                green.value  = 1;
                yellow.value = 0;
                red.value    = 0;
                break;
            case 1:
                green.value = 0;
                yellow.value = 1;
                break;
            case 2:
                red.value = 1;
                yellow.value = 0;
                break;
            case 3:
                yellow.value = 1;
                break;
            }
            state = (state + 1) % 4
        }
    }


Все, теперь при нажатии на кнопку будет менять состояние и включаться выключаться определенные диоды. Весь код выглядит так:
Код: Выделить всё
import QtQuick 2.0
import QtQuick.Controls 1.1
import Fr.Client 1.0

Rectangle {
    color: "gray"
    FrClient {
        id: remoteClient
        Component.onCompleted: connect( "192.168.3.1:12345" )
    }

    FrClientGpio {
        client: remoteClient
        direction: FrClientGpio.DirectOut
        id: red
        index: 12
    }

    FrClientGpio {
        client: remoteClient
        direction: FrClientGpio.DirectOut
        id: yellow
        index: 11
    }

    FrClientGpio {
        client: remoteClient
        direction: FrClientGpio.DirectOut
        id: green
        index: 10
    }

    Button {

        text: "Next State"
        property int state: 0

        onClicked: {
            switch ( state ) {
            case 0:
                green.value  = 1;
                yellow.value = 0;
                red.value    = 0;
                break;
            case 1:
                green.value = 0;
                yellow.value = 1;
                break;
            case 2:
                red.value = 1;
                yellow.value = 0;
                break;
            case 3:
                yellow.value = 1;
                break;
            }
            state = (state + 1) % 4
        }
    }

    Connections {
        target: remoteClient
        onTryConnect:   console.log( "local: I'm trying!!!" )
        onConnected:    console.log( "local connected" )
        onChannelReady: console.log( "local channel ready" )
        onDisconnected: console.log( "local disconnected" )
        onInitError:    console.log( "local init error " + message )
    }
}

Засим все. Этот интерфейс управляется через sysfs. Как ожидать события изменения состояния пина расскажу в следующий раз
Вложения
IMG_20160924_205441.jpg
nwnclv
 
Сообщения: 67
Зарегистрирован: 22 авг 2014, 19:04

GPIO events

Сообщение nwnclv » 25 сен 2016, 22:37

Тут про события.

Некоторые пины (на виртурилке это 1-9, если не ошибаюсь), умеют генерировать прерывания при смене своих состояний. Это пины, которые поддерживают так называемый edge (https://www.kernel.org/doc/Documentation/gpio/sysfs.txt поиск по edge). Агент может это событие отслеживать и сигналить подписанному на него клиенту.

Пример.

Изменю предыдущий пример для смены состояния светофора с кнопки, которая будет подключена к вирутрилке. Для кнопки я выбрал пин GPIO5, который будет работать в режиме in, второй хвост кнопки будет воткнут в 3 вольта.

План:
Кнопка нажимается - меняется состояние пина,
Меняется состояние пина - агент сигналит об этом клиенту.
Клиент включает нужные диоды и выключает ненужные, прибавляет к состоянию 1 и делит по модулю 4 (светофор имеет 4 состояния). В общем логику кнопки с формы я перенесу на кнопку физическую.

Для начала добавлю объект класса FrClientGpio, который будет ассоциирован с пино GPIO5.
Код: Выделить всё
    FrClientGpio {
        client:     remoteClient
        index:      5
        direction:  FrClientGpio.DirectIn // направление IN
        edge:       FrClientGpio.EdgeBoth // значение EDGE - сброс и подъем
        id:         button // имя
        events:     true /// параметр говорит, что объект будет ждать событий об изменении значения.
    }


Теперь при смене состояния пина объект button будет генерячить событие сhangeEvent, в котором будет приходить значение пина, и метка таймера события в микросекундах. Метка ставится на стороне агента и отсчитывает количество микросекунд, которое прошло со старта агента. При помощи этого значения можно, например, работать с дальномерами.

Когда еслть объект, который умеет генерячить события, можно на это событие подписать основной компонент в корне test.qml
Для начала добавить значение state, добавить соединение с объектом button и скопировать логику, котороя до этого была в копке на форме.
Код: Выделить всё
Rectangle {
    color: "gray"
    property int state: 0
.............
    Connections {
        target: button // цель - кнопка
        onChangeEvent: {
            if( button.value == 1 ) {
              console.log( value, interval ) // просто вывод состония и интервала на консоль
              switch ( state ) {
              case 0:
                green.value  = 1;
                yellow.value = 0;
                red.value    = 0;
                break;
              case 1:
                green.value = 0;
                yellow.value = 1;
                break;
              case 2:
                red.value = 1;
                yellow.value = 0;
                break;
              case 3:
                yellow.value = 1;
                break;
              }
            state = (state + 1) % 4
            }
        }
    }
}

Итого полный код qml будет выглядеть так:
Код: Выделить всё
import QtQuick 2.0
import QtQuick.Controls 1.1
import Fr.Client 1.0

Rectangle {
    color: "gray"
    property int state: 0

    FrClient {
        id: remoteClient
        Component.onCompleted: connect( "192.168.3.1:12345" )
    }

    FrClientGpio {
        client:     remoteClient
        index:      12
        direction:  FrClientGpio.DirectOut
        id:         red
    }

    FrClientGpio {
        client:     remoteClient
        index:      11
        direction:  FrClientGpio.DirectOut
        id:         yellow
    }

    FrClientGpio {
        client:     remoteClient
        index:      10
        direction:  FrClientGpio.DirectOut
        id:         green
    }

    FrClientGpio {
        client:     remoteClient
        index:      5
        direction:  FrClientGpio.DirectIn
        edge:       FrClientGpio.EdgeBoth
        id:         button
        events:     true
    }

    Connections {
        target: button
        onChangeEvent: {
            if( button.value == 1 ) {
                console.log( value, interval )
                switch ( state ) {
                case 0:
                    green.value  = 1;
                    yellow.value = 0;
                    red.value    = 0;
                    break;
                case 1:
                    green.value = 0;
                    yellow.value = 1;
                    break;
                case 2:
                    red.value = 1;
                    yellow.value = 0;
                    break;
                case 3:
                    yellow.value = 1;
                    break;
                }
                state = (state + 1) % 4
            }
        }
    }
    Connections {
        target: remoteClient
        onTryConnect:   console.log( "local: I'm trying!!!" )
        onConnected:    console.log( "local connected" )
        onChannelReady: console.log( "local channel ready" )
        onDisconnected: console.log( "local disconnected" )
        onInitError:    console.log( "local init error " + message )
    }
}

При старте приложения, агент на стороне виртурилки скажет примерно следующее:
Код: Выделить всё
2000-Jan-03 00:15:16.822540 M (inf) [listener] New connection: ep: tcp://0.0.0.0:12345 client: tcp://192.168.3.10:45712 total: 1
2000-Jan-03 00:15:16.854173 R (dbg) [gpio] Open device with ID: 5 fd: 10
2000-Jan-03 00:15:16.884135 R (dbg) [gpio] Add device to reactor; ID: 101 fd: 10
2000-Jan-03 00:15:16.907055 R (dbg) [gpio] Open device with ID: 10 fd: 11
2000-Jan-03 00:15:16.936535 R (dbg) [gpio] Open device with ID: 11 fd: 12
2000-Jan-03 00:15:16.966602 R (dbg) [gpio] Open device with ID: 12 fd: 13

То есть открыто 4 пина, и один их них подписан на изменение состояния.
При нажатии кнопки в консоль qml будет выведено
Код: Выделить всё
qml: local: I'm trying!!!
qml: local connected
qml: local channel ready
qml: 1 3568022371
qml: 1 3569058794
qml: 1 3569923058
qml: 1 3570805850
qml: 1 3571729453
....

При этом будет меняться состояние диодов светофора.
Изображение
Вложения
IMG_20160925_213247.jpg
nwnclv
 
Сообщения: 67
Зарегистрирован: 22 авг 2014, 19:04

GPIO events 2. encoder

Сообщение nwnclv » 02 окт 2016, 00:43

Тут опять про события и про несколько клиентов, подписанных на одно и тоже событие.

Для примера возьму такой девайс, как энкодер. Энкодер это такая штука
Изображение

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

И так есть 3 пина...

Смысл работы довольно прост. На средний пин подается (+). Путь пины будут именоваться A, P, B. Если на девайс смотреть сверху, то, по часовой стрелке, сначала идет А, потом P, потом B.

При повороте по часовой стрелке сначала срабатывает соединение A-P затем P-B, затем A-P отключается и последним отключается P-B. Итого имеем, как и в случае светофора, 4 состояния с 2 пинами.

0, 0 - начальная стадия
1, 0 - начало поворота
1,1 - поворот
0, 1 - завершение поворота.

Реализация:
Сделаю простое окошко с картинкой компасного табло (выложил сюда https://github.com/newenclave/ferro-rem ... compas.png), которое будет поворачиваться на определенный угол, когда я буду вращать энкодер.

Забегая вперед; вот так выглядит окошко с результатом:
Изображение
Табло крутится, spinbox показывает на сколько градусов повернуть табло при одном щелчке девайса. кнопка сбрасывает позицию на 0.

QML
Описывать создание и подключение клиента не буду. Отмечу только, что он имеет id remoteClient, как и во всех остальных примерах
Опишу сразу компонент Encoder. Который будет содержать счетчик уменьшая или увеличивая его при повороте ручки.
В qml это будет отдельный Item. Что это такое и какие у него есть свойства можно почитать тут http://doc.qt.io/qt-5/qml-qtquick-item.htm
Свойством элемента будет одно поле - счетчик.
Код: Выделить всё
    Item {
        id: encoder
        property int count: 0
        ....
    }


Сам девайс у меня подключен ногой A в 5 GPIO, и ногой B в GPIO4. Направление обоих пинов стоит в IN, edge в both. Плюс у каждого есть булевый флаг up, который показывает поднят ли текущий пин. Итого:

Код: Выделить всё
    Item {
        id: encoder
        property int count: 0

        FrClientGpio {
            property bool up: false
            client:     remoteClient
            index:      5
            direction:  FrClientGpio.DirectIn
            edge:       FrClientGpio.EdgeBoth
            id:         pinA
            events:     true
        }

        FrClientGpio {
            property bool up: false
            client:     remoteClient
            index:      4
            direction:  FrClientGpio.DirectIn
            edge:       FrClientGpio.EdgeBoth
            id:         pinB
            events:     true
        }
    }

Далее логика проста:
Подписываемся на события обоих пинов и когда оно приходит, устанавливаем флаг up, если значение в событии 1 и сбрасываем, если 0.
Если флаг поднялся, то посмотрим на другой флаг и если он тоже поднят, то можно сделать вывод, что мы находимся в процессе поворота ручки. В зависимости от того, в каком мы находимся обработчике, уменьшаем или увеличиваем значение на счетчика.
Код:
Код: Выделить всё
    Item {

        id: encoder
        property int count: 0

        FrClientGpio {
            property bool up: false
            client:     remoteClient
            index:      5
            direction:  FrClientGpio.DirectIn
            edge:       FrClientGpio.EdgeBoth
            id:         pinA
            events:     true
        }

        FrClientGpio {
            property bool up: false
            client:     remoteClient
            index:      4
            direction:  FrClientGpio.DirectIn
            edge:       FrClientGpio.EdgeBoth
            id:         pinB
            events:     true
        }
        Connections {
            target: pinB
            onChangeEvent: {
                pinB.up = (value != 0);
                if( pinB.up && pinA.up ) { /// вторым сработал пин B, значит ручка крутится по часовой стрелке
                    encoder.count = encoder.count + 1; /// увеличим значение
                }
            }
        }

        Connections {
            target: pinA
            onChangeEvent: {
                pinA.up = (value != 0);
                if( pinB.up && pinA.up ) { /// вторым сработал пин A, значит ручка крутится против часовой стрелки
                    encoder.count = encoder.count - 1; // минус 1 к счетчику
                }
            }
        }

Компонент готов. Теперь можно добавить картинку на окошко и привязать его свойство rotation к свойству count энкодера.
Код: Выделить всё
    Image {
        id: compas
        source: "compas.png"
        rotation: encoder.count
        Component.onCompleted: {
            parent.height = compas.height
            parent.width  = compas.width
        }
    }

+ картинка (а точнее компонент Image) сделает так, чтоб родительское окно приняло нужные размер по размеру картинки.
Дальше я просто добавлю кнопку, которая будет сбрасывать счетчик, и SpinBox, который будет устанавливать шаг в градусах.
Код: Выделить всё
    SpinBox {
        id: step
        minimumValue: -359
        maximumValue:  359
        value:         1 // по-умолчанию 1
        anchors { // слева вниpe
            left:   parent.left
            bottom: parent.bottom
        }
    }

    Button {
        id: clear
        text: "Drop"
        anchors { /// справа внизу
            right: parent.right
            bottom: parent.bottom
        }
    }

И теперь вместо строк encoder.count = encoder.count - 1; и encoder.count = encoder.count + 1; можно задать encoder.count = encoder.count - step.value; и encoder.count = encoder.count + step.value;
в элемент encoder же добавлю подписку на кнопку
Код: Выделить всё
    Item {

        id: encoder
        property int count: 0
.....
        Connections {
            target: clear
            onClicked: {
                encoder.count = 0
            }
        }
    }

С кодом все. Весь код можно увидеть >>> ТУТ <<<.

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

Далее я снял 4 видео:

Первое это запуск и работа 1 клиента на виртуалбоксе, образ которого описан в 1 топике.
https://www.youtube.com/watch?v=XnhfxSyHIHA

Второе запуск 6 клиентов на этом виртуал боксе. + установка разных шагов каждому клиенту.
https://www.youtube.com/watch?v=xdBe3yXHJyI

Третье - запуск на рабочем ноуте 1 клиента. Девайс между 2 картонками, чтоб держать удобнее было.
https://www.youtube.com/watch?v=2Zi9XgNpcVA

Четверное - запуск 10 клиентов на рабочем ноуте.
можно смотреть с 30 секунды.
https://youtu.be/9tKPsecC_5Y?t=30s
nwnclv
 
Сообщения: 67
Зарегистрирован: 22 авг 2014, 19:04

GPIO events 3. Interval. Дальномер

Сообщение nwnclv » 03 окт 2016, 01:42

Ну и чтоб совсем закончить с GPIO...

Есть игрушечный дальномер hc-sr04, который куплен за 50, кажется, центов. Написано, что измеряет от 4 см до 4 метров. Работает от 5 вольт.
Изображение

Принцип довольно простой. Есть 2 пина: триггер и эхо. Необходимо поднять триггер на некоторое время, далее девайс сам поднимет эхо и затем сбросит его через время, которое зависит от расстояния.

то есть нужно описать 2 GPIO, один из которых - триггер, будет работать в OUT режиме, а другой - эхо, будет работать на IN. От эхо нужно будет получать события и делать из них выводы о расстоянии.
Как обычно опишу все отдельным компонентом, который назову sonic

Код: Выделить всё
    Item {

        id:sonic
        property real tick: 0 /// последнее значение интервала времени
        property real lastDistance: 0.0 /// последнее измеренное расстония

        FrClientGpio {
            id:         trigger
            index:      4
            client:     remoteClient
            direction:  FrClientGpio.DirectOut
        }

        FrClientGpio {
            id:         echo
            index:      5
            client:     remoteClient
            direction:  FrClientGpio.DirectIn
            edge:       FrClientGpio.EdgeBoth
            events:     true
        }
....
}


Замер буду делать, например пару раз в секунду. Для этого возьму таймер
Код: Выделить всё
    Timer {
        id: shot
        interval: 500
        repeat: true
        running: true
    }

и от него буду обрабатывать событие onTriggered
Код: Выделить всё
        Connections {
            target: shot
            onTriggered: {
                sonic.tick = 0;
                trigger.makePulse( 70 )
            }
        }


вызов makePulse поднимет на время пин. Время тут в микросекундах. Так как на виртурилке есть pwm, то это можно сделать через него.
Далее вся остальная логика
Сначала значение последнего тика = 0
после этого девайс поднимает пин эхо,
потом скинет.

В событии GPIO есть значение interval, который показывает отметку времени, в который произошло событие. Отметка в микросекундах.
Обработчик:
Код: Выделить всё
        Connections {
            target: echo
            onChangeEvent: {
                if( value == 0 ) {
                    sonic.lastDistance = (interval - sonic.tick) / 58
                }
                sonic.tick = interval
            }
        }


Далее я просто добавлю lable на форму и буду выводить значения туда.
Весь получившийся код ТУТ

Ну и небольшое видео как все это работает. https://youtu.be/N5axiHeVgyw Из видео видно, что иногда показывает хрень. Но в среднем показывает довольно точные расстояния.
nwnclv
 
Сообщения: 67
Зарегистрирован: 22 авг 2014, 19:04

I2C. Гироскоп L3G4200D

Сообщение nwnclv » 10 окт 2016, 20:59

Интерфейс для работы с устройствами по интерфейсу I2C.

На примере девайса L3G4200D. Это гироскоп. Я не будут его калибровать, не буду ловить ошибки. Просто покажу, как записать и прочитать регистры.

QML клиент для I2C называется FrClientI2c

Как обычно у него есть свойство client, которое привязывает объект к объекту основного клиента, который соединен с агентом.

И как обычно опишу гироскоп отдельным компонентом, который будет менять x, y, z и регистр изменения температуры (почемуб и нет?).

Код: Выделить всё
    Item {
        id:gyro
        property int address: 0x69

        property int xReg: 0
        property int yReg: 0
        property int zReg: 0
        property int tempReg: 0
        FrClientI2c {
            id: gyroDev
            client: remoteClient
            busId: 1
            slaveAddress: gyro.address
            ......
        }
        ......
    }

Итого компонент будет иметь 5 свойств. address - slave address устройства, значение которого для L3G4200D равно 0x69
xReg, yReg, zReg, tempReg - 4 регистра данных, которые будут содержать значения из устройства. Будут это все обновляться, как обычно, через таймер. Плюс компонент содержит объект для работы с устройством с id gyroDev.

Init

Для начала устройство нужно инициализировать. Небольшая такая настройка. Для этого запишу регистры 0х20-0х24. Что они значат можно глянуть в даташит
Код: Выделить всё
            function init( )
            {
                gyroDev.writeBytes( [
                   {0x20: 15},
                   {0x21: 0 },
                   {0x22: 8 },
                   {0x23: 0 },
                   {0x24: 0 },
                ] )
            }

метод writeBytes объекта gyroDev пишет байты (!!). Имеет 2 возможных варианта параметров: 1 - хеш таблица {reg1: value1, reg2: value2, ... }; 2 - массив таких хеш-таблиц. 2 вариант применен в примере. Отличие у способов в том, что в первом случае нет гарантии последовательности записи регистров. Массив гарантирует то, что все его хеш таблицы будут выгружены в своей последовательности.
Init имеет смысл вызвать по готовности объекта к работе.
У каждого компонента есть свойство ready, которое эту готовность показывает. План такой:
1) подпишемся на событие изменения этого своейтсва
2) получим там true
3) выполним init для устройства.
Итого:
Код: Выделить всё
    Item {
        id:gyro
        property int address: 0x69

        property int xReg: 0
        property int yReg: 0
        property int zReg: 0
        property int tempReg: 0

        FrClientI2c {
            id: gyroDev
            client: remoteClient
            busId: 1
            slaveAddress: gyro.address

            function init( )
            {
                gyroDev.writeBytes( [
                   {0x20: 15},
                   {0x21: 0 },
                   {0x22: 8 },
                   {0x23: 0 },
                   {0x24: 0 },
                ] )
            }
..................
        Connections {
            target: gyroDev
            onReadyChanged: {
                if( value ) {
                    gyroDev.init( )
                }
            }
        }
    }

Устройство готово. Теперь можно его читать. X, Y, Z находятся в регистрах: 0x28, 0x29 - X, 0x2A, 0x2B - Y, 0x2C, 0x2D,- Z и регистр изменения температуры - 0x26. Значения xyz - 16 битные, температура - 8 битная.
Метод readBytes объекта gyroDev читает байты (!!). В качестве переметра принимает массив значений регистров, которые необходимо прочитать. Возвращает хеш-таблицу с {reg: value}
Итого нужно прочитать 7 регистров за 1 вызов, привести к 16 битным значениям xyz и установить значения объекта gyro.
Код: Выделить всё
            function getValues( )
            {
                var values = [ 0x28, 0x29, // X
                               0x2A, 0x2B, // Y
                               0x2C, 0x2D, // Z
                               0x26 ]      // Temp

                var res = gyroDev.readBytes( values )

                var x = 0, y = 0, z = 0, t = 0

                if( (0x28 in res) && (0x29 in res)) {
                    x = (res[0x29] << 8) | res[0x28]
                }

                if( (0x2A in res) && (0x2B in res)) {
                    y = (res[0x2B] << 8) | res[0x2A]
                }

                if( (0x2C in res) && (0x2D in res)) {
                    z = (res[0x2D] << 8) | res[0x2C]
                }

                if( 0x26 in res ) {
                    t = res[0x26]
                }

                gyro.xReg       = x
                gyro.yReg       = y
                gyro.zReg       = z
                gyro.tempReg    = t

            }

Все. Теперь это нужно сделать раз, например, в пол секунды и вывести куда-то эту информацию. Как это сделать объяснять не буду. Это уже совсем просто. Весь исходник QML можно глянуть здесь
По традиции видео того, что получилось: https://www.youtube.com/watch?v=CVaWFwW64gw
nwnclv
 
Сообщения: 67
Зарегистрирован: 22 авг 2014, 19:04

Re: Образ со сборкой ferro-remote

Сообщение verial » 25 окт 2016, 08:43

Читаю ваш топик очень интересно, только хочеться продолжения) вам подстать написать на хабре.
verial
 
Сообщения: 48
Зарегистрирован: 22 июл 2014, 13:38
Откуда: Москва

След.

Вернуться в Софт на управляющих устройствах (iOS, Android, Win/Mac/Linux)

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 2

cron