/**
* Copyright © 2016 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.coap.client;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@Slf4j
public class DeviceEmulator {
public static final String SN = "SN-" + new Random().nextInt(1000);
public static final String MODEL = "Model " + new Random().nextInt(1000);
private static final ObjectMapper mapper = new ObjectMapper();
private final String host;
private final int port;
private final String token;
private CoapClient attributesClient;
private CoapClient telemetryClient;
private CoapClient rpcClient;
private String[] keys;
private ExecutorService executor = Executors.newFixedThreadPool(1);
private AtomicInteger seq = new AtomicInteger(100);
private DeviceEmulator(String host, int port, String token, String keys) {
this.host = host;
this.port = port;
this.token = token;
this.attributesClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.ATTRIBUTES));
this.telemetryClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.TELEMETRY));
this.rpcClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.RPC));
this.keys = keys.split(",");
}
public void start() {
executor.submit(new Runnable() {
@Override
public void run() {
try {
sendObserveRequest(rpcClient);
while (!Thread.interrupted()) {
sendRequest(attributesClient, createAttributesRequest());
sendRequest(telemetryClient, createTelemetryRequest());
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendRequest(CoapClient client, JsonNode request) throws JsonProcessingException {
CoapResponse telemetryResponse = client.setTimeout(60000).post(mapper.writeValueAsString(request),
MediaTypeRegistry.APPLICATION_JSON);
log.info("Response: {}, {}", telemetryResponse.getCode(), telemetryResponse.getResponseText());
}
private void sendObserveRequest(CoapClient client) throws JsonProcessingException {
client.observe(new CoapHandler() {
@Override
public void onLoad(CoapResponse coapResponse) {
log.info("Command: {}, {}", coapResponse.getCode(), coapResponse.getResponseText());
try {
JsonNode node = mapper.readTree(coapResponse.getResponseText());
int requestId = node.get("id").asInt();
String method = node.get("method").asText();
ObjectNode params = (ObjectNode) node.get("params");
ObjectNode response = mapper.createObjectNode();
response.put("id", requestId);
response.set("response", params);
log.info("Command Response: {}, {}", requestId, mapper.writeValueAsString(response));
CoapClient commandResponseClient = new CoapClient(getFeatureTokenUrl(host, port, token, FeatureType.RPC));
commandResponseClient.post(new CoapHandler() {
@Override
public void onLoad(CoapResponse response) {
log.info("Command Response Ack: {}, {}", response.getCode(), response.getResponseText());
}
@Override
public void onError() {
}
}, mapper.writeValueAsString(response), MediaTypeRegistry.APPLICATION_JSON);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError() {
}
});
}
});
}
private ObjectNode createAttributesRequest() {
ObjectNode element = mapper.createObjectNode();
element.put("serialNumber", SN);
element.put("model", MODEL);
return element;
}
private ArrayNode createTelemetryRequest() {
ArrayNode rootNode = mapper.createArrayNode();
for (String key : keys) {
ObjectNode element = mapper.createObjectNode();
element.put(key, seq.incrementAndGet());
rootNode.add(element);
}
return rootNode;
}
protected void stop() {
executor.shutdownNow();
}
public static void main(String args[]) {
if (args.length != 4) {
System.out.println("Usage: java -jar " + DeviceEmulator.class.getSimpleName() + ".jar host port device_token keys");
}
final DeviceEmulator emulator = new DeviceEmulator(args[0], Integer.parseInt(args[1]), args[2], args[3]);
emulator.start();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
emulator.stop();
}
});
}
private String getFeatureTokenUrl(String host, int port, String token, FeatureType featureType) {
return getBaseUrl(host, port) + token + "/" + featureType.name().toLowerCase();
}
private String getBaseUrl(String host, int port) {
return "coap://" + host + ":" + port + "/api/v1/";
}
}