Java tutorial
/** * Copyright 2016-2018 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.rule.engine.mqtt; import io.netty.buffer.Unpooled; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.util.concurrent.Future; import lombok.extern.slf4j.Slf4j; import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttConnectResult; import org.springframework.util.StringUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.net.ssl.SSLException; import java.nio.charset.Charset; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @Slf4j @RuleNode(type = ComponentType.EXTERNAL, name = "mqtt", configClazz = TbMqttNodeConfiguration.class, nodeDescription = "Publish messages to the MQTT broker", nodeDetails = "Will publish message payload to the MQTT broker with QoS <b>AT_LEAST_ONCE</b>.", uiResources = { "static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css" }, configDirective = "tbActionNodeMqttConfig", icon = "call_split") public class TbMqttNode implements TbNode { private static final Charset UTF8 = Charset.forName("UTF-8"); private static final String ERROR = "error"; private TbMqttNodeConfiguration config; private EventLoopGroup eventLoopGroup; private MqttClient mqttClient; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { try { this.config = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class); this.eventLoopGroup = new NioEventLoopGroup(); this.mqttClient = initClient(); } catch (Exception e) { throw new TbNodeException(e); } } @Override public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { String topic = TbNodeUtils.processPattern(this.config.getTopicPattern(), msg.getMetaData()); this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE) .addListener(future -> { if (future.isSuccess()) { ctx.tellNext(msg, TbRelationTypes.SUCCESS); } else { TbMsg next = processException(ctx, msg, future.cause()); ctx.tellFailure(next, future.cause()); } }); } private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); } @Override public void destroy() { if (this.mqttClient != null) { this.mqttClient.disconnect(); } if (this.eventLoopGroup != null) { this.eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS); } } private MqttClient initClient() throws Exception { Optional<SslContext> sslContextOpt = initSslContext(); MqttClientConfig config = sslContextOpt.isPresent() ? new MqttClientConfig(sslContextOpt.get()) : new MqttClientConfig(); if (!StringUtils.isEmpty(this.config.getClientId())) { config.setClientId(this.config.getClientId()); } config.setCleanSession(this.config.isCleanSession()); this.config.getCredentials().configure(config); MqttClient client = MqttClient.create(config, null); client.setEventLoop(this.eventLoopGroup); Future<MqttConnectResult> connectFuture = client.connect(this.config.getHost(), this.config.getPort()); MqttConnectResult result; try { result = connectFuture.get(this.config.getConnectTimeoutSec(), TimeUnit.SECONDS); } catch (TimeoutException ex) { connectFuture.cancel(true); client.disconnect(); String hostPort = this.config.getHost() + ":" + this.config.getPort(); throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s.", hostPort)); } if (!result.isSuccess()) { connectFuture.cancel(true); client.disconnect(); String hostPort = this.config.getHost() + ":" + this.config.getPort(); throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s. Result code is: %s", hostPort, result.getReturnCode())); } return client; } private Optional<SslContext> initSslContext() throws SSLException { Optional<SslContext> result = this.config.getCredentials().initSslContext(); if (this.config.isSsl() && !result.isPresent()) { result = Optional.of(SslContextBuilder.forClient().build()); } return result; } }