net.solarnetwork.node.centameter.CentameterSupport.java Source code

Java tutorial

Introduction

Here is the source code for net.solarnetwork.node.centameter.CentameterSupport.java

Source

/* ==================================================================
 * CentameterSupport.java - Sep 20, 2010 9:24:54 PM
 * 
 * Copyright 2007-2010 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.centameter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentSkipListSet;
import net.solarnetwork.node.DataCollector;
import net.solarnetwork.node.DataCollectorFactory;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier;
import net.solarnetwork.node.settings.support.BasicTitleSettingSpecifier;
import net.solarnetwork.node.settings.support.BasicToggleSettingSpecifier;
import net.solarnetwork.node.support.DataCollectorSerialPortBeanParameters;
import net.solarnetwork.node.support.SerialPortBeanParameters;
import net.solarnetwork.node.util.PrefixedMessageSource;
import net.solarnetwork.util.DynamicServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;

/**
 * Base class for reading Centameter sensor data.
 * 
 * <p>
 * The Centameter packet contains a "magic" byte prefix (0x78) followed by 15
 * message bytes.
 * </p>
 * 
 * <p>
 * The configurable properties of this class are:
 * </p>
 * 
 * <dl class="class-properties">
 * <dt>dataCollectorFactory</dt>
 * <dd>The factory for creating {@link DataCollector} instances with.</dd>
 * 
 * <dt>serialParams</dt>
 * <dd>The serial port parameters to use.</dd>
 * 
 * <dt>voltage</dt>
 * <dd>A hard-coded voltage value to use for the Cent-a-meter, since it only
 * measures current. Defaults to {@link #DEFAULT_VOLTAGE}.</dd>
 * 
 * <dt>ampSensorIndex</dt>
 * <dd>The Cent-a-meter can report on 3 different currents. This index value is
 * the desired current to read. Possible values for this property are 1, 2, or
 * 3. Defaults to {@code 1}.</dd>
 * 
 * <dt>sourceIdFormat</dt>
 * <dd>A string format pattern for generating the {@code sourceId} value in
 * returned {@link PowerDatum} instances. This format will be passed the
 * Centameter address (as a <em>short</em>) and the Centameter amp sensor index
 * (as a <em>int</em>). Defaults to {@link #DEFAULT_SOURCE_ID_FORMAT}.</dd>
 * 
 * <dt>multiAmpSensorIndexFlags</dt>
 * <dd>A bitmask flag for which amp sensor index readings to return from
 * {@link #readMultipleDatum()}. The amp sensors number 1 - 3. Enable reading
 * each index by adding together each index as 2 ^ (index - 1). Thus to enable
 * reading from all 3 indexes set this value to <em>7</em> (2^0 + 2^1 + 2^2) =
 * 7). Defaults to 7.</dd>
 * 
 * <dt>addressSourceMapping</dt>
 * <dd>If configured, a mapping of Centameter address ID values to PowerDatum
 * sourceId values. This can be used to consistently collect data from
 * Centameters, even after the Centameter has been reset and it generates a new
 * random address ID value for itself.</dd>
 * 
 * <dt>sourceIdFilter</dt>
 * <dd>If configured, a set of PowerDatum sourceId values to accept data for,
 * rejecting all others. Sometimes bogus data can be received or some other
 * Centameter not part of this node might be received. Configuring this field
 * prevents data from sources other than those configured here from being
 * collected. Note the source values configured here should be the values
 * <em>after</em> any {@code addressSourceMapping} translation has occurred.</dd>
 * 
 * <dt>collectAllSourceIds</dt>
 * <dd>If <em>true</em> and the
 * {@link net.solarnetwork.node.MultiDatumDataSource} API is used, then attempt
 * to read values for all sources configured in the {@code sourceIdFilter}
 * property and return all the data collected. The
 * {@code collectAllSourceIdsTimeout} property is used to limit the amount of
 * time spent collecting data, as there is no guarantee the application can read
 * from all sources: the Centamter data is captured somewhat randomly. Defaults
 * to <em>true</em>.</dd>
 * 
 * <dt>collectAllSourceIdsTimeout</dt>
 * <dd>When {@code collectAllSourceIds} is configured as <em>true</em> this is a
 * timeout value, in seconds, the application should spend attempting to collect
 * data from all configured sources. If this amount of time is passed before
 * data for all sources has been collected, the application will give up and
 * just return whatever data it has collected at that point. Defaults to
 * {@link #DEFAULT_COLLECT_ALL_SOURCE_IDS_TIMEOUT}.</dd>
 * </dl>
 * 
 * @author matt
 * @version 1.1
 */
