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.
*/
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);
}
}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -95,5 +95,5 @@ public enum ECayenneItem {
public void encode(ByteBuffer bb, Number[] 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;
/**
* 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();
}
}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

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.
*/
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.
*

Wyświetl plik

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

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.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()) {