net.solarnetwork.node.power.sma.yasdi4j.SMAyasdi4jPowerDatumDataSource.java Source code

Java tutorial

Introduction

Here is the source code for net.solarnetwork.node.power.sma.yasdi4j.SMAyasdi4jPowerDatumDataSource.java

Source

/* ==================================================================
 * SMAyasdi4jPowerDatumDataSource.java - Mar 7, 2013 11:57:06 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.power.sma.yasdi4j;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.solarnetwork.domain.GeneralDatumMetadata;
import net.solarnetwork.node.DatumDataSource;
import net.solarnetwork.node.dao.SettingDao;
import net.solarnetwork.node.domain.ACEnergyDatum;
import net.solarnetwork.node.domain.GeneralNodeACEnergyDatum;
import net.solarnetwork.node.hw.sma.SMAInverterDataSourceSupport;
import net.solarnetwork.node.io.yasdi4j.YasdiMaster;
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.util.DynamicServiceTracker;
import net.solarnetwork.util.StringUtils;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.MessageSource;
import de.michaeldenk.yasdi4j.YasdiChannel;
import de.michaeldenk.yasdi4j.YasdiDevice;

/**
 * SMA {@link DatumDataSource} for {@link PowerDatum}, using the {@code yasdi4j}
 * library.
 * 
 * <p>
 * This class is not generally not thread-safe. Only one thread should execute
 * {@link #readCurrentDatum()} at a time.
 * </p>
 * 
 * <p>
 * The configurable properties of this class are:
 * </p>
 * 
 * <dl class="class-properties">
 * <dt>yasdi</dt>
 * <dd>The dynamic service for the {@link YasdiMaster} instance to use.</dd>
 * 
 * <dt>settingDao</dt>
 * <dd>The {@link SettingDao} to use, required by the
 * {@code channelNamesToResetDaily} property.</dd>
 * </dl>
 * 
 * @author matt
 * @version 2.0
 */
