net.solarnetwork.node.power.impl.centameter.CentameterPowerDatumDataSource.java Source code

Java tutorial

Introduction

Here is the source code for net.solarnetwork.node.power.impl.centameter.CentameterPowerDatumDataSource.java

Source

/* ==================================================================
 * CentameterPowerDatumDataSource.java - Apr 25, 2010 12:57:01 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.power.impl.centameter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.solarnetwork.node.DataCollector;
import net.solarnetwork.node.DataCollectorFactory;
import net.solarnetwork.node.DatumDataSource;
import net.solarnetwork.node.MultiDatumDataSource;
import net.solarnetwork.node.centameter.CentameterDatum;
import net.solarnetwork.node.centameter.CentameterSupport;
import net.solarnetwork.node.centameter.CentameterUtils;
import net.solarnetwork.node.power.PowerDatum;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.SettingSpecifierProvider;
import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier;
import net.solarnetwork.node.support.DataCollectorSerialPortBeanParameters;
import net.solarnetwork.node.util.ClassUtils;
import net.solarnetwork.node.util.DataUtils;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;

/**
 * Implementation of {@link DatumDataSource} {@link PowerDatum} objects, using a
 * Centameter amp sensor.
 * 
 * <p>
 * Normally Centameters are used to monitor consumption, but in some situations
 * they can be used as a low-cost montior for generation, especially if the
 * generation device cannot be communicated with.
 * </p>
 * 
 * <p>
 * This implementation relies on a device that can listen to the radio signal
 * broadcast by a Cent-a-meter monitor and write that data to a local serial
 * port. This class will read the Cent-a-meter data from the serial port to
 * generate consumption data.
 * </p>
 * 
 * <p>
 * It assumes the {@link DataCollector} implementation blocks until appropriate
 * data is available when the {@link DataCollector#collectData()} method is
 * called.
 * </p>
 * 
 * <p>
 * Serial parameters that are known to work are:
 * </p>
 * 
 * <pre>
 * magicBytes = x (0x78)
 * baud = 4800
 * bufferSize = 16
 * readSize = 15
 * receiveThreshold = -1
 * maxWait = 60000
 * </pre>
 * 
 * <p>
 * The configurable properties of this class are:
 * </p>
 * 
 * <dl class="class-properties">
 * <dt>ampsFieldName</dt>
 * <dd>The bean property on {@link PowerDatum} to set the amp reading value
 * collected from the Centameter. Defaults to {@link #DEFAULT_AMPS_FIELD_NAME}.</dd>
 * 
 * <dt>voltsFieldName</dt>
 * <dd>The bean property on {@link PowerDatum} to set the {@code voltage} value.
 * Defaults to {@link #DEFAULT_AMPS_FIELD_NAME}.</dd>
 * </dl>
 * 
 * @author matt
 * @version 1.0
 */
