Sincronizando Threads para ler e escrever na serial

Ola

Estou tentando sincronizar algumas tarefas porem nao estou obtendo sucesso.
O problema ocorre ao tentar ler e escrever em uma porta serial, sendo que a leitura nao pode ser aleatoria, pois depende da resposta da porta serial.

Vejamos o seguinte situacao, apos estabelecer comunicacao com a porta serial eu envio um comando AT, porem o proximo comando AT so pode ser enviado após a resposta da serial.
Eu tentei usar os metodos wait() e notify(), mas estes nao funcionaram como eu imaginei.
Alguem pode me dar umas dicas se eu devo usar tais metodos, ou indicar algum material que fale sobre algo parecido.

Abaixo segue o codigo

//imports

public class Modem{
	
	private static final Properties PROP = Util.readResource();
	private static final String PORT = PROP.getProperty("comPort");

	private static SerialPort serialPort = null;
	private static CommPortIdentifier portId = null;
	private static OutputStream outputStream = null;
	private static InputStream inputStream = null;
	private static ModemListener listener = null;
	
	static {
		try {
			portId = CommPortIdentifier.getPortIdentifier(PORT);
			serialPort = (SerialPort) portId.open("Modem-MC35i", 2000);
			serialPort.notifyOnOutputEmpty(true);
			serialPort.notifyOnDataAvailable(true);
			serialPort.setSerialPortParams(115200,
					SerialPort.DATABITS_8,
					SerialPort.STOPBITS_1,
					SerialPort.PARITY_NONE);
			inputStream = serialPort.getInputStream();
			outputStream = serialPort.getOutputStream();
			
			listener = new ModemListener(inputStream);
			serialPort.addEventListener(listener);
			listener.start();
		} catch (Exception ex){
			ex.printStackTrace();
		} 
	}
	
