Manual
do
Maker
.
com
Com esse artigo, introduziremos alguns novos conceitos importantes para integrar recursos do C++ como backend para se comunicar com a interface em QML, agora usando também o QT MQTT para fazer a comunicação, como prometido no artigo anterior. Se não está acompanhando a série, sugiro que comece desse artigo.
No artigo anterior vimos como interagir entre arquivos QML, através de alias e import. Agora vamos manipular as propriedades dos componentes a partir do C++.
Para relembrar um ponto importante, abaixo disponho do código que insere uma imagem de fundo à janela que será exibida no display:
Image {
anchors.fill: parent
x: 0
y: 0
id: rpi
source: "raspberry.jpeg"
opacity: 0.2
}
Repare que, como anteriormente explicado, o identificador do componente é o id. Ele é utilizado para acessar propriedades de um componente a partir de outra parte do código QML. Porém, para acessar uma propriedade do componente a partir do QML, alguns procedimentos iniciais serão necessários.
Assim como id é utilizado para identificar um componente no código QML, para acessar o mesmo componente a partir do C++ devemos utilizar outro tipo de identificador; o objectName. Dessa vez, o nome do identificador deve ser protegido por aspas, tal como o exemplo acima o faz para identificar a imagem a ser utilizada para o background do display, especificado na propriedade source.
Não há problema algum em ter ambos os identificadores na declaração do componente. Para o componente Dial eu utilizei a seguinte porção de código:
Dial {
objectName: "dialTemp"
id: dialTemp
x: 19
y: 48
width: 53
height: 53
value: 0.374
background: Rectangle {
x: dialTemp.width / 2 - width / 2
y: dialTemp.height / 2 - height / 2
width: Math.max(64, Math.min(dialTemp.width, dialTemp.height))
height: width
color: "transparent"
radius: width / 2
border.color: dialTemp.pressed ? "#17a81a" : "#21be2b"
opacity: dialTemp.enabled ? 1 : 0.3
}
handle: Rectangle {
id: handleItemTemp
x: dialTemp.background.x + dialTemp.background.width / 2 - width / 2
y: dialTemp.background.y + dialTemp.background.height / 2 - height / 2
width: 6
height: 3
color: dialTemp.pressed ? "#17a81a" : "#21be2b"
radius: 8
antialiasing: true
//opacity: control.enabled ? 1 : 0.3
transform: [
Translate {
y: -Math.min(
dialTemp.background.width,
dialTemp.background.height) * 0.4 + handleItemTemp.height / 2
},
Rotation {
angle: dialTemp.angle
origin.x: handleItemTemp.width / 2
origin.y: handleItemTemp.height / 2
}
]
}
Apenas as primeiras 3 linhas são importantes nesse momento. O restante do código é personalização do componente, também descrito no artigo anterior. A personalização não é obrigatória, caso ache código demais, apenas quis modificar a aparência do componente para harmonizar com a janela.
Repare que em ambos os identificadores utilizei o mesmo nome. Não é mandatório e sendo iguais ou diferentes, funcionarão do mesmo modo, desde que respeitada a proteção de objectName por aspas.
Nenhum dos identificadores pode se repetir em outro componente. Por exemplo, se for definido:
Dial {
objectName: "dial"
...
}
Outros componentes não poderão ter o nome "dial" como idenficador.
Já no código C++, precisamos criar um QObject para acessar os dados do QML, que será recebido de um QQuickItem. Por essa razão, o primeiro passo no código C++ é incluir a biblioteca QQuickItem:
#include <QQuickItem>
Depois, criamos um QObject, trazendo o objeto raiz com todos seus componentes:
QObject* mainPage = engine.rootObjects().first();
Depois disso, já podemos começar a acessar as propriedades dos componentes do nosso arquivo QML:
mainPage->findChild<QQuickItem*>("labelTempRPi")->setProperty("text", "42.2 C");
No exemplo acima, o objectName a ser tratado é o "labelTempRPi", definido previamente no arquivo QML. A propriedade a ser modificada é o texto, cujo identificador é text, que modificamos através do método setProperty, passando a propriedade e o valor.
Não se preocupe com o código, ele estará disponível para download mais ao final do artigo, agora tente apenas entender os conceitos, sem compromisso com desenvolvimento.
No projeto originado a partir do exemplo swipe, disponível na própria IDE QtCreator, temos apenas os arquivos ui.qml, .qml e o main.cpp.
Particularmente, não gosto de escrever código de rotina dentro do arquivo main.cpp. Para testar o funcionamento dos recursos a serem utilizados, tudo bem, mas a implementação fica mais "limpa" se for feita em uma classe à parte. Para isso criei uma classe que interage com os componentes QML e de quebra recebe as atualizações das informações advindas de sensores por MQTT, como é o caso dos componentes dial.
A temperatura do Raspberry é pega do próprio sistema, utilizando outro recurso do Qt, que discorro mais adiante.
Tendo o projeto aberto, basta ir ao menu File > New File or Project > C++ Class.
Na tela seguinte, dê um nome à classe (eu escolhi MyComm, mas pode ser o que quiser). Em Base Class escolha QObject e marque a caixa de seleção Include QObject. No vídeo mostrarei a criação de uma classe base, mas é bastante simples. Uma estrutura inicial será criada e só precisamos implementar o código sobre ela.
Nessa classe, criei alguns slots e alguns métodos, nada complexo. No arquivo .h definimos os construtores e implementamos no arquivo .cpp. Mas antes disso, será necessário adicionar o suporte ao QT MQTT.
Clone ou baixe o arquivo zip do meu repositório. No notebook, utilizei o qmake do Qt baixado diretamente do site. Instalei também outra versão, mas essa não utilizei ainda. Se quiser, aproveite e baixe-a também.
Exceto pelo nome do diretório, o processo restante é o mesmo:
cd qmqtt
qmake -r
make
sudo make install
Se tudo correr bem, siga adiante, senão, resolva o problema primeiro.
No Qt temos o arquivo de projeto, que é o nome do projeto seguido pela extensão .pro. Nela, devemos adicionar outros recusos do Qt que não o quick, já presente.
QT += quick network core qmqtt
Agora vá ao arquivo MyComm.h (ou no arquivo de header com o nome que você definiu) e inclua, dentre outras, o qmqtt:
#include <QObject>
#include <QQuickItem>
#include <QDebug>
#include <QTimer>
#include <QProcess>
#include "qmqtt.h"
Explico previamente o que usaremos de cada uma delas.
O QObject já estará incluso, desde a criação da classe. O QQuickItem é o que utilizaremos para pegar os componentes do arquivo QML. O QDebug é como o cout, o printf ou o Serial.println do Arduino, apenas para exibir mensagens, que é mais rápido do que usar o debugger para analisar a implementação. O debugger uso normalmente quando há realmente alguma anomalia que precisa ser analisada. Devo mostrar isso em algum artigo posterior relacionado a Qt.
A biblioteca QTimer está sendo utilizada para temporizar a captura de temperatura do Raspberry, cujo processo é feito a partir de um slot, descrito mais adiante.
A biblioteca QProcess é muito próximo de uma chamada system, mas ela realmente consegue interagir com um binário de sistema, de forma a ser uma excelente escolha para criar front-ends. É muito comum em Linux a utilização de front-ends, de forma que o binário com função específica se torna compatível com utilização por terminal ou por GUI.
A implementação é curta e os feedbacks são emitidos por sinais, chamados no Qt de signals. Para interagir com os signals, utilizamos os slots.
Os sinais são como as interrupções na MCU e os slots são como as ISR. Para relacionar um sinal a um slot, utilizamos a chamada connect do QObject.
Para implementar a conexão, precisamos de 6 linhas de código, mas para ter uma implementação mais robusta é fundamental implementar alguns desses sinais. Vou manter a minha senha de teste e o IP do meu broker no código para fidelizar o que está funcional. Criei o método start() com todo o código necessário para a conexão acontecer:
this->client = new QMQTT::Client(QHostAddress("192.168.1.104"), 1883);
client->setClientId("teste2");
client->setUsername("dobitaobyte");
client->setPassword("fsjmr112");
connect(client,SIGNAL(connected()),this,SLOT(onConnected()));
connect(client,SIGNAL(disconnected()),this,SLOT(onDisconnected()));
connect(client,SIGNAL(received(const QMQTT::Message&)),this,SLOT(onReceived(const QMQTT::Message&)));
client->connectToHost();
Repare que, tirando o connect, temos 5 linhas. A sexta linha é a subescrição ao tópico MQTT. Já explico onde está.
Aqui temos um objeto client criado a partir da classe QMQTT::Client do QT MQTT. Ao instanciar o objeto, passamos o endereço e a porta. O endereço deve ser um QHostAddress, por isso o IP está dentro de outro método.
Devemos configurar um client ID. No caso, usei teste2 porque fiz uma conexão pelo smartphone utilizando o aplicativo MQTT Dashpara Android.
Configurei um username e password para acessar o broker MQTT, sem permitir acesso anônimo. A senha de laboratório uso para WiFi, MQTT e qualquer outro artigo que necessite um usuário e senha. Se quiser reproduzir na íntegra, use-os em seu ambiente de teste.
Agora o que mais me apaixona no Qt; os signals e slots.
Quando o programa se conecta ao broker, ele emite um sinal connected(). Quando perde a conexão com o broker, emite um sinal disconnected(). Quando chega dados, ele recebe um sinal received() que recebe como argumento um objeto QMQTT::Message. Para receber esses sinais, precisamos criar nossos slots, com o nome desejado e quando necessário for, criar com o respectivo argumento de função.
Para finalizar, iniciamos a conexão, com client->connectToHost(), da própria biblioteca.
Nos slots de conexão e desconexão, podemos inserir qualquer tratamento que acharmos devido, conforme a situação. Mesmo sem essas implementações, podemos executar o programa sem problemas. Já para a recepção da mensagem, não tenha dúvidas quanto à necessidade de implementá-la.
void MyComm::onReceived(const QMQTT::Message &message)
{
qDebug() << "Message inbox..." << endl;
QString topic = message.topic();
QString payload = QString(message.payload());
double valueDouble = payload.toDouble() / 100;
qDebug() << topic;
if (topic.contains("temperature")){
this->mainPage->findChild<QQuickItem*>("labelTempExt")->setProperty("text", payload);
this->mainPage->findChild<QQuickItem*>("dialTemp")->setProperty("value", valueDouble);
}
else if (topic.contains("humidity")){
this->mainPage->findChild<QQuickItem*>("labelHumidity")->setProperty("text", payload);
this->mainPage->findChild<QQuickItem*>("dialHumidity")->setProperty("value", valueDouble);
}
}
No QMQTT::Message temos duas informações importantes utilizadas nessa implementação; o topic e o payload. Conforme o tópico, adicionamos um tratamento específico, assim criamos apenas um método para tratar todas as mensagens. O payload contém a informação que iremos exibir nos componentes da interface.
O tipo utilizado nos componentes label é um QString e para os valores de ponto flutuante, utilize double. Os valores double que fazem a variação da posição dos componentes dial e progressBar vão de 0.0 à 1.0. Nesse caso, foi necessário dividir o valor de leitura por 100, considerando que o valor máximo de temperatura seja esse. De outro modo, seria necessário fazer um mapeamento dos valores.
Após essas implementações, ainda precisaremos utilizar o programa com o driver do display ST7789 para carregar a imagem no display.
O código já foi descrito em outro artigo. A melhor implementação de tempo para evitar sobrecarga do sistema foi essa porção de código (mas o código completo estará disponível para download, não se preocupe):
for i in range(10000):
#os.system("raspistill -t 1 --width 240 --height 240 -o /dev/shm/teste.jpg")
try:
image4 = Image.open('/dev/shm/merda.png')
image4.thumbnail((240, 240), Image.ANTIALIAS)
image4 = expand2square(image4, (0,0,0))
disp.display(image4)
sleep(0.300)
except:
pass
Nela, estou fazendo um range de 10.000 interações, mas para manter em loop infinito, basta trocar a primeira linha por:
while True:
O intervalo de tempo é bastante baixo, como pode ser visto; 3 frames por segundo, aproximadamente. A linha comentada do raspistill foi utilizada para fazer streaming da câmera do Raspberry, mostrada nesse outro artigo.
Desenvolvi tudo no notebook, depois copiei o diretório do projeto para o Raspberry e então compilei nativamente. Basicamente:
cd 240x240
qmake
make
./240x240 -platform vnc
Desse modo, pude também depurar o funcionamento mesmo antes de enviar para o display, utilizando o vncviewer apontando para o IP do Raspberry.
Cada frame capturado será gravado em /dev/shm, que é um sistema de arquivos em memória, de modo que não haverá gargalo nem escrita no cartão micro SD.
Mantendo o programa em Qt rodando em um terminal, executei em outro terminal o programa clock_NEW.py, modificado a partir do exemplo contido no diretório da biblioteca do display ST7789.
Rodei um broker no próprio Raspberry. Se ainda não fez nenhuma implementação do broker, recomendo. Não leva mais que 5 minutos, basta seguir esse artigo.
Já configurei diversos tipos de sensores de temperatura e umidade, como você pode ter acompanhado no blog. Mas não mantenho os sensores conectados e como o objetivo era testar o programa de visualização e não o sensor, preferi escrever um código curto para enviar dados randômicos ao broker MQTT. Um mero shell script, com o seguinte código:
#!/bin/bash
for i in `seq 1 100`; do
VALUE_ONE=`for i in {1..100}; do echo .$(( ( RANDOM % 100 ) + 1 )); done | awk '{ a += $1 } END { print a }'`
mosquitto_pub -h 192.168.1.104 -p 1883 -i djames -u dobitaobyte -P fsjmr112 -t /qml/temperature -m "$VALUE_ONE"
sleep 1
VALUE_TWO=`for i in {1..100}; do echo .$(( ( RANDOM % 100 ) + 1 )); done | awk '{ a += $1 } END { print a }'`
mosquitto_pub -h 192.168.1.104 -p 1883 -i djames -u dobitaobyte -P fsjmr112 -t /qml/humidity -m "$VALUE_TWO"
echo $VALUE_ONE
echo $VALUE_TWO
done
Salve o conteúdo acima em um arquivo.she mude as permissões para torná-lo executável:
chmod 700 arquivo.sh
Para executá-lo:
./arquivo.sh
A temperatura do Raspberry é obtida através do comandovcgencmd measure_temp. Esse valor que aparece na imagem de destaque para a temperatura do Raspberry Pi 3B+ é real. A galera fica em polvorosa por causa da temperatura do Raspberry Pi 4, mas particularmente não vejo problema algum. Dissipe o calor! Hoje é necessário uma fonte de 5V@3A para atender a alimentação do Raspberry, as coisas mudam.
Vou colocar dissipador em ambas, a versão 3B+ e a 4B, depois mostro como farei, não gaste nenhum centavo com dissipadores por enquanto!
Esse display pode ser encontrado facilmente na UsinaInfo, através desse link.
Para quem procura essa versão, ainda pode recorrer à CurtoCircuito, através desse link.
Um bom lugar para comprar, inclusive com loja física na Santa Efigênia - São Paulo - SP, é a Saravati, através desse link.
Já chegou um novo lote essa semana e pelo visto está para acabar. As últimas 3 peças estão disponíveis nesse link.
Gravarei à noite para reduzir o ruído ambiente, pretendo mostrar o código e explicar um pouco de todos os recursos utilizados, mas mostrando cada ponto, para tentar deixar mais claro ainda. Se não é inscrito no canal, inscreva em DobitaobyteBrasil no Youtube e clique no sininho para receber notificações.
O programa está disponível para download em meu repositório no github. Acesse-o através desse link.
Inscreva-se no nosso canal Manual do Maker no YouTube.
Também estamos no Instagram.
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.