Manual
do
Maker
.
com
Esse artigo é um pouco "solto". Programar em C++ no Raspberry é a mesma coisa que programar em C++ para qualquer Linux; ou bem próximo disso, mas não vou entrar nesses detalhes porque senão teremos um livro invés de um artigo.
Basicamente, o que você precisa para programar o básico em Raspberry já está lá. A vantagem em relação a um computador de arquitetura x86 é que temos GPIOs disponíveis, que podem ser acessados de dois modos; ou pelo BCM ou WiringPi etc. Mas alguma coisa interessantes de se fazer não são típicas em programas do dia a dia. Por exemplo, podemos dar prioridade de execução a um processo ajustando o suid, trocar as prioridade de acesso a diretórios e arquivos para que um usuário sem nível administrativo possa interagir com os dados e coisas do tipo. Mas e se o processo precisa necessariamente ser executado como root? Bem, vamos ao primeiro exemplo.
Se o programa precisar ser executado como root para acessar recursos privilegiados e esse programa for executado como usuário comum, haverá uma mensagem de erro que poderá até causar a impressão de que é bug do programa, para quem não sabe. A primeira coisa a fazer então é garantir que o programa só executará suas rotinas após identificar o usuário como sendo root. Façamos um código de exemplo:
#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
if (getuid()){
cout << "O programa deve ser rodado como root. Saindo..." << endl;
exit(0);
}
cout << "ola, root!" << endl;
return 0;
}
No Arduino programamos em C++ também, mas as coisas por lá tem peculiaridades que causam estranheza para quem programa em outras plataformas. Do mesmo modo, quem vem de outras plataformas pode estranhar esse código básico acima, então vou dar uma explicada de leve.
Os includes são claros, do mesmo modo que fazemos em Arduino. A diretiva using namespace std diz ao programa para importar para o ambiente tudo o que haveria de ser chamado precedido por std. Por exemplo, std::cout. Mas para economizar uns bytes, basta usar a diretiva.
No Arduino usamos setup() e loop(). No background da coisa ele executa um loop infinito em main() também. No nosso caso, para manter o programa rodando haveriamos de fazer o loop, mas para o exemplo acima, simplesmente executamos e o programa sai em seguida. Como main() precisa de um retorno, basta devolver um 0 para saída normal.
O cout é o Serial.println do Arduino. É com ele que exibimos mensagens do programa.
A imagem a seguir mostra a compilação, a execução como usuário e a execução como root:
A lógica é simples, mas conceitos do sistema operacional são necessários; o getuid pega o uid do usuário que está executando o programa. O uid do root é 0, portanto, qualquer coisa diferente de 0 não é root.
Nesse outro artigo mostrei como fazer a configuração do banco de dados SQLite no Raspberry. Adicionalmente, a configuração do Blynk.
Supondo que um programa criado por você deve ser exclusivo, pois uma segunda instância manipularia dados que já estavam sendo gerenciados. Nesse caso, se uma segunda instância sobe pode ser difícil perceber o ocorrido e talvez seja tarde demais parar o programa no momento seguinte à sua execução. Para evitar esse tipo de problema, podemos utilizar outro recurso:
#include <iostream>
#include <unistd.h>
#include <sys/file.h>
using namespace std;
bool isAlreadyRunning(){
int pid_file = open("/var/run/DobitAoByte.pid", O_CREAT | O_RDWR, 0666);
int rc = flock(pid_file, LOCK_EX | LOCK_NB);
if (rc){
if (EWOULDBLOCK == errno){
perror("\033[1;31mOutra instancia rodando ou lock não removido (/var/run/hidroSmart.pid)\033[0m\n\n");
exit(0);
}
}
}
int main(){
isAlreadyRunning();
if (getuid()){
cout << "O programa deve ser rodado como root. Saindo..." << endl;
exit(0);
}
cout << "ola, root!" << endl;
while (true);
return 0;
}
Aqui utilizamos um recurso de gerenciamento do próprio sistema, que gera um arquivo de lock em /var/run e não permite a execução de uma segunda instância se já estiver rodando.
Podemos obter dois tipos de retorno, sendo um deles o unix epoch, também chamado de unix timestamp, que teve início em 1 de janeiro de 1970 às 00:00:00 do tempo universal de coordenadas do calendário gregoriano proléptico, que tornou-se o marco zero do sistema de calendário utiizado pelos UNIX. Nesse caso, obtemos o retorno em segundos desde a data de início dessa contagem de tempo. O outro modo é retornar em formato de leitura humana. Ambos estão dispostos no código a seguir:
#include <time.h>
#include <iostream>
#include <string.h>
#include <malloc.h>
//#include <cstring>
using namespace std;
char* replace_char(char* str, char find, char replace){
char *current_pos = strchr(str,find);
while (current_pos){
*current_pos = replace;
current_pos = strchr(current_pos,find);
}
return str;
}
string dateTime(){
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
cout << to_string(rawtime) << endl;
char * logdt = asctime(timeinfo);
char *log_datetime = replace_char(logdt,'\n',' ');
return log_datetime;
}
int main(){
string teste = dateTime();
cout << teste << endl;
return 0;
}
O código acima tem também uma função para fazer replace, aproveite-a bem!
O retorno desse programa é algo como:
Não tem. Mas dá pra programar em C++ no Raspberry o seu próprio millis. Eu fiz o porte de um programa que rodava em Atmel, migrando-o para o Raspberry e preservando sua lógica. Quando chegou na função millis, fui obrigado a implementar algo um pouco diferente, mas funciona.
O código a seguir está fazendo o delay de 1 segundo para mostrar o resultado do millis:
#include <iostream>
#include <sys/timeb.h>
#include <unistd.h>
using namespace std;
int getMillis()
{
timeb tb;
ftime(&tb);
int nCount = tb.millitm + (tb.time & 0xfffff) * 1000;
return nCount;
}
int millis(int val)
{
int nSpan = getMillis() - val;
if(nSpan < 0)
nSpan += 0x100000 * 1000;
return nSpan;
}
int main(){
unsigned long start = getMillis();
sleep(1);
unsigned long end = millis(start);
cout << end;
}
Com o delay de 1 segundo, o resultado da execução é esse:
Compilação cruzada é quando você está em uma arquitetura compilando programas para uma outra. Sem perceber, é exatamente isso que você está fazendo quando compila um programa para Atmel no seu computador x86. Para fazer a compilação, é necessário o uso de toolchains específicas para a arquitetura. Já escrevi um bocado a respeito e para Raspberry escrevi esse artigo.
Existem outros tipo de compilação; a nativa (ao programar em C++ no Raspberry diretamente), a cruzada, virtualizada e outra que não lembro agora, mas é quando se usa uma plataforma virtualizada para compilar programas para uma terceira arquitetura.
A melhor é sempre a nativa, quando se trata de computadores parrudos, mas dependendo da complexidade, pode ser melhor virtualizar um sistema para compilar programas e depois enviar para o Raspberry. Nesse artigo mostro como emular uma máquina ARM em um computador X86.
Claro que essas dicas acima não são o suficiente para fazer a coisa acontecer. Certamente você quererá acessar os GPIO, entre outros recursos, mas é melhor escrever por etapas até chegar em um ponto satisfatório. Muita informação de uma vez pode desanimar.
Um dos parceiros do site vende Raspberry. É a mesma placa em torno do globo terrestre ( ou da panqueca terrestre, se preferir), mas se é sua intenção ter um para brincar ou produzir, compre seu Raspberry na CurtoCircuito e fortaleça nossa parceria.
No próximo artigo relacionado pretendo mostrar um pouco mais de macetes e dicas básicas, mantendo mais ou menos esse formato desorganizado, mas com recursos gostosos de brincar.
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.