/* ================================================================== * YasdiMasterDeviceFactory.java - Mar 7, 2013 9:33:17 AM * * Copyright 2007-2013 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.yasdi4j; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; 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.node.settings.support.BasicToggleSettingSpecifier; import net.solarnetwork.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.context.MessageSource; import org.springframework.context.support.ResourceBundleMessageSource; import de.michaeldenk.yasdi4j.YasdiDevice; import de.michaeldenk.yasdi4j.YasdiDriver; /** * Factory for {@link YasdiMaster} instances configured to use a serial port. * * <p> * The configurable properties of this class are: * </p> * * <dl class="class-properties"> * <dt>device</dt> * <dd>The serial port to use, e.g. {@code /dev/ttyS0}.</dd> * * <dt>baud</dt> * <dd>The port speed. Defaults to <b>1200</b>.</dd> * * <dt>media</dt> * <dd>The YASDI media type. Defaults to <b>RS485</b>.</dd> * * <dt>protocol</dt> * <dd>The packet format. Defaults to <b>SMANet</b>.</dd> * * <dt>expectedDeviceCount</dt> * <dd>The number of expected devices for a given device. Defaults to <b>1</b>.</dd> * </dl> * * @author matt * @version 1.1 */ public class YasdiMasterDeviceFactory implements SettingSpecifierProvider, ObjectFactory<YasdiMaster> { private static final Object MONITOR = new Object(); private static MessageSource MESSAGE_SOURCE; private static de.michaeldenk.yasdi4j.YasdiMaster MASTER; private static List<YasdiDevice> DEVICES = new CopyOnWriteArrayList<YasdiDevice>(); private static Map<YasdiMasterDeviceFactory, Object> FACTORIES = new WeakHashMap<YasdiMasterDeviceFactory, Object>( 2); private static File INI_FILE = null; private final Logger log = LoggerFactory.getLogger(getClass()); private String driver = "libyasdi_drv_serial"; private String device = "/dev/ttyS0"; private int baud = 1200; private String media = "RS485"; private String protocol = "SMANet"; private int expectedDeviceCount = 1; private boolean debugYasdi = false; private YasdiMasterDevice master; /** * Default constructor. */ public YasdiMasterDeviceFactory() { super(); synchronized ( FACTORIES ) { FACTORIES.put(this, new WeakReference<Object>(MONITOR)); } } /** * Get a UID for this factory, based on device. value. * * @return the UID */ public String getUID() { return device; } @Override public synchronized YasdiMaster getObject() throws BeansException { if ( master != null ) { return master; } if ( MASTER != null ) { MASTER.reset(); } else { // generate our configuration, based on all configured factories setupConfigIniFile(); log.debug("Initializing YASDI from config {}", INI_FILE.getAbsolutePath()); MASTER = de.michaeldenk.yasdi4j.YasdiMaster.getInstance(); try { MASTER.initialize(INI_FILE.getAbsolutePath()); } catch ( IOException e ) { throw new RuntimeException("Unable to initialize YasdiMaster", e); } } try { YasdiDriver[] drivers = MASTER.getDrivers(); for ( YasdiDriver d : MASTER.getDrivers() ) { MASTER.setDriverOnline(d); } log.debug("Initialized {} drivers", drivers.length); } catch ( IOException e ) { throw new RuntimeException("Unable to initialize YasdiMaster", e); } // detect devices int expectedCount = 0; for ( YasdiMasterDeviceFactory factory : FACTORIES.keySet() ) { expectedCount += factory.expectedDeviceCount; } log.debug("Detecting devices, looking for {}", expectedCount); try { MASTER.detectDevices(expectedCount); } catch ( IOException e ) { throw new RuntimeException("Unable to detect devices", e); } if ( log.isInfoEnabled() ) { List<String> deviceNames = new ArrayList<String>(); for ( YasdiDevice dev : MASTER.getDevices() ) { deviceNames.add(dev.getName()); } log.info("Detected {} SMA devices: {}", deviceNames.size(), StringUtils.commaDelimitedStringFromCollection(deviceNames)); } DEVICES.clear(); DEVICES.addAll(Arrays.asList(MASTER.getDevices())); master = new YasdiMasterDevice(DEVICES, this.device); return master; } private void setupConfigIniFile() { Set<String> drivers = new LinkedHashSet<String>(2); List<YasdiMasterDeviceFactory> comDevices = new ArrayList<YasdiMasterDeviceFactory>(2); List<YasdiMasterDeviceFactory> ipDevices = new ArrayList<YasdiMasterDeviceFactory>(2); for ( YasdiMasterDeviceFactory factory : FACTORIES.keySet() ) { drivers.add(factory.driver); if ( "libyasdi_drv_serial".equals(factory.driver) ) { comDevices.add(factory); } else { ipDevices.add(factory); } } PrintWriter writer = null; try { if ( INI_FILE == null ) { String filePath = System.getProperty("sn.home", ""); if ( filePath.length() > 0 ) { filePath += '/'; } filePath += "var/yasdi.ini"; INI_FILE = new File(filePath); INI_FILE.deleteOnExit(); } writer = new PrintWriter(new BufferedWriter(new FileWriter(INI_FILE)), false); } catch ( IOException e ) { throw new RuntimeException("Unable to create YASDI ini file", e); } log.debug("Generating YASDI configuration file {}", INI_FILE.getAbsolutePath()); int i = 0; try { writer.println("[DriverModules]"); for ( String driver : drivers ) { writer.printf("Driver%d=%s\n", i++, driver); } writer.println(); if ( comDevices.size() > 0 ) { i = 1; for ( YasdiMasterDeviceFactory factory : comDevices ) { writer.printf("[COM%d]\n", i++); writer.printf("Device=%s\n", factory.device); writer.printf("Media=%s\n", factory.media); writer.printf("Baudrate=%d\n", factory.baud); writer.printf("Protocol=%s\n", factory.protocol); } writer.println(); } if ( ipDevices.size() > 0 ) { i = 1; for ( YasdiMasterDeviceFactory factory : ipDevices ) { writer.printf("[IP%d]\n", i++); writer.printf("Device=%s\n", factory.device); writer.printf("Protocol=%s\n", factory.protocol); } writer.println(); } if ( debugYasdi ) { writer.println("[Misc]"); writer.println("DebugOutput=/dev/stderr"); } } finally { writer.flush(); writer.close(); } } @Override public String getSettingUID() { return "net.solarnetwork.node.io.yasdi4j"; } @Override public String getDisplayName() { return "YASDI Master"; } @Override public MessageSource getMessageSource() { synchronized ( MONITOR ) { if ( MESSAGE_SOURCE == null ) { ResourceBundleMessageSource source = new ResourceBundleMessageSource(); source.setBundleClassLoader(getClass().getClassLoader()); source.setBasename(getClass().getName()); MESSAGE_SOURCE = source; } } return MESSAGE_SOURCE; } public static List<SettingSpecifier> getDefaultSettingSpecifiers() { List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(4); YasdiMasterDeviceFactory defaults = new YasdiMasterDeviceFactory(); results.add(new BasicTextFieldSettingSpecifier("expectedDeviceCount", String .valueOf(defaults.expectedDeviceCount))); results.add(new BasicTextFieldSettingSpecifier("driver", defaults.driver)); results.add(new BasicTextFieldSettingSpecifier("device", defaults.device)); results.add(new BasicTextFieldSettingSpecifier("baud", String.valueOf(defaults.baud))); results.add(new BasicTextFieldSettingSpecifier("media", String.valueOf(defaults.media))); results.add(new BasicTextFieldSettingSpecifier("protocol", String.valueOf(defaults.protocol))); results.add(new BasicToggleSettingSpecifier("debugYasdi", Boolean.FALSE)); return results; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((device == null) ? 0 : device.hashCode()); result = prime * result + ((driver == null) ? 0 : driver.hashCode()); return result; } @Override public boolean equals(Object obj) { if ( this == obj ) return true; if ( obj == null ) return false; if ( getClass() != obj.getClass() ) return false; YasdiMasterDeviceFactory other = (YasdiMasterDeviceFactory) obj; if ( device == null ) { if ( other.device != null ) return false; } else if ( !device.equals(other.device) ) return false; if ( driver == null ) { if ( other.driver != null ) return false; } else if ( !driver.equals(other.driver) ) return false; return true; } @Override public List<SettingSpecifier> getSettingSpecifiers() { List<SettingSpecifier> results = getDefaultSettingSpecifiers(); // add in read-only device UIDs Set<String> deviceNames = new TreeSet<String>(); try { // call getObject() to initialize getObject(); for ( YasdiMasterDeviceFactory factory : FACTORIES.keySet() ) { YasdiMaster master = factory.getObject(); deviceNames.add(master.getName()); } for ( String deviceName : deviceNames ) { results.add(0, new BasicTitleSettingSpecifier("availableDevice", deviceName, true)); } } catch ( RuntimeException e ) { log.warn("Exception getting YASDI device names: {}", e.getMessage()); } return results; } public void setDriver(String driver) { this.driver = driver; } public void setDevice(String device) { this.device = device; } public void setBaud(int baud) { this.baud = baud; } public void setMedia(String media) { this.media = media; } public void setProtocol(String protocol) { this.protocol = protocol; } public void setExpectedDeviceCount(int expectedDeviceCount) { this.expectedDeviceCount = expectedDeviceCount; } public void setDebugYasdi(boolean debugYasdi) { this.debugYasdi = debugYasdi; } }