A Utilização de Portas de Comunicação através de Java Vicente Peruffo Minotto Universidade do Vale do Rio dos Sinos [email protected] Resumo sobre a API usada na comunicação de portas; a seção 4 mostra o funcionamento dessa API através de exemplos de códigos fontes; a seção 5 ilustra o funcionamento de um programa que utiliza comunicação através da porta serial; e, por fim, a seção 6 conclui o artigo. Este artigo discute a utilização de portas de comunicação de um computador comum através da linguagem de programação Java. Será analisado por que os desenvolvedores da Sun implementaram a utilização da porta serial, e da porta paralela, e por que ainda não existe suporte para USB, Firewire, ISDN e entre outros protocolos de comunicação a este nível. Também será mostrado exemplos de programas, através dos seus códigos fontes, utilizando API de comunicações do Java na comunicação com porta serial. 2. Portas de comunicação em geral Resumidamente, o funcionamento de um porta de comunicação é baseado na troca de bits entre dois dispositivos. O modo como os bits são enviados e recebidos, varia de padrão para padrão, mas o princípio, em geral é esse [3]. Este artigo não entrará em detalhes mais aprofundados em relação ao funcionamento de quaisquer portas. Todavia, há algumas confusões causadas normalmente em torno das portas de comunicação. Um exemplo é o de confundirem-nas com rede. Uma rede opera sobre um conceito diferente; nela é realizada “conversa” apenas entre dispositivos que possuem um endereço de IP fixo, os quais geralmente são computadores, mas podem variar para celulares, handhelds, etc. Em portas de comunicação, a “conversa” pode ser realizada com qualquer dispositivo, como por exemplo, outro computador, mouse, teclado, motores, CIs, pen drives, etc. Também, usar Java para redes, é algo completamente diferente do que usar portas de comunicação. Em redes é necessário usar classes como Sockets, RMI, Threads (obrigatoriamente), entre outros. Em portas de comunicação, é necessário apenas o uso de poucas classes pertencentes à API específica daquelas portas. Em suma, portas de comunicação são diferentes de redes. Redes são mais complexas, e utilizam de um conceito mais avançado em Java. 1. Introdução O uso de portas de comunicação está presente no diaa-dia de qualquer um que use o computador; no momento em que usamos o mouse, o teclado, impressora, pen drives, entre outros, estamos usando algum tipo de porta de comunicação. Tudo começou em 1970 quando a primeira impressora utilizou uma porta paralela para se conectar a um computador, a Centronic Model 101 [1]. A partir daí, grandes empresas começaram a lançar computadores com portas paralelas já integradas. Mas foi em 1981 que a porta paralela, conhecida como SPP (Standard Parallel Port) na época, teve seu auge, quando a IBM lançou o Computador Pessoal (PC). A partir daí, seguindo uma linha de evolução, foram surgindo novas portas de comunicação, mais rápidas, simplificadas e podendo executar mais funções, como: o padrão RS-232 para portas seriais, o qual teve seu auge em 1990; em 1995 a Intel introduziu a porta USB, a qual ficou famosa e começou a ser usada em larga escala aproximadamente na metade de 1997; o padrão Firewire (ou IEEE 1394), que, criado pela Macintosh e IBM em 1995, que é muito usado para captura de som, vídeo, imagens, e outros [2]. Neste cenário, à medida que novos padrões para portas de comunicação surgiam, as linguagens de programação precisavam criar um suporte (bibliotecas, APIs, etc) para estas portas, precisavam acompanhar esta evolução. E assim foi com a maioria delas, pois grande parte das linguagens de programação tem suporte para maioria das portas. O enfoque deste artigo é na relação que Java tem com estas portas. Quais ela suporta, quais ela não suporta, se todas APIs são oficiais da Sun ou não, como elas funcionam, etc. Deste modo, o artigo está organizado da seguinte maneira: a seção 2 apresenta uma visão geral das portas de comunicação; na seção 3 é feita uma breve explicação 3. Java Communication API A Java Communication API (javax.comm) mais conhecida como Java Comm, a qual foi criada em 1997 e pode ser encontrada no link da referência [4], é a API oficial da Sun usada para comunicação com portas do padrão RS-232 (portas seriais), e portas paralelas no modo SPP (a extensão da porta paralela não está totalmente completa, pois ainda está em desenvolvimento [5]). Segundo a Sun, a parte de portas seriais está desenvolvida ao máximo possível, a API fornece tudo que é necessário para sua utilização da maneira mais simplificada possível [6]. Eis algumas características da Java Comm [5]: • Enumeração de portas (mapeamento das portas); 1 • • • • Configuração de portas (baud rate, stop bits, paridade); Acesso ao padrão EIA232; Opções de controle de fluxo para hardware e software; Opção assíncrona de eventos para notificação de: o Data disponível na porta RS-232; o Mudança de portas no hardware; o Mudança de titularidade em alguma porta dentro de uma única JVM. int i = 0; portas = new String[10]; //... while (listaDePortas.hasMoreElements()) { CommPortIdentifier tempPorta = (CommPortIdentifier)listaDePortas.nextElement(); portas[i] = tempPorta.getName(); i++; } //... O método hasMoreElements() retorna o próximo elemento da estrutura listaDePortas, mas o loop while garante que todos os elementos sejam passados ao array portas através do método getName(). E através de um simples for, podemos escrever na tela todas as portas reconhecidas: Apesar de ainda não existir uma API oficial da Sun para a utilização de outras portas de comunicação que não sejam serial e paralela, ela afirma que está investigando a possibilidade da inserção de outros protocolos de baixo nível, com USB e Firewire na Java Comm. E também afirma que não irão dar suporte à protocolos de alto nível como X/Y/Z-modem, ou Kermit [6, 9]. Em 2003 a Sun começou um projeto para incluir tais protocolos em uma API. Eles inicialmente começaram com o javax.usb, para portas USB; atualmente esta API existe somente para o sistema operacional Solaris, o qual também é da Sun [7]. Contudo, grupos de terceiros (desenvolvedores que não são da Sun, assim chamados pela própria Sun) já fizeram uma API para a comunicação USB para os sistemas operacionais Windows e Linux. Apesar de exister mais de uma API para USB, a jUSB (Java USB), assim chamada pelos seus criadores, é atualmente a mais famosa e mais usada por aqueles que buscam um caminho alternativo que não seja o de esperar até a Sun lançar uma API oficial [8]. for (int j = 0; j < portas.length; j++) { System.out.println(portas[j]); } 4.2. Abrindo as portas O método getPortIdentifier(String porta) da classe CommPortIdentifier retorna um identificador da porta escolhida. Note que a string “porta” passada, seria um dos nomes obtidos das portas que foram reconhecidas e impressas na tela através do for. É necessário instanciar um objeto para receber esse identificador: CommPortIdentifier cpi = CommPortIdentifier.getPortIdentifier(portaEscolhida); 4. Utilizando a javax.comm Em seguida deve-se criar uma instância da classe SerialPort utilizando o identificador obtido. Note que uma conversão deverá ser feita. A porta só pode ser instanciada através de um casting, e ao mesmo tempo será aberta a porta para comunicação: Muitos programas podem ser desenvolvidos com o auxílio da javax.comm, pois como dito antes, ela dá suporte à tudo que uma porta serial necessita. Mas quando se usa a javax.comm, é necessário partir de um princípio básico que qualquer programa necessitará: reconhecer as portas disponíveis no computador, abrir as portas desejadas, enviar bytes, receber bytes, e verificar se algum byte realmente foi lido. SerialPort porta = (SerialPort)cpi.open("SComm",timeout); O método open() tem como parâmetros o nome da classe principal onde o código está sendo escrito (isso é aconselhável para não gerar conflitos com outros usuários/programas que estejam já usando, ou que forem tentar usar a porta escolhida anteriormente), e o valor desejado para timeout (tempo em milissegundos antes de a porta ser aberta, após a execução do código). Este método, além de abrir a porta escolhida, retorna um objeto do tipo CommPort, que através do casting torna-se um objeto do tipo SerialPort. O fato de o método open() retornar um objeto do tipo CommPort é porque pode-se obter um objeto do tipo ParallelPort através de um casting também, visto que ParallelPort e SerialPort herdam de CommPort. 4.1. Reconhecendo as portas A API de comunicação fornece o método getPortIdentifiers() integrante da classe CommPortIdentifier que retorna, em uma estrutura Enumeration, as portas disponíveis. A classe CommPortIdentifier pode ser instanciada e representar uma porta. Para isso, precisa-se varrer a estrutura retornada por getPortIdentifiers() e instanciando cada porta através de uma conversão (casting) simples: //... Enumeration listaDePortas; listaDePortas = CommPortIdentifier.getPortIdentifiers(); //... 2 4.3. Enviando bytes para a porta serial porta serial que podem existir dados à serem lidos, através do método notifyOnDataAvailable(). A classe SerialPort implementa as classes abstratas InputStream e OutputStream, o que torna o problema mais simples. Para realizar o envio de dados, basta instanciar a uma variável do tipo OutputStream a stream da porta serial, a qual pode ser obtida através do método getOutputStream(). porta.addEventListener(this); porta.notifyOnDataAvailable(true); Agora o evento precisa ser tratado. Primeiro instanciase um array de bytes. Esse array será o buffer de dados. OutputStream saida = porta.getOutputStream(); public void serialEvent(SerialPortEvent ev){ switch (ev.getEventType()) { //… case SerialPortEvent.DATA_AVAILABLE: byte[] bufferLeitura = new byte[20]; //... Em seguida é preciso configurar os parâmetros de comunicação serial, para isso utiliza-se o método setSerialPortParams(). porta.setSerialPortParams(baudrate, porta.DATABITS_8, porta.STOPBITS_2, porta.PARITY_NONE); O método getEventType() da classe SerialPortEvent, retorna um inteiro, o qual é usado em um switch. Este inteiro representará o tipo do evento. Dependendo de qual inteiro foi retornado, o qual vai depender do dado que está sendo lido, o switch cairá em algum case. Os cases são variáveis do tipo inteiro e constantes que já são predefinidas na API e representam os tipos de eventos possíveis. Com o fluxo de entrada de dados já definido basta usar o método available(), que retorna sempre 0 se InputStream (nesse caso entrada) é classe da qual ele é invocado. Estes parâmetros passados são variáveis já predefinidas na API. Exceto pelo baudrate, que deve ser escolhido pelo usuário. Para escrever os dados na porta serial basta escolher os bytes que se deseja enviar, usar o método write(), e logo após, o método flush() da classe OutputStream. String msg = “Olá Mundo!”; saida.write(msg.getBytes()); saida.flush(); //... Como a classe OutputStream suporta escrever apenas bytes, e se o usuário quer escrever uma string como no exemplo acima, basta usar o método getBytes() que qualquer tipo de variável tem, ou seja, o mesmo valeria para enviar chars, ints, doubles, etc. int nodebyte; while ( entrada.available() > 0 ) { nodeBytes = entrada.read(bufferLeitura); } //… 4.4 Recebendo bytes na porta serial O método read(), faz 3 operações ao mesmo tempo. Ele lê os dados de entrada, escreve eles no array de bytes passados como parâmetro, e retorna um valor inteiro do número de bytes que foram lidos. Os bytes lidos e armazenados na variável bufferLeitura podem agora serem convertidos para uma String através de um construtor que a classe String possui. Apesar de a Java Comm facilitar bastante o trabalho, receber bytes na porta serial é mais complicado do que enviar; são várias linhas de código. Os passos que devem ser seguidos para realizar a leitura de dados são os seguintes: • Criar um fluxo de entrada; • Adicionar um gerenciador de eventos para dados na porta serial; • Instanciar uma thread para aguardar os eventos; • Tratar o evento e receber os dados. Primeiramente é preciso criar uma instancia para a stream de entrada (fluxo de entrada) da porta serial, da mesma maneira que foi feito para a stream de saída. //… String dadosLidos = new String(bufferLeitura); //… 4.5. Verificando se algum byte foi realmente lido Para finalizar, agora é possível verificar se os bytes foram realmente lidos ou não. Se a dimensão do buffer for igual zero, isso significa que nenhum byte foi lido. Se a dimensão do buffer for igual a 1, apenas um byte foi lido. Caso contrário, a estrutura bufferLeitura recebe os bytes lidos. O primerio byte lido é armazenado em InputStream entrada = porta.getInputStream(); Após a criação do fluxo de entrada, é preciso adicionar um gerenciador de eventos para a porta serial. Isso é possível através do método addEventListener() que a classe SerialPort possui. Em seguida basta notificar a 3 bufferLeitura[0], o segundo em bufferLeitura[1] e assim por diante. configurada para ler bytes de entrada, ou seja, para receber um SMS; e para escrever a mensagem desejada, basta digita-la no campo cinza superior, e pressionar enter. //... if (bufferLeitura.length == 0) { System.out.println("Nada lido!"); } else if (bufferLeitura.length == 1 ){ System.out.println("Apenas um byte foi lido!"); } else { System.out.println(dadosLidos); } System.out.println("n.o de bytes lidos : " + nodeBytes ); break; 6. Conclusão Apesar das lacunas que a Java Comm apresenta, é seguro afirmar que ela simplifica bastante o uso da porta serial. Como mostrado nos códigos fontes, o básico a ser feito quando desenvolvendo um programa que necessite de comunicação serial, é algo de simples imlementação para um programador que já tenha alguma base em Java. Com um programa feito em Java, é possível realizar a mesma função que qualquer outro programa desenvolvido em outras linguagens; na maioria das vezes, com soluções até mais simples e práticas, pelo fato de Java ser uma linguagem Orientada a Objetos, e possuir um grande suporte da comunidade de desenvolvedores. Entretanto, enquanto a Sun não desenvolve uma API oficial para outros protocolos de comunicação, na maioria das vezes, existirá APIs de terceiros que, na maior parte dos casos, são muito eficientes, e sufucientes para a resolução de muitos problemas. Note que o case SerialPortEvent.DATA_AVAILABLE só termina agora. Todas as linhas de códigos apresentadas após ele terminam no break acima. Se o usuário desejasse tratar algum outro evento, seria necessário mais cases. 5. Exemplo de um programa desenvolvido usando a javax.comm A diversidade de programas, que necessitam comunicação de porta serial, e que podem ser implementados utilizando a Java Comm é inacabável, como por exemplo, um simples software que envia e/ou recebe SMSs (Short Message Service) para/de um celular. Este software, o Serial SMS, assim chamado e desenvolvido por Stefan Moscibroda em 2001, está listado nas referências [9]. 7. Referências [1]Guia do Hardware - a evolução das portas de comunicação. http://www.guiadohardware.net/artigos/evolucao-portas/, Junho de 2008. [2]Ackadia - Serial & parallel ports. UART, USB, Firewire & FC-AL http://www.ackadia.com/computer/system-architecture/systemarchitecture-comms.php, Junho de 2008. [3]Rogercom – Controle de acesso da porta serial. http://www.rogercom.com/PortaSerial/ControleAcesso/Controle .htm, Junho de 2008. [4]Sun - Java Communication API (javax.comm) http://java.sun.com/products/javacomm/reference/api/index.htm, Junho de 2008. [5]Sun – Java communication API features. http://java.sun.com/products/javacomm/, Junho de 2008. [6]Sun – Java communication API FAQ. http://java.sun.com/products/javacomm/reference/faqs/index.ht ml, Junho de 2008. [7]Sun – Java USB communication for Solaris. http://developers.sun.com/solaris/developer/support/driver/wps/ usb/usb.pdf, Junho de 2008. [8]Sourceforge – Java third party USB API. http://jusb.sourceforge.net/, Junho de 2008. [9]Stefan Moscibroda – Serial SMS http://ufpr.dl.sourceforge.net/sourceforge/serialsmsclient/Serial_ SMS.zip, Junho de 2008. Fig. 1. – Programa Serial SMS em execução. A Fig. 1 mostra o programa em execução. A sua implementação é basicamente: uma interface gráfica desenvolvida usando as Interfaces AWT e Swing, na qual é possível configurar as características principais da porta serial, como mostrado na figura; e uma lógica de envio e recebimento das mensagens SMS. No momento que o usuário clica no botão open port, a porta escolhida é 4