From 7ef2ded7aefbc02d994a5c9e57293892442046fa Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Fri, 10 Sep 2021 16:28:45 +0200 Subject: [PATCH] Add LwM2m Test Object (Id:3441) to Leshan demos. --- .../leshan/client/object/LwM2mTestObject.java | 237 ++++++++++++++++ .../client/resource/BaseInstanceEnabler.java | 8 + .../resource/SimpleInstanceEnabler.java | 63 ++++- .../leshan/client/demo/LeshanClientDemo.java | 4 + .../leshan/core/demo/LwM2mDemoConstant.java | 30 +- .../src/main/resources/models/3441.xml | 262 ++++++++++++++++++ .../eclipse/leshan/core/node/ObjectLink.java | 2 +- 7 files changed, 587 insertions(+), 19 deletions(-) create mode 100644 leshan-client-core/src/main/java/org/eclipse/leshan/client/object/LwM2mTestObject.java create mode 100644 leshan-core-demo/src/main/resources/models/3441.xml diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/LwM2mTestObject.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/LwM2mTestObject.java new file mode 100644 index 0000000000..4859007176 --- /dev/null +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/LwM2mTestObject.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2021 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.object; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import org.eclipse.leshan.client.resource.SimpleInstanceEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.node.LwM2mResourceInstance; +import org.eclipse.leshan.core.node.ObjectLink; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.util.Hex; +import org.eclipse.leshan.core.util.RandomStringUtils; + +/** + * This aims to implements LWM2M Test Object(Id:3441) from LWM2M registry. + */ +public class LwM2mTestObject extends SimpleInstanceEnabler { + + private Random random = new Random(System.currentTimeMillis()); + + public LwM2mTestObject() { + super(); + initialValues = new HashMap<>(); + + initialValues.put(4, Collections.EMPTY_MAP); + + // single + initialValues.put(110, "initial value"); + initialValues.put(120, 64l); + initialValues.put(130, 3.14159d); + initialValues.put(140, true); + initialValues.put(150, Hex.decodeHex("0123456789ABCDEF".toCharArray())); + initialValues.put(160, new Date(946684800000l)); + initialValues.put(170, new ObjectLink(3, 0)); + + // multi + initialValues.put(1110, LwM2mResourceInstance.newStringInstance(0, "initial value")); + initialValues.put(1120, LwM2mResourceInstance.newIntegerInstance(0, 64l)); + initialValues.put(1130, LwM2mResourceInstance.newFloatInstance(0, 3.14159d)); + initialValues.put(1140, LwM2mResourceInstance.newBooleanInstance(0, true)); + initialValues.put(1150, + LwM2mResourceInstance.newBinaryInstance(0, Hex.decodeHex("0123456789ABCDEF".toCharArray()))); + initialValues.put(1160, LwM2mResourceInstance.newDateInstance(0, new Date(946684800000l))); + initialValues.put(1170, LwM2mResourceInstance.newObjectLinkInstance(0, new ObjectLink(3, 0))); + } + + private void clearValues() { + Map clearedValues = new HashMap<>(); + + clearedValues.put(4, Collections.EMPTY_MAP); + + // single + clearedValues.put(110, ""); + clearedValues.put(120, 0l); + clearedValues.put(130, 0.0d); + clearedValues.put(140, false); + clearedValues.put(150, new byte[0]); + clearedValues.put(160, new Date(0l)); + clearedValues.put(170, new ObjectLink()); + + // multi + clearedValues.put(1110, Collections.EMPTY_MAP); + clearedValues.put(1120, Collections.EMPTY_MAP); + clearedValues.put(1130, Collections.EMPTY_MAP); + clearedValues.put(1140, Collections.EMPTY_MAP); + clearedValues.put(1150, Collections.EMPTY_MAP); + clearedValues.put(1160, Collections.EMPTY_MAP); + clearedValues.put(1170, Collections.EMPTY_MAP); + + fireResourcesChange(applyValues(clearedValues)); + } + + private void resetValues() { + fireResourcesChange(applyValues(initialValues)); + } + + private void randomValues() { + + Map randomValues = new HashMap<>(); + randomValues.put(4, generateResourceInstances(new StringGenerator())); + + // single + randomValues.put(110, new StringGenerator().generate()); + randomValues.put(120, new LongGenerator().generate()); + randomValues.put(130, new DoubleGenerator().generate()); + randomValues.put(140, new BooleanGenerator().generate()); + randomValues.put(150, new BytesGenerator().generate()); + randomValues.put(160, new DateGenerator().generate()); + randomValues.put(170, new ObjectLinkGenerator().generate()); + + // multi + randomValues.put(1110, generateResourceInstances(new StringGenerator())); + randomValues.put(1120, generateResourceInstances(new LongGenerator())); + randomValues.put(1130, generateResourceInstances(new DoubleGenerator())); + randomValues.put(1140, generateResourceInstances(new BooleanGenerator())); + randomValues.put(1150, generateResourceInstances(new BytesGenerator())); + randomValues.put(1160, generateResourceInstances(new DateGenerator())); + randomValues.put(1170, generateResourceInstances(new ObjectLinkGenerator())); + + fireResourcesChange(applyValues(randomValues)); + } + + private void storeParams(String params) { + // TODO add a real Execute Argument Parser + // https://github.com/eclipse/leshan/issues/1097 + Map arguments = new HashMap<>(); + if (params != null && !params.isEmpty()) { + String[] args = params.split(","); + + for (String arg : args) { + String[] part = arg.split("="); + Integer id = Integer.parseInt(part[0]); + if (part.length > 1) { + arguments.put(id, part[1].substring(1, part[1].length() - 1)); + } else { + arguments.put(id, ""); + } + } + } + + Map newParams = new HashMap<>(); + newParams.put(4, arguments); + fireResourcesChange(applyValues(newParams)); + } + + private Map generateResourceInstances(ValueGenerator generator) { + HashMap instances = new HashMap<>(); + for (int i = 0; i < random.nextInt(10); i++) { + instances.put(i, generator.generate()); + } + return instances; + } + + @Override + public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) { + switch (resourceid) { + case 0: + resetValues(); + return ExecuteResponse.success(); + case 1: + randomValues(); + return ExecuteResponse.success(); + case 2: + clearValues(); + return ExecuteResponse.success(); + case 3: + storeParams(params); + return ExecuteResponse.success(); + default: + return super.execute(identity, resourceid, params); + } + } + + /* ***************** Random Generator ****************** */ + + static interface ValueGenerator { + T generate(); + } + + class StringGenerator implements ValueGenerator { + @Override + public String generate() { + return RandomStringUtils.randomAlphanumeric(random.nextInt(20)); + } + } + + class LongGenerator implements ValueGenerator { + @Override + public Long generate() { + return random.nextLong(); + } + } + + class DoubleGenerator implements ValueGenerator { + @Override + public Double generate() { + return random.nextDouble(); + } + } + + class BooleanGenerator implements ValueGenerator { + @Override + public Boolean generate() { + return random.nextInt(2) == 1; + } + } + + class BytesGenerator implements ValueGenerator { + @Override + public byte[] generate() { + byte[] bytes = new byte[random.nextInt(20)]; + random.nextBytes(bytes); + return bytes; + } + } + + class DateGenerator implements ValueGenerator { + @Override + public Date generate() { + // try to generate random date which is not so out of date. + long rd = System.currentTimeMillis(); + + // remove year randomly + rd -= (random.nextInt(20) - 10) * 31557600000l; + + // add some variance in the year + rd += random.nextInt(3155760) * 1000l; + + return new Date(rd); + } + } + + class ObjectLinkGenerator implements ValueGenerator { + @Override + public ObjectLink generate() { + return new ObjectLink(random.nextInt(ObjectLink.MAXID - 1), random.nextInt(ObjectLink.MAXID - 1)); + } + } +} \ No newline at end of file diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseInstanceEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseInstanceEnabler.java index b9f4fc436e..b6bc10bd35 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseInstanceEnabler.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/BaseInstanceEnabler.java @@ -131,6 +131,14 @@ protected LwM2mPath getResourcePath(int resourceId) { return new LwM2mPath(getModel().id, getId(), resourceId); } + protected LwM2mPath[] getResourcePaths(int... resourceIds) { + LwM2mPath[] paths = new LwM2mPath[resourceIds.length]; + for (int i = 0; i < paths.length; i++) { + paths[i] = getResourcePath(resourceIds[i]); + } + return paths; + } + protected LwM2mPath getResourceInstancePath(int resourceId, int resourceInstanceId) { return new LwM2mPath(getModel().id, getId(), resourceId, resourceInstanceId); } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/SimpleInstanceEnabler.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/SimpleInstanceEnabler.java index b576e6537e..bf8de70587 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/SimpleInstanceEnabler.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/resource/SimpleInstanceEnabler.java @@ -22,9 +22,11 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.core.model.ObjectModel; @@ -143,6 +145,53 @@ public void setModel(ObjectModel objectModel) { } } + protected LwM2mPath[] applyValues(Map values) { + Set changingResources = new HashSet<>(); + + for (ResourceModel resourceModel : getModel().resources.values()) { + if (resourceModel.operations.isReadable()) { + Object value = values.get(resourceModel.id); + // create the resource + LwM2mResource newResource = null; + if (value != null) { + if (resourceModel.multiple) { + // handle multi instances + if (value instanceof LwM2mResourceInstance) { + newResource = new LwM2mMultipleResource(resourceModel.id, resourceModel.type, + (LwM2mResourceInstance) value); + changingResources.add(getResourcePath(resourceModel.id)); + } else if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map val = (Map) value; + newResource = LwM2mMultipleResource.newResource(resourceModel.id, val, resourceModel.type); + } + } else { + // handle single instances + newResource = LwM2mSingleResource.newResource(resourceModel.id, value, resourceModel.type); + } + } + // add the resource + if (newResource != null) { + if (newResource.isMultiInstances()) { + for (Integer instanceID : newResource.getInstances().keySet()) { + changingResources.add(getResourceInstancePath(newResource.getId(), instanceID)); + } + } else { + changingResources.add(getResourcePath(newResource.getId())); + } + LwM2mResource previous = resources.put(newResource.getId(), newResource); + if (previous.isMultiInstances()) { + for (Integer instanceID : previous.getInstances().keySet()) { + changingResources.add(getResourceInstancePath(previous.getId(), instanceID)); + } + } + } + } + } + + return changingResources.toArray(new LwM2mPath[changingResources.size()]); + } + protected LwM2mResource initializeResource(ObjectModel objectModel, ResourceModel resourceModel) { if (!resourceModel.multiple) { return initializeSingleResource(objectModel, resourceModel); @@ -186,11 +235,19 @@ protected LwM2mSingleResource initializeSingleResource(ObjectModel objectModel, protected LwM2mMultipleResource initializeMultipleResource(ObjectModel objectModel, ResourceModel resourceModel) { if (initialValues != null) { - @SuppressWarnings("unchecked") - Map initialValue = (Map) initialValues.get(resourceModel.id); + Object initialValue = initialValues.get(resourceModel.id); if (initialValue == null) return null; - return LwM2mMultipleResource.newResource(resourceModel.id, initialValue, resourceModel.type); + + if (initialValue instanceof LwM2mResourceInstance) { + return new LwM2mMultipleResource(resourceModel.id, resourceModel.type, + (LwM2mResourceInstance) initialValue); + } else if (initialValue instanceof Map) { + @SuppressWarnings("unchecked") + Map val = (Map) initialValue; + return LwM2mMultipleResource.newResource(resourceModel.id, val, resourceModel.type); + } + return null; } else { // no default value Map emptyMap = Collections.emptyMap(); diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java index 239ee3b576..1909a64d91 100644 --- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java +++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java @@ -42,6 +42,7 @@ import org.eclipse.leshan.client.demo.cli.interactive.InteractiveCommands; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Server; +import org.eclipse.leshan.client.object.LwM2mTestObject; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.resource.listener.ObjectsListenerAdapter; @@ -71,6 +72,7 @@ public class LeshanClientDemo { private static final Logger LOG = LoggerFactory.getLogger(LeshanClientDemo.class); private static final int OBJECT_ID_TEMPERATURE_SENSOR = 3303; + private static final int OBJECT_ID_LWM2M_TEST_OBJECT = 3441; public static void main(String[] args) { @@ -182,6 +184,8 @@ public static LeshanClient createClient(LeshanClientDemoCLI cli, LwM2mModelRepos initializer.setInstancesForObject(DEVICE, new MyDevice()); initializer.setInstancesForObject(LOCATION, locationInstance); initializer.setInstancesForObject(OBJECT_ID_TEMPERATURE_SENSOR, new RandomTemperatureSensor()); + initializer.setInstancesForObject(OBJECT_ID_LWM2M_TEST_OBJECT, new LwM2mTestObject()); + List enablers = initializer.createAll(); // Create CoAP Config diff --git a/leshan-core-demo/src/main/java/org/eclipse/leshan/core/demo/LwM2mDemoConstant.java b/leshan-core-demo/src/main/java/org/eclipse/leshan/core/demo/LwM2mDemoConstant.java index 796bd6c1e1..26eccbc49f 100644 --- a/leshan-core-demo/src/main/java/org/eclipse/leshan/core/demo/LwM2mDemoConstant.java +++ b/leshan-core-demo/src/main/java/org/eclipse/leshan/core/demo/LwM2mDemoConstant.java @@ -43,19 +43,19 @@ public class LwM2mDemoConstant { "3407.xml", "3408.xml", "3410.xml", "3411.xml", "3412.xml", "3413.xml", "3414.xml", "3415.xml", "3416.xml", "3417.xml", "3418.xml", "3419.xml", "3420.xml", "3421.xml", "3423.xml", "3424.xml", "3425.xml", "3426.xml", "3427.xml", "3428.xml", "3429.xml", "3430.xml", "3431.xml", "3432.xml", "3433.xml", "3434.xml", "3435.xml", - "3436.xml", "3437.xml", "3438.xml", "3439.xml", "10241.xml", "10242.xml", "10243.xml", "10244.xml", - "10245.xml", "10246.xml", "10247.xml", "10248.xml", "10249.xml", "10250.xml", "10251.xml", "10252.xml", - "10253.xml", "10254.xml", "10255.xml", "10256.xml", "10257.xml", "10258.xml", "10259.xml", "10260-1_0.xml", - "10260.xml", "10262.xml", "10263.xml", "10264.xml", "10265.xml", "10266.xml", "10267.xml", "10268.xml", - "10269.xml", "10270.xml", "10271.xml", "10272.xml", "10273.xml", "10274.xml", "10275.xml", "10276.xml", - "10277.xml", "10278.xml", "10279.xml", "10280.xml", "10281.xml", "10282.xml", "10283.xml", "10284.xml", - "10286.xml", "10290.xml", "10291.xml", "10292.xml", "10299.xml", "10300.xml", "10308-1_0.xml", "10308.xml", - "10309.xml", "10311-1_0.xml", "10311.xml", "10313.xml", "10314.xml", "10315.xml", "10316.xml", "10318.xml", - "10319.xml", "10320.xml", "10322.xml", "10323.xml", "10324.xml", "10326.xml", "10327.xml", "10328.xml", - "10329.xml", "10330.xml", "10331.xml", "10332.xml", "10333.xml", "10334.xml", "10335.xml", "10336.xml", - "10337.xml", "10338.xml", "10339.xml", "10340.xml", "10341.xml", "10342.xml", "10343.xml", "10344.xml", - "10345.xml", "10346.xml", "10347.xml", "10348.xml", "10349.xml", "10350.xml", "10351.xml", "10352.xml", - "10353.xml", "10354.xml", "10355.xml", "10356.xml", "10357.xml", "10358.xml", "10359.xml", "10360.xml", - "10361.xml", "10362.xml", "10363.xml", "10364.xml", "10365.xml", "10366.xml", "10368.xml", "10369.xml", - "10371.xml", "18830.xml", "18831.xml", }; + "3436.xml", "3437.xml", "3438.xml", "3439.xml", "3441.xml", "10241.xml", "10242.xml", "10243.xml", + "10244.xml", "10245.xml", "10246.xml", "10247.xml", "10248.xml", "10249.xml", "10250.xml", "10251.xml", + "10252.xml", "10253.xml", "10254.xml", "10255.xml", "10256.xml", "10257.xml", "10258.xml", "10259.xml", + "10260-1_0.xml", "10260.xml", "10262.xml", "10263.xml", "10264.xml", "10265.xml", "10266.xml", "10267.xml", + "10268.xml", "10269.xml", "10270.xml", "10271.xml", "10272.xml", "10273.xml", "10274.xml", "10275.xml", + "10276.xml", "10277.xml", "10278.xml", "10279.xml", "10280.xml", "10281.xml", "10282.xml", "10283.xml", + "10284.xml", "10286.xml", "10290.xml", "10291.xml", "10292.xml", "10299.xml", "10300.xml", "10308-1_0.xml", + "10308.xml", "10309.xml", "10311-1_0.xml", "10311.xml", "10313.xml", "10314.xml", "10315.xml", "10316.xml", + "10318.xml", "10319.xml", "10320.xml", "10322.xml", "10323.xml", "10324.xml", "10326.xml", "10327.xml", + "10328.xml", "10329.xml", "10330.xml", "10331.xml", "10332.xml", "10333.xml", "10334.xml", "10335.xml", + "10336.xml", "10337.xml", "10338.xml", "10339.xml", "10340.xml", "10341.xml", "10342.xml", "10343.xml", + "10344.xml", "10345.xml", "10346.xml", "10347.xml", "10348.xml", "10349.xml", "10350.xml", "10351.xml", + "10352.xml", "10353.xml", "10354.xml", "10355.xml", "10356.xml", "10357.xml", "10358.xml", "10359.xml", + "10360.xml", "10361.xml", "10362.xml", "10363.xml", "10364.xml", "10365.xml", "10366.xml", "10368.xml", + "10369.xml", "10371.xml", "18830.xml", "18831.xml", }; } diff --git a/leshan-core-demo/src/main/resources/models/3441.xml b/leshan-core-demo/src/main/resources/models/3441.xml new file mode 100644 index 0000000000..04ef95a5b0 --- /dev/null +++ b/leshan-core-demo/src/main/resources/models/3441.xml @@ -0,0 +1,262 @@ + + + + + LwM2M Specification Test Object + + 3441 + urn:oma:lwm2m:ext:3441 + 1.0 + 1.0 + Multiple + Optional + + + Reset values + E + Single + Optional + + + + Reset all resources of this object with their initial value. + + + Randomize values + E + Single + Optional + + + + + + + + + Clear values + E + Single + Optional + + + + + + + + + Exec With Arguments + E + Single + Optional + + + + + + + + + Arguments List + R + Multiple + Optional + String + + + List of Arguments from last execute on "Exec With Arguments"(3) resource. + + + + String Value + RW + Single + Optional + String + + + Initial value must be "initial value". + + + + Integer Value + RW + Single + Optional + Integer + + + Initial value must be "1024". + + + + Float Value + RW + Single + Optional + Float + + + Initial value must be "3.14159". + + + + Boolean Value + RW + Single + Optional + Boolean + + + Initial value must be "true". + + + Opaque Value + RW + Single + Optional + Opaque + + + Initial value must be the bytes sequence "0123456789ABCDEF" (Hexadecimal notation). + + + + Time Value + RW + Single + Optional + Time + + + Initial value must be the time to an 1st, 2000 in the UTC time zone. (Timestamp value : 946684800) + + + ObjLink Value + RW + Single + Optional + Objlnk + + + Initial value must be a link to instance 0 of Device Object 3 (3:0). + + + Multiple String Value + RW + Multiple + Optional + String + + + Initial value must be 1 instance with ID 0 and value "initial value". + + + Multiple Integer Value + RW + Multiple + Optional + Integer + + + Initial value must be 1 instance with ID 0 and value "1024". + + + Multiple Float Value + RW + Multiple + Optional + Float + + + Initial value must be 1 instance with ID 0 and value "3.14159". + + + Multiple Boolean Value + RW + Multiple + Optional + Boolean + + + Initial value must be 1 instance with ID 0 and value "true". + + + Multiple Opaque Value + RW + Multiple + Optional + Opaque + + + Initial value must be 1 instance with ID 0 and value "0123456789ABCDEF"(Hexadecimal notation of the bytes sequence). + + + Multiple Time Value + RW + Multiple + Optional + Time + + + Initial value must be 1 instance with ID 0 and value 1st, 2000 in the UTC time zone (Timestamp value : 946684800). + + + Multiple ObjLink Value + RW + Multiple + Optional + Objlnk + + + Initial value must be 1 instance with ID 0 and value "3:0". + + + + + diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/ObjectLink.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/ObjectLink.java index c8b27fcdc1..eb52677597 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/ObjectLink.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/ObjectLink.java @@ -28,7 +28,7 @@ * MAX-ID = 65535. */ public class ObjectLink { - private final static int MAXID = 65535; + public final static int MAXID = 65535; private final int objectId; private final int objectInstanceId;