/* ==================================================================
* EM5600ConsumptionDatumDataSource.java - Mar 26, 2014 10:13:12 AM
*
* Copyright 2007-2014 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.datum.hc.em5600;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import net.solarnetwork.domain.GeneralDatumMetadata;
import net.solarnetwork.node.DatumDataSource;
import net.solarnetwork.node.MultiDatumDataSource;
import net.solarnetwork.node.domain.ACEnergyDatum;
import net.solarnetwork.node.domain.ACPhase;
import net.solarnetwork.node.domain.GeneralNodeACEnergyDatum;
import net.solarnetwork.node.hw.hc.EM5600Data;
import net.solarnetwork.node.hw.hc.EM5600Support;
import net.solarnetwork.node.io.modbus.ModbusConnection;
import net.solarnetwork.node.io.modbus.ModbusConnectionAction;
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.BasicToggleSettingSpecifier;
import org.springframework.context.MessageSource;
/**
* {@link DatumDataSource} implementation for {@link ConsumptionDatum} with the
* EM5600 series watt meter.
*
* <p>
* The configurable properties of this class are:
* </p>
*
* <dl class="class-properties">
* <dt>messageSource</dt>
* <dd>The {@link MessageSource} to use with {@link SettingSpecifierProvider}.</dd>
*
* <dt>sampleCacheMs</dt>
* <dd>The maximum number of milliseconds to cache data read from the meter,
* until the data will be read from the meter again.</dd>
*
* <dt>tagConsumption</dt>
* <dd>If {@link #getDatumMetadataService()} is available, then tag the
* configured source with
* </dl>
*
* @author matt
* @version 1.2
*/
public class EM5600ConsumptionDatumDataSource extends EM5600Support implements
DatumDataSource<GeneralNodeACEnergyDatum>, MultiDatumDataSource<GeneralNodeACEnergyDatum>,
SettingSpecifierProvider {
private static final long MIN_TIME_READ_ENERGY_RATIOS = 1000L * 60L * 60L; // 1 hour
private MessageSource messageSource;
private long sampleCacheMs = 5000;
private boolean tagConsumption = true;
private long energyRatioReadTime = 0;
private EM5600Data getCurrentSample() {
EM5600Data currSample;
if ( isCachedSampleExpired() ) {
try {
currSample = performAction(new ModbusConnectionAction<EM5600Data>() {
@Override
public EM5600Data doWithConnection(ModbusConnection conn) throws IOException {
if ( getUnitFactor() == null ) {
Integer model = getMeterModel(conn);
log.debug("Found meter model {}", model);
}
final long lastReadDiff = System.currentTimeMillis() - energyRatioReadTime;
if ( lastReadDiff > MIN_TIME_READ_ENERGY_RATIOS ) {
sample.readEnergyRatios(conn);
log.info("Refreshed energy ratios from meter: PT {} CT {}",
sample.getPtRatio(), sample.getCtRatio());
energyRatioReadTime = System.currentTimeMillis();
}
sample.readMeterData(conn);
return new EM5600Data(sample);
}
});
if ( log.isTraceEnabled() ) {
log.trace(currSample.dataDebugString());
}
log.debug("Read EM5600 data: {}", currSample);
} catch ( IOException e ) {
throw new RuntimeException("Communication problem reading from Modbus device "
+ modbusNetwork(), e);
}
} else {
currSample = new EM5600Data(sample);
}
return currSample;
}
private boolean isCachedSampleExpired() {
final long lastReadDiff = System.currentTimeMillis() - sample.getDataTimestamp();
if ( lastReadDiff > sampleCacheMs ) {
return true;
}
return false;
}
@Override
public Class<? extends GeneralNodeACEnergyDatum> getDatumType() {
return EM5600ConsumptionDatum.class;
}
@Override
public GeneralNodeACEnergyDatum readCurrentDatum() {
final long start = System.currentTimeMillis();
final EM5600Data currSample = getCurrentSample();
EM5600ConsumptionDatum d = new EM5600ConsumptionDatum(currSample, ACPhase.Total);
d.setSourceId(getSourceMapping().get(ACPhase.Total));
if ( currSample.getDataTimestamp() >= start ) {
// we read from the meter
postDatumCapturedEvent(d, ACEnergyDatum.class);
addEnergyDatumSourceMetadata(d);
}
return d;
}
@Override
public Class<? extends GeneralNodeACEnergyDatum> getMultiDatumType() {
return EM5600ConsumptionDatum.class;
}
@Override
public Collection<GeneralNodeACEnergyDatum> readMultipleDatum() {
final long start = System.currentTimeMillis();
final EM5600Data currSample = getCurrentSample();
final List<GeneralNodeACEnergyDatum> results = new ArrayList<GeneralNodeACEnergyDatum>(4);
final List<EM5600ConsumptionDatum> capturedResults = new ArrayList<EM5600ConsumptionDatum>(4);
if ( currSample == null ) {
return results;
}
final boolean postCapturedEvent = (currSample.getDataTimestamp() >= start);
if ( isCaptureTotal() || postCapturedEvent ) {
EM5600ConsumptionDatum d = new EM5600ConsumptionDatum(currSample, ACPhase.Total);
d.setSourceId(getSourceMapping().get(ACPhase.Total));
if ( postCapturedEvent ) {
// we read from the meter
postDatumCapturedEvent(d, ACEnergyDatum.class);
}
if ( isCaptureTotal() ) {
results.add(d);
if ( postCapturedEvent ) {
capturedResults.add(d);
}
}
}
if ( isCapturePhaseA() || postCapturedEvent ) {
EM5600ConsumptionDatum d = new EM5600ConsumptionDatum(currSample, ACPhase.PhaseA);
d.setSourceId(getSourceMapping().get(ACPhase.PhaseA));
if ( postCapturedEvent ) {
// we read from the meter
postDatumCapturedEvent(d, ACEnergyDatum.class);
}
if ( isCapturePhaseA() ) {
results.add(d);
if ( postCapturedEvent ) {
capturedResults.add(d);
}
}
}
if ( isCapturePhaseB() || postCapturedEvent ) {
EM5600ConsumptionDatum d = new EM5600ConsumptionDatum(currSample, ACPhase.PhaseB);
d.setSourceId(getSourceMapping().get(ACPhase.PhaseB));
if ( postCapturedEvent ) {
// we read from the meter
postDatumCapturedEvent(d, ACEnergyDatum.class);
}
if ( isCapturePhaseB() ) {
results.add(d);
if ( postCapturedEvent ) {
capturedResults.add(d);
}
}
}
if ( isCapturePhaseC() || postCapturedEvent ) {
EM5600ConsumptionDatum d = new EM5600ConsumptionDatum(currSample, ACPhase.PhaseC);
d.setSourceId(getSourceMapping().get(ACPhase.PhaseC));
if ( postCapturedEvent ) {
// we read from the meter
postDatumCapturedEvent(d, ACEnergyDatum.class);
}
if ( isCapturePhaseC() ) {
results.add(d);
if ( postCapturedEvent ) {
capturedResults.add(d);
}
}
}
for ( EM5600ConsumptionDatum d : capturedResults ) {
addEnergyDatumSourceMetadata(d);
}
return results;
}
private void addEnergyDatumSourceMetadata(EM5600ConsumptionDatum d) {
// associate consumption/generation tags with this source
GeneralDatumMetadata sourceMeta = new GeneralDatumMetadata();
if ( isTagConsumption() ) {
sourceMeta.addTag(net.solarnetwork.node.domain.EnergyDatum.TAG_CONSUMPTION);
} else {
sourceMeta.addTag(net.solarnetwork.node.domain.EnergyDatum.TAG_GENERATION);
}
addSourceMetadata(d.getSourceId(), sourceMeta);
}
// SettingSpecifierProvider
@Override
public String getSettingUID() {
return "net.solarnetwork.node.datum.hc.em5600";
}
@Override
public String getDisplayName() {
return "EM5600 Series Meter";
}
@Override
public MessageSource getMessageSource() {
return messageSource;
}
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
EM5600ConsumptionDatumDataSource defaults = new EM5600ConsumptionDatumDataSource();
List<SettingSpecifier> results = super.getSettingSpecifiers();
SettingSpecifier energyTag = new BasicToggleSettingSpecifier("tagConsumption",
Boolean.valueOf(defaults.isTagConsumption()));
results.add(energyTag);
results.add(new BasicTextFieldSettingSpecifier("sampleCacheMs", String.valueOf(defaults
.getSampleCacheMs())));
return results;
}
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public long getSampleCacheMs() {
return sampleCacheMs;
}
public void setSampleCacheMs(long sampleCacheMs) {
this.sampleCacheMs = sampleCacheMs;
}
public boolean isTagConsumption() {
return tagConsumption;
}
public void setTagConsumption(boolean tagConsumption) {
this.tagConsumption = tagConsumption;
}
}