public class CentameterPowerDatumDataSource extends CentameterSupport
        implements DatumDataSource<PowerDatum>, MultiDatumDataSource<PowerDatum>, SettingSpecifierProvider {

    /** The default value for the {@code ampsFieldName} property. */
    public static final String DEFAULT_AMPS_FIELD_NAME = "pvAmps";

    /** The default value for the {@code voltsFieldName} property. */
    public static final String DEFAULT_VOLTS_FIELD_NAME = "pvVolts";

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

    private String ampsFieldName = DEFAULT_AMPS_FIELD_NAME;
    private String voltsFieldName = DEFAULT_VOLTS_FIELD_NAME;

    @Override
    public Class<? extends PowerDatum> getDatumType() {
        return PowerDatum.class;
    }

    @Override
    public PowerDatum readCurrentDatum() {
        DataCollectorFactory<DataCollectorSerialPortBeanParameters> df = getDataCollectorFactory().service();
        if (df == null) {
            log.debug("No DataCollectorFactory available");
            return null;
        }

        DataCollector dataCollector = df.getDataCollectorInstance(getSerialParams());
        byte[] data = null;
        try {
            dataCollector.collectData();
            data = dataCollector.getCollectedData();
        } finally {
            if (dataCollector != null) {
                dataCollector.stopCollecting();
            }
        }

        if (data == null) {
            log.warn("Null serial data received, serial communications problem");
            return null;
        }

        return getPowerDatumInstance(DataUtils.getUnsignedValues(data), getAmpSensorIndex());
    }

    @Override
    public Class<? extends PowerDatum> getMultiDatumType() {
        return PowerDatum.class;
    }

    @Override
    public Collection<PowerDatum> readMultipleDatum() {
        DataCollectorFactory<DataCollectorSerialPortBeanParameters> df = getDataCollectorFactory().service();
        if (df == null) {
            return null;
        }

        List<PowerDatum> result = new ArrayList<PowerDatum>(3);
        long endTime = isCollectAllSourceIds() && getSourceIdFilter().size() > 1
                ? System.currentTimeMillis() + (getCollectAllSourceIdsTimeout() * 1000)
                : 0;
        Set<String> sourceIdSet = new HashSet<String>(getSourceIdFilter().size());
        DataCollector dataCollector = null;
        try {
            dataCollector = df.getDataCollectorInstance(getSerialParams());
            do {
                dataCollector.collectData();
                byte[] data = dataCollector.getCollectedData();
                if (data == null) {
                    log.warn("Null serial data received, serial communications problem");
                    return null;
                }
                short[] unsigned = DataUtils.getUnsignedValues(data);

                // add a known address for this reading
                addKnownAddress(new CentameterDatum(String.format("%X", unsigned[CENTAMETER_ADDRESS_IDX]),
                        (float) CentameterUtils.getAmpReading(unsigned, 1),
                        (float) CentameterUtils.getAmpReading(unsigned, 2),
                        (float) CentameterUtils.getAmpReading(unsigned, 3)));

                if (log.isDebugEnabled()) {
                    log.debug(String.format("Centameter address %X, count %d, amp1 %.1f, amp2 %.1f, amp3 %.1f",
                            unsigned[CENTAMETER_ADDRESS_IDX], (unsigned[1] & 0xF),
                            CentameterUtils.getAmpReading(unsigned, 1), CentameterUtils.getAmpReading(unsigned, 2),
                            CentameterUtils.getAmpReading(unsigned, 3)));
                }

                for (int ampIndex = 1; ampIndex <= 3; ampIndex++) {
                    PowerDatum datum = getPowerDatumInstance(unsigned, ampIndex);
                    if ((ampIndex & getMultiAmpSensorIndexFlags()) != ampIndex) {
                        continue;
                    }
                    if (datum != null) {
                        if (!sourceIdSet.contains(datum.getSourceId())) {
                            result.add(datum);
                            sourceIdSet.add(datum.getSourceId());
                        }
                    }
                }
            } while (System.currentTimeMillis() < endTime && sourceIdSet.size() < getSourceIdFilter().size());
        } finally {
            if (dataCollector != null) {
                dataCollector.stopCollecting();
            }
        }

        return result.size() < 1 ? null : result;
    }

    private PowerDatum getPowerDatumInstance(short[] unsigned, int ampIndex) {
        // report the Centameter address as upper-case hex value
        String addr = String.format(getSourceIdFormat(), unsigned[CENTAMETER_ADDRESS_IDX], ampIndex);
        float amps = (float) CentameterUtils.getAmpReading(unsigned, ampIndex);

        PowerDatum datum = new PowerDatum();

        if (getAddressSourceMapping() != null && getAddressSourceMapping().containsKey(addr)) {
            addr = getAddressSourceMapping().get(addr);
        }
        if (getSourceIdFilter() != null && !getSourceIdFilter().contains(addr)) {
            if (log.isInfoEnabled()) {
                log.info("Rejecting source [" + addr + "] not in source ID filter set");
            }
            return null;
        }
        datum.setSourceId(addr);

        datum.setCreated(new Date());

        Map<String, Object> props = new HashMap<String, Object>();
        props.put(ampsFieldName, amps);
        props.put(voltsFieldName, getVoltage());
        ClassUtils.setBeanProperties(datum, props);

        return datum;
    }

    @Override
    public String getSettingUID() {
        return "net.solarnetwork.node.power.centameter";
    }

    @Override
    public String getDisplayName() {
        return "Cent-a-meter power meter";
    }

    @Override
    public MessageSource getMessageSource() {
        synchronized (MONITOR) {
            if (MESSAGE_SOURCE == null) {
                MessageSource parent = getDefaultSettingsMessageSource();

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

    @Override
    public List<SettingSpecifier> getSettingSpecifiers() {
        List<SettingSpecifier> results = getDefaultSettingSpecifiers();
        results.add(new BasicTextFieldSettingSpecifier("ampsFieldName", DEFAULT_AMPS_FIELD_NAME));
        results.add(new BasicTextFieldSettingSpecifier("voltsFieldName", DEFAULT_VOLTS_FIELD_NAME));
        return results;
    }

    public String getAmpsFieldName() {
        return ampsFieldName;
    }

    public void setAmpsFieldName(String ampsFieldName) {
        this.ampsFieldName = ampsFieldName;
    }

    public String getVoltsFieldName() {
        return voltsFieldName;
    }

    public void setVoltsFieldName(String voltsFieldName) {
        this.voltsFieldName = voltsFieldName;
    }

}