/* ================================================================== * SerialPortNetwork.java - Oct 23, 2014 3:58:36 PM * * Copyright 2007-2014 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.io.serial.rxtx; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import net.solarnetwork.node.LockTimeoutException; import net.solarnetwork.node.io.serial.SerialConnection; import net.solarnetwork.node.io.serial.SerialConnectionAction; import net.solarnetwork.node.io.serial.SerialNetwork; import net.solarnetwork.node.settings.SettingSpecifier; import net.solarnetwork.node.settings.SettingSpecifierProvider; import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier; import net.solarnetwork.node.support.SerialPortBean; import net.solarnetwork.node.support.SerialPortBeanParameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; /** * RXTX implementation of {@link SerialNetwork}. * * @author matt * @version 1.1 */ public class SerialPortNetwork implements SerialNetwork, SettingSpecifierProvider { private SerialPortBeanParameters serialParams = getDefaultSerialParametersInstance(); private String uid = "Serial Port"; private String groupUID; private long timeout = 10L; private TimeUnit unit = TimeUnit.SECONDS; private MessageSource messageSource; private final ExecutorService executor = Executors.newCachedThreadPool(); private final ReentrantLock lock = new ReentrantLock(true); // use fair lock to prevent starvation private final Logger log = LoggerFactory.getLogger(getClass()); private static SerialPortBeanParameters getDefaultSerialParametersInstance() { SerialPortBeanParameters params = new SerialPortBeanParameters(); params.setSerialPort("/dev/ttyS0"); params.setBaud(9600); params.setDataBits(8); params.setReceiveThreshold(4); params.setReceiveTimeout(9000); params.setMaxWait(90000); return params; } /** * Call to shut down internal resources. Once called, this network may not * be used again. */ public void shutdown() { if ( executor.isShutdown() == false ) { executor.shutdown(); } } @Override public String getUID() { return uid; } @Override public String getGroupUID() { return groupUID; } @Override public <T> T performAction(SerialConnectionAction<T> action) throws IOException { SerialConnection conn = null; try { conn = createConnection(); conn.open(); return action.doWithConnection(conn); } finally { if ( conn != null ) { try { conn.close(); } catch ( RuntimeException e ) { // ignore this } } } } @Override public SerialConnection createConnection() { return new LockingSerialConnection(); } /** * Internal extension of {@link SerialPortConnection} that utilizes a * {@link Lock} to serialize access to the connection between threads. */ private class LockingSerialConnection extends SerialPortConnection { /** * Construct with {@link SerialParameters}. * * @param parameters * the parameters */ private LockingSerialConnection() { super(serialParams, executor); } @Override public void open() throws IOException, LockTimeoutException { if ( !isOpen() ) { acquireLock(); super.open(); } } @Override public void close() { try { if ( isOpen() ) { super.close(); } } finally { releaseLock(); } } @Override protected void finalize() throws Throwable { releaseLock(); // as a catch-all super.finalize(); } } /** * Acquire the port lock, returning if lock acquired. * * @throws LockTimeoutException * if the lock cannot be obtained */ private void acquireLock() throws LockTimeoutException { if ( lock.isLocked() ) { log.debug("Port {} lock already acquired", serialParams.getSerialPort()); return; } log.debug("Acquiring lock on serial port {}; waiting at most {} {}", new Object[] { serialParams.getSerialPort(), timeout, unit }); try { if ( lock.tryLock(timeout, unit) ) { log.debug("Acquired port {} lock", serialParams.getSerialPort()); return; } log.debug("Timeout acquiring port {} lock", serialParams.getSerialPort()); } catch ( InterruptedException e ) { log.debug("Interrupted waiting for port {} lock", serialParams.getSerialPort()); } throw new LockTimeoutException("Could not acquire port " + serialParams.getSerialPort() + " lock"); } /** * Release the lock previously obtained via {@link #acquireLock()}. This * method is safe to call even if the lock has already been released. */ private void releaseLock() { if ( lock.isLocked() ) { log.debug("Releasing lock on serial port {}", serialParams.getSerialPort()); lock.unlock(); } } // SettingSpecifierProvider @Override public String getSettingUID() { return "net.solarnetwork.node.io.serial"; } @Override public String getDisplayName() { return "Serial port"; } @Override public List<SettingSpecifier> getSettingSpecifiers() { return getDefaultSettingSpecifiers(); } @Override public MessageSource getMessageSource() { return messageSource; } public static List<SettingSpecifier> getDefaultSettingSpecifiers() { SerialPortNetwork defaults = new SerialPortNetwork(); List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(20); results.add(new BasicTextFieldSettingSpecifier("uid", defaults.uid)); results.add(new BasicTextFieldSettingSpecifier("groupUID", defaults.groupUID)); SerialPortBeanParameters defaultSerialParams = getDefaultSerialParametersInstance(); results.add(new BasicTextFieldSettingSpecifier("serialParams.serialPort", defaultSerialParams .getSerialPort())); results.add(new BasicTextFieldSettingSpecifier("timeout", String.valueOf(defaults.timeout))); results.add(new BasicTextFieldSettingSpecifier("serialParams.maxWait", String .valueOf(defaultSerialParams.getMaxWait()))); results.addAll(SerialPortBean.getDefaultSettingSpecifiers(defaultSerialParams, "serialParams.")); return results; } public void setSerialParams(SerialPortBeanParameters serialParams) { this.serialParams = serialParams; } public SerialPortBeanParameters getSerialParams() { return serialParams; } public String getUid() { return uid; } public long getTimeout() { return timeout; } public TimeUnit getUnit() { return unit; } public void setUid(String uid) { this.uid = uid; } public void setGroupUID(String groupUID) { this.groupUID = groupUID; } public void setTimeout(long timeout) { this.timeout = timeout; } public void setUnit(TimeUnit unit) { this.unit = unit; } public void setMessageSource(MessageSource messageSource) { this.messageSource = messageSource; } }