/* ==================================================================
* JamodSerialModbusNetwork.java - Jul 29, 2014 12:54:53 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.modbus;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import net.solarnetwork.node.LockTimeoutException;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.SettingSpecifierProvider;
import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier;
import net.wimpi.modbus.net.SerialConnection;
import net.wimpi.modbus.util.SerialParameters;
/**
* Jamod implementation of {@link ModbusNetwork}.
*
* @author matt
* @version 1.1
* @since 2.0
*/
public class JamodSerialModbusNetwork implements ModbusNetwork, SettingSpecifierProvider {
private SerialParametersBean serialParams = getDefaultSerialParametersInstance();
private String uid = "Serial Port";
private String groupUID;
private long timeout = 10L;
private TimeUnit unit = TimeUnit.SECONDS;
private MessageSource messageSource;
private final ReentrantLock lock = new ReentrantLock(true); // use fair lock to prevent starvation
private final Logger log = LoggerFactory.getLogger(getClass());
private static SerialParametersBean getDefaultSerialParametersInstance() {
SerialParametersBean params = new SerialParametersBean();
params.setPortName("/dev/ttyS0");
params.setBaudRate(9600);
params.setDatabits(8);
params.setParityString("None");
params.setStopbits(1);
params.setEncoding("rtu");
params.setEcho(false);
params.setReceiveTimeout(1600);
return params;
}
@Override
public String toString() {
return "JamodSerialModbusNetwork{port=" + serialParams.getPortName() + '}';
}
@Override
public String getUID() {
if ( uid != null ) {
return uid;
}
return serialParams.getPortName();
}
@Override
public <T> T performAction(ModbusConnectionAction<T> action, final int unitId) throws IOException {
ModbusConnection conn = null;
try {
conn = createConnection(unitId);
conn.open();
return action.doWithConnection(conn);
} finally {
if ( conn != null ) {
try {
conn.close();
} catch ( RuntimeException e ) {
// ignore this
}
}
}
}
@Override
public ModbusConnection createConnection(int unitId) {
return new JamodModbusConnection(new LockingSerialConnection(serialParams), unitId);
}
/**
* Internal extension of {@link SerialConnection} that utilizes a
* {@link Lock} to serialize access to the connection between threads.
*/
private class LockingSerialConnection extends SerialConnection {
/**
* Construct with {@link SerialParameters}.
*
* @param parameters
* the parameters
*/
private LockingSerialConnection(SerialParameters parameters) {
super(parameters);
}
@Override
public void open() throws Exception {
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.getPortName());
return;
}
log.debug("Acquiring lock on Modbus port {}; waiting at most {} {}",
new Object[] { serialParams.getPortName(), timeout, unit });
try {
if ( lock.tryLock(timeout, unit) ) {
log.debug("Acquired port {} lock", serialParams.getPortName());
return;
}
log.debug("Timeout acquiring port {} lock", serialParams.getPortName());
} catch ( InterruptedException e ) {
log.debug("Interrupted waiting for port {} lock", serialParams.getPortName());
}
throw new LockTimeoutException("Could not acquire port " + serialParams.getPortName() + " 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 Modbus port {}", serialParams.getPortName());
lock.unlock();
}
}
// SettingSpecifierProvider
@Override
public String getSettingUID() {
return "net.solarnetwork.node.io.modbus";
}
@Override
public String getDisplayName() {
return "Modbus port";
}
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
return getDefaultSettingSpecifiers();
}
@Override
public MessageSource getMessageSource() {
return messageSource;
}
public static List<SettingSpecifier> getDefaultSettingSpecifiers() {
JamodSerialModbusNetwork defaults = new JamodSerialModbusNetwork();
List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(20);
results.add(new BasicTextFieldSettingSpecifier("uid", String.valueOf(defaults.uid)));
results.add(new BasicTextFieldSettingSpecifier("serialParams.portName",
defaults.serialParams.getPortName()));
results.add(new BasicTextFieldSettingSpecifier("timeout", String.valueOf(defaults.timeout)));
results.add(new BasicTextFieldSettingSpecifier("serialParams.baudRate",
String.valueOf(defaults.serialParams.getBaudRate())));
results.add(new BasicTextFieldSettingSpecifier("serialParams.databits",
String.valueOf(defaults.serialParams.getDatabits())));
results.add(new BasicTextFieldSettingSpecifier("serialParams.stopbits",
String.valueOf(defaults.serialParams.getStopbits())));
results.add(new BasicTextFieldSettingSpecifier("serialParams.parityString",
defaults.serialParams.getParityString()));
results.add(new BasicTextFieldSettingSpecifier("serialParams.encoding",
defaults.serialParams.getEncoding()));
results.add(new BasicTextFieldSettingSpecifier("serialParams.receiveTimeout",
String.valueOf(defaults.serialParams.getReceiveTimeout())));
results.add(new BasicTextFieldSettingSpecifier("serialParams.echo",
String.valueOf(defaults.serialParams.isEcho())));
results.add(new BasicTextFieldSettingSpecifier("serialParams.flowControlInString",
defaults.serialParams.getFlowControlInString()));
results.add(new BasicTextFieldSettingSpecifier("serialParams.flowControlOutString",
defaults.serialParams.getFlowControlInString()));
return results;
}
// Accessors
public SerialParametersBean getSerialParams() {
return serialParams;
}
public void setSerialParams(SerialParametersBean serialParams) {
this.serialParams = serialParams;
}
@Override
public String getGroupUID() {
return groupUID;
}
public void setGroupUID(String groupUID) {
this.groupUID = groupUID;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public TimeUnit getUnit() {
return unit;
}
public void setUnit(TimeUnit unit) {
this.unit = unit;
}
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
}