Update Cayenne code with support for packed encoding.

ttnv3
Bertrik Sikken 2020-05-02 10:36:54 +02:00
rodzic dd9b7fa50e
commit 4635e8c79e
15 zmienionych plików z 332 dodań i 186 usunięć

Wyświetl plik

@ -4,7 +4,7 @@ package nl.sikken.bertrik.cayenne;
* Cayenne parsing exception. * Cayenne parsing exception.
*/ */
public class CayenneException extends Exception { public class CayenneException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
@ -24,5 +24,5 @@ public class CayenneException extends Exception {
public CayenneException(Throwable e) { public CayenneException(Throwable e) {
super(e); super(e);
} }
} }

Wyświetl plik

@ -10,7 +10,7 @@ import java.util.Locale;
* Representation of one measurement item in a cayenne message. * Representation of one measurement item in a cayenne message.
*/ */
public final class CayenneItem { public final class CayenneItem {
private final int channel; private final int channel;
private final ECayenneItem type; private final ECayenneItem type;
private final Number[] values; private final Number[] values;
@ -19,8 +19,8 @@ public final class CayenneItem {
* Constructor. * Constructor.
* *
* @param channel the unique channel * @param channel the unique channel
* @param type the type * @param type the type
* @param values the values * @param values the values
*/ */
public CayenneItem(int channel, ECayenneItem type, Number[] values) { public CayenneItem(int channel, ECayenneItem type, Number[] values) {
this.channel = channel; this.channel = channel;
@ -32,11 +32,11 @@ public final class CayenneItem {
* Constructor for a single value * Constructor for a single value
* *
* @param channel the unique channel * @param channel the unique channel
* @param type the type * @param type the type
* @param value the value * @param value the value
*/ */
public CayenneItem(int channel, ECayenneItem type, Number value) { public CayenneItem(int channel, ECayenneItem type, Number value) {
this(channel, type, new Number[] {value}); this(channel, type, new Number[] { value });
} }
public int getChannel() { public int getChannel() {
@ -50,7 +50,7 @@ public final class CayenneItem {
public Number[] getValues() { public Number[] getValues() {
return values.clone(); return values.clone();
} }
public Number getValue() { public Number getValue() {
return values[0]; return values[0];
} }
@ -58,7 +58,7 @@ public final class CayenneItem {
public String[] format() { public String[] format() {
return type.format(values); return type.format(values);
} }
/** /**
* Parses one item from the byte buffer and returns it. * 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 { public static CayenneItem parse(ByteBuffer bb) throws CayenneException {
try { try {
int channel = bb.get(); 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; int type = bb.get() & 0xFF;
ECayenneItem ct = ECayenneItem.parse(type); ECayenneItem ct = ECayenneItem.parse(type);
Number[] values = ct.parse(bb); Number[] values = ct.parse(bb);
@ -77,21 +92,21 @@ public final class CayenneItem {
throw new CayenneException(e); throw new CayenneException(e);
} }
} }
@Override @Override
public String toString() { 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 { public void encode(ByteBuffer bb) throws CayenneException {
try { try {
bb.put((byte)channel); bb.put((byte) channel);
bb.put((byte)type.getType()); bb.put((byte) type.getType());
type.encode(bb, values); type.encode(bb, values);
} catch (BufferOverflowException e) { } catch (BufferOverflowException e) {
throw new CayenneException(e); throw new CayenneException(e);
} }
} }
} }

Wyświetl plik

@ -11,9 +11,30 @@ import java.util.List;
* A cayenne message containing cayenne data items. * A cayenne message containing cayenne data items.
*/ */
public final class CayenneMessage { public final class CayenneMessage {
private final ECayennePayloadFormat format;
private final List<CayenneItem> items = new ArrayList<>(); 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. * Parses the byte array into a cayenne message.
* *
@ -21,16 +42,26 @@ public final class CayenneMessage {
* @return the cayenne message * @return the cayenne message
* @throws CayenneException in case of a parsing problem * @throws CayenneException in case of a parsing problem
*/ */
public static CayenneMessage parse(byte[] data) throws CayenneException { public void parse(byte[] data) throws CayenneException {
CayenneMessage message = new CayenneMessage();
ByteBuffer bb = ByteBuffer.wrap(data); ByteBuffer bb = ByteBuffer.wrap(data);
int channel = 0;
while (bb.hasRemaining()) { while (bb.hasRemaining()) {
CayenneItem item = CayenneItem.parse(bb); CayenneItem item;
message.add(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. * Adds a cayenne measurement item to the message.
* *
@ -39,29 +70,31 @@ public final class CayenneMessage {
public void add(CayenneItem item) { public void add(CayenneItem item) {
items.add(item); items.add(item);
} }
/** /**
* Encodes the cayenne message into a byte array. * Encodes the cayenne message into a byte array.
* *
* @param maxSize the maximum size of the cayenne message * @param maxSize the maximum size of the cayenne message
* @return the byte array. * @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 { public byte[] encode(int maxSize) throws CayenneException {
ByteBuffer bb = ByteBuffer.allocate(maxSize).order(ByteOrder.LITTLE_ENDIAN); ByteBuffer bb = ByteBuffer.allocate(maxSize).order(ByteOrder.LITTLE_ENDIAN);
for (CayenneItem item : items) { for (CayenneItem item : items) {
item.encode(bb); item.encode(bb);
} }
return Arrays.copyOfRange(bb.array(), 0, bb.position()); 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() { public List<CayenneItem> getItems() {
return Collections.unmodifiableList(items); return Collections.unmodifiableList(items);
} }
/** /**
* Finds an item by type. * Finds an item by type.
* *
@ -71,7 +104,7 @@ public final class CayenneMessage {
public CayenneItem ofType(ECayenneItem type) { public CayenneItem ofType(ECayenneItem type) {
return items.stream().filter(i -> (i.getType() == type)).findFirst().orElse(null); return items.stream().filter(i -> (i.getType() == type)).findFirst().orElse(null);
} }
/** /**
* Finds an item by channel. * Finds an item by channel.
* *
@ -86,5 +119,5 @@ public final class CayenneMessage {
public String toString() { public String toString() {
return Arrays.toString(items.toArray()); return Arrays.toString(items.toArray());
} }
} }

Wyświetl plik

@ -95,5 +95,5 @@ public enum ECayenneItem {
public void encode(ByteBuffer bb, Number[] values) { public void encode(ByteBuffer bb, Number[] values) {
formatter.encode(bb, values); formatter.encode(bb, values);
} }
} }

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -4,57 +4,59 @@ import java.util.HashSet;
import java.util.Set; 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 { public final class SimpleCayenne {
private final CayenneMessage message = new CayenneMessage(); private final CayenneMessage message = new CayenneMessage();
private final Set<Integer> channels = new HashSet<>(); private final Set<Integer> channels = new HashSet<>();
public void addDigitalInput(int channel, int value) throws CayenneException { public void addDigitalInput(int channel, int value) throws CayenneException {
CayenneItem item = new CayenneItem(channel, ECayenneItem.DIGITAL_INPUT, value); CayenneItem item = new CayenneItem(channel, ECayenneItem.DIGITAL_INPUT, value);
addItem(item); addItem(item);
} }
public void addDigitalOutput(int channel, int value) throws CayenneException { public void addDigitalOutput(int channel, int value) throws CayenneException {
CayenneItem item = new CayenneItem(channel, ECayenneItem.DIGITAL_OUTPUT, value); CayenneItem item = new CayenneItem(channel, ECayenneItem.DIGITAL_OUTPUT, value);
addItem(item); addItem(item);
} }
public void addAnalogInput(int channel, double value) throws CayenneException { public void addAnalogInput(int channel, double value) throws CayenneException {
CayenneItem item = new CayenneItem(channel, ECayenneItem.ANALOG_INPUT, value); CayenneItem item = new CayenneItem(channel, ECayenneItem.ANALOG_INPUT, value);
addItem(item); addItem(item);
} }
public void addAnalogOutput(int channel, double value) throws CayenneException { public void addAnalogOutput(int channel, double value) throws CayenneException {
CayenneItem item = new CayenneItem(channel, ECayenneItem.ANALOG_OUTPUT, value); CayenneItem item = new CayenneItem(channel, ECayenneItem.ANALOG_OUTPUT, value);
addItem(item); addItem(item);
} }
public void addIlluminance(int channel, double lux) throws CayenneException { public void addIlluminance(int channel, double lux) throws CayenneException {
CayenneItem item = new CayenneItem(channel, ECayenneItem.ILLUMINANCE, lux); CayenneItem item = new CayenneItem(channel, ECayenneItem.ILLUMINANCE, lux);
addItem(item); addItem(item);
} }
public void addPresence(int channel, int value) throws CayenneException { public void addPresence(int channel, int value) throws CayenneException {
CayenneItem item = new CayenneItem(channel, ECayenneItem.PRESENCE, value); CayenneItem item = new CayenneItem(channel, ECayenneItem.PRESENCE, value);
addItem(item); addItem(item);
} }
public void addTemperature(int channel, double temperature) throws CayenneException { public void addTemperature(int channel, double temperature) throws CayenneException {
CayenneItem item = new CayenneItem(channel, ECayenneItem.TEMPERATURE, temperature); CayenneItem item = new CayenneItem(channel, ECayenneItem.TEMPERATURE, temperature);
addItem(item); addItem(item);
} }
public void addRelativeHumidity(int channel, double humidity) throws CayenneException { public void addRelativeHumidity(int channel, double humidity) throws CayenneException {
CayenneItem item = new CayenneItem(channel, ECayenneItem.HUMIDITY, humidity); CayenneItem item = new CayenneItem(channel, ECayenneItem.HUMIDITY, humidity);
addItem(item); addItem(item);
} }
public void addAccelerometer(int channel, double x, double y, double z) throws CayenneException { 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); addItem(item);
} }
@ -64,41 +66,42 @@ public final class SimpleCayenne {
} }
public void addGyrometer(int channel, double x, double y, double z) throws CayenneException { 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); addItem(item);
} }
public void addGps(int channel, double latitude, double longitude, double altitude) throws CayenneException { public void addGps(int channel, double latitude, double longitude, double altitude) throws CayenneException {
CayenneItem item = CayenneItem item = new CayenneItem(channel, ECayenneItem.GPS_LOCATION,
new CayenneItem(channel, ECayenneItem.GPS_LOCATION, new Double[] {latitude, longitude, altitude}); new Double[] { latitude, longitude, altitude });
addItem(item); addItem(item);
} }
private void addItem(CayenneItem item) throws CayenneException { private void addItem(CayenneItem item) throws CayenneException {
// verify that channel is unique // verify that channel is unique
int channel = item.getChannel(); int channel = item.getChannel();
if (channels.contains(channel)) { 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 // add the item
channels.add(channel); channels.add(channel);
message.add(item); message.add(item);
} }
/** /**
* Encodes the data into the supplied buffer. * Encodes the data into the supplied buffer.
* *
* @param maxSize maximum size of the message * @param maxSize maximum size of the message
* @return the length of data encoded * @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 { public byte[] encode(int maxSize) throws CayenneException {
return message.encode(maxSize); return message.encode(maxSize);
} }
@Override @Override
public String toString() { public String toString() {
return message.toString(); return message.toString();
} }
} }

Wyświetl plik

@ -10,8 +10,8 @@ public abstract class BaseFormatter implements IFormatter {
/** /**
* Gets an integer value from the byte buffer. * Gets an integer value from the byte buffer.
* *
* @param bb the byte buffer * @param bb the byte buffer
* @param n the number of bytes to get * @param n the number of bytes to get
* @param signed whether it should be interpreted as signed value or not * @param signed whether it should be interpreted as signed value or not
* @return the value * @return the value
*/ */
@ -24,12 +24,12 @@ public abstract class BaseFormatter implements IFormatter {
} }
return val; return val;
} }
/** /**
* Puts an integer value into a byte buffer * Puts an integer value into a byte buffer
* *
* @param bb the byte buffer * @param bb the byte buffer
* @param n the number of bytes to put * @param n the number of bytes to put
* @param value the value to encode * @param value the value to encode
*/ */
protected void putValue(ByteBuffer bb, int n, int value) { protected void putValue(ByteBuffer bb, int n, int value) {
@ -40,5 +40,5 @@ public abstract class BaseFormatter implements IFormatter {
shift -= 8; shift -= 8;
} }
} }
} }

Wyświetl plik

@ -7,7 +7,7 @@ import java.util.Locale;
* Formatter for cayenne items which represent a GPS position. * Formatter for cayenne items which represent a GPS position.
*/ */
public final class GpsFormatter extends BaseFormatter { public final class GpsFormatter extends BaseFormatter {
private static final double LAT_LON_SCALE = 1E-4; private static final double LAT_LON_SCALE = 1E-4;
private static final double ALT_SCALE = 1E-2; 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 lat = LAT_LON_SCALE * getValue(bb, 3, true);
double lon = LAT_LON_SCALE * getValue(bb, 3, true); double lon = LAT_LON_SCALE * getValue(bb, 3, true);
double alt = ALT_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 @Override
public String[] format(Number[] values) { public String[] format(Number[] values) {
return new String[] { return new String[] { String.format(Locale.ROOT, "%.4f", values[0]),
String.format(Locale.ROOT, "%.4f", values[0]), String.format(Locale.ROOT, "%.4f", values[1]), String.format(Locale.ROOT, "%.2f", values[2]) };
String.format(Locale.ROOT, "%.4f", values[1]),
String.format(Locale.ROOT, "%.2f", values[2])
};
} }
@Override @Override
public void encode(ByteBuffer bb, Number[] values) { 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[0].doubleValue() / LAT_LON_SCALE));
putValue(bb, 3, (int)Math.round(values[1].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[2].doubleValue() / ALT_SCALE));
} }
} }

Wyświetl plik

@ -3,7 +3,8 @@ package nl.sikken.bertrik.cayenne.formatter;
import java.nio.ByteBuffer; 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 { public interface IFormatter {
@ -15,9 +16,9 @@ public interface IFormatter {
*/ */
Number[] parse(ByteBuffer bb); Number[] parse(ByteBuffer bb);
/** /**
* Formats the data into an array of strings. * Formats the data into an array of strings. For example, for a GPS location it
* For example, for a GPS location it outputs: latitude in [0], longitude in [1], altitude in [2]. * outputs: latitude in [0], longitude in [1], altitude in [2].
* *
* @param values the value as number array * @param values the value as number array
* @return the string representation * @return the string representation
@ -27,7 +28,7 @@ public interface IFormatter {
/** /**
* Encodes the data into the byte buffer * 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 * @param values the values to encode
*/ */
void encode(ByteBuffer bb, Number[] values); void encode(ByteBuffer bb, Number[] values);

Wyświetl plik

@ -6,14 +6,14 @@ import java.util.Locale;
public final class IntegerFormatter extends BaseFormatter { public final class IntegerFormatter extends BaseFormatter {
private final int length; private final int length;
private final int size; private final int size;
private final boolean signed; private final boolean signed;
/** /**
* Constructor. * Constructor.
* *
* @param length the number of elements * @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 * @param signed if the element is signed
*/ */
public IntegerFormatter(int length, int size, boolean signed) { public IntegerFormatter(int length, int size, boolean signed) {
@ -22,29 +22,29 @@ public final class IntegerFormatter extends BaseFormatter {
this.signed = signed; this.signed = signed;
} }
@Override @Override
public Number[] parse(ByteBuffer bb) { public Number[] parse(ByteBuffer bb) {
Integer[] values = new Integer[length]; Integer[] values = new Integer[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
values[i] = getValue(bb, size, signed); values[i] = getValue(bb, size, signed);
} }
return values; return values;
} }
@Override @Override
public String[] format(Number[] values) { public String[] format(Number[] values) {
String[] formatted = new String[length]; String[] formatted = new String[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
formatted[i] = String.format(Locale.ROOT, "%d", values[i].intValue()); formatted[i] = String.format(Locale.ROOT, "%d", values[i].intValue());
} }
return formatted; return formatted;
} }
@Override @Override
public void encode(ByteBuffer bb, Number[] values) { public void encode(ByteBuffer bb, Number[] values) {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
putValue(bb, size, values[i].intValue()); putValue(bb, size, values[i].intValue());
} }
} }
} }

Wyświetl plik

@ -11,145 +11,162 @@ import org.slf4j.LoggerFactory;
* Unit tests for CayenneMessage. * Unit tests for CayenneMessage.
*/ */
public final class CayenneMessageTest { public final class CayenneMessageTest {
private static final Logger LOG = LoggerFactory.getLogger(CayenneMessageTest.class); private static final Logger LOG = LoggerFactory.getLogger(CayenneMessageTest.class);
private final int MAX_BUF_SIZE = 500; private final int MAX_BUF_SIZE = 500;
/** /**
* Verifies example from specification. * Verifies example from specification.
*
* @throws CayenneException in case of a parsing exception * @throws CayenneException in case of a parsing exception
*/ */
@Test @Test
public void testTwoTemperatureSensors() throws CayenneException { public void testTwoTemperatureSensors() throws CayenneException {
final byte[] data = {0x03, 0x67, 0x01, 0x10, 0x05, 0x67, 0x00, (byte) 0xFF}; byte[] data = { 0x03, 0x67, 0x01, 0x10, 0x05, 0x67, 0x00, (byte) 0xFF };
final CayenneMessage payload = CayenneMessage.parse(data); CayenneMessage payload = new CayenneMessage();
payload.parse(data);
LOG.info("payload: {}", payload); LOG.info("payload: {}", payload);
Assert.assertArrayEquals(new String[] {"27.2"}, payload.ofChannel(3).format()); Assert.assertArrayEquals(new String[] { "27.2" }, payload.ofChannel(3).format());
Assert.assertArrayEquals(new String[] {"25.5"}, payload.ofChannel(5).format()); Assert.assertArrayEquals(new String[] { "25.5" }, payload.ofChannel(5).format());
} }
/** /**
* Verifies example from specification. * Verifies example from specification.
*
* @throws CayenneException in case of a parsing exception * @throws CayenneException in case of a parsing exception
*/ */
@Test @Test
public void testTemperaturePlusAccel() throws CayenneException { public void testTemperaturePlusAccel() throws CayenneException {
final byte[] data = byte[] data = { 0x01, 0x67, (byte) 0xFF, (byte) 0xD7, 0x06, 0x71, 0x04, (byte) 0xD2, (byte) 0xFB, 0x2E, 0x00,
{0x01, 0x67, (byte) 0xFF, (byte) 0xD7, 0x06, 0x71, 0x04, (byte) 0xD2, (byte) 0xFB, 0x2E, 0x00, 0x00}; 0x00 };
final CayenneMessage payload = CayenneMessage.parse(data); CayenneMessage payload = new CayenneMessage();
payload.parse(data);
Assert.assertArrayEquals(new String[] {"-4.1"}, payload.ofChannel(1).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()); Assert.assertArrayEquals(new String[] { "1.234", "-1.234", "0.000" }, payload.ofChannel(6).format());
} }
/** /**
* Verifies example from specification. * Verifies example from specification.
*
* @throws CayenneException in case of a parsing exception * @throws CayenneException in case of a parsing exception
*/ */
@Test @Test
public void testGps() throws CayenneException { public void testGps() throws CayenneException {
final byte[] data = byte[] data = { 0x01, (byte) 0x88, 0x06, 0x076, 0x5f, (byte) 0xf2, (byte) 0x96, 0x0a, 0x00, 0x03, (byte) 0xe8 };
{0x01, (byte) 0x88, 0x06, 0x076, 0x5f, (byte) 0xf2, (byte) 0x96, 0x0a, 0x00, 0x03, (byte) 0xe8}; CayenneMessage payload = new CayenneMessage();
final CayenneMessage payload = CayenneMessage.parse(data); 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. * Verifies parsing of humidity value.
*
* @throws CayenneException in case of a parsing exception * @throws CayenneException in case of a parsing exception
*/ */
@Test @Test
public void testHumidity() throws CayenneException { public void testHumidity() throws CayenneException {
final byte[] data = {1, 104, 100}; byte[] data = { 1, 104, 100 };
final CayenneMessage payload = CayenneMessage.parse(data); CayenneMessage payload = new CayenneMessage();
payload.parse(data);
Assert.assertArrayEquals(new String[] {"50.0"}, payload.ofChannel(1).format());
Assert.assertArrayEquals(new String[] { "50.0" }, payload.ofChannel(1).format());
} }
/** /**
* Verifies parsing of some actual data from a sodaq one. * Verifies parsing of some actual data from a sodaq one.
*
* @throws CayenneException in case of a parsing exception * @throws CayenneException in case of a parsing exception
*/ */
@Test @Test
public void testActualData() throws CayenneException { public void testActualData() throws CayenneException {
final String base64 = "AYgH8CEAt1D//zgCAmDQA2cBDg=="; String base64 = "AYgH8CEAt1D//zgCAmDQA2cBDg==";
final byte[] data = Base64.getDecoder().decode(base64); byte[] data = Base64.getDecoder().decode(base64);
final CayenneMessage payload = CayenneMessage.parse(data); 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[] { "52.0225", "4.6928", "-2.00" }, payload.ofChannel(1).format());
Assert.assertArrayEquals(new String[] {"247.84"}, payload.ofChannel(2).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[] { "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 * @throws CayenneException in case of a parsing exception
*/ */
@Test @Test
public void testActualData2() throws CayenneException { public void testActualData2() throws CayenneException {
final String base64 = "AYgH8CEAt03/+VwCAgGfA2cA8A=="; String base64 = "AYgH8CEAt03/+VwCAgGfA2cA8A==";
final byte[] data = Base64.getDecoder().decode(base64); byte[] data = Base64.getDecoder().decode(base64);
final CayenneMessage payload = CayenneMessage.parse(data); CayenneMessage payload = new CayenneMessage();
payload.parse(data);
// verify we can get at the data by channel // 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[] { "52.0225", "4.6925", "-17.00" }, payload.ofChannel(1).format());
Assert.assertArrayEquals(new String[] {"4.15"}, payload.ofChannel(2).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[] { "24.0" }, payload.ofChannel(3).format());
// verify we can also get data by type // verify we can also get data by type
Assert.assertArrayEquals(new String[] {"52.0225", "4.6925", "-17.00"}, Assert.assertArrayEquals(new String[] { "52.0225", "4.6925", "-17.00" },
payload.ofType(ECayenneItem.GPS_LOCATION).format()); payload.ofType(ECayenneItem.GPS_LOCATION).format());
Assert.assertArrayEquals(new String[] {"4.15"}, payload.ofType(ECayenneItem.ANALOG_INPUT).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[] { "24.0" }, payload.ofType(ECayenneItem.TEMPERATURE).format());
// verify non-existing channel and type // verify non-existing channel and type
Assert.assertNull(payload.ofChannel(0)); Assert.assertNull(payload.ofChannel(0));
Assert.assertNull(payload.ofType(ECayenneItem.BAROMETER)); Assert.assertNull(payload.ofType(ECayenneItem.BAROMETER));
// verify toString method // verify toString method
Assert.assertNotNull(payload.toString()); Assert.assertNotNull(payload.toString());
} }
/** /**
* Verifies parsing an empty string. * Verifies parsing an empty string.
*
* @throws CayenneException in case of a parsing exception * @throws CayenneException in case of a parsing exception
*/ */
@Test @Test
public void testParseEmpty() throws CayenneException { 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()); Assert.assertTrue(payload.getItems().isEmpty());
} }
/** /**
* Verifies parsing a short buffer * Verifies parsing a short buffer
*
* @throws CayenneException in case of a parsing exception * @throws CayenneException in case of a parsing exception
*/ */
@Test(expected = CayenneException.class) @Test(expected = CayenneException.class)
public void testShortBuffer() throws CayenneException { 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. * Verifies parsing of a buffer containing a non-supported data type.
*
* @throws CayenneException in case of a parsing exception * @throws CayenneException in case of a parsing exception
*/ */
@Test(expected = CayenneException.class) @Test(expected = CayenneException.class)
public void testInvalidType() throws CayenneException { 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. * Verifies parsing of a buffer containing insufficient data during parsing.
*
* @throws CayenneException in case of a parsing exception * @throws CayenneException in case of a parsing exception
*/ */
@Test(expected = CayenneException.class) @Test(expected = CayenneException.class)
public void testShortData() throws CayenneException { public void testShortData() throws CayenneException {
CayenneMessage.parse(new byte[] {2, 1}); new CayenneMessage().parse(new byte[] { 2, 1 });
} }
/** /**
* Verifies encoding of a float value. * Verifies encoding of a float value.
* *
@ -157,15 +174,16 @@ public final class CayenneMessageTest {
*/ */
@Test @Test
public void encodeFloat() throws CayenneException { public void encodeFloat() throws CayenneException {
final CayenneMessage message = new CayenneMessage(); CayenneMessage message = new CayenneMessage();
message.add(new CayenneItem(1, ECayenneItem.ANALOG_INPUT, -12.34)); message.add(new CayenneItem(1, ECayenneItem.ANALOG_INPUT, -12.34));
final byte[] encoded = message.encode(MAX_BUF_SIZE); byte[] encoded = message.encode(MAX_BUF_SIZE);
final CayenneMessage decoded = CayenneMessage.parse(encoded); CayenneMessage decoded = new CayenneMessage();
decoded.parse(encoded);
Assert.assertEquals(-12.34, decoded.getItems().get(0).getValues()[0].doubleValue(), 0.01); Assert.assertEquals(-12.34, decoded.getItems().get(0).getValues()[0].doubleValue(), 0.01);
} }
/** /**
* Verifies encoding of a humidity value. * Verifies encoding of a humidity value.
* *
@ -173,18 +191,19 @@ public final class CayenneMessageTest {
*/ */
@Test @Test
public void encodeHumidity() throws CayenneException { public void encodeHumidity() throws CayenneException {
final CayenneMessage message = new CayenneMessage(); CayenneMessage message = new CayenneMessage();
message.add(new CayenneItem(1, ECayenneItem.HUMIDITY, 35.5)); 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(ECayenneItem.HUMIDITY, item.getType());
Assert.assertEquals(35.5, item.getValues()[0].doubleValue(), 0.1); Assert.assertEquals(35.5, item.getValues()[0].doubleValue(), 0.1);
Assert.assertEquals("35.5", item.format()[0]); Assert.assertEquals("35.5", item.format()[0]);
} }
/** /**
* Verifies encoding of a digital input. * Verifies encoding of a digital input.
* *
@ -192,17 +211,18 @@ public final class CayenneMessageTest {
*/ */
@Test @Test
public void testDigitalInput() throws CayenneException { public void testDigitalInput() throws CayenneException {
final CayenneMessage message = new CayenneMessage(); CayenneMessage message = new CayenneMessage();
message.add(new CayenneItem(1, ECayenneItem.DIGITAL_INPUT, 1)); 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(ECayenneItem.DIGITAL_INPUT, item.getType());
Assert.assertEquals(1, item.getValues()[0].intValue()); Assert.assertEquals(1, item.getValues()[0].intValue());
} }
/** /**
* Verifies encoding/decoding of a presence value (e.g. number of satellites) * Verifies encoding/decoding of a presence value (e.g. number of satellites)
* *
@ -210,17 +230,30 @@ public final class CayenneMessageTest {
*/ */
@Test @Test
public void testPresence() throws CayenneException { public void testPresence() throws CayenneException {
final CayenneMessage message = new CayenneMessage(); CayenneMessage message = new CayenneMessage();
message.add(new CayenneItem(1, ECayenneItem.PRESENCE, 7)); 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(ECayenneItem.PRESENCE, item.getType());
Assert.assertEquals(7, item.getValues()[0].intValue()); Assert.assertEquals(7, item.getValues()[0].intValue());
Assert.assertEquals("7", item.format()[0]); 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);
}
}

Wyświetl plik

@ -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));
}
}

Wyświetl plik

@ -9,11 +9,13 @@ import org.slf4j.LoggerFactory;
* Unit test for SimpleCayenne. * Unit test for SimpleCayenne.
*/ */
public final class SimpleCayenneTest { public final class SimpleCayenneTest {
private static final Logger LOG = LoggerFactory.getLogger(SimpleCayenneTest.class); 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 * @throws CayenneException in case of a problem encoding/decoding
*/ */
@Test @Test
@ -32,13 +34,14 @@ public final class SimpleCayenneTest {
cayenne.addRelativeHumidity(11, 50.0); cayenne.addRelativeHumidity(11, 50.0);
cayenne.addTemperature(12, 19.0); cayenne.addTemperature(12, 19.0);
LOG.info("Encoded message: {}", cayenne); LOG.info("Encoded message: {}", cayenne);
// encode it // encode it
byte[] data = cayenne.encode(500); byte[] data = cayenne.encode(500);
Assert.assertNotNull(data); Assert.assertNotNull(data);
// decode it // decode it
CayenneMessage message = CayenneMessage.parse(data); CayenneMessage message = new CayenneMessage();
message.parse(data);
Assert.assertEquals(12, message.getItems().size()); Assert.assertEquals(12, message.getItems().size());
Assert.assertEquals(3.82, message.ofType(ECayenneItem.ANALOG_INPUT).getValue().doubleValue(), 0.01); Assert.assertEquals(3.82, message.ofType(ECayenneItem.ANALOG_INPUT).getValue().doubleValue(), 0.01);
Assert.assertEquals(55, message.ofType(ECayenneItem.DIGITAL_INPUT).getValue().intValue()); 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(42, message.ofType(ECayenneItem.PRESENCE).getValue().intValue(), 42);
Assert.assertEquals(19.0, message.ofType(ECayenneItem.TEMPERATURE).getValue().doubleValue(), 0.1); Assert.assertEquals(19.0, message.ofType(ECayenneItem.TEMPERATURE).getValue().doubleValue(), 0.1);
} }
/** /**
* Verifies that a simple cayenne message with non-unique channels is rejected. * Verifies that a simple cayenne message with non-unique channels is rejected.
* *

Wyświetl plik

@ -7,21 +7,21 @@ import org.junit.Test;
public final class GpsFormatterTest { public final class GpsFormatterTest {
@Test @Test
public void testEncodeDecode() { public void testEncodeDecode() {
GpsFormatter formatter = new GpsFormatter(); GpsFormatter formatter = new GpsFormatter();
Double[] coords = new Double[] {52.0, 4.1, -3.5}; Double[] coords = new Double[] { 52.0, 4.1, -3.5 };
// encode // encode
ByteBuffer bb = ByteBuffer.allocate(100); ByteBuffer bb = ByteBuffer.allocate(100);
formatter.encode(bb, coords); 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);
}
} }

Wyświetl plik

@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import nl.sikken.bertrik.cayenne.CayenneException; import nl.sikken.bertrik.cayenne.CayenneException;
import nl.sikken.bertrik.cayenne.CayenneItem; import nl.sikken.bertrik.cayenne.CayenneItem;
import nl.sikken.bertrik.cayenne.CayenneMessage; import nl.sikken.bertrik.cayenne.CayenneMessage;
import nl.sikken.bertrik.cayenne.ECayennePayloadFormat;
import nl.sikken.bertrik.hab.ttn.TtnMessage; import nl.sikken.bertrik.hab.ttn.TtnMessage;
/** /**
@ -148,7 +149,9 @@ public final class PayloadDecoder {
try { try {
Instant time = message.getMetaData().getTime(); Instant time = message.getMetaData().getTime();
Sentence sentence = new Sentence(callSign, counter, time); 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 // add all items, in the order they appear in the cayenne message
for (CayenneItem item : cayenne.getItems()) { for (CayenneItem item : cayenne.getItems()) {