/*
*
* Copyright (c) 2011-2013 Dario Bonino
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package tuwien.auto.calimero.dptxlator;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import javax.measure.quantity.Power;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import javax.measure.unit.UnitFormat;
import tuwien.auto.calimero.exception.KNXFormatException;
import tuwien.auto.calimero.exception.KNXIllegalArgumentException;
import tuwien.auto.calimero.log.LogLevel;
/**
* Translator for KNX DPTs with main number 13, type 4-byte signed.
*
* The KNX data type width is 4 bytes.
*
* This type is a two byte floating format with a maximum usable range of
* {@link Integer}.MIN_VALUE to +{@link Integer}.MAX_VALUE.
*
* This translator will check and enforce DPT specific limits in all methods
* working with java values (e.g. setValue(float)).
*
* Data methods for KNX data (e.g. DPTXlator.setData(byte[]) accept all data
* within the maximum usable range.
*
* In value methods expecting a string type, the value is a float type
* representation.
*
* The default return value after creation is 0.0.
*
* @author <a href="mailto:dario.bonino@polito.it">Dario Bonino </a>
*
*/
public class DPTXlator4ByteInteger extends DPTXlator
{
public static Unit<Power> VAR = SI.WATT.alternate("var");
public static Unit<Power> VA = SI.WATT.alternate("VA");
static
{
// create the var and va units
VAR.alternate("Var");
// add unit of measure aliases (to fix notation problems...)
UnitFormat uf = UnitFormat.getInstance();
uf.alias(SI.WATT.times(NonSI.HOUR), "Wh");
uf.label(SI.KILO(SI.WATT.times(NonSI.HOUR)), "kWh");
uf.alias(VAR.times(NonSI.HOUR), "Varh");
uf.label(SI.KILO(VAR.times(NonSI.HOUR)), "kVarh");
uf.alias(VA.times(NonSI.HOUR), "VAh");
uf.label(SI.KILO(VA.times(NonSI.HOUR)), "kVAh");
}
public static final DPT DPT_COUNTER = new DPT("13.001", "Counter pulses", "" + Integer.MIN_VALUE, ""
+ Integer.MAX_VALUE, Unit.ONE.toString());
public static final DPT DPT_ACTIVE_ENERGY = new DPT("13.010", "Active Energy", "" + Integer.MIN_VALUE, ""
+ Integer.MAX_VALUE, SI.WATT.times(NonSI.HOUR).toString());
public static final DPT DPT_APPARENT_ENERGY = new DPT("13.011", "Apparent Energy", "" + Integer.MIN_VALUE, ""
+ Integer.MAX_VALUE, VA.times(NonSI.HOUR).toString());
public static final DPT DPT_REACTIVE_ENERGY = new DPT("13.012", "Reactive Energy", "" + Integer.MIN_VALUE, ""
+ Integer.MAX_VALUE, VAR.times(NonSI.HOUR).toString());
public static final DPT DPT_ACTIVE_ENERGY_K = new DPT("13.013", "Active Energy", "" + Integer.MIN_VALUE, ""
+ Integer.MAX_VALUE, SI.KILO(SI.WATT.times(NonSI.HOUR)).toString());
public static final DPT DPT_APPARENT_ENERGY_K = new DPT("13.014", "Apparent Energy", "" + Integer.MIN_VALUE, ""
+ Integer.MAX_VALUE, SI.KILO(VA.times(NonSI.HOUR)).toString());
public static final DPT DPT_REACTIVE_ENERGY_K = new DPT("13.015", "Reactive Energy", "" + Integer.MIN_VALUE, ""
+ Integer.MAX_VALUE, SI.KILO(VAR.times(NonSI.HOUR)).toString());
public static final DPT DPT_TIME_LAG = new DPT("13.100", "Time Lag", "" + Integer.MIN_VALUE, ""
+ Integer.MAX_VALUE, SI.SECOND.toString());
private static final Map<String, DPT> types;
static
{
types = new HashMap<String, DPT>();
types.put(DPT_COUNTER.getID(), DPT_COUNTER);
types.put(DPT_ACTIVE_ENERGY.getID(), DPT_ACTIVE_ENERGY);
types.put(DPT_APPARENT_ENERGY.getID(), DPT_APPARENT_ENERGY);
types.put(DPT_REACTIVE_ENERGY.getID(), DPT_REACTIVE_ENERGY);
types.put(DPT_ACTIVE_ENERGY_K.getID(), DPT_ACTIVE_ENERGY_K);
types.put(DPT_APPARENT_ENERGY_K.getID(), DPT_APPARENT_ENERGY_K);
types.put(DPT_REACTIVE_ENERGY_K.getID(), DPT_REACTIVE_ENERGY_K);
types.put(DPT_TIME_LAG.getID(), DPT_TIME_LAG);
}
private final int min;
private final int max;
/**
* Creates a translator for the given datapoint type.
* <p>
*
* @param dpt
* the requested datapoint type
* @throws KNXFormatException
* on not supported or not available DPT
*/
public DPTXlator4ByteInteger(DPT dpt) throws KNXFormatException
{
this(dpt.getID());
}
/**
* Creates a translator for <code>dptID</code>.
* <p>
*
* @param dptID
* available implemented datapoint type ID
* @throws KNXFormatException
* on wrong formatted or not expected (available) DPT
*/
public DPTXlator4ByteInteger(String dptID) throws KNXFormatException
{
super(4);
setTypeID(types, dptID);
min = getLimit(dpt.getLowerValue());
max = getLimit(dpt.getUpperValue());
data = new short[4];
}
/**
* Sets the translation value from a float.
* <p>
* If succeeded, any other items in the translator are discarded.
*
* @param value
* the int value
* @throws KNXFormatException
* if <code>value</code>doesn't fit into KNX data type
*/
public void setValue(int value) throws KNXFormatException
{
final short[] buf = new short[4];
toDPT(value, buf, 0);
data = buf;
}
/**
* Returns the first translation item formatted as float.
* <p>
*
* @return value as int
*/
public final int getValueInt()
{
return fromDPT(0);
}
/*
* (non-Javadoc)
*
* @see tuwien.auto.calimero.dptxlator.DPTXlator#getAllValues()
*/
public String[] getAllValues()
{
final String[] buf = new String[data.length / 2];
for (int i = 0; i < buf.length; ++i)
buf[i] = makeString(i);
return buf;
}
/*
* (non-Javadoc)
*
* @see tuwien.auto.calimero.dptxlator.DPTXlator#setData(byte[], int)
*/
public void setData(byte[] data, int offset)
{
if (offset < 0 || offset > data.length)
throw new KNXIllegalArgumentException("illegal offset " + offset);
final int size = (data.length - offset) & ~0x01;
if (size == 0)
throw new KNXIllegalArgumentException("data length " + size + " < KNX data type width "
+ Math.max(1, getTypeSize()));
this.data = new short[size];
for (int i = 0; i < size; ++i)
this.data[i] = ubyte(data[offset + i]);
}
/*
* (non-Javadoc)
*
* @see tuwien.auto.calimero.dptxlator.DPTXlator#getData(byte[], int)
*/
public byte[] getData(byte[] dst, int offset)
{
final int end = Math.min(data.length, dst.length - offset) & ~0x01;
for (int i = 0; i < end; ++i)
dst[offset + i] = (byte) data[i];
return dst;
}
/*
* (non-Javadoc)
*
* @see tuwien.auto.calimero.dptxlator.DPTXlator#getSubTypes()
*/
public Map<String, DPT> getSubTypes()
{
return types;
}
/**
* @return the subtypes of the 2-byte float translator type
* @see DPTXlator#getSubTypesStatic()
*/
protected static Map<String, DPT> getSubTypesStatic()
{
return types;
}
private String makeString(int index)
{
return appendUnit(String.valueOf(fromDPT(index)));
}
private int fromDPT(int index)
{
// convert data into a buffer array
byte[] bData = new byte[data.length];
for (int j = 0; j < data.length; j++)
bData[j] = (byte) data[j];
// wrap the byte array with a byte buffer object
ByteBuffer bBuffer = ByteBuffer.wrap(bData);
// extract an IEEE754 float
return bBuffer.getInt();
}
private void toDPT(int value, short[] dst, int index) throws KNXFormatException
{
if (value < min || value > max)
throw logThrow(LogLevel.WARN, "translation error for " + value,
"value out of range [" + dpt.getLowerValue() + ".." + dpt.getUpperValue() + "]",
Integer.toString(value));
// encoding
byte[] bData = new byte[4];
// wrap an empty byte buffer
ByteBuffer bBuffer = ByteBuffer.wrap(bData);
// encode a float
bBuffer.putInt(value);
// extract the byte buffer and convert to an array of short...
for (int i = 0; i < bData.length; i++)
dst[i] = ubyte(bData[i]);
}
protected void toDPT(String value, short[] dst, int index) throws KNXFormatException
{
try
{
toDPT(Integer.parseInt(removeUnit(value)), dst, index);
}
catch (final NumberFormatException e)
{
throw logThrow(LogLevel.WARN, "wrong value format " + value, null, value);
}
}
private int getLimit(String limit) throws KNXFormatException
{
try
{
final int f = Integer.parseInt(limit);
if (f >= Integer.MIN_VALUE && f <= Integer.MAX_VALUE)
return f;
}
catch (final NumberFormatException e)
{
}
throw logThrow(LogLevel.ERROR, "limit " + limit, "invalid DPT range", limit);
}
}