diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 8a0258b9..64f99a6b 100644 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -28,13 +28,13 @@ using namespace std; static const struct argp_option g_mqtt_argp_options[] = { {NULL, 0, NULL, 0, "MQTT options:", 1 }, {"mqttport", 1, "PORT", 0, "Connect to MQTT broker on PORT, 0 to disable [0]", 0 }, - {"mqtttopic", 2, "TOPIC", 0, "Use MQTT TOPIC (prefix if without wildcards) [ebusd]", 0 }, + {"mqtttopic", 2, "TOPIC", 0, "Use MQTT TOPIC (prefix before /%circuit/%name or complete format) [ebusd]", 0 }, {NULL, 0, NULL, 0, NULL, 0 }, }; static uint16_t g_port = 0; //!< optional port of MQTT broker, 0 to disable [0] -static const char* g_topic = PACKAGE; //!< MQTT topic to use (prefix if without wildcards) [ebusd/$circuit/$name] +static const char* g_topic = PACKAGE; //!< MQTT topic to use (prefix if without wildcards) [ebusd] /** @@ -77,9 +77,9 @@ const struct argp_child* mqtthandler_getargs() return &g_mqtt_argp_child; } -DataHandler* mqtthandler_register(BusHandler* busHandler) +DataHandler* mqtthandler_register(BusHandler* busHandler, MessageMap* messages) { - return new MqttHandler(busHandler); + return new MqttHandler(busHandler, messages); } /** the known topic column names. */ @@ -111,7 +111,7 @@ bool parseTopic(const string topic, vector &strs, vector &cols) size_t lastpos = 0; size_t end = topic.length(); vector columns; - for (size_t pos=topic.find('$', lastpos); pos!=string::npos; ) { + for (size_t pos=topic.find('%', lastpos); pos!=string::npos; ) { size_t col = columnCount; size_t len = 0; for (size_t i = 0; i < columnCount; i++) { @@ -124,77 +124,78 @@ bool parseTopic(const string topic, vector &strs, vector &cols) if (col==columnCount) { return false; } - strs.push_back(topic.substr(lastpos, pos)); + for (vector::iterator it=cols.begin(); it!=cols.end(); it++) { + if (*it==col) { + return false; // duplicate column + } + } + strs.push_back(topic.substr(lastpos, pos-lastpos)); cols.push_back(col); lastpos = pos+1+len; + pos = topic.find('%', lastpos); } if (lastpos(getpid()); - m_mosquitto = mosquitto_new(clientId.c_str(), NULL); - if (!m_mosquitto) { - logOtherError("mqtt", "unable to instantiate"); + m_topicCols.push_back(COLUMN_CIRCUIT); // circuit + m_topicStrs.push_back("/"); + m_topicCols.push_back(COLUMN_NAME); // name + } else { + for (size_t i=0; i(getpid()); + m_mosquitto = mosquitto_new(clientId.c_str(), this); + if (!m_mosquitto) { + logOtherError("mqtt", "unable to instantiate"); + } + } + if (m_mosquitto) { + /*mosquitto_log_init(m_mosquitto, MOSQ_LOG_DEBUG | MOSQ_LOG_ERR | MOSQ_LOG_WARNING + | MOSQ_LOG_NOTICE | MOSQ_LOG_INFO, MOSQ_LOG_STDERR);*/ + string willTopic = m_globalTopic+"running"; + string willData = "false"; + size_t len = willData.length(); + mosquitto_will_set(m_mosquitto, true, willTopic.c_str(), (uint32_t)len, (uint8_t*)(willData.c_str()), 0, true); + if (mosquitto_connect(m_mosquitto, "localhost", g_port, 60, true)!=MOSQ_ERR_SUCCESS) { + logOtherError("mqtt", "unable to connect"); + mosquitto_destroy(m_mosquitto); + m_mosquitto = NULL; + } else { + logOtherNotice("mqtt", "connection established"); } } } @@ -216,6 +217,107 @@ void MqttHandler::start() } } +void on_message(void *obj, const struct mosquitto_message *message) +{ + MqttHandler* handler = (MqttHandler*)obj; + if (!handler || !message || !handler->isRunning()) { + return; + } + string topic(message->topic); + string data(message->payloadlen>0 ? (char*)message->payload : ""); + handler->notifyTopic(topic, data); +} + +void MqttHandler::notifyTopic(string topic, string data) +{ + size_t pos = topic.rfind('/'); + if (pos==string::npos) { + return; + } + string suffix = topic.substr(pos+1); + bool isWrite = false; + if (suffix.empty()) { + return; + } + string direction = suffix.substr(0, 3); + isWrite = direction=="set"; + if (!isWrite && direction!="get") { + return; + } + suffix = suffix.substr(3); // security level + logOtherDebug("mqtt", "received topic %s", topic.c_str(), data.c_str()); + string remain = topic.substr(0, pos); + size_t last = 0; + string circuit, name; + size_t idx; + for (idx=0; idx0) { + return; + } + } else { + if (field.empty()) { + return; + } + switch (m_topicCols[idx-1]) { + case COLUMN_CIRCUIT: + circuit = field; + break; + case COLUMN_NAME: + name = field; + break; + case COLUMN_FIELDS: + //field = field; // TODO add support for writing a single field + break; + default: + return; + } + } + } + if (circuit.empty() || name.empty()) { + return; + } + logOtherInfo("mqtt", "received topic for %s %s", circuit.c_str(), name.c_str()); + if (suffix.length()>0) { + circuit += "#"+suffix; + } + Message* message = m_messages->find(circuit, name, isWrite); + if (message==NULL) { + message = m_messages->find(circuit, name, isWrite, true); + } + if (message==NULL) { + logOtherError("mqtt", "%s message %s %s not found", isWrite?"write":"read", circuit.c_str(), name.c_str()); + return; + } + if (!message->isPassive()) { + result_t result = m_busHandler->readFromBus(message, data); + if (result!=RESULT_OK) { + logOtherError("mqtt", "%s %s %s: %s", isWrite?"write":"read", circuit.c_str(), name.c_str(), getResultCode(result)); + return; + } + logOtherNotice("mqtt", "%s %s %s: %s", isWrite?"write":"read", circuit.c_str(), name.c_str(), data.c_str()); + } + ostringstream ostream; + publishMessage(message, ostream); +} + void MqttHandler::run() { time_t lastTaskRun, now, start, lastSignal = 0; @@ -229,6 +331,9 @@ void MqttHandler::run() publishTopic(m_globalTopic+"version", PACKAGE_STRING "." REVISION); publishTopic(m_globalTopic+"running", "true"); publishTopic(signalTopic, "false"); + mosquitto_message_callback_set(m_mosquitto, on_message); + string subTopic = getTopic(NULL)+"#"; + mosquitto_subscribe(m_mosquitto, NULL, subTopic.c_str(), 0); while (isRunning()) { handleTraffic(); time(&now); @@ -264,15 +369,7 @@ void MqttHandler::run() updates.str(""); updates.clear(); updates << dec; - if (message->decodeLastData(updates)==RESULT_OK) { - if (m_publishByField) { - //message->hasField() - } else { - string topic = getTopic(message); - string data = updates.str(); - publishTopic(topic, data); - } - } + publishMessage(message, updates); } m_updatedMessages.clear(); } @@ -286,7 +383,7 @@ void MqttHandler::handleTraffic() } } -string MqttHandler::getTopic(Message* message) +string MqttHandler::getTopic(Message* message, signed char fieldIndex) { ostringstream ret; for (size_t i=0; idumpColumn(ret, m_topicCols[i], false); + if (m_topicCols[i]==COLUMN_FIELDS && fieldIndex>=0) { + ret << message->getFieldName(fieldIndex); + } else { + message->dumpColumn(ret, m_topicCols[i]); + } } } return ret.str(); } +void MqttHandler::publishMessage(Message* message, ostringstream& updates) +{ + result_t result = message->decodeLastData(updates); + if (result!=RESULT_OK) { + logOtherError("mqtt", "decode %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(), getResultCode(result)); + return; + } + if (m_publishByField) { + signed char index = 0; + istringstream input(updates.str()); + string token; + while (getline(input, token, UI_FIELD_SEPARATOR)) { + string topic = getTopic(message, index); + publishTopic(topic, token); + index++; + } + } else { + publishTopic(getTopic(message), updates.str()); + } +} + void MqttHandler::publishTopic(string topic, string data, bool retain) { logOtherDebug("mqtt", "publish %s %s", topic.c_str(), data.c_str()); diff --git a/src/ebusd/mqtthandler.h b/src/ebusd/mqtthandler.h index 96ff19c1..f5069c1d 100644 --- a/src/ebusd/mqtthandler.h +++ b/src/ebusd/mqtthandler.h @@ -39,22 +39,24 @@ const struct argp_child* mqtthandler_getargs(); /** * Registration function that is called once during initialization. * @param busHandler the @a BusHandler instance. + * @param messages the @a MessageMap instance. * @return the create @a DataHandler, or NULL on error. */ -DataHandler* mqtthandler_register(BusHandler* busHandler); +DataHandler* mqtthandler_register(BusHandler* busHandler, MessageMap* messages); /** * The main class supporting MQTT data handling. */ -class MqttHandler : public DataSink, DataSource, Thread +class MqttHandler : public DataSink, public DataSource, public Thread { public: /** * Constructor. * @param busHandler the @a BusHandler instance. + * @param messages the @a MessageMap instance. */ - MqttHandler(BusHandler* busHandler); + MqttHandler(BusHandler* busHandler, MessageMap* messages); /** * Destructor. @@ -64,6 +66,13 @@ public: // @copydoc virtual void start(); + /** + * Notify the handler of a received MQTT message. + * @param topic the topic string. + * @param data the data string. + */ + void notifyTopic(string topic, string data); + protected: // @copydoc @@ -79,9 +88,17 @@ private: /** * Build the MQTT topic string for the @a Message. * @param message the @a Message to build the topic string for. + * @param fieldIndex the optional field index for the field column, or -1. * @return the topic string. */ - string getTopic(Message* message); + string getTopic(Message* message, signed char fieldIndex=-1); + + /** + * Prepare a @a Message and publish as topic. + * @param message the @a Message to publish. + * @param updates the @a ostringstream for preparation. + */ + void publishMessage(Message* message, ostringstream& updates); /** * Publish a topic update to MQTT. @@ -91,6 +108,9 @@ private: */ void publishTopic(string topic, string data, bool retain=true); + /** the @a MessageMap instance. */ + MessageMap* m_messages; + /** the MQTT topic string parts. */ vector m_topicStrs;