public class SMAyasdi4jPowerDatumDataSource extends SMAInverterDataSourceSupport
        implements DatumDataSource<GeneralNodeACEnergyDatum>, SettingSpecifierProvider {

    /** The default value for the {@code sourceId} property. */
    public static final String DEFAULT_SOURCE_ID = "Main";

    /** The watts output channel name. */
    public static final String CHANNEL_NAME_WATTS = "Pac";

    /** The PV current channel name. */
    public static final String CHANNEL_NAME_PV_AMPS = "Ipv";

    /** The PV voltage channel name. */
    public static final String CHANNEL_NAME_PV_VOLTS = "Upv-Ist";

    /** The accumulative kWh channel name. */
    public static final String CHANNEL_NAME_KWH = "E-Total";

    /** The default device serial number value. */
    public static final Long DEFAULT_SERIAL_NUMBER = 1000L;

    /**
     * Default value for the {@code channelNamesToMonitor} property.
     * 
     * <p>
     * Contains the PV voltage, PV current, and kWh channels.
     * </p>
     */
    public static final Set<String> DEFAULT_CHANNEL_NAMES_TO_MONITOR = Collections
            .unmodifiableSet(new LinkedHashSet<String>(
                    Arrays.asList(CHANNEL_NAME_PV_AMPS, CHANNEL_NAME_PV_VOLTS, CHANNEL_NAME_KWH)));

    private String pvVoltsChannelName = CHANNEL_NAME_PV_VOLTS;
    private String pvAmpsChannelName = CHANNEL_NAME_PV_AMPS;
    private Set<String> pvWattsChannelNames = Collections.singleton(CHANNEL_NAME_WATTS);
    private String kWhChannelName = CHANNEL_NAME_KWH;
    private Set<String> otherChannelNames = null;
    private int channelMaxAgeSeconds = 30;
    private long deviceSerialNumber = DEFAULT_SERIAL_NUMBER;
    private long deviceLockTimeoutSeconds = 20;

    private DynamicServiceTracker<ObjectFactory<YasdiMaster>> yasdi;
    private MessageSource messageSource;

    public SMAyasdi4jPowerDatumDataSource() {
        super();
        setChannelNamesToMonitor(DEFAULT_CHANNEL_NAMES_TO_MONITOR);
    }

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

    private YasdiDevice getYasdiDevice() {
        final ObjectFactory<YasdiMaster> service = yasdi.service();
        if (service == null) {
            log.debug("No YASDI service available.");
            return null;
        }
        final YasdiMaster master = service.getObject();
        YasdiDevice device = master.getDevice(this.deviceSerialNumber);
        if (device == null) {
            log.info("YASDI device {} not available", this.deviceSerialNumber);
        }
        return device;
    }

    private void releaseYasdiDevice(YasdiDevice device) {
        if (device == null) {
            return;
        }
        final ObjectFactory<YasdiMaster> service = yasdi.service();
        if (service == null) {
            log.debug("No YASDI service available.");
            return;
        }
        final YasdiMaster master = service.getObject();
        master.releaseDeviceLock(device);
    }

    @Override
    public GeneralNodeACEnergyDatum readCurrentDatum() {
        YasdiDevice device = null;
        final SMAPowerDatum datum = new SMAPowerDatum();
        datum.setCreated(new Date());
        try {
            device = getYasdiDevice();
            if (device == null) {
                return null;
            }

            datum.setSourceId(getSourceId());

            if (this.pvWattsChannelNames != null && this.pvWattsChannelNames.size() > 0) {
                // we sum up all channels into a single value
                int totalWatts = 0;
                for (String channelName : this.pvWattsChannelNames) {
                    Object v = captureChannelValue(device, channelName);
                    if (v instanceof Number) {
                        totalWatts += ((Number) v).intValue();
                    }
                }
                datum.setWatts(totalWatts);
            } else {
                Object volts = captureChannelValue(device, this.pvVoltsChannelName);
                Object amps = captureChannelValue(device, this.pvAmpsChannelName);
                if (volts instanceof Number && amps instanceof Number) {
                    datum.setWatts(((Number) volts).intValue() * ((Number) amps).intValue());
                }
            }
            Object wh = captureChannelValue(device, this.kWhChannelName);
            if (wh instanceof Number) {
                datum.setWattHourReading(((Number) wh).longValue());
            }

            if (otherChannelNames != null) {
                Map<String, Object> map = new LinkedHashMap<String, Object>(otherChannelNames.size());
                for (String channelName : otherChannelNames) {
                    captureDataValue(device, channelName, channelName, map);
                }
                if (map.size() > 0) {
                    if (datum.getChannelData() == null) {
                        datum.setChannelData(map);
                    } else {
                        datum.getChannelData().putAll(map);
                    }
                }
            }
        } finally {
            releaseYasdiDevice(device);
        }

        if (!isValidDatum(datum)) {
            log.debug("No valid data available.");
            return null;
        }

        postDatumCapturedEvent(datum, ACEnergyDatum.class);
        addEnergyDatumSourceMetadata(datum);

        return datum;
    }

    private void addEnergyDatumSourceMetadata(SMAPowerDatum d) {
        // associate generation tags with this source
        GeneralDatumMetadata sourceMeta = new GeneralDatumMetadata();
        sourceMeta.addTag(net.solarnetwork.node.domain.EnergyDatum.TAG_GENERATION);
        addSourceMetadata(d.getSourceId(), sourceMeta);
    }

    private boolean isValidDatum(SMAPowerDatum d) {
        if ((d.getWatts() == null || d.getWatts() < 1)
                && (d.getWattHourReading() == null || d.getWattHourReading() < 1)) {
            return false;
        }
        return true;
    }

    private Object captureChannelValue(YasdiDevice device, String channelName) {
        if (channelName == null || channelName.length() < 1) {
            return null;
        }
        log.trace("Reading SMA channel {}", channelName);
        YasdiChannel channel = device.getChannel(channelName);
        if (channel == null) {
            log.warn("Channel {} not available from YASDI device", channelName);
            return null;
        }
        try {
            // get updated value, at most channelMaxAgeSeconds old
            channel.updateValue(channelMaxAgeSeconds);
        } catch (IOException e) {
            log.debug("Exception updating channel {} value: {}", channelName, e.toString());
            return null;
        }
        Object value = null;
        if (channel.hasText()) {
            value = channel.getValueText();
        } else {
            double v = channel.getValue();
            String unit = channel.getUnit();
            if (unit != null) {
                if (unit.startsWith("m")) {
                    v /= 1000.0;
                } else if (unit.startsWith("k")) {
                    v *= 1000;
                }
            }
            value = Double.valueOf(v);
        }
        log.trace("Read SMA channel {}: {}", channelName, value);
        return value;
    }

    /**
     * Read a specific channel and set that value into a Map.
     * 
     * @param device
     *        the YasdiDevice to collect the data from
     * @param channelName
     *        the name of the channel to read
     * @param key
     *        the Map key to set with the data value
     * @param accessor
     *        the datum to update
     */
    private void captureDataValue(YasdiDevice device, String channelName, String key, Map<String, Object> map) {
        Object value = captureChannelValue(device, channelName);
        if (value != null) {
            map.put(key, value);
        }
    }

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

    @Override
    public String getDisplayName() {
        return "SMA inverter (YASDI)";
    }

    @Override
    public MessageSource getMessageSource() {
        return messageSource;
    }

    @Override
    public List<SettingSpecifier> getSettingSpecifiers() {
        List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(20);
        SMAyasdi4jPowerDatumDataSource defaults = new SMAyasdi4jPowerDatumDataSource();

        String yasdiDeviceName = "N/A";
        YasdiDevice device = null;
        try {
            device = getYasdiDevice();
            if (device != null) {
                yasdiDeviceName = device.getName();
            }
        } catch (RuntimeException e) {
            log.warn("Exception getting YASDI device name: {}", e.getMessage());
        } finally {
            releaseYasdiDevice(device);
        }
        results.add(new BasicTitleSettingSpecifier("address", yasdiDeviceName, true));

        results.add(new BasicTextFieldSettingSpecifier("deviceSerialNumber",
                String.valueOf(defaults.deviceSerialNumber)));

        results.add(new BasicTextFieldSettingSpecifier("sourceId", defaults.getSourceId()));
        results.add(new BasicTextFieldSettingSpecifier("groupUID", defaults.getGroupUID()));

        results.add(new BasicTextFieldSettingSpecifier("channelMaxAgeSeconds",
                String.valueOf(defaults.getChannelMaxAgeSeconds())));
        results.add(new BasicTextFieldSettingSpecifier("deviceLockTimeoutSeconds",
                String.valueOf(defaults.getDeviceLockTimeoutSeconds())));

        results.add(new BasicTextFieldSettingSpecifier("pvWattsChannelNamesValue",
                defaults.getPvWattsChannelNamesValue()));
        results.add(new BasicTextFieldSettingSpecifier("pvVoltsChannelName", defaults.getPvVoltsChannelName()));
        results.add(new BasicTextFieldSettingSpecifier("pvAmpsChannelName", defaults.getPvAmpsChannelName()));
        results.add(new BasicTextFieldSettingSpecifier("kWhChannelName", defaults.getkWhChannelName()));
        results.add(
                new BasicTextFieldSettingSpecifier("otherChannelNamesValue", defaults.getOtherChannelNamesValue()));

        return results;
    }

    private void setupChannelNamesToMonitor() {
        Set<String> s = new LinkedHashSet<String>(3);
        if (getPvWattsChannelNames() != null && getPvWattsChannelNames().size() > 0) {
            s.addAll(getPvWattsChannelNames());
        } else {
            if (getPvVoltsChannelName() != null) {
                s.add(getPvVoltsChannelName());
            }
            if (getPvAmpsChannelName() != null) {
                s.add(getPvAmpsChannelName());
            }
        }
        s.add(getkWhChannelName());
        if (otherChannelNames != null) {
            s.addAll(otherChannelNames);
        }

        if (!s.equals(this.getChannelNamesToMonitor())) {
            setChannelNamesToMonitor(s);
        }
    }

    public String getPvVoltsChannelName() {
        return pvVoltsChannelName;
    }

    public void setPvVoltsChannelName(String pvVoltsChannelName) {
        this.pvVoltsChannelName = pvVoltsChannelName;
        setupChannelNamesToMonitor();
    }

    public String getPvAmpsChannelName() {
        return pvAmpsChannelName;
    }

    public void setPvAmpsChannelName(String pvAmpsChannelName) {
        this.pvAmpsChannelName = pvAmpsChannelName;
        setupChannelNamesToMonitor();
    }

    public String getkWhChannelName() {
        return kWhChannelName;
    }

    public void setkWhChannelName(String kWhChannelName) {
        this.kWhChannelName = kWhChannelName;
        setupChannelNamesToMonitor();
    }

    public DynamicServiceTracker<ObjectFactory<YasdiMaster>> getYasdi() {
        return yasdi;
    }

    public void setYasdi(DynamicServiceTracker<ObjectFactory<YasdiMaster>> yasdi) {
        this.yasdi = yasdi;
    }

    public void setChannelMaxAgeSeconds(int channelMaxAgeSeconds) {
        this.channelMaxAgeSeconds = channelMaxAgeSeconds;
    }

    /**
     * Get {@code pvWattsChannelNames} as a comma-delimited string value.
     * 
     * @return the channel names, as a delimited string
     */
    public String getPvWattsChannelNamesValue() {
        return (pvWattsChannelNames == null ? null
                : StringUtils.commaDelimitedStringFromCollection(pvWattsChannelNames));
    }

    /**
     * Set {@code pvWattsChannelNames} as a comma-delimited string value.
     * 
     * @param value
     *        the channel names, as a delimited string
     */
    public void setPvWattsChannelNamesValue(String value) {
        setPvWattsChannelNames(StringUtils.commaDelimitedStringToSet(value));
    }

    public Set<String> getPvWattsChannelNames() {
        return pvWattsChannelNames;
    }

    public void setPvWattsChannelNames(Set<String> pvWattsChannelNames) {
        this.pvWattsChannelNames = pvWattsChannelNames;
        setupChannelNamesToMonitor();
    }

    public void setDeviceSerialNumber(long deviceSerialNumber) {
        this.deviceSerialNumber = deviceSerialNumber;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    public Set<String> getOtherChannelNames() {
        return otherChannelNames;
    }

    public void setOtherChannelNames(Set<String> otherChannelNames) {
        this.otherChannelNames = otherChannelNames;
    }

    /**
     * Get {@code otherChannelNames} as a comma-delimited string value.
     * 
     * @return the other channel names, as a delimited string
     */
    public String getOtherChannelNamesValue() {
        return (otherChannelNames == null ? null
                : StringUtils.commaDelimitedStringFromCollection(otherChannelNames));
    }

    /**
     * Set {@code otherChannelNames} as a comma-delimited string value.
     * 
     * @param value
     *        the channel names, as a delimited string
     */
    public void setOtherChannelNamesValue(String value) {
        setOtherChannelNames(StringUtils.commaDelimitedStringToSet(value));
    }

    public long getDeviceLockTimeoutSeconds() {
        return deviceLockTimeoutSeconds;
    }

    public void setDeviceLockTimeoutSeconds(long deviceLockTimeoutSeconds) {
        this.deviceLockTimeoutSeconds = deviceLockTimeoutSeconds;
    }

    public int getChannelMaxAgeSeconds() {
        return channelMaxAgeSeconds;
    }

}