/* ================================================================== * RfidService.java - 29/07/2016 3:32:45 PM * * Copyright 2007-2016 SolarNetwork.net Dev Team * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * ================================================================== */ package net.solarnetwork.node.hw.rfid; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.net.SocketFactory; import org.osgi.service.event.Event; import org.osgi.service.event.EventAdmin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import net.solarnetwork.node.settings.SettingSpecifier; import net.solarnetwork.node.settings.SettingSpecifierProvider; import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier; import net.solarnetwork.node.settings.support.BasicTitleSettingSpecifier; import net.solarnetwork.util.OptionalService; /** * Service that configures a {@link RfidSocketReader} and posts any RFID * messages as {@link EventAdmin} events. * * @author matt * @version 1.0 */ public class RfidSocketReaderService implements SettingSpecifierProvider, Runnable { /** The "heartbeat" message sent by the server after read timeouts. */ public static final String HEARTBEAT_MSG = "ping"; /** Topic for when a RFID message has been received. */ public static final String TOPIC_RFID_MESSAGE_RECEIVED = "net/solarnetwork/node/hw/rfid/MESSAGE_RECEIVED"; /** Event parameter for the RFID message value. */ public static final String EVENT_PARAM_MESSAGE = "message"; /** * Event parameter for the RFID message date, as milliseconds since the * epoch. */ public static final String EVENT_PARAM_DATE = "date"; /** Event parameter for the RFID message counter value. */ public static final String EVENT_PARAM_COUNT = "count"; /** Event parameter for the configured {@code uid}. */ public static final String EVENT_PARAM_UID = "uid"; /** Event parameter for the configured {@code groupUID}. */ public static final String EVENT_PARAM_GROUP_UID = "groupUID"; private OptionalService<EventAdmin> eventAdmin; private MessageSource messageSource; private int port = 9090; private String host = "localhost"; private String uid; private String groupUID; private int connectRetryMinutes = 1; private Thread readerThread; private Thread tryLaterThread; // stats private boolean initialized = false; private long lastHeartbeatDate; private long lastMessageDate; private long messageCount = 0; private final Logger log = LoggerFactory.getLogger(getClass()); /** * Initialize after all properties are configured. */ public void init() { initialized = true; tryStartReaderLater(); } /** * Shut down the service. */ public void destroy() { stopReader(false); } private synchronized void startReader() { tryLaterThread = null; if ( readerThread != null ) { log.debug("RfidSocketReader already started, not starting again."); return; } if ( getPort() < 1 || getHost() == null || getHost().length() < 1 ) { log.debug("RFID server details not configured ({}:{}); cannot start.", getHost(), getPort()); return; } readerThread = new Thread(this); readerThread.setDaemon(true); readerThread.setName("RFID Scanner Client"); readerThread.start(); } private synchronized void stopReader(boolean tryAgain) { if ( readerThread != null ) { if ( readerThread.isAlive() && Thread.currentThread() != readerThread ) { try { readerThread.interrupt(); } catch ( Exception e ) { log.debug("Exception interrupting RfidSocketReader thread", e); } } readerThread = null; } if ( tryAgain ) { tryStartReaderLater(); } else if ( tryLaterThread != null ) { tryLaterThread.interrupt(); } } private synchronized void tryStartReaderLater() { if ( !(initialized && tryLaterThread == null) ) { return; } log.info("Will try to connect to RFID server in {} minutes", connectRetryMinutes); tryLaterThread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(TimeUnit.MINUTES.toMillis(connectRetryMinutes)); startReader(); } catch ( InterruptedException e ) { // ignore this; } } }); tryLaterThread.setDaemon(true); tryLaterThread.setName("RFID Server Connector"); tryLaterThread.start(); } @Override public void run() { BufferedReader in = null; Socket s = null; boolean tryAgain = false; log.info("Connecting to RFID server {}:{}", host, port); try { s = SocketFactory.getDefault().createSocket(host, port); s.setKeepAlive(true); in = new BufferedReader(new InputStreamReader(s.getInputStream())); boolean readSomething = false; log.info("Connected to RFID server {}:{}", host, port); String line; while ( true ) { line = in.readLine(); if ( line == null ) { tryAgain = true; break; } // the first line read is a status line... if ( readSomething && !HEARTBEAT_MSG.equalsIgnoreCase(line) ) { lastMessageDate = System.currentTimeMillis(); messageCount += 1; postRfidMessageReceivedEvent(line); } else { lastHeartbeatDate = System.currentTimeMillis(); log.debug("RFID status message: {}", line); } readSomething = true; } } catch ( IOException e ) { log.error("RFID server communication error: {}", e.getMessage()); tryAgain = true; } catch ( Throwable t ) { log.error("RFID server error: {}", t.getMessage(), t); } finally { log.info("Disconnected from RFID server {}:{}", host, port); if ( in != null ) { try { in.close(); } catch ( IOException e ) { // ignore } } if ( s != null ) { try { s.close(); } catch ( IOException e ) { // ignore } } stopReader(tryAgain); } } private void postRfidMessageReceivedEvent(String msg) { OptionalService<EventAdmin> eaService = eventAdmin; EventAdmin ea = (eaService == null ? null : eaService.service()); if ( ea == null ) { return; } log.debug("RFID client posting message received event: {}", msg); Map<String, Object> props = new HashMap<String, Object>(5); props.put(EVENT_PARAM_COUNT, messageCount); props.put(EVENT_PARAM_DATE, System.currentTimeMillis()); if ( msg != null ) { props.put(EVENT_PARAM_MESSAGE, msg); } if ( uid != null ) { props.put(EVENT_PARAM_UID, uid); } if ( groupUID != null ) { props.put(EVENT_PARAM_GROUP_UID, groupUID); } Event event = new Event(TOPIC_RFID_MESSAGE_RECEIVED, props); ea.postEvent(event); } @Override public String getSettingUID() { return "net.solarnetwork.node.hw.rfid.client"; } @Override public String getDisplayName() { return getClass().getSimpleName(); } @Override public MessageSource getMessageSource() { return messageSource; } @Override public List<SettingSpecifier> getSettingSpecifiers() { RfidSocketReaderService defaults = new RfidSocketReaderService(); List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(8); results.add(new BasicTitleSettingSpecifier("info", getInfoMessage(Locale.getDefault()), true)); results.add(new BasicTextFieldSettingSpecifier("uid", defaults.uid)); results.add(new BasicTextFieldSettingSpecifier("groupUID", defaults.groupUID)); results.add(new BasicTextFieldSettingSpecifier("host", defaults.host)); results.add(new BasicTextFieldSettingSpecifier("port", String.valueOf(defaults.port))); return results; } private String getInfoMessage(Locale local) { Thread t = readerThread; StringBuilder buf = new StringBuilder(); if ( t != null && t.isAlive() ) { buf.append("Connected and listening."); long count = messageCount; if ( count > 0 ) { buf.append(count).append(" messages received."); long mDate = lastMessageDate; if ( mDate > 0 ) { buf.append(" Last message received at ").append(new Date(mDate)).append("."); } } long hDate = lastHeartbeatDate; if ( hDate > 0 ) { buf.append(" Last heartbeat received at ").append(new Date(hDate)).append("."); } } return buf.toString(); } /** * The RFID server port to connect to. * * @return The configured port. Defaults to {@code 9090}. */ public int getPort() { return port; } /** * The RFID server port to connect to. * * @param port * The port to set. */ public void setPort(int port) { if ( port != this.port ) { this.port = port; stopReader(true); } } /** * The host name of the RFID server to connect to. * * @return The configured host. Defaults to {@link localhost}. */ public String getHost() { return host; } /** * Set the host name of the RFID server to connect to. * * @param host * The host to set. */ public void setHost(String host) { if ( host == null ) { return; } if ( !host.equalsIgnoreCase(this.host) ) { this.host = host; stopReader(true); } } /** * Get the number of minutes between retrying to connect to the RFID server. * * @return The configured number of minutes. Defaults to {@code 1}. */ public int getConnectRetryMinutes() { return connectRetryMinutes; } /** * Set the number of minutes to wait before retrying to connect to the RFID * server. * * @param connectRetryMinutes * The number of minutes to set. */ public void setConnectRetryMinutes(int connectRetryMinutes) { if ( connectRetryMinutes < 0 ) { return; } this.connectRetryMinutes = connectRetryMinutes; } public String getUid() { return uid; } public void setUid(String uid) { this.uid = uid; } public String getGroupUID() { return groupUID; } public void setGroupUID(String groupUID) { this.groupUID = groupUID; } public void setMessageSource(MessageSource messageSource) { this.messageSource = messageSource; } public void setEventAdmin(OptionalService<EventAdmin> eventAdmin) { this.eventAdmin = eventAdmin; } }