Na primeira parte dessa série criamos uma interface gráfica semelhante a um terminal, testamos as formas de comunicação possíveis e enviamos e recebemos informações para as portas do Arduino.
Nesta continuação, iremos desenvolver a aplicação usando padrões de projeto e o protótipo da GUI previamente desenvolvido, o qual vai nos proporcionar maior extensibilidade da aplicação.
Abordaremos também o uso de:
- Threads, para execução de tarefas em paralelo;
- Callback, para acessar informação vindas de processos em threads;
- Extends class, para adicionar funções a alguma classe;
- UpdateListener, para que tarefas só sejam executadas depois de um determinado período de tempo;
- Criação de novas classes, para organização do projeto;
- Conceito de threads produtora e consumidora, para sincronização de processos.
Montando a estrutura do projeto
O projeto terá 3 pastas principais para as partes do projeto, respectivamente: serialexpansion, ui e utils.
A lista de arquivos que devem ser criados em cada pasta são mostrados na imagem abaixo.
Estes são todos os arquivos de código que vamos precisar para desenvolver o projeto, a estrutura final de pastas do projeto deve ficar como a seguir.
RPIExpansionPorts
└───src
└───main
├───java
│ └───com
│ └───totalcross
│ └───sample
│ └───rpiexpansionports
│ ├───serialexpansion
│ ├───ui
│ └───utils
└───resources
├───Fonts
└───Images
Hardware
- 2 Resistores de 1K ohms;
- 3 LEDs;
- 1 push button;
- 1 joystick ou 2 potenciômetros;
- 1 sensor de temperatura Termistor NTC de 10K ohms e um resistor de 10K ohms ou um modulo NTC de 10K ou um potenciômetro de 10K ohms e um resistor de 10K ohms;
- 1 protoboard;
- 1 Arduino;
- jumpers.
Abaixo veja o diagrama esquemático. Na figura o potenciômetro mais a direita representa o sensor de temperatura NTC e os outros dois o joystick.
O sensor Termistor NTC a seguir é um sensor de temperatura com as seguintes características.
- Tensão de operação: 3,3 ou 5VDC
- Faixa de medição: -55°C a 125° celsius
- Precisão: ±1%
Para a montagem, ele deve ser colocado no lugar do potenciômetro mais a direita na imagem, com uma das pernas no GND e a outra no ponto que liga o resistor a porta A1.
O módulo joystick à esquerda já possui a indicação de como deve ser feita as ligações, sendo que VRx deve ser ligado no pino A1 e VRy no A2 do arduino.
Por fim, temos a imagem a seguir com o esquema real do hardware que será usado.
Utils
A imagem abaixo foi construída por um designer de UX da TotalCross para ser a última etapa do desafio, como comentei no artigo anterior, criar uma GUI que seja atrativa para os usuários. O protótipo com as informações necessárias está no projeto dashboard sensors no site da adobe XD, lá podemos ver as imagens cores e fontes utilizadas para o design e adicioná-las as utils do projeto.
As imagens e fontes usadas no projeto se encontram no repositório do github, basta apenas baixar os arquivos e adicioná-los a pasta resources do projeto.
Fonts
A fonte escolhida para o projeto foi a Roboto bold e será usado apenas 4 das formas possíveis que estão no arquivo para download indicado no item anterior.
Para usar as fontes, criaremos uma classe nova a qual poderá carregar as fontes e definir os elementos necessários com elas.
Nessa nova classe, que nomeei de Fonts (linha 4) são criadas 4 variáveis que serão usadas de acordo com suas nomeações, e definidas usando “getFont(“path”,false,int size)” função da classe Font que tratará o arquivo ttf, permitindo o uso das fontes como visto da linha 11 a 14 do código abaixo.
package com.totalcross.sample.rpiexpansionports.utils;
import totalcross.ui.font.Font;
public class Fonts {
public static Font Title;
public static Font TextBold;
public static Font TextRegular;
public static Font Value;
public static void loadFonts() {
Title = Font.getFont("Fonts/Roboto-Bold", false, 14);
TextBold = Font.getFont("Fonts/Roboto-Bold", false, 12);
TextRegular = Font.getFont("Fonts/Roboto-Regular", false, 10);
Value = Font.getFont("Fonts/Roboto-Regular", false, 9);
}
}
Colors
As cores escolhidas para o projeto serão adicionadas a uma classe Colors como o código a seguir mostra, para facilitar sua utilização e manutenção do código pois dessa forma, caso seja necessário fazer alguma alteração na paleta de cores do projeto, é necessário apenas alterá-las nessa classe.
Para esse caso as variáveis das cores são criadas e têm seus valores definidos simultaneamente e já podem ser utilizadas normalmente no projeto.
package com.totalcross.sample.rpiexpansionports.utils;
import totalcross.ui.gfx.Color;
public class Colors {
public static int WHITE = Color.WHITE;
//General
public static int BACKGROUND = 0x272727;
public static int SHADOW = 0x414141;
public static int FONT_COLOR = Color.WHITE;
//Components
public static int LED_ONE = 0xF6EE85;
public static int LED_ONE_CIRCLE = 0xF9EA1C;
public static int LED_TWO = 0x6CD7FD;
public static int LED_TWO_CIRCLE = 0x2FC3FB;
public static int LED_THREE = 0xF96666;
public static int LED_THREE_CIRCLE = 0xF91C1C;
public static int OFF = 0xC1C1C1 ;
public static int OFF_CIRCLE = 0x707070;
}
Images
A classe Images tem como objetivo facilitar a utilização das imagens contidas no projeto. Dessa forma, centralizando o carregamento das imagens, caso sejam necessárias alterações no futuro, apenas um local deve ser alterado, refletindo por todo o código do projeto que utiliza as imagens, sendo mais fácil manter o código fonte.
package com.totalcross.sample.rpiexpansionports.utils;
import totalcross.io.IOException;
import totalcross.ui.image.Image;
import totalcross.ui.image.ImageException;
public class Images {
public static Image crossy, input_white, yellow_led,blue_led,red_led,sensor,status_grey,status;
private Images() {
}
public static void loadImages() {
try {
crossy = new Image("Images/crossy.png");
input_white = new Image("Images/input_white.png");
sensor = new Image("Images/sensor.png");
status_grey = new Image("Images/status-grey.png");
status = new Image("Images/status.png");
} catch (ImageException | IOException e) {
e.printStackTrace();
}
}
}
MaterialConstants
Essa classe contém as constantes e duas variáveis que serão usadas para facilitar a utilização dos dados recebidos e o envio de dados para o serial.
- command receberá todos os comandos que devem ser enviados em sequência.
- feedback receberá os valores para cada porta requisitada em suas respectivas posições.
package com.totalcross.sample.rpiexpansionports.utils;
import java.util.LinkedList;
import totalcross.ui.Control;
import totalcross.util.UnitsConverter;
public class MaterialConstants {
public static final int BORDER_SPACING = UnitsConverter.toPixels(16 + Control.DP);
public static final int COMPONENT_SPACING = UnitsConverter.toPixels(8);
public static final int EDIT_HEIGHT = UnitsConverter.toPixels(35 + Control.DP);
public static LinkedList<String> command= new LinkedList<String>();
public static String[] feadback = new String[20];
}
SerialExpansion
Esta classe é onde a comunicação ocorrerá, através dela será inicializado a conexão com o Arduino é feito o envio e recebimento de informação.
O processo consiste de duas partes: leitura e escrita no serial, onde a leitura deve ser iniciada antes da primeira escrita pois é através dela que é iniciada a conexão com o Arduino.
O processo para fazer a leitura do serial através do PortConnector, explicado na parte 1 do artigo, consiste de ler as informações recebidas, gravá-las em uma variável em ordem para que seja retornada através do callback, linha 75, que é uma interface que deve ser implementada na classe main para que dessa forma sejam usados os dados disponibilizados pela thread consumidora o que será feito mais a frente neste artigo.
Para a escrita é bem mais simples, com a comunicação iniciada na leitura o serial fica aberto para que possa ser enviado informação, dessa forma basta apenas enviar o comando pelo método apropriado o qual pode ser visto na função PortConnectorSendCommand, linha 79 a 87 do código a seguir.
Para que haja uma comunicação harmônica com o serial e que os dados recebidos sejam usados com o mínimo de perdas possíveis serão usadas Threads: uma geradora que receberá os valores e os espalhará em uma variável, e outra consumidora que desempilhar os valores e permitirá que sejam lidos.
A função start inicia a comunicação definindo o PortConnector para comunicação usando USB com uma velocidade de 115200, como é visto na linha 25. Na linha seguinte é criado um LineReader e passado como parâmetro o pc, com isso será possível verificar e ler a informação recebida.
Partindo para a thread geradora, linha 28 a 51, enquanto a capacidade da variável data não for alcançada é verificado em loop se algo foi recebido no LineReader, se houver, será armazenado em data, quando for alcançado o limite de data, a thread deve dormir para que esses dados sejam consumidos, pela consumidora.
Já a thread consumidora, linha 53 a 73, colocará a disposição do callback as informações adquiridas pela geradora sempre que ela estiver dormindo, enquanto houver dados, para então dormir e permitir novamente o funcionamento da geradora.
package com.totalcross.sample.rpiexpansionports.serialexpansion;
import java.io.IOException;
import java.util.LinkedList;
import totalcross.io.LineReader;
import totalcross.io.device.PortConnector;
import totalcross.util.concurrent.Lock;
public class SerialExpansion {
private PortConnector pc;
private LinkedList<String> data = new LinkedList<>();
private final int CAPACITY = 100;
private Lock lock = new Lock();
SerialXpansionCallback callback;
public String FeedBack;
public SerialExpansion(SerialXpansionCallback callback) {
this.callback = callback;
}
public void start() throws IOException {
pc = new PortConnector(PortConnector.USB, 115200);
LineReader lineReader = new LineReader(pc);
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
synchronized (lock) {
if (data.size() < CAPACITY) {
String input;
try {
if ((input = lineReader.readLine()) != null) {
data.add(input);
}
} catch (totalcross.io.IOException e) {
e.printStackTrace();
}
}
}
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
synchronized (lock) {
if (data.size() > 0) {
String s = data.removeFirst();
callback.callback(s);
}
}
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}).start();
}
public static interface SerialXpansionCallback {
void callback(String d);
}
public void PortConnectorSendCommand(String command) {
try {
pc.writeBytes(command);
} catch ( totalcross.io.IOException ioe) {
ioe.printStackTrace();
}
}
}
Ui
Agora é o momento de criar as partes da Ui, começando pela expansão da classe label, para que a mesma se adeque a necessidade do projeto e em seguida a criação de cada bloco da UI.
ShowContainer
Nessa parte do artigo construiremos a base da interface para que possa ser terminada na parte 3. Nos arquivos AnalogInput, DigitalInput devem ser feito como foi feito DigitalOutput a seguir, alterando apenas a linha 7 para o nome do arquivo.
package com.totalcross.sample.rpiexpansionports.ui;
import com.totalcross.sample.rpiexpansionports.utils.Colors;
import totalcross.ui.Container;
import totalcross.ui.ScrollContainer;
public class DigitalOutput extends Container {
@Override
public void initUI(){
super.initUI();
ScrollContainer sc = new ScrollContainer(true, true);
sc.setBorderStyle(sc.BORDER_ROUNDED);
sc.setBackForeColors(Colors.SHADOW, Colors.SHADOW);
add(sc, LEFT, TOP, FILL, FILL);
}
}
A partir disso podemos montar a base da interface, bem como no código anterior fazemos uma extensão da classe Container, “como é visto na linha 12”, que possibilita chamar este e os anteriores como se fosse um elemento nativo da classe.
Os tamanhos de cada um dos elementos foi feito com base no design apresentado no item 3. Cada elemento possui um tamanho fixo em pixel definido no protótipo mencionado, porém para que a aplicação seja responsiva os valores foram convertidos para porcentagem do tamanho da tela.
O primeiro a ser feito é exibir os textos, título do projeto, autor e company, para isso estou colocando minhas informações, mas vocês que está desenvolvendo a aplicação junto comigo pode colocar seus dados da forma que quiser.
Das linhas 20 a 26 é definido o texto e adicionado ao container, os labels que estão sendo usados, os RPIExpansionPortsLabel, serão criados e explicados nos próximos passos.
Das linhas 28 a 32 é criado, definido e exibido a imagem do mascote sendo que, primeiro a imagem já definida no item 3.3 é adicionada a um imageControl para que possa ser modificada de acordo com a necessidade, que nesse caso é definindo o background como transparente, linha 29, o que pode ser feito pois a imagem é um png sem fundo, e tornar a escala da imagem proporcional ao espaço onde está contida, o que é feito na linha 30 e faz com que se ajuste automaticamente as proporções definidas quando adicionada na tela, linhas 31 e 32.
Por fim é adicionado ao container dessa classe os elementos das classes de input e output para que toda a interface possa ser chamada na classe principal apenas com a declaração e exibição dessa única classe.
package com.totalcross.sample.rpiexpansionports.ui;
import com.totalcross.sample.rpiexpansionports.utils.Colors;
import com.totalcross.sample.rpiexpansionports.utils.Images;
import com.totalcross.sample.rpiexpansionports.utils.MaterialConstants;
import totalcross.sys.Settings;
import totalcross.ui.Container;
import totalcross.ui.ImageControl;
import totalcross.ui.ScrollContainer;
public class ShowContainer extends Container {
ScrollContainer sc;
public void initUI() {
sc = new ScrollContainer(true, true);
sc.setBackColor(Colors.BACKGROUND);
add(sc, LEFT, TOP, FILL, FILL);
RPIExpansionPortsLabel Title = new RPIExpansionPortsLabel("GUI Dashboard Sample",1);
RPIExpansionPortsLabel Autor = new RPIExpansionPortsLabel("Autor: Patrick Martins",3);
RPIExpansionPortsLabel Company = new RPIExpansionPortsLabel("Company: TotalCross",3);
sc.add(Title,LEFT+(int) (Settings.screenWidth * 0.09),TOP + (int) (Settings.screenHeight * 0.12),PREFERRED,PREFERRED);
sc.add(Autor,Title.getX(),AFTER);
sc.add(Company,Title.getX(),AFTER);
ImageControl crossy= new ImageControl(Images.crossy);
crossy.transparentBackground=true;
crossy.scaleToFit=true;
sc.add(crossy,LEFT+Title.getX2(),
TOP+Title.getY(),(int)(0.10*Settings.screenWidth),(int)(0.20*Settings.screenHeight),Autor);
AnalogInput Ainp= new AnalogInput();
sc.add(Ainp,Title.getX(), AFTER+MaterialConstants.COMPONENT_SPACING,
(int) (Settings.screenWidth * 0.37), (int) (Settings.screenHeight * 0.61));
DigitalOutput Dout = new DigitalOutput();
sc.add(Dout, (int) (Settings.screenWidth * 0.52) , TOP+(int)(Settings.screenHeight*0.06), (int) (Settings.screenWidth * 0.41),
(int) (Settings.screenHeight * 0.43));
DigitalInput Dinp= new DigitalInput();
sc.add(Dinp,SAME , AFTER+MaterialConstants.COMPONENT_SPACING*2, (int) (Settings.screenWidth * 0.41),
(int) (Settings.screenHeight * 0.43));
}
}
Para que seja possível obter uma breve visualização de como está ficando a interface podemos executar a classe principal, inicializando as classes de imagens e fontes, segue o código a seguir:
package com.totalcross.sample.rpiexpansionports;
import com.totalcross.sample.rpiexpansionports.ui.ShowContainer;
import com.totalcross.sample.rpiexpansionports.utils.Fonts;
import com.totalcross.sample.rpiexpansionports.utils.Images;
import totalcross.sys.Settings;
import totalcross.ui.MainWindow;
public class RPIExpansionPorts extends MainWindow {
public RPIExpansionPorts() {
super("RPIExpansionPorts", NO_BORDER);
setUIStyle(Settings.MATERIAL_UI);
}
@Override
public void initUI() {
Fonts.loadFonts();
Images.loadImages();
super.initUI();
ShowContainer container = new ShowContainer();
swap(container);
}
}
RPIExpansionPortsLabel
A principal utilidade dessa classe vai ser ler as informações adquiridas e armazenadas na variável feedback e exibi-las da melhor forma, mas também será usada para os outros casos que necessitem de labels, para facilitar algumas adaptações e futuras atualizações caso necessite.
Essa classe será criada como uma extensão da classe Label como é possível observar na linha 12 do código a seguir, para aproveitar todos os métodos e comportamentos da classe mãe.
Algumas variáveis definidas das linhas 13 a 19, serão necessárias para o funcionamento dessa classe, e tem como suas respectivas funções:
- type: variável que indicará como deve ser feita a leitura dos dados
- port: variável que indicará qual porta deve ser lida.
- refreshTime: variável que indicará tempo para uma nova leitura e envio de comando para o serial.
- elapsedRefreshTime: variável que contará quanto tempo passou desde a última atualização.
- idle: variável que servirá para medir tempo sem enviar comandos.
- im: variável que definirá uma imagem no caso de ser uma entrada digital.
- termoMode: variável que acionará método de conversão de entrada para temperatura.
Os métodos construtores dessa classe além de chamar o construtor da classe mãe definem algumas características extras que serão necessárias para termos o funcionamento que necessitamos, como é visto na linha 76, além da variável string nativa da classe Label, a variável type que definirá o tipo de fonte que será usada para label e a variável opcional im que definirá o estado de uma imagem caso a label seja responsável por um input digital, é opcional pois caso esse não seja o caso é possível criar a label normalmente com a segunda instância do construtor na linha 102, que não necessita dessa imagem a definindo como null.
Agora a parte mais importante dessa classe, ler e exibir as informações. Com a utilização do updateListener criado na linha 21, é possível saber a quanto tempo ele foi executado e armazenar esse valor em uma variável, elapsedRefreshTime. Isso vai permitir com que possamos definir um tempo entre as leituras para que não haja uma interrupções na aplicação durante a utilização pelo usuário, o que é feito na linha 27, onde a cada 200 ms uma nova leitura e um novo comando é enviado para o Arduino.
Há ainda a possibilidade de que esse comando seja perdido ou tenha conflito com algum comando enviado pelo usuário, o que faria o label não atualizar por isso é usado a variável idle que na linha 64 é verificada e caso seu valor for maior que 2000ms|2s, um novo comando é enviado e então reinicia a contagem.
Esse tempo ocioso ocorre devido que a atualização da label e o envio de um novo pedido de resposta só é feito caso exista algum valor na sua posição respectiva na variável feedback, linha 30, existindo valor, é exibido a informação de acordo com o tipo da variável que está sendo lida, o que é definido pela função setSendCommand que recebe a porta e seu tipo para a leitura e inicializa o updateListener, como vista na linha 106.
Existem 4 configurações possíveis sendo elas:
- Quando o tipo é “>” e não existe uma imagem, nesse caso sera exibido ON ou OFF no componente, linha 42.
- Quando o tipo é “>” e existe uma imagem, nesse caso sera alterado a imagem de acordo com o estado do pino,linha 34.
- Quando o tipo é diferente de “>”, para essa configuração sera exibido um valor entre 0 e 1024 seguindo o estado do pino analógico,linha 55.
- Quando o tipo é diferente de “>” e a função setTermoMode() foi chamada, para este, o valor de 0 a 1024 é convertido para temperatura em C°, linha 52.
A conversão de temperatura é feita baseada no sensor de temperatura resistivo NTC de 10 Kohms, e um resistor de 10 Kohms associado a ele, configuração comum mais recomendada para sua forma de trabalho, e para esse projetos vamos nos prender a ela já que o objetivo direto não é trabalhar com o ajuste desse periférico.
package com.totalcross.sample.rpiexpansionports.ui;
import com.totalcross.sample.rpiexpansionports.utils.Fonts;
import java.math.BigDecimal;
import com.totalcross.sample.rpiexpansionports.utils.Colors;
import com.totalcross.sample.rpiexpansionports.utils.Images;
import com.totalcross.sample.rpiexpansionports.utils.MaterialConstants;
import totalcross.ui.ImageControl;
import totalcross.ui.Label;
import totalcross.ui.MainWindow;
import totalcross.ui.event.UpdateListener;
public class RPIExpansionPortsLabel extends Label {
private char type;
private String port;
int refreshTime = 200;
int elapsedRefreshTime = 0;
int idle = 0;
ImageControl im;
private boolean termoMode;
UpdateListener updateListener = new UpdateListener() {
public void updateListenerTriggered(int elapsedMiliseconds) {
elapsedRefreshTime += elapsedMiliseconds;
idle += elapsedMiliseconds;
if (elapsedRefreshTime >= refreshTime) {
String feedback = MaterialConstants.feedback[appId];
if (feedback != "" && feedback != null) {
if (type == '>')
if(im!=null){
if (feedback.equals("0")){
im.setImage(Images.status_grey);
setText("OFF");
}
else{
setText("ON");
im.setImage(Images.status);
}
}else{
if (feedback.equals("0"))
setText("OFF");
else
setText("ON");
}
else{
if (termoMode){
setText(thermometer( feedback));
}
else{
setText(feedback);
}
}
MaterialConstants.command.add(type+port);
MaterialConstants.feedback[appId]="";
elapsedRefreshTime = 0;
idle=0;
return;
}
else if(idle>2000){
MaterialConstants.command.add(type+port);
idle=0;
return;
}
elapsedRefreshTime = 0;
}
}
};
public RPIExpansionPortsLabel(String string,int type,ImageControl im) {
super(string);
termoMode=false;
if(im!= null){
this.im=im;
this.im.transparentBackground=true;
this.im.scaleToFit=true;
}
transparentBackground=true;
setForeColor(Colors.FONT_COLOR);
switch(type){
case 1:
setFont(Fonts.Title);
break;
case 2:
setFont(Fonts.TextBold);
break;
case 3:
setFont(Fonts.TextRegular);
break;
case 4:
setFont(Fonts.Value);
break;
}
}
public RPIExpansionPortsLabel(String string,int type){
this(string, type, null);
}
public void setSendCommand(char type,String port){
this.type=type;
this.port=port;
MainWindow.getMainWindow().addUpdateListener(updateListener);
MaterialConstants.feedback[appId]="0";
}
public void setTermoMode(){
termoMode=true;
}
private String thermometer(String analog){
double in= (double) Integer.parseInt(analog);
if(in!=0){
double Resistance=(double)(((1024.0*10000.0)/in) - 10000.0);
double Temp = Math.log(Resistance);
Temp = 1 / (0.001129148 + (0.000234125 * Temp) + (0.0000000876741 * Temp * Temp * Temp));
Temp = Temp - 273.15;
BigDecimal bd = new BigDecimal(Math.round(Temp));
String result= (bd.toString());
return result;
}
else
return "0";
}
}
RPIExpansionPorts
Agora podemos dar continuidade a classe principal do projeto apresentada no ponto 5.1 onde agora será definida os tipos das portas, alocado os valores que chegam da serial de forma correta e feito o envio dos comandos.
Essa classe, além de ser uma extensão da MainWindow, implementa obrigatoriamente o Callback mencionado no tópico 4. Essa implementação é tornada obrigatória na definição da classe, linha 12, onde, como implements, temos o Callback.
A implementação é feita na linha 49 e consiste de verificar se o separador “:” é contido no dado e se ao dividi-lo possui duas partes, linha 54, isso é feito pois o Arduino retorna os valores com o número da porta, o separador e o valor como por exemplo “3:200” que indica que a porta 3 está com o valor de 200, estando dentro dessa estrutura, o dado é dividido, onde a primeira parte indica o index onde deve ser adicionada a segunda na variável feedback, o que pode ser visto nas linhas 55 e 56.
O envio de dados é semelhante ao processo feito pela classe RPIExpansionPortsLabel, com a utilização de um UpdateListener, linha 21, nesse caso é feito a leitura do primeiro elemento da variável command onde é armazenado os comandos que as Labels tentaram enviar, nesse ponto cada um dos comando é enviado em sequência com um intervalo de 150 ms, indicado pela variável refreshTime na linha 15, até que não haja mais o que enviar.
Para a definição do tipo de cada porta, a função defineMode, linha 87, recebe como entrada um conjunto de portas e se deve ser um input ou output, sendo 1 para output e 0 para input, então é feito o envio com a função PortConnectorSendCommand.
Por fim, a chamada dessas funções começando pela inicialização da comunicação na linha 75, após ela é necessário esperar um período de tempo de 3 segundos para que a comunicação seja efetuada com sucesso, feito isso é possível definir os tipos das variáveis e iniciar o UpdateListener,linha 91, dessa forma finalizando os processos para a comunicação constante.
package com.totalcross.sample.rpiexpansionports;
import com.totalcross.sample.rpiexpansionports.serialexpansion.SerialExpansion;
import com.totalcross.sample.rpiexpansionports.ui.ShowContainer;
import com.totalcross.sample.rpiexpansionports.utils.Fonts;
import com.totalcross.sample.rpiexpansionports.utils.Images;
import com.totalcross.sample.rpiexpansionports.utils.MaterialConstants;
import totalcross.sys.Settings;
import totalcross.ui.MainWindow;
import totalcross.ui.event.UpdateListener;
public class RPIExpansionPorts extends MainWindow implements SerialExpansion.SerialXpansionCallback {
SerialExpansion Conection;
int refreshTime = 150;
int elapsedRefreshTime = 0;
UpdateListener updateListener = new UpdateListener() {
public void updateListenerTriggered(int elapsedMiliseconds) {
elapsedRefreshTime += elapsedMiliseconds;
if (elapsedRefreshTime >= refreshTime) {
if(MaterialConstants.command.size()>0){
String c=MaterialConstants.command.removeFirst();
Conection.PortConnectorSendCommand(c+"*");
elapsedRefreshTime = 0;
return;
}
else{
System.out.println("comandos vazios");
}
elapsedRefreshTime = 0;
}
}
};
public RPIExpansionPorts() {
super("RPIExpansionPorts", NO_BORDER);
setUIStyle(Settings.MATERIAL_UI);
}
@Override
public void callback(String d) {
MainWindow.getMainWindow().runOnMainThread(new Runnable() {
@Override
public void run() {
if (d.contains(":") && d.split(":").length==2) {
String[] aux = d.split(":");
MaterialConstants.feedback[Integer.parseInt(aux[0])]=aux[1];
}
}
});
}
@Override
public void initUI() {
Fonts.loadFonts();
Images.loadImages();
super.initUI();
ShowContainer container = new ShowContainer();
swap(container);
Conection = new SerialExpansion(this);
try {
Conection.start();
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
defineMode(2, 6, '1');
defineMode(9, 9, '1');
defineMode(7, 8, '0');
defineMode(10, 19, '0');
MainWindow.getMainWindow().addUpdateListener(updateListener);
}
public void defineMode(int in, int f, char mode) {
for (int i = in; i <= f; i++) {
Conection.PortConnectorSendCommand("#"+i+","+mode+"*");
}
}
}
Conclusão
Nessa parte do artigo avançamos muito no projeto, preparamos totalmente a comunicação, utilizando as classes apresentadas na primeira parte, fizemos sincronização de threads para que não haja conflito na utilização dos dados e iniciamos a criação da interface já exibindo a estrutura base do projeto.
Para o próxima parte do projeto terminaremos a montagem das classes que faltam e teremos o projeto completo para utilizarmos no Raspberry Pi, com isso poderemos testar tudo que foi desenvolvido e visualizar uma interface completa desenvolvida a partir de um designer profissional.









Muito bom! Cadê a proxima parte?