public class CentameterSupport {

    /** The data byte index for the Centameter's address ID. */
    public static final int CENTAMETER_ADDRESS_IDX = 2;

    /**
     * The data byte index for the Centameter's amp reading, as integer (amps *
     * 10) value.
     */
    public static final int CENTAMETER_AMPS_IDX = 7;

    /** The default value for the {@code voltage} property. */
    public static final float DEFAULT_VOLTAGE = 230.0F;

    /** The default value for the {@code sourceIdFormat} property. */
    public static final String DEFAULT_SOURCE_ID_FORMAT = "%X.%d";

    /** The default value for the {@code collectAllSourceIdsTimeout} property. */
    public static final int DEFAULT_COLLECT_ALL_SOURCE_IDS_TIMEOUT = 30;

    /** The default value for the {@code multiAmpSensorIndexFlags} property. */
    public static final int DEFAULT_MULTI_AMP_SENSOR_INDEX_FLAGS = (1 | 2 | 4);

    /** The default value for the {@code ampSensorIndex} property. */
    public static final int DEFAULT_AMP_SENSOR_INDEX = 1;

    /** A class-level logger. */
    protected final Logger log = LoggerFactory.getLogger(getClass());

    private static final Object MONITOR = new Object();
    private static MessageSource MESSAGE_SOURCE;

    private DynamicServiceTracker<DataCollectorFactory<DataCollectorSerialPortBeanParameters>> dataCollectorFactory;
    private DataCollectorSerialPortBeanParameters serialParams = getDefaultSerialParams();

    private float voltage = DEFAULT_VOLTAGE;
    private int ampSensorIndex = DEFAULT_AMP_SENSOR_INDEX;
    private int multiAmpSensorIndexFlags = DEFAULT_MULTI_AMP_SENSOR_INDEX_FLAGS;
    private String sourceIdFormat = DEFAULT_SOURCE_ID_FORMAT;
    private Map<String, String> addressSourceMapping = null;
    private Set<String> sourceIdFilter = null;
    private boolean collectAllSourceIds = true;
    private int collectAllSourceIdsTimeout = DEFAULT_COLLECT_ALL_SOURCE_IDS_TIMEOUT;
    private final SortedSet<CentameterDatum> knownAddresses = new ConcurrentSkipListSet<CentameterDatum>();
    private String uid = null;
    private String groupUID = null;

    protected static final DataCollectorSerialPortBeanParameters getDefaultSerialParams() {
        DataCollectorSerialPortBeanParameters defaults = new DataCollectorSerialPortBeanParameters();
        defaults.setMagic(new byte[] { 120 });
        defaults.setBaud(4800);
        defaults.setBufferSize(16);
        defaults.setReadSize(15);
        defaults.setReceiveThreshold(-1);
        defaults.setMaxWait(90000);
        return defaults;
    }

    /**
     * Add a new cached "known" address value.
     * 
     * <p>
     * This adds the address to the cached set of <em>known</em> addresses,
     * which are shown as a read-only setting property to aid in mapping the
     * right Cent-a-meter address.
     * </p>
     * 
     * @param datum
     *        the datum to add
     */
    protected void addKnownAddress(CentameterDatum datum) {
        knownAddresses.add(datum);
    }

