net.solarnetwork.node.consumption.impl.CentameterConsumptionDatumDataSource.java Source code

Java tutorial

Introduction

Here is the source code for net.solarnetwork.node.consumption.impl.CentameterConsumptionDatumDataSource.java

Source

/* ===================================================================
 * CentameterConsumptionDatumDataSource.java
 * 
 * Created Jul 23, 2009 9:18:41 AM
 * 
 * Copyright (c) 2009 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.consumption.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
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.consumption.ConsumptionDatum;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.SettingSpecifierProvider;
import net.solarnetwork.node.support.DataCollectorSerialPortBeanParameters;
import net.solarnetwork.node.util.DataUtils;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;

/**
 * {@link ConsumptionDataSource} implementation for Cent-a-meter monitors,
 * reading data via a serial port.
 * 
 * <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>
 * 
 * @author matt
 * @version 1.1
 */
public class CentameterConsumptionDatumDataSource extends CentameterSupport implements
        DatumDataSource<ConsumptionDatum>, MultiDatumDataSource<ConsumptionDatum>, SettingSpecifierProvider {

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

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

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

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

        if (data == null || data.length == 0) {
            log.warn("No serial data received, serial communications problem");
            return null;
        }

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

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

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

        List<ConsumptionDatum> result = new ArrayList<ConsumptionDatum>(3);
        long endTime = isCollectAllSourceIds() && getSourceIdFilter() != null && getSourceIdFilter().size() > 1
                ? System.currentTimeMillis() + (getCollectAllSourceIdsTimeout() * 1000)
                : 0;
        Set<String> sourceIdSet = new HashSet<String>(getSourceIdFilter() == null ? 0 : getSourceIdFilter().size());
        DataCollector dc = null;
        try {
            dc = df.getDataCollectorInstance(getSerialParams());
            do {
                dc.collectData();
                byte[] data = dc.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++) {
                    ConsumptionDatum datum = getConsumptionDatumInstance(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() == null ? 0 : getSourceIdFilter().size()));
        } finally {
            if (dc != null) {
                dc.stopCollecting();
            }
        }

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

    private ConsumptionDatum getConsumptionDatumInstance(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);

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

        ConsumptionDatum datum = new ConsumptionDatum(addr, amps, getVoltage());
        datum.setCreated(new Date());
        return datum;
    }

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

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

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

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

    @Override
    public List<SettingSpecifier> getSettingSpecifiers() {
        return getDefaultSettingSpecifiers();
    }

}