/* * * Fosstrak LLRP Commander (www.fosstrak.org) * * Copyright (C) 2014 KAIST * @author Janggwan Im <limg00n@kaist.ac.kr> * * Copyright (C) 2008 ETH Zurich * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * */ package kr.ac.kaist.resl.fosstrak.ale; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.LinkedList; import kr.ac.kaist.resl.ltk.net.LLRPAcceptor; import kr.ac.kaist.resl.ltk.net.LLRPConnection; import kr.ac.kaist.resl.ltk.net.LLRPConnectionAttemptFailedException; import kr.ac.kaist.resl.ltk.net.LLRPConnector; import kr.ac.kaist.resl.ltk.net.LLRPEndpoint; import kr.ac.kaist.resl.ltk.net.LLRPIoHandlerAdapter; import org.apache.log4j.Logger; import org.apache.mina.core.session.IoSession; import org.fosstrak.ale.server.readers.llrp.LLRPAdaptor; import org.fosstrak.llrp.adaptor.AsynchronousNotifiable; import org.fosstrak.llrp.adaptor.Constants; import org.fosstrak.llrp.adaptor.ReaderMetaData; import org.fosstrak.llrp.adaptor.exception.LLRPRuntimeException; import org.fosstrak.llrp.adaptor.util.AsynchronousNotifiableList; import org.fosstrak.llrp.client.LLRPExceptionHandlerTypeMap; import org.llrp.ltk.exceptions.InvalidLLRPMessageException; import kr.ac.kaist.resl.ltk.generated.LLRPMessageFactory; import kr.ac.kaist.resl.ltk.generated.enumerations.KeepaliveTriggerType; import kr.ac.kaist.resl.ltk.generated.messages.KEEPALIVE; import kr.ac.kaist.resl.ltk.generated.messages.SET_READER_CONFIG; import kr.ac.kaist.resl.ltk.generated.parameters.KeepaliveSpec; import org.llrp.ltk.types.Bit; import org.llrp.ltk.types.LLRPMessage; import org.llrp.ltk.types.UnsignedInteger; /** * This class implements the ReaderInterface. The Reader implementation * maintains two queues to decouple the user interface from the actual message * delivery over the network.<br/> * 1. from the user to the LLRP reader: the message to be sent is put into a * queue. a queue watch-dog awakes as soon as there are messages in the queue * and delivers them via LTK.<br/> * 2. from the LLRP reader to user: the incoming message from the reader is * stored into a queue. a queue watch-dog awakes as soon as there are messages * in the queue and delivers them to the user. * @author sawielan * */ public class ReaderImpl extends UnicastRemoteObject implements LLRPEndpoint, Reader { /** * serial version. */ private static final long serialVersionUID = 1L; /** the logger. */ private static Logger log = Logger.getLogger(ReaderImpl.class); /** the llrp connector to the physical reader. */ private LLRPConnection connector = null; /** the adaptor where the reader belongs to. */ private Adaptor adaptor = null; /** a list with all the receivers of asynchronous messages. */ private AsynchronousNotifiableList toNotify = new AsynchronousNotifiableList(); /** the default keep-alive interval for the reader. */ public static final int DEFAULT_KEEPALIVE_PERIOD = 10000; /** default how many times a keep-alive can be missed. */ public static final int DEFAULT_MISS_KEEPALIVE = 3; /** flag whether to throw an exception when a timeout occurred. */ private boolean throwExceptionKeepAlive = true; /** meta-data about the reader, if connection is up, number of packages, etc... */ private ReaderMetaData metaData = new ReaderMetaData(); /** IO handler. */ private LLRPIoHandlerAdapter handler = null; /** handle to the connection watch-dog. */ private Thread wd = null; /** handle to the out queue worker. */ private Thread outQueueWorker = null; /** handle to the in queue worker. */ private Thread inQueueWorker = null; /** queue to hold the incoming messages.*/ private LinkedList<byte[]> inqueue = new LinkedList<byte[]> (); /** queue to hold the outgoing messages. */ private LinkedList<LLRPMessage> outqueue = new LinkedList<LLRPMessage> (); /** queue policies. */ public enum QueuePolicy { DROP_QUEUE_ON_ERROR, KEEP_QUEUE_ON_ERROR }; /** * ReaderImpl and LLRPAdaptor have 1:1 correspondence. * This provides the link to the LLRPAdaptor */ private LLRPAdaptor llrpAdaptor = null; /** * Session for send LLRP message */ private IoSession ioSession = null; /** * constructor for a local reader stub. the stub maintains connection * to the llrp reader. * @param adaptor the adaptor responsible for this reader. * @param readerName the name of this reader. * @param readerAddress the address where to connect. * @throws RemoteException whenever there is an RMI exception */ public ReaderImpl(Adaptor adaptor, String readerName, String readerAddress) throws RemoteException { this.adaptor = adaptor; metaData._setAllowNKeepAliveMisses(DEFAULT_MISS_KEEPALIVE); metaData._setKeepAlivePeriod(DEFAULT_KEEPALIVE_PERIOD); metaData._setReaderName(readerName); metaData._setReaderAddress(readerAddress); } /** * constructor for a local reader stub. the stub maintains connection * to the llrp reader. * @param adaptor the adaptor responsible for this reader. * @param readerName the name of this reader. * @param readerAddress the address where to connect. * @param port the port where to connect. * @throws RemoteException whenever there is an RMI exception */ public ReaderImpl(Adaptor adaptor, String readerName, String readerAddress, int port) throws RemoteException { this.adaptor = adaptor; metaData._setAllowNKeepAliveMisses(DEFAULT_MISS_KEEPALIVE); metaData._setKeepAlivePeriod(DEFAULT_KEEPALIVE_PERIOD); metaData._setReaderName(readerName); metaData._setReaderAddress(readerAddress); metaData._setPort(port); } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#connect(boolean) */ public void connect(boolean clientInitiatedConnection) throws LLRPRuntimeException, RemoteException { try { String address = metaData.getReaderAddress(); metaData._setClientInitiated(clientInitiatedConnection); // start a new counter session metaData._newSession(); if (metaData.getPort() == -1) { metaData._setPort(Constants.DEFAULT_LLRP_PORT); log.warn("port for reader '" + metaData.getReaderName() + "' not specified. using default port " + metaData.getPort()); } if (clientInitiatedConnection) { if (address == null) { log.error("address for reader '" + metaData.getReaderName() + "' is empty!"); reportException(new LLRPRuntimeException("address for reader '" + metaData.getReaderName() + "' is empty!")); return; } // run ltk connector. LLRPConnector connector = new LLRPConnector(this, address, metaData.getPort()); connector.getHandler().setKeepAliveAck(true); connector.getHandler().setKeepAliveForward(true); try { connector.connect(); } catch (LLRPConnectionAttemptFailedException e) { log.error("connection attempt to reader " + metaData.getReaderName() + " failed"); reportException(new LLRPRuntimeException("connection attempt to reader " + metaData.getReaderName() + " failed")); } this.connector = connector; } else { // do nothing // in case of reader-initiated connection, // LogicalReaderAcceptor already did binding, and // LLRPConnection is already established } metaData._setConnected(true); outQueueWorker = new Thread(getOutQueueWorker()); outQueueWorker.start(); inQueueWorker = new Thread(getInQueueWorker()); inQueueWorker.start(); // only do heart beat in client initiated mode. if (clientInitiatedConnection) { enableHeartBeat(); } log.info(String.format("reader %s connected.", metaData.getReaderName())); } catch (Exception e) { // catch all unexpected errors... LLRPRuntimeException ex = new LLRPRuntimeException( String.format("Could not connect to reader %s on adapter %s:\nException: %s", getReaderName(), adaptor.getAdaptorName(), e.getMessage())); reportException(ex); throw ex; } } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#disconnect() */ public void disconnect() throws RemoteException { log.debug("disconnecting the reader."); setReportKeepAlive(false); if (connector != null) { try { if (connector instanceof LLRPConnector) { // disconnect from the reader. ((LLRPConnector)connector).disconnect(); } else if (connector instanceof LLRPAcceptor) { // close the acceptor. ((LLRPAcceptor)connector).close(); } } catch (Exception e) { connector = null; } } metaData._setConnected(false); // stop the outqueue worker if (null != outQueueWorker) { outQueueWorker.interrupt(); } // stop the inqueue worker if (null != inQueueWorker) { inQueueWorker.interrupt(); } // stop the connection watch-dog. if (null != wd) { wd.interrupt(); } } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#reconnect() */ public void reconnect() throws LLRPRuntimeException, RemoteException { // first try to disconnect disconnect(); connect(isClientInitiated()); } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#send(byte[]) */ public void send(byte[] message) throws RemoteException { if (!metaData.isConnected() || (connector == null)) { reportException(new LLRPRuntimeException(String.format("reader %s is not connected", metaData.getReaderName()))); return; } // try to create the llrp message from the byte array. LLRPMessage llrpMessage = null; try { llrpMessage = LLRPMessageFactory.createLLRPMessage(message); } catch (InvalidLLRPMessageException e) { reportException(new LLRPRuntimeException(e.getMessage())); } if (llrpMessage == null) { log.warn(String.format("do not send empty llrp message on reader %s", metaData.getReaderName())); return; } // put the message into the outqueue synchronized (outqueue) { outqueue.add(llrpMessage); outqueue.notifyAll(); } } /** * performs the actual sending of the LLRP message to the reader. * @param llrpMessage the LLRP message to be sent. * @throws RemoteException at RMI exception. */ private void sendLLRPMessage(LLRPMessage llrpMessage) throws RemoteException { try { // send the message asynchronous. //connector.send(llrpMessage); ioSession.write(llrpMessage); metaData._packageSent(); } catch (NullPointerException npe) { // a null-pointer exception occurs when the reader is no more connected. // we therefore report the exception to the GUI. disconnect(); reportException(new LLRPRuntimeException(String.format("reader %s is not connected", metaData.getReaderName()), LLRPExceptionHandlerTypeMap.EXCEPTION_READER_LOST)); } catch (Exception e) { // just to be sure... disconnect(); } } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#isConnected() */ public boolean isConnected() throws RemoteException { return metaData.isConnected(); } /** * when there is an error, the ltk will call this method. * @param message the error message from ltk. */ public void errorOccured(String message) { reportException(new LLRPRuntimeException(message)); } /** * when a message arrives through ltk, this method is called. * @param message the llrp message delivered by ltk. */ public void messageReceived(LLRPMessage message) { if (message == null) { return; } byte[] binaryEncoded; try { binaryEncoded = message.encodeBinary(); } catch (InvalidLLRPMessageException e1) { reportException(new LLRPRuntimeException(e1.getMessage())); return; } metaData._packageReceived(); if (message instanceof KEEPALIVE) { metaData._setAlive(true); log.debug("received keepalive message from the reader:" + metaData.getReaderName()); if (!metaData.isReportKeepAlive()) { return; } } // put the message into the inqueue. synchronized (inqueue) { inqueue.add(binaryEncoded); inqueue.notifyAll(); } } /** * deliver a received message to the handlers. * @param binaryEncoded the binary encoded LLRP message. */ private void deliverMessage(byte[] binaryEncoded) { try { adaptor.messageReceivedCallback(binaryEncoded, metaData.getReaderName()); } catch (RemoteException e) { reportException(new LLRPRuntimeException(e.getMessage())); } // also notify all the registered notifyables. try { toNotify.notify(binaryEncoded, metaData.getReaderName()); } catch (RemoteException e) { reportException(new LLRPRuntimeException(e.getMessage())); } } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#getReaderAddress() */ public String getReaderAddress() throws RemoteException { return metaData.getReaderAddress(); } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#getPort() */ public int getPort() throws RemoteException { return metaData.getPort(); } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#isClientInitiated() */ public boolean isClientInitiated() throws RemoteException { return metaData.isClientInitiated(); } public void setClientInitiated(boolean clientInitiated) throws RemoteException { metaData._setClientInitiated(clientInitiated); } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#registerForAsynchronous(org.fosstrak.llrp.adaptor.AsynchronousNotifiable) */ public void registerForAsynchronous(AsynchronousNotifiable receiver) throws RemoteException { toNotify.add(receiver); } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.ReaderIface#deregisterFromAsynchronous(org.fosstrak.llrp.adaptor.AsynchronousNotifiable) */ public void deregisterFromAsynchronous(AsynchronousNotifiable receiver) throws RemoteException { toNotify.remove(receiver); } public String getReaderName() throws RemoteException { return metaData.getReaderName(); } public boolean isConnectImmediate() throws RemoteException { return metaData.isConnectImmediately(); } public void setConnectImmediate(boolean value) throws RemoteException { metaData._setConnectImmediately(value); } /** * reports an exception the the adaptor. if the reporting of the * exception also fails, the stack-trace gets logged but the * reader continues to work. * @param e the exception to report. */ private void reportException(LLRPRuntimeException e) { if (adaptor == null) { log.error("no adaptor to report exception to on reader: " + metaData.getReaderName()); return; } try { adaptor.errorCallback(e, metaData.getReaderName()); } catch (RemoteException e1) { // print the stacktrace to the console log.debug(e1.getStackTrace().toString()); } } /** * sends a SET_READER_CONFIG message that sets the keepalive value. */ private void enableHeartBeat() { // build the keepalive settings SET_READER_CONFIG sr = new SET_READER_CONFIG(); KeepaliveSpec ks = new KeepaliveSpec(); ks.setKeepaliveTriggerType(new KeepaliveTriggerType(KeepaliveTriggerType.Periodic)); ks.setPeriodicTriggerValue(new UnsignedInteger(metaData.getKeepAlivePeriod())); sr.setKeepaliveSpec(ks); sr.setResetToFactoryDefault(new Bit(0)); log.debug(String.format("using keepalive periode: %d", metaData.getKeepAlivePeriod())); try { send(sr.encodeBinary()); } catch (RemoteException e) { if (throwExceptionKeepAlive) { reportException(new LLRPRuntimeException("Could not install keepalive message: " + e.getMessage())); } else { e.printStackTrace(); } } catch (InvalidLLRPMessageException e) { e.printStackTrace(); } // run the watch-dog wd = new Thread(new Runnable() { public void run() { log.debug("starting connection watchdog."); try { while (isConnected()) { try { Thread.sleep(metaData.getAllowNKeepAliveMisses() * metaData.getKeepAlivePeriod()); if (!metaData.isAlive()) { log.debug("connection timed out..."); disconnect(); if (throwExceptionKeepAlive) { reportException(new LLRPRuntimeException("Connection timed out", LLRPExceptionHandlerTypeMap.EXCEPTION_READER_LOST)); } } metaData._setAlive(false); } catch (InterruptedException e) { log.debug("received interrupt - stopping watchdog."); } } } catch (RemoteException e) { e.printStackTrace(); } log.debug("connection watchdog stopped."); } }); wd.start(); } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.Reader#getKeepAlivePeriod() */ public int getKeepAlivePeriod() throws RemoteException { return metaData.getKeepAlivePeriod(); } /* (non-Javadoc) * @see org.fosstrak.llrp.adaptor.Reader#setKeepAlivePeriod(int, int, boolean, boolean) */ public void setKeepAlivePeriod(int keepAlivePeriod, int times, boolean report, boolean throwException) throws RemoteException { metaData._setKeepAlivePeriod(keepAlivePeriod); metaData._setAllowNKeepAliveMisses(times); metaData._setReportKeepAlive(report); this.throwExceptionKeepAlive = throwException; } public void setReportKeepAlive(boolean report) throws RemoteException { metaData._setReportKeepAlive(report); } public boolean isReportKeepAlive() throws RemoteException { return metaData.isReportKeepAlive(); } public final ReaderMetaData getMetaData() throws RemoteException { return new ReaderMetaData(metaData); } /** * creates a runnable that watches the out queue for new messages to be * sent. at arrival of a new message, the message is sent via LTK. * @return a runnable. */ private Runnable getOutQueueWorker() { final LinkedList<LLRPMessage> queue = outqueue; return new Runnable() { public void run() { try { while (true) { synchronized (queue) { while (queue.isEmpty()) queue.wait(); LLRPMessage msg = queue.removeFirst(); try { sendLLRPMessage(msg); } catch (RemoteException e) { log.debug(String.format( "Could not send message: %s", e.getMessage())); } } } } catch (InterruptedException e) { log.debug("stopping out queue worker."); } } }; } /** * creates a runnable that watches the in queue for new messages and at * arrival, delivers them to the management. * @return a runnable. */ private Runnable getInQueueWorker() { final LinkedList<byte[]> queue = inqueue; return new Runnable() { public void run() { try { while (true) { synchronized (queue) { while (queue.isEmpty()) queue.wait(); byte[] msg = queue.removeFirst(); deliverMessage(msg); } } } catch (InterruptedException e) { log.debug("stopping in queue worker."); } } }; } /* public void setConnection(LLRPConnection conn) { this.connector = conn; } public LLRPConnection getConnection() { return connector; }*/ public LLRPAdaptor getLlrpAdaptor() { return llrpAdaptor; } public void setLlrpAdaptor(LLRPAdaptor llrpAdaptor) { this.llrpAdaptor = llrpAdaptor; } public IoSession getIoSession() { return ioSession; } public void setIoSession(IoSession ioSession) { this.ioSession = ioSession; } }