/** * * Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com * * This file is part of Freedomotic * * This Program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2, or (at your option) any later version. * * This Program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * Freedomotic; see the file COPYING. If not, see * <http://www.gnu.org/licenses/>. */ /** * @author Mauro Cicolella */ package com.freedomotic.plugins.devices.mqttbroker; import com.freedomotic.api.EventTemplate; import com.freedomotic.api.Protocol; import com.freedomotic.events.ProtocolRead; import com.freedomotic.exceptions.PluginStartupException; import com.freedomotic.exceptions.UnableToExecuteException; import com.freedomotic.reactions.Command; import com.freedomotic.settings.Info; import io.moquette.BrokerConstants; import io.moquette.interception.AbstractInterceptHandler; import io.moquette.interception.InterceptHandler; import io.moquette.interception.messages.InterceptPublishMessage; import io.moquette.server.Server; import io.moquette.server.config.ClasspathConfig; import io.moquette.server.config.IConfig; import io.moquette.server.config.MemoryConfig; import java.io.File; import java.io.IOException; import static java.util.Arrays.asList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Mauro Cicolella */ public class MQTTBroker extends Protocol { private static final Logger LOG = LoggerFactory.getLogger(MQTTBroker.class.getName()); private final String BROKER_HOST = configuration.getStringProperty("broker-host", "0.0.0.0"); private final Integer BROKER_PORT = configuration.getIntProperty("broker-port", 1883); private Server mqttBroker; private IConfig config; private List<? extends InterceptHandler> userHandlers; private Map<String, MqttTopic> topics = new HashMap<String, MqttTopic>(); /** * */ public MQTTBroker() { super("MQTT broker", "/mqtt-broker/mqtt-broker-manifest.xml"); setPollingWait(-1); // onRun() disabled } @Override protected void onShowGui() { } @Override protected void onHideGui() { } @Override protected void onRun() { } @Override protected void onStart() throws PluginStartupException { // load topics from manifest file loadTopics(); mqttBroker = new Server(); Properties props = new Properties(); // get properties from manifest file props.setProperty(BrokerConstants.PORT_PROPERTY_NAME, Integer.toString(BROKER_PORT)); props.setProperty(BrokerConstants.HOST_PROPERTY_NAME, BROKER_HOST); props.setProperty(BrokerConstants.PASSWORD_FILE_PROPERTY_NAME, Info.PATHS.PATH_DEVICES_FOLDER + "/mqtt-broker/config/password_file.conf"); props.setProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, Info.PATHS.PATH_DEVICES_FOLDER + "/mqtt-broker/config/moquette_store.mapdb"); config = new MemoryConfig(props); userHandlers = asList(new PublisherListener()); try { mqttBroker.startServer(config, userHandlers); } catch (IOException ex) { throw new PluginStartupException("Plugin can't start for an IOException.", ex); } setDescription("MQTT broker listening to " + config.getProperty(BrokerConstants.HOST_PROPERTY_NAME) + ":" + config.getProperty(BrokerConstants.PORT_PROPERTY_NAME)); LOG.info("MQTT broker started"); // bind a shutdown hook Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { LOG.info("Stopping MQTT broker"); mqttBroker.stopServer(); LOG.info("MQTT broker stopped"); } }); } @Override protected void onStop() { mqttBroker.stopServer(); setDescription("MQTT broker stopped"); LOG.info("MQTT broker stopped"); } @Override protected void onCommand(Command c) throws IOException, UnableToExecuteException { } @Override protected boolean canExecute(Command c) { //don't mind this method for now throw new UnsupportedOperationException("Not supported yet."); } @Override protected void onEvent(EventTemplate event) { //don't mind this method for now throw new UnsupportedOperationException("Not supported yet."); } /** * Sends a Freedomotic event. * * @param topic mqtt publishing topic * @param value message payload */ private void sendEvent(String topic, String payload) { ProtocolRead event = new ProtocolRead(this, "mqtt-broker", topic); event.addProperty("mqtt.topic", topic); // check if there is a tuple for this topic MqttTopic mqttTopic = topics.get(topic); if (mqttTopic != null) { String[] fields = payload.split(mqttTopic.getFieldsDelimiter()); for (int i = 0; i < mqttTopic.getNumberOfFields(); i++) { event.addProperty("mqtt.payload.field" + (i + 1), fields[i]); } } event.addProperty("mqtt.payload", payload); notifyEvent(event); } /** * This class is used to listen to messages on topics and manage them. * */ class PublisherListener extends AbstractInterceptHandler { @Override public void onPublish(InterceptPublishMessage msg) { String topic = msg.getTopicName(); String payload = new String(msg.getPayload().array()); LOG.info("Received on topic: [{}] content: [{}]", topic, payload); sendEvent(topic, payload); } } /** * */ private void loadTopics() { for (int i = 0; i < configuration.getTuples().size(); i++) { String topicPath; String fieldsDelimiter; Integer numberOfFields; topicPath = configuration.getTuples().getStringProperty(i, "topic-path", ""); fieldsDelimiter = configuration.getTuples().getStringProperty(i, "fields-delimiter", ""); numberOfFields = configuration.getTuples().getIntProperty(i, "number-of-fields", 0); MqttTopic mqttTopic = new MqttTopic(topicPath, numberOfFields, fieldsDelimiter); topics.put(mqttTopic.getTopicPath(), mqttTopic); } } }