/* ================================================================== * PM3200Data.java - Mar 30, 2014 1:44:23 PM * * 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.hw.schneider.meter; import gnu.trove.map.TIntIntMap; import gnu.trove.map.hash.TIntIntHashMap; import java.util.Arrays; import net.solarnetwork.node.io.modbus.ModbusConnection; import net.solarnetwork.node.io.modbus.ModbusHelper; /** * Encapsulates raw Modbus register data from the PM3200 meters. * * @author matt * @version 1.2 */ public class PM3200Data { // current (Float32) public static final int ADDR_DATA_I1 = 2999; public static final int ADDR_DATA_I2 = 3001; public static final int ADDR_DATA_I3 = 3003; public static final int ADDR_DATA_I_NEUTRAL = 3005; public static final int ADDR_DATA_I_AVERAGE = 3009; // voltage public static final int ADDR_DATA_V_L1_L2 = 3019; public static final int ADDR_DATA_V_L2_L3 = 3021; public static final int ADDR_DATA_V_L3_L1 = 3023; public static final int ADDR_DATA_V_L_L_AVERAGE = 3025; public static final int ADDR_DATA_V_L1_NEUTRAL = 3027; public static final int ADDR_DATA_V_L2_NEUTRAL = 3029; public static final int ADDR_DATA_V_L3_NEUTRAL = 3031; public static final int ADDR_DATA_V_NEUTRAL_AVERAGE = 3035; // power (Float32) public static final int ADDR_DATA_ACTIVE_POWER_P1 = 3053; public static final int ADDR_DATA_ACTIVE_POWER_P2 = 3055; public static final int ADDR_DATA_ACTIVE_POWER_P3 = 3057; public static final int ADDR_DATA_ACTIVE_POWER_TOTAL = 3059; public static final int ADDR_DATA_REACTIVE_POWER_P1 = 3061; public static final int ADDR_DATA_REACTIVE_POWER_P2 = 3063; public static final int ADDR_DATA_REACTIVE_POWER_P3 = 3065; public static final int ADDR_DATA_REACTIVE_POWER_TOTAL = 3067; public static final int ADDR_DATA_APPARENT_POWER_P1 = 3069; public static final int ADDR_DATA_APPARENT_POWER_P2 = 3071; public static final int ADDR_DATA_APPARENT_POWER_P3 = 3073; public static final int ADDR_DATA_APPARENT_POWER_TOTAL = 3075; // power factor (Float32) public static final int ADDR_DATA_POWER_FACTOR_P1 = 3077; public static final int ADDR_DATA_POWER_FACTOR_P2 = 3079; public static final int ADDR_DATA_POWER_FACTOR_P3 = 3081; public static final int ADDR_DATA_POWER_FACTOR_TOTAL = 3083; // tangent phi, frequency, temp (Float32) public static final int ADDR_DATA_REACTIVE_FACTOR_TOTAL = 3107; public static final int ADDR_DATA_FREQUENCY = 3109; public static final int ADDR_DATA_TEMP = 3131; // total energy (Int64) public static final int ADDR_DATA_ACTIVE_ENERGY_IMPORT_TOTAL = 3203; public static final int ADDR_DATA_ACTIVE_ENERGY_EXPORT_TOTAL = 3207; public static final int ADDR_DATA_REACTIVE_ENERGY_IMPORT_TOTAL = 3219; public static final int ADDR_DATA_REACTIVE_ENERGY_EXPORT_TOTAL = 3223; public static final int ADDR_DATA_APPARENT_ENERGY_IMPORT_TOTAL = 3235; public static final int ADDR_DATA_APPARENT_ENERGY_EXPORT_TOTAL = 3239; // total energy (Int64) - deprecated @Deprecated public static final int ADDR_DATA_TOTAL_ACTIVE_ENERGY_IMPORT = ADDR_DATA_ACTIVE_ENERGY_IMPORT_TOTAL; @Deprecated public static final int ADDR_DATA_TOTAL_ACTIVE_ENERGY_EXPORT = ADDR_DATA_ACTIVE_ENERGY_EXPORT_TOTAL; @Deprecated public static final int ADDR_DATA_TOTAL_REACTIVE_ENERGY_IMPORT = ADDR_DATA_REACTIVE_ENERGY_IMPORT_TOTAL; @Deprecated public static final int ADDR_DATA_TOTAL_REACTIVE_ENERGY_EXPORT = ADDR_DATA_REACTIVE_ENERGY_EXPORT_TOTAL; @Deprecated public static final int ADDR_DATA_TOTAL_APPARENT_ENERGY_IMPORT = ADDR_DATA_APPARENT_ENERGY_IMPORT_TOTAL; @Deprecated public static final int ADDR_DATA_TOTAL_APPARENT_ENERGY_EXPORT = ADDR_DATA_APPARENT_ENERGY_EXPORT_TOTAL; // total phase energy import (Int64) public static final int ADDR_DATA_ACTIVE_ENERGY_IMPORT_P1 = 3517; public static final int ADDR_DATA_ACTIVE_ENERGY_IMPORT_P2 = 3521; public static final int ADDR_DATA_ACTIVE_ENERGY_IMPORT_P3 = 3525; public static final int ADDR_DATA_REACTIVE_ENERGY_IMPORT_P1 = 3529; public static final int ADDR_DATA_REACTIVE_ENERGY_IMPORT_P2 = 3533; public static final int ADDR_DATA_REACTIVE_ENERGY_IMPORT_P3 = 3537; public static final int ADDR_DATA_APPARENT_ENERGY_IMPORT_P1 = 3541; public static final int ADDR_DATA_APPARENT_ENERGY_IMPORT_P2 = 3545; public static final int ADDR_DATA_APPARENT_ENERGY_IMPORT_P3 = 3549; private final TIntIntMap dataRegisters; private long dataTimestamp = 0; /** * Default constructor. */ public PM3200Data() { super(); this.dataRegisters = new TIntIntHashMap(64); } /** * Copy constructor. * * @param other * the object to copy */ public PM3200Data(PM3200Data other) { super(); this.dataRegisters = new TIntIntHashMap(other.dataRegisters); this.dataTimestamp = other.dataTimestamp; } @Override public String toString() { return "PM3200Data{V1=" + getVoltage(ADDR_DATA_V_L1_NEUTRAL) + ",V2=" + getVoltage(ADDR_DATA_V_L2_NEUTRAL) + ",V3=" + getVoltage(ADDR_DATA_V_L3_NEUTRAL) + ",A1=" + getCurrent(ADDR_DATA_I1) + ",A2=" + getCurrent(ADDR_DATA_I2) + ",A3=" + getCurrent(ADDR_DATA_I3) + ",PF=" + getPowerFactor(ADDR_DATA_REACTIVE_FACTOR_TOTAL) + ",Hz=" + getFrequency(ADDR_DATA_FREQUENCY) + ",W=" + getPower(ADDR_DATA_ACTIVE_POWER_TOTAL) + ",var=" + getPower(ADDR_DATA_REACTIVE_POWER_TOTAL) + ",VA=" + getPower(ADDR_DATA_APPARENT_POWER_TOTAL) + ",Wh-I=" + getEnergy(ADDR_DATA_ACTIVE_ENERGY_IMPORT_TOTAL) + ",varh-I=" + getEnergy(ADDR_DATA_REACTIVE_ENERGY_IMPORT_TOTAL) + ",Wh-E=" + getEnergy(ADDR_DATA_ACTIVE_ENERGY_EXPORT_TOTAL) + ",varh-E=" + getEnergy(ADDR_DATA_REACTIVE_ENERGY_EXPORT_TOTAL) + "}"; } /** * Get a string of data values, useful for debugging. The generated string * will contain a register address followed by two register values per line, * printed as hexidecimal integers, with a prefix and suffix line. The * register addresses will be printed as {@bold 1-based} values, to * match Schneider's documentation. For example: * * <pre> * PM3200Data{ * 3000: 0x4141, 0x727E * 3002: 0xFFC0, 0x0000 * ... * 3240: 0x0000, 0x0000 * } * </pre> * * @return debug string */ public String dataDebugString() { final StringBuilder buf = new StringBuilder("PM3200Data{\n"); PM3200Data snapshot = new PM3200Data(this); int[] keys = snapshot.dataRegisters.keys(); Arrays.sort(keys); boolean odd = true; for ( int k : keys ) { if ( odd ) { buf.append("\t").append(String.format("%5d", k + 1)).append(": "); } buf.append(String.format("0x%04X", snapshot.dataRegisters.get(k))); if ( odd ) { buf.append(", "); } else { buf.append("\n"); } odd = !odd; } buf.append("}"); return buf.toString(); } /** * Read data from the meter and store it internally. If data is populated * successfully, the {@link dataTimestamp} will be updated to the current * system time. <b>Note</b> this does <b>not</b> call * {@link #readEnergyRatios(SerialConnection, int)}. Those values are not * expected to change much, so those values should be called manually as * needed. * * @param conn * the Modbus connection */ public synchronized void readMeterData(final ModbusConnection conn) { // current readIntData(conn, ADDR_DATA_I1, ADDR_DATA_I_AVERAGE + 1); // voltage readIntData(conn, ADDR_DATA_V_L1_L2, ADDR_DATA_V_NEUTRAL_AVERAGE + 1); // power, power factor readIntData(conn, ADDR_DATA_ACTIVE_POWER_P1, ADDR_DATA_POWER_FACTOR_TOTAL + 1); // tangent phi, frequency, temp (Float32) readIntData(conn, ADDR_DATA_REACTIVE_FACTOR_TOTAL, ADDR_DATA_TEMP + 1); // total energy (Int64) readIntData(conn, ADDR_DATA_ACTIVE_ENERGY_IMPORT_TOTAL, ADDR_DATA_APPARENT_ENERGY_EXPORT_TOTAL + 3); // total phase energy import (Int64) readIntData(conn, ADDR_DATA_ACTIVE_ENERGY_IMPORT_P1, ADDR_DATA_APPARENT_ENERGY_IMPORT_P3 + 3); dataTimestamp = System.currentTimeMillis(); } private void readIntData(final ModbusConnection conn, final int startAddr, final int endAddr) { int[] data = conn.readInts(startAddr, (endAddr - startAddr + 1)); saveDataArray(data, startAddr); } /** * Internally store an array of integer register data values, starting at a * given address. * * @param data * the data array to save * @param addr * the starting address of the data */ protected void saveDataArray(final int[] data, int addr) { if ( data == null || data.length < 1 ) { return; } for ( int v : data ) { dataRegisters.put(addr, v); addr++; } } private Float getFloat32(final int addr) { return ModbusHelper.parseFloat32(dataRegisters.get(addr), dataRegisters.get(addr + 1)); } private Long getInt64(final int addr) { return ModbusHelper.parseInt64(dataRegisters.get(addr), dataRegisters.get(addr + 1), dataRegisters.get(addr + 2), dataRegisters.get(addr + 3)); } /** * Get an effective voltage value in V. * * @param addr * the register address to read * @return the value interpreted as a voltage value */ public Float getVoltage(final int addr) { return getFloat32(addr); } /** * Get an effective current value in A. * * @param addr * the register address to read * @return the value interpreted as a current value */ public Float getCurrent(int addr) { return getFloat32(addr); } /** * Get an effective frequency value in Hz. * * @param addr * the register address to read * @return the value interpreted as a frequency value */ public Float getFrequency(int addr) { return getFloat32(addr); } /** * Get an effective temperature value in C. * * @param addr * the register address to read * @return the value interpreted as a temperature value */ public Float getTemperature(int addr) { return getFloat32(addr); } /** * Get an effective power factor value. * * @param addr * the register address to read * @return the value interpreted as a power factor value */ public Float getPowerFactor(int addr) { return getFloat32(addr); } /** * Get the effective total power factor, in terms of cos(phi). The result * range is from -1 to 1. * * @return the effective power factor */ public Float getEffectiveTotalPowerFactor() { Float tangentPhi = getFloat32(ADDR_DATA_REACTIVE_FACTOR_TOTAL); if ( tangentPhi == null ) { return null; } float result = (float) (1.0 / Math.sqrt(1 + tangentPhi.doubleValue() * tangentPhi.doubleValue())); if ( tangentPhi.floatValue() < 0 ) { result = -result; } return result; } /** * Get an effective power value in W (active), Var (reactive) or VA * (apparent). * * @param addr * the register address to read * @return the value interpreted as a power value */ public Integer getPower(int addr) { Float kiloValue = getFloat32(addr); if ( kiloValue == null ) { return null; } return Integer.valueOf((int) Math.ceil(kiloValue.doubleValue() * 1000.0)); } /** * Get an effective energy value in Wh (real), Varh (reactive). * * @param addr * the register address to read * @return the value interpreted as an energy value */ public Long getEnergy(int addr) { return getInt64(addr); } public long getDataTimestamp() { return dataTimestamp; } }