    /**
     * Get a read-only set of known addresses.
     * 
     * <p>
     * This will contain all the addresses previously passed to
     * {@link #addKnownAddress(String)} and that have not been removed via
     * {@link #clearKnownAddresses(Collection)}.
     * </p>
     * 
     * @return a read-only set of known addresses
     */
    protected SortedSet<CentameterDatum> getKnownAddresses() {
        return Collections.unmodifiableSortedSet(knownAddresses);
    }

    /**
     * Remove known address values from the known address cache.
     * 
     * <p>
     * You can clear out the entire cache by passing in the result of
     * {@link #getKnownAddresses()}.
     * </p>
     * 
     * @param toRemove
     *        the collection of addresses to remove
     */
    protected void clearKnownAddresses(Collection<CentameterDatum> toRemove) {
        knownAddresses.removeAll(toRemove);
    }

    /**
     * Set a {@code addressSourceMapping} Map via an encoded String value.
     * 
     * <p>
     * The format of the {@code mapping} String should be:
     * </p>
     * 
     * <pre>
     * key=val[,key=val,...]
     * </pre>
     * 
     * <p>
     * Whitespace is permitted around all delimiters, and will be stripped from
     * the keys and values.
     * </p>
     * 
     * @param mapping
     */
    public void setAddressSourceMappingValue(String mapping) {
        if (mapping == null || mapping.length() < 1) {
            setAddressSourceMapping(null);
            return;
        }
        String[] pairs = mapping.split("\\s*,\\s*");
        Map<String, String> map = new LinkedHashMap<String, String>();
        for (String pair : pairs) {
            String[] kv = pair.split("\\s*=\\s*");
            if (kv == null || kv.length != 2) {
                continue;
            }
            map.put(kv[0], kv[1]);
        }
        setAddressSourceMapping(map);
    }

    /**
     * Set a {@link sourceIdFilter} List via an encoded String value.
     * 
     * <p>
     * The format of the {@code filters} String should be a comma-delimited list
     * of values. Whitespace is permitted around the commas, and will be
     * stripped from the values.
     * </p>
     * 
     * @param filters
     */
    public void setSourceIdFilterValue(String filters) {
        if (filters == null || filters.length() < 1) {
            setSourceIdFilter(null);
            return;
        }
        String[] data = filters.split("\\s*,\\s*");
        Set<String> s = new LinkedHashSet<String>(data.length);
        for (String d : data) {
            s.add(d);
        }
        setSourceIdFilter(s);
    }

    public List<SettingSpecifier> getDefaultSettingSpecifiers() {
        List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(20);
        StringBuilder status = new StringBuilder();
        for (CentameterDatum datum : knownAddresses) {
            if (status.length() > 0) {
                status.append(",\n");
            }
            status.append(datum.getStatusMessage());
        }
        results.add(new BasicTitleSettingSpecifier("knownAddresses", status.toString(), true));
        results.add(
                new BasicTextFieldSettingSpecifier("dataCollectorFactory.propertyFilters['UID']", "/dev/ttyUSB0"));
        results.add(new BasicTextFieldSettingSpecifier("uid", null));
        results.add(new BasicTextFieldSettingSpecifier("groupUID", null));
        results.add(new BasicTextFieldSettingSpecifier("voltage", String.valueOf(DEFAULT_VOLTAGE)));
        // the multiAmpSensorIndexFlags override this settings, so let's not expose it
        // results.add(new BasicTextFieldSettingSpecifier("ampSensorIndex", 
        //      String.valueOf(DEFAULT_AMP_SENSOR_INDEX)));
        results.add(new BasicTextFieldSettingSpecifier("multiAmpSensorIndexFlags",
                String.valueOf(DEFAULT_MULTI_AMP_SENSOR_INDEX_FLAGS)));
        results.add(new BasicTextFieldSettingSpecifier("sourceIdFormat", DEFAULT_SOURCE_ID_FORMAT));
        results.add(new BasicTextFieldSettingSpecifier("addressSourceMappingValue", ""));
        results.add(new BasicTextFieldSettingSpecifier("sourceIdFilterValue", ""));
        results.add(new BasicToggleSettingSpecifier("collectAllSourceIds", Boolean.TRUE));
        results.add(new BasicTextFieldSettingSpecifier("collectAllSourceIdsTimeout",
                String.valueOf(DEFAULT_COLLECT_ALL_SOURCE_IDS_TIMEOUT)));
        results.addAll(DataCollectorSerialPortBeanParameters.getDefaultSettingSpecifiers(getDefaultSerialParams(),
                "serialParams."));
        return results;
    }