	public static void stop(){
		try {
			listener.interrupt();
			inputStream.close();
			outputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void sendCommandAT(String message) {
		try {
			outputStream.write(message.getBytes());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

public class ModemListener extends Thread implements SerialPortEventListener{
	
	private static final Logger logger = Util.startLogger(ModemListener.class);
	private InputStream inputStream;
	
	public ModemListener(InputStream inputStream){
		this.inputStream = inputStream;
	}
	
	public void serialEvent(SerialPortEvent event) {
		switch (event.getEventType()) {
			case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
				break;
	
			case SerialPortEvent.DATA_AVAILABLE:
				byte[] readBuffer = new byte[1];
	
				try {
					while (inputStream.available() > 0) {
						inputStream.read(readBuffer);
						logger.info(new String(readBuffer));
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
}


public class ServiceModem {
	
	private static final String ENTER = "\r\n";
		
	public static void sendSMS(String phone, String message){
		Modem.sendCommandAT("AT+CMGS="+phone + ENTER);
		Modem.sendCommandAT(message+(char)26 + ENTER);
		Modem.stop();
	}
}

O problema do codigo acima, é que o metodo sendSMS executa o metodo Modem.sendCommandAT sem esperar a resposta do modem.
A resposta do Modem é enviada ao arquivo de log, porem isso nao me ajuda muito.
Alguem sabe como sincronizar isso ?

putz, nao da pra voce registrar um listener que eh chamado ao final da execução do comando ?

Um jeito simples é você fazer assim, crie duas threads, uma que escreve na porta serial e outra que lê da porta serial, a thread que lê da porta serial fica tentando pegar um item de uma BlockingQueue (você pode usar a LinkedBlockingQueue) que é uma fila bloqueante pra fazer isso, então quando você quiser mandar um comando pra o modem, você não manda direto pra ele, coloca esse comando na fila, a thread vai ler o comando da fila e vai mandar pra o modem.

A outra thread é a thread que fica lendo do modem, ela ficaria o tempo todo tentando ler do stream do modem e quando ela conseguisse ler alguma coisa, ela colocaria em outra fila bloqueante, que é a fila dos bytes lidos do modem. Você ficaria então lendo sempre dessa fila até acabar os bytes que precisam ser lidos. É o caso clássico de “produtor-consumidor” usando threads, só que com filas bloqueantes fica MUITO mais simples, veja na documentação do Java que tem lá a BlockingQueue e a LinkedBlockingQueue.

Uma opção ainda a esse mesmo modo é não usar filas pra escrita, já que você sempre escreve e fica esperando pra ler, você poderia mandar o comando at do modem e esperar a thread que lê lá dele preencher a fila de bytes lidos.

[quote=Maurício Linhares]Um jeito simples[/quote] :shock: rsrs

Eu tentei criar duas Thread separadas assim como vc falou.
Uma para escrever na serial, e outra para ler da porta serial, após eu fazer isso eu iria tentar sincronizar as duas Thread.
Percebi que isso seria um problema, pois nao consigo ler os bytes da porta serial independentemente, pois é necessário adicionar um listener na porta serial ao invés de ficar lendo sucetivamente os bytes da porta serial.
Veja como eu tinha feito:

class Escreve{
	public void out(String command) throws Exception{
		CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier("COM4");
		SerialPort serialPort = (SerialPort) portId.open("Modem-MC35i", 2000);
		serialPort.notifyOnOutputEmpty(true);
		serialPort.notifyOnDataAvailable(true);
		serialPort.setSerialPortParams(115200,
				SerialPort.DATABITS_8,
				SerialPort.STOPBITS_1,
				SerialPort.PARITY_NONE);
		
		OutputStream outputStream = serialPort.getOutputStream();
		outputStream.write(command.getBytes());
		outputStream.close();
		serialPort.close();
	}
}

class Le{
	private byte[] readBuffer = new byte[1];
	public void in() throws Exception{
		CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier("COM4");
		SerialPort serialPort = (SerialPort) portId.open("Modem-MC35i", 2000);
		serialPort.notifyOnOutputEmpty(true);
		serialPort.notifyOnDataAvailable(true);
		serialPort.setSerialPortParams(115200,
				SerialPort.DATABITS_8,
				SerialPort.STOPBITS_1,
				SerialPort.PARITY_NONE);
		
		InputStream inputStream = serialPort.getInputStream();
		while(true){
			inputStream.read(readBuffer);
			System.out.println(new String(readBuffer));
		}
	}
}

Eu consigo enviar os comandos, porem a leitura fica travada em inputStream.read(readBuffer); e os poucos dados que saem no system.out nao estao condizentes.
Eu acredito que nao vou conseguir fazer muita coisa com o codigo acima, entao eu fiz uns ajustes e o deixei dessa forma

Na classe de escrita eu so adicionei o Listener

serialPort.addEventListener(new Le(serialPort));

Agora na classe Le eu mudei bastante

class Le extends Thread implements SerialPortEventListener{
	SerialPort serialPort;
	public Le(SerialPort serialPort){this.serialPort = serialPort;}
	
	public void serialEvent(SerialPortEvent event) {
		InputStream inputStream = serialPort.getInputStream();//Omissao do try catch
		
		switch (event.getEventType()) {
			case SerialPortEvent.DATA_AVAILABLE:
				byte[] readBuffer = new byte[1];
	
				try {
					while (inputStream.available() > 0) {
						inputStream.read(readBuffer);
						System.out.println(new String(readBuffer));
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
}

Portanto acho que agora eu possuo algo mais proximo do que vc falou, e o código acima funciona perfeitamente, porem nao entendi como sincronizar, pois eu so posso enviar um comando, se eu enviar dois comando seguidos vai dar erro :frowning:
Qual seria o objeto que eu devo adicionar no BlockingQueue ?

Bem, simples considerando a complicação do problema rapaz :stuck_out_tongue:

O que você tem que colocar na BlockingQueue de leituras é o que está lendo lido da porta serial, os bytes mesmo, porque a thread que está lá recebendo os eventos da porta serial simplesmente alimenta a blockingqueue e você fica só lendo da blockingqueue.

[quote=Maurício Linhares]Bem, simples considerando a complicação do problema rapaz :stuck_out_tongue:

O que você tem que colocar na BlockingQueue de leituras é o que está lendo lido da porta serial, os bytes mesmo, porque a thread que está lá recebendo os eventos da porta serial simplesmente alimenta a blockingqueue e você fica só lendo da blockingqueue.[/quote]

Ok, consegui algumas coisas, ainda estou com alguns problemas mas acho que da pra resolver
Segue código abaixo, alguma idéia para melhorar ?

public class Alpha {
	public static void main(String args[]) throws Exception{
		BlockingQueue<String> queue = new LinkedBlockingQueue<String>();
		Escreve escreve = new Escreve(queue);
		escreve.out("ATD50841134\r\n");
		String saida = queue.take().toString();
		//Proximo comando
	}
}

class Escreve{
	private final BlockingQueue<String> queue;
	public Escreve(BlockingQueue<String> queue){this.queue = queue;}
	
	public void out(String command) throws Exception{
		CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier("COM4");
		SerialPort serialPort = (SerialPort) portId.open("Modem-MC35i", 2000);
		serialPort.notifyOnOutputEmpty(true);
		serialPort.notifyOnDataAvailable(true);
		serialPort.setSerialPortParams(115200,
				SerialPort.DATABITS_8,
				SerialPort.STOPBITS_1,
				SerialPort.PARITY_NONE);
		serialPort.addEventListener(new Listener(serialPort,queue));
		
		OutputStream outputStream = serialPort.getOutputStream();
		outputStream.write(command.getBytes());
		outputStream.close();
		Thread.sleep(2000);
		serialPort.close();
	}
}

class Listener extends Thread implements SerialPortEventListener{
	private final SerialPort serialPort;
	private final BlockingQueue<String> queue;
	public Listener(SerialPort serialPort, BlockingQueue<String> queue){
		this.serialPort = serialPort;
		this.queue = queue;
	}
	
	public void serialEvent(SerialPortEvent event) {
		
		try{
			InputStream inputStream = serialPort.getInputStream();
			
			switch (event.getEventType()) {
				case SerialPortEvent.DATA_AVAILABLE:
					byte[] readBuffer = new byte[1];
					try {
						while (inputStream.available() > 0) {
							inputStream.read(readBuffer);
							queue.put(new String(readBuffer));
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
			}
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
}