kopia lustrzana https://github.com/bertrik/ttnhabbridge
Update Cayenne code with support for packed encoding.
rodzic
dd9b7fa50e
commit
4635e8c79e
|
@ -4,7 +4,7 @@ package nl.sikken.bertrik.cayenne;
|
|||
* Cayenne parsing exception.
|
||||
*/
|
||||
public class CayenneException extends Exception {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
|
@ -24,5 +24,5 @@ public class CayenneException extends Exception {
|
|||
public CayenneException(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import java.util.Locale;
|
|||
* Representation of one measurement item in a cayenne message.
|
||||
*/
|
||||
public final class CayenneItem {
|
||||
|
||||
|
||||
private final int channel;
|
||||
private final ECayenneItem type;
|
||||
private final Number[] values;
|
||||
|
@ -19,8 +19,8 @@ public final class CayenneItem {
|
|||
* Constructor.
|
||||
*
|
||||
* @param channel the unique channel
|
||||
* @param type the type
|
||||
* @param values the values
|
||||
* @param type the type
|
||||
* @param values the values
|
||||
*/
|
||||
public CayenneItem(int channel, ECayenneItem type, Number[] values) {
|
||||
this.channel = channel;
|
||||
|
@ -32,11 +32,11 @@ public final class CayenneItem {
|
|||
* Constructor for a single value
|
||||
*
|
||||
* @param channel the unique channel
|
||||
* @param type the type
|
||||
* @param value the value
|
||||
* @param type the type
|
||||
* @param value the value
|
||||
*/
|
||||
public CayenneItem(int channel, ECayenneItem type, Number value) {
|
||||
this(channel, type, new Number[] {value});
|
||||
this(channel, type, new Number[] { value });
|
||||
}
|
||||
|
||||
public int getChannel() {
|
||||
|
@ -50,7 +50,7 @@ public final class CayenneItem {
|
|||
public Number[] getValues() {
|
||||
return values.clone();
|
||||
}
|
||||
|
||||
|
||||
public Number getValue() {
|
||||
return values[0];
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public final class CayenneItem {
|
|||
public String[] format() {
|
||||
return type.format(values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses one item from the byte buffer and returns it.
|
||||
*
|
||||
|
@ -69,6 +69,21 @@ public final class CayenneItem {
|
|||
public static CayenneItem parse(ByteBuffer bb) throws CayenneException {
|
||||
try {
|
||||
int channel = bb.get();
|
||||
return parsePacked(bb, channel);
|
||||
} catch (BufferUnderflowException e) {
|
||||
throw new CayenneException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses one item from the byte buffer and returns it.
|
||||
*
|
||||
* @param bb the byte buffer
|
||||
* @return a new cayenne item
|
||||
* @throws CayenneException if an error occurs during parsing
|
||||
*/
|
||||
public static CayenneItem parsePacked(ByteBuffer bb, int channel) throws CayenneException {
|
||||
try {
|
||||
int type = bb.get() & 0xFF;
|
||||
ECayenneItem ct = ECayenneItem.parse(type);
|
||||
Number[] values = ct.parse(bb);
|
||||
|
@ -77,21 +92,21 @@ public final class CayenneItem {
|
|||
throw new CayenneException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.ROOT, "{chan=%d,type=%s,value=%s}", channel, type, Arrays.toString(format()));
|
||||
return String.format(Locale.ROOT, "{chan=%d,type=%s,value=%s}", channel, type, Arrays.toString(format()));
|
||||
}
|
||||
|
||||
public void encode(ByteBuffer bb) throws CayenneException {
|
||||
try {
|
||||
bb.put((byte)channel);
|
||||
bb.put((byte)type.getType());
|
||||
bb.put((byte) channel);
|
||||
bb.put((byte) type.getType());
|
||||
type.encode(bb, values);
|
||||
} catch (BufferOverflowException e) {
|
||||
throw new CayenneException(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -11,9 +11,30 @@ import java.util.List;
|
|||
* A cayenne message containing cayenne data items.
|
||||
*/
|
||||
public final class CayenneMessage {
|
||||
|
||||
|
||||
private final ECayennePayloadFormat format;
|
||||
private final List<CayenneItem> items = new ArrayList<>();
|
||||
|
||||
|
||||
public CayenneMessage() {
|
||||
this(ECayennePayloadFormat.DYNAMIC_SENSOR_PAYLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param format the payload format (e.g. parsed from the LoRaWAN port),
|
||||
* currently only DYNAMIC_SENSOR_PAYLOAD and PACKED_SENSOR_PAYLOAD
|
||||
* are supported.
|
||||
*/
|
||||
public CayenneMessage(ECayennePayloadFormat format) {
|
||||
switch (format) {
|
||||
case DYNAMIC_SENSOR_PAYLOAD:
|
||||
case PACKED_SENSOR_PAYLOAD:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Payload format not supported: " + format);
|
||||
}
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the byte array into a cayenne message.
|
||||
*
|
||||
|
@ -21,16 +42,26 @@ public final class CayenneMessage {
|
|||
* @return the cayenne message
|
||||
* @throws CayenneException in case of a parsing problem
|
||||
*/
|
||||
public static CayenneMessage parse(byte[] data) throws CayenneException {
|
||||
CayenneMessage message = new CayenneMessage();
|
||||
public void parse(byte[] data) throws CayenneException {
|
||||
ByteBuffer bb = ByteBuffer.wrap(data);
|
||||
int channel = 0;
|
||||
while (bb.hasRemaining()) {
|
||||
CayenneItem item = CayenneItem.parse(bb);
|
||||
message.add(item);
|
||||
CayenneItem item;
|
||||
switch (format) {
|
||||
case DYNAMIC_SENSOR_PAYLOAD:
|
||||
item = CayenneItem.parse(bb);
|
||||
break;
|
||||
case PACKED_SENSOR_PAYLOAD:
|
||||
item = CayenneItem.parsePacked(bb, channel);
|
||||
channel++;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported cayenne payload: " + format);
|
||||
}
|
||||
add(item);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a cayenne measurement item to the message.
|
||||
*
|
||||
|
@ -39,29 +70,31 @@ public final class CayenneMessage {
|
|||
public void add(CayenneItem item) {
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes the cayenne message into a byte array.
|
||||
*
|
||||
* @param maxSize the maximum size of the cayenne message
|
||||
* @return the byte array.
|
||||
* @throws CayenneException in case something went wrong during encoding (e.g. message too big)
|
||||
* @throws CayenneException in case something went wrong during encoding (e.g.
|
||||
* message too big)
|
||||
*/
|
||||
public byte[] encode(int maxSize) throws CayenneException {
|
||||
ByteBuffer bb = ByteBuffer.allocate(maxSize).order(ByteOrder.LITTLE_ENDIAN);
|
||||
for (CayenneItem item : items) {
|
||||
item.encode(bb);
|
||||
item.encode(bb);
|
||||
}
|
||||
return Arrays.copyOfRange(bb.array(), 0, bb.position());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return an immutable list of measurement items in the order it appears in the raw data
|
||||
* @return an immutable list of measurement items in the order it appears in the
|
||||
* raw data
|
||||
*/
|
||||
public List<CayenneItem> getItems() {
|
||||
return Collections.unmodifiableList(items);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds an item by type.
|
||||
*
|
||||
|
@ -71,7 +104,7 @@ public final class CayenneMessage {
|
|||
public CayenneItem ofType(ECayenneItem type) {
|
||||
return items.stream().filter(i -> (i.getType() == type)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds an item by channel.
|
||||
*
|
||||
|
@ -86,5 +119,5 @@ public final class CayenneMessage {
|
|||
public String toString() {
|
||||
return Arrays.toString(items.toArray());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -95,5 +95,5 @@ public enum ECayenneItem {
|
|||
public void encode(ByteBuffer bb, Number[] values) {
|
||||
formatter.encode(bb, values);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package nl.sikken.bertrik.cayenne;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* See https://community.mydevices.com/t/cayenne-lpp-2-0/7510
|
||||
*/
|
||||
public enum ECayennePayloadFormat {
|
||||
|
||||
DYNAMIC_SENSOR_PAYLOAD(1),
|
||||
PACKED_SENSOR_PAYLOAD(2),
|
||||
FULL_SCALE_GPS_PAYLOAD(3),
|
||||
|
||||
ACTUATOR_COMMANDS(10),
|
||||
DEVICE_PERIOD_CONFIGURATION(11),
|
||||
SENSOR_PERIOD_CONFIGURATION(13),
|
||||
SENSOR_ENABLE_CONFIGURATION(14);
|
||||
|
||||
private int port;
|
||||
|
||||
ECayennePayloadFormat(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
private static Map<Integer, ECayennePayloadFormat> MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
Arrays.stream(values()).forEach(v -> MAP.put(v.port, v));
|
||||
}
|
||||
|
||||
public static ECayennePayloadFormat fromPort(int port) {
|
||||
return MAP.getOrDefault(port, DYNAMIC_SENSOR_PAYLOAD);
|
||||
}
|
||||
|
||||
}
|
|
@ -4,57 +4,59 @@ import java.util.HashSet;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Wrapper around CayenneMessage to make composing a Cayenne message a bit easier.
|
||||
* Wrapper around CayenneMessage to make composing a Cayenne message a bit
|
||||
* easier.
|
||||
*
|
||||
* This makes it similar to the C API shown on https://mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload
|
||||
* This makes it similar to the C API shown on
|
||||
* https://mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload
|
||||
*/
|
||||
public final class SimpleCayenne {
|
||||
|
||||
private final CayenneMessage message = new CayenneMessage();
|
||||
private final Set<Integer> channels = new HashSet<>();
|
||||
|
||||
|
||||
public void addDigitalInput(int channel, int value) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.DIGITAL_INPUT, value);
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void addDigitalOutput(int channel, int value) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.DIGITAL_OUTPUT, value);
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void addAnalogInput(int channel, double value) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.ANALOG_INPUT, value);
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void addAnalogOutput(int channel, double value) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.ANALOG_OUTPUT, value);
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void addIlluminance(int channel, double lux) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.ILLUMINANCE, lux);
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void addPresence(int channel, int value) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.PRESENCE, value);
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void addTemperature(int channel, double temperature) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.TEMPERATURE, temperature);
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void addRelativeHumidity(int channel, double humidity) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.HUMIDITY, humidity);
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
||||
public void addAccelerometer(int channel, double x, double y, double z) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.ACCELEROMETER, new Double[] {x, y, z});
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.ACCELEROMETER, new Double[] { x, y, z });
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
@ -64,41 +66,42 @@ public final class SimpleCayenne {
|
|||
}
|
||||
|
||||
public void addGyrometer(int channel, double x, double y, double z) throws CayenneException {
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.GYROMETER, new Double[] {x, y, z});
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.GYROMETER, new Double[] { x, y, z });
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
public void addGps(int channel, double latitude, double longitude, double altitude) throws CayenneException {
|
||||
CayenneItem item =
|
||||
new CayenneItem(channel, ECayenneItem.GPS_LOCATION, new Double[] {latitude, longitude, altitude});
|
||||
CayenneItem item = new CayenneItem(channel, ECayenneItem.GPS_LOCATION,
|
||||
new Double[] { latitude, longitude, altitude });
|
||||
addItem(item);
|
||||
}
|
||||
|
||||
|
||||
private void addItem(CayenneItem item) throws CayenneException {
|
||||
// verify that channel is unique
|
||||
int channel = item.getChannel();
|
||||
if (channels.contains(channel)) {
|
||||
throw new CayenneException("Channel id " + channel + " need to be unique!");
|
||||
throw new CayenneException("Channel id " + channel + " need to be unique!");
|
||||
}
|
||||
// add the item
|
||||
channels.add(channel);
|
||||
message.add(item);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encodes the data into the supplied buffer.
|
||||
*
|
||||
* @param maxSize maximum size of the message
|
||||
* @return the length of data encoded
|
||||
* @throws CayenneException in case something went wrong during encoding (e.g. message too big)
|
||||
* @throws CayenneException in case something went wrong during encoding (e.g.
|
||||
* message too big)
|
||||
*/
|
||||
public byte[] encode(int maxSize) throws CayenneException {
|
||||
return message.encode(maxSize);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ public abstract class BaseFormatter implements IFormatter {
|
|||
/**
|
||||
* Gets an integer value from the byte buffer.
|
||||
*
|
||||
* @param bb the byte buffer
|
||||
* @param n the number of bytes to get
|
||||
* @param bb the byte buffer
|
||||
* @param n the number of bytes to get
|
||||
* @param signed whether it should be interpreted as signed value or not
|
||||
* @return the value
|
||||
*/
|
||||
|
@ -24,12 +24,12 @@ public abstract class BaseFormatter implements IFormatter {
|
|||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Puts an integer value into a byte buffer
|
||||
*
|
||||
* @param bb the byte buffer
|
||||
* @param n the number of bytes to put
|
||||
* @param bb the byte buffer
|
||||
* @param n the number of bytes to put
|
||||
* @param value the value to encode
|
||||
*/
|
||||
protected void putValue(ByteBuffer bb, int n, int value) {
|
||||
|
@ -40,5 +40,5 @@ public abstract class BaseFormatter implements IFormatter {
|
|||
shift -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.util.Locale;
|
|||
* Formatter for cayenne items which represent a GPS position.
|
||||
*/
|
||||
public final class GpsFormatter extends BaseFormatter {
|
||||
|
||||
|
||||
private static final double LAT_LON_SCALE = 1E-4;
|
||||
private static final double ALT_SCALE = 1E-2;
|
||||
|
||||
|
@ -16,23 +16,20 @@ public final class GpsFormatter extends BaseFormatter {
|
|||
double lat = LAT_LON_SCALE * getValue(bb, 3, true);
|
||||
double lon = LAT_LON_SCALE * getValue(bb, 3, true);
|
||||
double alt = ALT_SCALE * getValue(bb, 3, true);
|
||||
return new Double[] {lat, lon, alt};
|
||||
return new Double[] { lat, lon, alt };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] format(Number[] values) {
|
||||
return new String[] {
|
||||
String.format(Locale.ROOT, "%.4f", values[0]),
|
||||
String.format(Locale.ROOT, "%.4f", values[1]),
|
||||
String.format(Locale.ROOT, "%.2f", values[2])
|
||||
};
|
||||
return new String[] { String.format(Locale.ROOT, "%.4f", values[0]),
|
||||
String.format(Locale.ROOT, "%.4f", values[1]), String.format(Locale.ROOT, "%.2f", values[2]) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuffer bb, Number[] values) {
|
||||
putValue(bb, 3, (int)Math.round(values[0].doubleValue() / LAT_LON_SCALE));
|
||||
putValue(bb, 3, (int)Math.round(values[1].doubleValue() / LAT_LON_SCALE));
|
||||
putValue(bb, 3, (int)Math.round(values[2].doubleValue() / ALT_SCALE));
|
||||
putValue(bb, 3, (int) Math.round(values[0].doubleValue() / LAT_LON_SCALE));
|
||||
putValue(bb, 3, (int) Math.round(values[1].doubleValue() / LAT_LON_SCALE));
|
||||
putValue(bb, 3, (int) Math.round(values[2].doubleValue() / ALT_SCALE));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ package nl.sikken.bertrik.cayenne.formatter;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Interface for cayenne data structures that can be formatted as an array of strings.
|
||||
* Interface for cayenne data structures that can be formatted as an array of
|
||||
* strings.
|
||||
*/
|
||||
public interface IFormatter {
|
||||
|
||||
|
@ -15,9 +16,9 @@ public interface IFormatter {
|
|||
*/
|
||||
Number[] parse(ByteBuffer bb);
|
||||
|
||||
/**
|
||||
* Formats the data into an array of strings.
|
||||
* For example, for a GPS location it outputs: latitude in [0], longitude in [1], altitude in [2].
|
||||
/**
|
||||
* Formats the data into an array of strings. For example, for a GPS location it
|
||||
* outputs: latitude in [0], longitude in [1], altitude in [2].
|
||||
*
|
||||
* @param values the value as number array
|
||||
* @return the string representation
|
||||
|
@ -27,7 +28,7 @@ public interface IFormatter {
|
|||
/**
|
||||
* Encodes the data into the byte buffer
|
||||
*
|
||||
* @param bb the buffer to encode to
|
||||
* @param bb the buffer to encode to
|
||||
* @param values the values to encode
|
||||
*/
|
||||
void encode(ByteBuffer bb, Number[] values);
|
||||
|
|
|
@ -6,14 +6,14 @@ import java.util.Locale;
|
|||
public final class IntegerFormatter extends BaseFormatter {
|
||||
|
||||
private final int length;
|
||||
private final int size;
|
||||
private final boolean signed;
|
||||
private final int size;
|
||||
private final boolean signed;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param length the number of elements
|
||||
* @param size the size of each element
|
||||
* @param size the size of each element
|
||||
* @param signed if the element is signed
|
||||
*/
|
||||
public IntegerFormatter(int length, int size, boolean signed) {
|
||||
|
@ -22,29 +22,29 @@ public final class IntegerFormatter extends BaseFormatter {
|
|||
this.signed = signed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number[] parse(ByteBuffer bb) {
|
||||
@Override
|
||||
public Number[] parse(ByteBuffer bb) {
|
||||
Integer[] values = new Integer[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
values[i] = getValue(bb, size, signed);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] format(Number[] values) {
|
||||
@Override
|
||||
public String[] format(Number[] values) {
|
||||
String[] formatted = new String[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
formatted[i] = String.format(Locale.ROOT, "%d", values[i].intValue());
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuffer bb, Number[] values) {
|
||||
@Override
|
||||
public void encode(ByteBuffer bb, Number[] values) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
putValue(bb, size, values[i].intValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,145 +11,162 @@ import org.slf4j.LoggerFactory;
|
|||
* Unit tests for CayenneMessage.
|
||||
*/
|
||||
public final class CayenneMessageTest {
|
||||
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CayenneMessageTest.class);
|
||||
|
||||
|
||||
private final int MAX_BUF_SIZE = 500;
|
||||
|
||||
|
||||
/**
|
||||
* Verifies example from specification.
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test
|
||||
public void testTwoTemperatureSensors() throws CayenneException {
|
||||
final byte[] data = {0x03, 0x67, 0x01, 0x10, 0x05, 0x67, 0x00, (byte) 0xFF};
|
||||
final CayenneMessage payload = CayenneMessage.parse(data);
|
||||
byte[] data = { 0x03, 0x67, 0x01, 0x10, 0x05, 0x67, 0x00, (byte) 0xFF };
|
||||
CayenneMessage payload = new CayenneMessage();
|
||||
payload.parse(data);
|
||||
LOG.info("payload: {}", payload);
|
||||
|
||||
Assert.assertArrayEquals(new String[] {"27.2"}, payload.ofChannel(3).format());
|
||||
Assert.assertArrayEquals(new String[] {"25.5"}, payload.ofChannel(5).format());
|
||||
|
||||
Assert.assertArrayEquals(new String[] { "27.2" }, payload.ofChannel(3).format());
|
||||
Assert.assertArrayEquals(new String[] { "25.5" }, payload.ofChannel(5).format());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies example from specification.
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test
|
||||
public void testTemperaturePlusAccel() throws CayenneException {
|
||||
final byte[] data =
|
||||
{0x01, 0x67, (byte) 0xFF, (byte) 0xD7, 0x06, 0x71, 0x04, (byte) 0xD2, (byte) 0xFB, 0x2E, 0x00, 0x00};
|
||||
final CayenneMessage payload = CayenneMessage.parse(data);
|
||||
byte[] data = { 0x01, 0x67, (byte) 0xFF, (byte) 0xD7, 0x06, 0x71, 0x04, (byte) 0xD2, (byte) 0xFB, 0x2E, 0x00,
|
||||
0x00 };
|
||||
CayenneMessage payload = new CayenneMessage();
|
||||
payload.parse(data);
|
||||
|
||||
Assert.assertArrayEquals(new String[] {"-4.1"}, payload.ofChannel(1).format());
|
||||
Assert.assertArrayEquals(new String[] {"1.234", "-1.234", "0.000"}, payload.ofChannel(6).format());
|
||||
Assert.assertArrayEquals(new String[] { "-4.1" }, payload.ofChannel(1).format());
|
||||
Assert.assertArrayEquals(new String[] { "1.234", "-1.234", "0.000" }, payload.ofChannel(6).format());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies example from specification.
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test
|
||||
public void testGps() throws CayenneException {
|
||||
final byte[] data =
|
||||
{0x01, (byte) 0x88, 0x06, 0x076, 0x5f, (byte) 0xf2, (byte) 0x96, 0x0a, 0x00, 0x03, (byte) 0xe8};
|
||||
final CayenneMessage payload = CayenneMessage.parse(data);
|
||||
byte[] data = { 0x01, (byte) 0x88, 0x06, 0x076, 0x5f, (byte) 0xf2, (byte) 0x96, 0x0a, 0x00, 0x03, (byte) 0xe8 };
|
||||
CayenneMessage payload = new CayenneMessage();
|
||||
payload.parse(data);
|
||||
|
||||
Assert.assertArrayEquals(new String[] {"42.3519", "-87.9094", "10.00"}, payload.ofChannel(1).format());
|
||||
Assert.assertArrayEquals(new String[] { "42.3519", "-87.9094", "10.00" }, payload.ofChannel(1).format());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies parsing of humidity value.
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test
|
||||
public void testHumidity() throws CayenneException {
|
||||
final byte[] data = {1, 104, 100};
|
||||
final CayenneMessage payload = CayenneMessage.parse(data);
|
||||
|
||||
Assert.assertArrayEquals(new String[] {"50.0"}, payload.ofChannel(1).format());
|
||||
byte[] data = { 1, 104, 100 };
|
||||
CayenneMessage payload = new CayenneMessage();
|
||||
payload.parse(data);
|
||||
|
||||
Assert.assertArrayEquals(new String[] { "50.0" }, payload.ofChannel(1).format());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies parsing of some actual data from a sodaq one.
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test
|
||||
public void testActualData() throws CayenneException {
|
||||
final String base64 = "AYgH8CEAt1D//zgCAmDQA2cBDg==";
|
||||
final byte[] data = Base64.getDecoder().decode(base64);
|
||||
final CayenneMessage payload = CayenneMessage.parse(data);
|
||||
String base64 = "AYgH8CEAt1D//zgCAmDQA2cBDg==";
|
||||
byte[] data = Base64.getDecoder().decode(base64);
|
||||
CayenneMessage payload = new CayenneMessage();
|
||||
payload.parse(data);
|
||||
|
||||
Assert.assertArrayEquals(new String[] {"52.0225", "4.6928", "-2.00"}, payload.ofChannel(1).format());
|
||||
Assert.assertArrayEquals(new String[] {"247.84"}, payload.ofChannel(2).format());
|
||||
Assert.assertArrayEquals(new String[] {"27.0"}, payload.ofChannel(3).format());
|
||||
Assert.assertArrayEquals(new String[] { "52.0225", "4.6928", "-2.00" }, payload.ofChannel(1).format());
|
||||
Assert.assertArrayEquals(new String[] { "247.84" }, payload.ofChannel(2).format());
|
||||
Assert.assertArrayEquals(new String[] { "27.0" }, payload.ofChannel(3).format());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies parsing of some actual data from a sodaq one, with a fix applied to the voltage value.
|
||||
* Verifies parsing of some actual data from a sodaq one, with a fix applied to
|
||||
* the voltage value.
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test
|
||||
public void testActualData2() throws CayenneException {
|
||||
final String base64 = "AYgH8CEAt03/+VwCAgGfA2cA8A==";
|
||||
final byte[] data = Base64.getDecoder().decode(base64);
|
||||
final CayenneMessage payload = CayenneMessage.parse(data);
|
||||
String base64 = "AYgH8CEAt03/+VwCAgGfA2cA8A==";
|
||||
byte[] data = Base64.getDecoder().decode(base64);
|
||||
CayenneMessage payload = new CayenneMessage();
|
||||
payload.parse(data);
|
||||
|
||||
// verify we can get at the data by channel
|
||||
Assert.assertArrayEquals(new String[] {"52.0225", "4.6925", "-17.00"}, payload.ofChannel(1).format());
|
||||
Assert.assertArrayEquals(new String[] {"4.15"}, payload.ofChannel(2).format());
|
||||
Assert.assertArrayEquals(new String[] {"24.0"}, payload.ofChannel(3).format());
|
||||
Assert.assertArrayEquals(new String[] { "52.0225", "4.6925", "-17.00" }, payload.ofChannel(1).format());
|
||||
Assert.assertArrayEquals(new String[] { "4.15" }, payload.ofChannel(2).format());
|
||||
Assert.assertArrayEquals(new String[] { "24.0" }, payload.ofChannel(3).format());
|
||||
|
||||
// verify we can also get data by type
|
||||
Assert.assertArrayEquals(new String[] {"52.0225", "4.6925", "-17.00"},
|
||||
payload.ofType(ECayenneItem.GPS_LOCATION).format());
|
||||
Assert.assertArrayEquals(new String[] {"4.15"}, payload.ofType(ECayenneItem.ANALOG_INPUT).format());
|
||||
Assert.assertArrayEquals(new String[] {"24.0"}, payload.ofType(ECayenneItem.TEMPERATURE).format());
|
||||
|
||||
Assert.assertArrayEquals(new String[] { "52.0225", "4.6925", "-17.00" },
|
||||
payload.ofType(ECayenneItem.GPS_LOCATION).format());
|
||||
Assert.assertArrayEquals(new String[] { "4.15" }, payload.ofType(ECayenneItem.ANALOG_INPUT).format());
|
||||
Assert.assertArrayEquals(new String[] { "24.0" }, payload.ofType(ECayenneItem.TEMPERATURE).format());
|
||||
|
||||
// verify non-existing channel and type
|
||||
Assert.assertNull(payload.ofChannel(0));
|
||||
Assert.assertNull(payload.ofType(ECayenneItem.BAROMETER));
|
||||
|
||||
|
||||
// verify toString method
|
||||
Assert.assertNotNull(payload.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies parsing an empty string.
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test
|
||||
public void testParseEmpty() throws CayenneException {
|
||||
final CayenneMessage payload = CayenneMessage.parse(new byte[0]);
|
||||
CayenneMessage payload = new CayenneMessage();
|
||||
payload.parse(new byte[0]);
|
||||
Assert.assertTrue(payload.getItems().isEmpty());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies parsing a short buffer
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test(expected = CayenneException.class)
|
||||
public void testShortBuffer() throws CayenneException {
|
||||
CayenneMessage.parse(new byte[] {0});
|
||||
new CayenneMessage().parse(new byte[] { 0 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies parsing of a buffer containing a non-supported data type.
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test(expected = CayenneException.class)
|
||||
public void testInvalidType() throws CayenneException {
|
||||
CayenneMessage.parse(new byte[] {0, 100});
|
||||
new CayenneMessage().parse(new byte[] { 0, 100 });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies parsing of a buffer containing insufficient data during parsing.
|
||||
*
|
||||
* @throws CayenneException in case of a parsing exception
|
||||
*/
|
||||
@Test(expected = CayenneException.class)
|
||||
public void testShortData() throws CayenneException {
|
||||
CayenneMessage.parse(new byte[] {2, 1});
|
||||
new CayenneMessage().parse(new byte[] { 2, 1 });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies encoding of a float value.
|
||||
*
|
||||
|
@ -157,15 +174,16 @@ public final class CayenneMessageTest {
|
|||
*/
|
||||
@Test
|
||||
public void encodeFloat() throws CayenneException {
|
||||
final CayenneMessage message = new CayenneMessage();
|
||||
CayenneMessage message = new CayenneMessage();
|
||||
message.add(new CayenneItem(1, ECayenneItem.ANALOG_INPUT, -12.34));
|
||||
|
||||
final byte[] encoded = message.encode(MAX_BUF_SIZE);
|
||||
|
||||
final CayenneMessage decoded = CayenneMessage.parse(encoded);
|
||||
|
||||
byte[] encoded = message.encode(MAX_BUF_SIZE);
|
||||
|
||||
CayenneMessage decoded = new CayenneMessage();
|
||||
decoded.parse(encoded);
|
||||
Assert.assertEquals(-12.34, decoded.getItems().get(0).getValues()[0].doubleValue(), 0.01);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies encoding of a humidity value.
|
||||
*
|
||||
|
@ -173,18 +191,19 @@ public final class CayenneMessageTest {
|
|||
*/
|
||||
@Test
|
||||
public void encodeHumidity() throws CayenneException {
|
||||
final CayenneMessage message = new CayenneMessage();
|
||||
CayenneMessage message = new CayenneMessage();
|
||||
message.add(new CayenneItem(1, ECayenneItem.HUMIDITY, 35.5));
|
||||
|
||||
final byte[] encoded = message.encode(MAX_BUF_SIZE);
|
||||
final CayenneMessage decoded = CayenneMessage.parse(encoded);
|
||||
|
||||
final CayenneItem item = decoded.getItems().get(0);
|
||||
byte[] encoded = message.encode(MAX_BUF_SIZE);
|
||||
CayenneMessage decoded = new CayenneMessage();
|
||||
decoded.parse(encoded);
|
||||
|
||||
CayenneItem item = decoded.getItems().get(0);
|
||||
Assert.assertEquals(ECayenneItem.HUMIDITY, item.getType());
|
||||
Assert.assertEquals(35.5, item.getValues()[0].doubleValue(), 0.1);
|
||||
Assert.assertEquals("35.5", item.format()[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies encoding of a digital input.
|
||||
*
|
||||
|
@ -192,17 +211,18 @@ public final class CayenneMessageTest {
|
|||
*/
|
||||
@Test
|
||||
public void testDigitalInput() throws CayenneException {
|
||||
final CayenneMessage message = new CayenneMessage();
|
||||
CayenneMessage message = new CayenneMessage();
|
||||
message.add(new CayenneItem(1, ECayenneItem.DIGITAL_INPUT, 1));
|
||||
|
||||
final byte[] encoded = message.encode(MAX_BUF_SIZE);
|
||||
final CayenneMessage decoded = CayenneMessage.parse(encoded);
|
||||
|
||||
final CayenneItem item = decoded.getItems().get(0);
|
||||
byte[] encoded = message.encode(MAX_BUF_SIZE);
|
||||
CayenneMessage decoded = new CayenneMessage();
|
||||
decoded.parse(encoded);
|
||||
|
||||
CayenneItem item = decoded.getItems().get(0);
|
||||
Assert.assertEquals(ECayenneItem.DIGITAL_INPUT, item.getType());
|
||||
Assert.assertEquals(1, item.getValues()[0].intValue());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies encoding/decoding of a presence value (e.g. number of satellites)
|
||||
*
|
||||
|
@ -210,17 +230,30 @@ public final class CayenneMessageTest {
|
|||
*/
|
||||
@Test
|
||||
public void testPresence() throws CayenneException {
|
||||
final CayenneMessage message = new CayenneMessage();
|
||||
CayenneMessage message = new CayenneMessage();
|
||||
message.add(new CayenneItem(1, ECayenneItem.PRESENCE, 7));
|
||||
|
||||
final byte[] encoded = message.encode(MAX_BUF_SIZE);
|
||||
final CayenneMessage decoded = CayenneMessage.parse(encoded);
|
||||
|
||||
final CayenneItem item = decoded.getItems().get(0);
|
||||
byte[] encoded = message.encode(MAX_BUF_SIZE);
|
||||
CayenneMessage decoded = new CayenneMessage();
|
||||
decoded.parse(encoded);
|
||||
|
||||
CayenneItem item = decoded.getItems().get(0);
|
||||
Assert.assertEquals(ECayenneItem.PRESENCE, item.getType());
|
||||
Assert.assertEquals(7, item.getValues()[0].intValue());
|
||||
Assert.assertEquals("7", item.format()[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies decoding of packed Cayenne format
|
||||
*/
|
||||
@Test
|
||||
public void testPackedFormat() throws CayenneException {
|
||||
CayenneMessage message = new CayenneMessage(ECayennePayloadFormat.PACKED_SENSOR_PAYLOAD);
|
||||
byte[] data = { 0x67, 0x01, 0x10, 0x67, 0x00, (byte) 0xFF };
|
||||
message.parse(data);
|
||||
|
||||
Assert.assertEquals(27.2, message.ofChannel(0).getValue().doubleValue(), 0.01);
|
||||
Assert.assertEquals(25.5, message.ofChannel(1).getValue().doubleValue(), 0.01);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package nl.sikken.bertrik.cayenne;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* See https://community.mydevices.com/t/cayenne-lpp-2-0/7510
|
||||
*/
|
||||
public final class CayennePayloadFormatTest {
|
||||
|
||||
@Test
|
||||
public void testPort() {
|
||||
Assert.assertEquals(ECayennePayloadFormat.DYNAMIC_SENSOR_PAYLOAD, ECayennePayloadFormat.fromPort(1));
|
||||
Assert.assertEquals(ECayennePayloadFormat.PACKED_SENSOR_PAYLOAD, ECayennePayloadFormat.fromPort(2));
|
||||
}
|
||||
|
||||
}
|
|
@ -9,11 +9,13 @@ import org.slf4j.LoggerFactory;
|
|||
* Unit test for SimpleCayenne.
|
||||
*/
|
||||
public final class SimpleCayenneTest {
|
||||
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SimpleCayenneTest.class);
|
||||
|
||||
|
||||
/**
|
||||
* Verifies basic functionality by adding some items and encoding it into a message
|
||||
* Verifies basic functionality by adding some items and encoding it into a
|
||||
* message
|
||||
*
|
||||
* @throws CayenneException in case of a problem encoding/decoding
|
||||
*/
|
||||
@Test
|
||||
|
@ -32,13 +34,14 @@ public final class SimpleCayenneTest {
|
|||
cayenne.addRelativeHumidity(11, 50.0);
|
||||
cayenne.addTemperature(12, 19.0);
|
||||
LOG.info("Encoded message: {}", cayenne);
|
||||
|
||||
|
||||
// encode it
|
||||
byte[] data = cayenne.encode(500);
|
||||
Assert.assertNotNull(data);
|
||||
|
||||
|
||||
// decode it
|
||||
CayenneMessage message = CayenneMessage.parse(data);
|
||||
CayenneMessage message = new CayenneMessage();
|
||||
message.parse(data);
|
||||
Assert.assertEquals(12, message.getItems().size());
|
||||
Assert.assertEquals(3.82, message.ofType(ECayenneItem.ANALOG_INPUT).getValue().doubleValue(), 0.01);
|
||||
Assert.assertEquals(55, message.ofType(ECayenneItem.DIGITAL_INPUT).getValue().intValue());
|
||||
|
@ -47,7 +50,7 @@ public final class SimpleCayenneTest {
|
|||
Assert.assertEquals(42, message.ofType(ECayenneItem.PRESENCE).getValue().intValue(), 42);
|
||||
Assert.assertEquals(19.0, message.ofType(ECayenneItem.TEMPERATURE).getValue().doubleValue(), 0.1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that a simple cayenne message with non-unique channels is rejected.
|
||||
*
|
||||
|
|
|
@ -7,21 +7,21 @@ import org.junit.Test;
|
|||
|
||||
public final class GpsFormatterTest {
|
||||
|
||||
@Test
|
||||
public void testEncodeDecode() {
|
||||
GpsFormatter formatter = new GpsFormatter();
|
||||
Double[] coords = new Double[] {52.0, 4.1, -3.5};
|
||||
@Test
|
||||
public void testEncodeDecode() {
|
||||
GpsFormatter formatter = new GpsFormatter();
|
||||
Double[] coords = new Double[] { 52.0, 4.1, -3.5 };
|
||||
|
||||
// encode
|
||||
ByteBuffer bb = ByteBuffer.allocate(100);
|
||||
formatter.encode(bb, coords);
|
||||
// encode
|
||||
ByteBuffer bb = ByteBuffer.allocate(100);
|
||||
formatter.encode(bb, coords);
|
||||
|
||||
// decode
|
||||
bb.flip();
|
||||
Double[] parsed = formatter.parse(bb);
|
||||
Assert.assertEquals(coords[0], parsed[0], 0.01);
|
||||
Assert.assertEquals(coords[1], parsed[1], 0.01);
|
||||
Assert.assertEquals(coords[2], parsed[2], 0.01);
|
||||
}
|
||||
|
||||
// decode
|
||||
bb.flip();
|
||||
Double[] parsed = formatter.parse(bb);
|
||||
Assert.assertEquals(coords[0], parsed[0], 0.01);
|
||||
Assert.assertEquals(coords[1], parsed[1], 0.01);
|
||||
Assert.assertEquals(coords[2], parsed[2], 0.01);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
|||
import nl.sikken.bertrik.cayenne.CayenneException;
|
||||
import nl.sikken.bertrik.cayenne.CayenneItem;
|
||||
import nl.sikken.bertrik.cayenne.CayenneMessage;
|
||||
import nl.sikken.bertrik.cayenne.ECayennePayloadFormat;
|
||||
import nl.sikken.bertrik.hab.ttn.TtnMessage;
|
||||
|
||||
/**
|
||||
|
@ -148,7 +149,9 @@ public final class PayloadDecoder {
|
|||
try {
|
||||
Instant time = message.getMetaData().getTime();
|
||||
Sentence sentence = new Sentence(callSign, counter, time);
|
||||
CayenneMessage cayenne = CayenneMessage.parse(message.getPayloadRaw());
|
||||
ECayennePayloadFormat cayenneFormat = ECayennePayloadFormat.fromPort(message.getPort());
|
||||
CayenneMessage cayenne = new CayenneMessage(cayenneFormat);
|
||||
cayenne.parse(message.getPayloadRaw());
|
||||
|
||||
// add all items, in the order they appear in the cayenne message
|
||||
for (CayenneItem item : cayenne.getItems()) {
|
||||
|
|
Ładowanie…
Reference in New Issue