    public MessageSource getDefaultSettingsMessageSource() {
        synchronized (MONITOR) {
            if (MESSAGE_SOURCE == null) {
                ResourceBundleMessageSource serial = new ResourceBundleMessageSource();
                serial.setBundleClassLoader(SerialPortBeanParameters.class.getClassLoader());
                serial.setBasenames(new String[] { SerialPortBeanParameters.class.getName(),
                        DataCollectorSerialPortBeanParameters.class.getName() });

                PrefixedMessageSource serialSource = new PrefixedMessageSource();
                serialSource.setDelegate(serial);
                serialSource.setPrefix("serialParams.");

                ResourceBundleMessageSource source = new ResourceBundleMessageSource();
                source.setBundleClassLoader(CentameterSupport.class.getClassLoader());
                source.setBasename(CentameterSupport.class.getName());
                source.setParentMessageSource(serialSource);
                MESSAGE_SOURCE = source;
            }
        }
        return MESSAGE_SOURCE;
    }

    public DynamicServiceTracker<DataCollectorFactory<DataCollectorSerialPortBeanParameters>> getDataCollectorFactory() {
        return dataCollectorFactory;
    }

    public void setDataCollectorFactory(
            DynamicServiceTracker<DataCollectorFactory<DataCollectorSerialPortBeanParameters>> dataCollectorFactory) {
        this.dataCollectorFactory = dataCollectorFactory;
    }

    public DataCollectorSerialPortBeanParameters getSerialParams() {
        return serialParams;
    }

    public void setSerialParams(DataCollectorSerialPortBeanParameters serialParams) {
        this.serialParams = serialParams;
    }

    public float getVoltage() {
        return voltage;
    }

    public void setVoltage(float voltage) {
        this.voltage = voltage;
    }

    public int getAmpSensorIndex() {
        return ampSensorIndex;
    }

    public void setAmpSensorIndex(int ampSensorIndex) {
        this.ampSensorIndex = ampSensorIndex;
    }

    public int getMultiAmpSensorIndexFlags() {
        return multiAmpSensorIndexFlags;
    }

    public void setMultiAmpSensorIndexFlags(int multiAmpSensorIndexFlags) {
        this.multiAmpSensorIndexFlags = multiAmpSensorIndexFlags;
    }

    public String getSourceIdFormat() {
        return sourceIdFormat;
    }

    public void setSourceIdFormat(String sourceIdFormat) {
        this.sourceIdFormat = sourceIdFormat;
    }

    public Map<String, String> getAddressSourceMapping() {
        return addressSourceMapping;
    }

    public void setAddressSourceMapping(Map<String, String> addressSourceMapping) {
        this.addressSourceMapping = addressSourceMapping;
    }

    public Set<String> getSourceIdFilter() {
        return sourceIdFilter;
    }

    public void setSourceIdFilter(Set<String> sourceIdFilter) {
        this.sourceIdFilter = sourceIdFilter;
    }

    public boolean isCollectAllSourceIds() {
        return collectAllSourceIds;
    }

    public void setCollectAllSourceIds(boolean collectAllSourceIds) {
        this.collectAllSourceIds = collectAllSourceIds;
    }

    public int getCollectAllSourceIdsTimeout() {
        return collectAllSourceIdsTimeout;
    }

    public void setCollectAllSourceIdsTimeout(int collectAllSourceIdsTimeout) {
        this.collectAllSourceIdsTimeout = collectAllSourceIdsTimeout;
    }

    public String getUID() {
        return getUid();
    }

    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;
    }

}