/** * NEURON IIoT System for Industry 4.0 * Copyright (C) 2020-2021 EMQ Technologies Co., Ltd All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ #include #include #include #include #include "command/command.h" #include "mqttc_client.h" #include "mqttc_util.h" #include #define UNUSED(x) (void) (x) #define INTERVAL 100000U #define TIMEOUT 5000000U #define MQTT_SEND(client, topic, qos, json_str) \ { \ if (NULL != json_str) { \ client_error_e error = mqttc_client_publish( \ client, topic, qos, (unsigned char *) json_str, \ strlen(json_str)); \ log_debug("Publish error code:%d, json:%s", error, json_str); \ free(json_str); \ } \ } const neu_plugin_module_t neu_plugin_module; struct context { neu_list_node node; double timestamp; // Armv7l support int req_id; neu_json_mqtt_t parse_header; char * result; bool ready; }; struct neu_plugin { neu_plugin_common_t common; option_t option; pthread_t daemon; pthread_mutex_t running_mutex; // lock publish thread running state pthread_mutex_t list_mutex; // lock context state bool running; neu_list context_list; int mqtt_client_type; void * mqtt_client; }; static struct context *context_create() { struct context *ctx = NULL; ctx = malloc(sizeof(struct context)); if (NULL != ctx) { memset(ctx, 0, sizeof(struct context)); } return ctx; } static void context_destroy(struct context *ctx) { if (NULL != ctx) { if (NULL != ctx->parse_header.uuid) { free(ctx->parse_header.uuid); } if (NULL != ctx->result) { free(ctx->result); } free(ctx); } } static double current_time_get() { struct timeval tv; gettimeofday(&tv, NULL); double ms = tv.tv_sec; // Armv7l support return ms * 1000 + tv.tv_usec / 1000; } static int context_timeout(struct context *ctx) { double time = current_time_get(); if (TIMEOUT < (time - ctx->timestamp)) { return 0; } return -1; } static void context_list_add(neu_plugin_t *plugin, int req_id, neu_json_mqtt_t *parse_header, char *result, bool ready) { struct context *ctx = context_create(); if (NULL == ctx) { return; } ctx->timestamp = current_time_get(); ctx->req_id = req_id; if (NULL != parse_header) { ctx->parse_header.function = parse_header->function; ctx->parse_header.uuid = strdup(parse_header->uuid); } ctx->result = result; ctx->ready = ready; NEU_LIST_NODE_INIT(&ctx->node); neu_list_append(&plugin->context_list, ctx); } static struct context *context_list_find(neu_plugin_t *plugin, const int id) { struct context *item = NULL; NEU_LIST_FOREACH(&plugin->context_list, item) { if (id == item->req_id) { return item; } } return NULL; } static void mqtt_context_add(neu_plugin_t *plugin, uint32_t req_id, neu_json_mqtt_t *parse_header, char *result, bool ready) { pthread_mutex_lock(&plugin->list_mutex); context_list_add(plugin, req_id, parse_header, result, ready); pthread_mutex_unlock(&plugin->list_mutex); } static void mqtt_response_handle(const char *topic_name, size_t topic_len, void *payload, const size_t len, void *context) { mqtt_response_t response = { .topic_name = topic_name, .topic_len = topic_len, .payload = payload, .len = len, .context = context, .context_add = mqtt_context_add }; command_response_handle(&response); return; } static void *mqtt_send_loop(void *argument) { neu_plugin_t *plugin = (neu_plugin_t *) argument; bool run_flag = true; while (1) { // Get run state pthread_mutex_lock(&plugin->running_mutex); run_flag = plugin->running; pthread_mutex_unlock(&plugin->running_mutex); if (!run_flag) { break; } // Get context and publish struct context *ctx = NULL; char * result = NULL; pthread_mutex_lock(&plugin->list_mutex); ctx = neu_list_first(&plugin->context_list); if (NULL != ctx) { if (ctx->ready) { neu_list_remove(&plugin->context_list, ctx); if (NULL != ctx->result) { result = strdup(ctx->result); } context_destroy(ctx); } } pthread_mutex_unlock(&plugin->list_mutex); MQTT_SEND(plugin->mqtt_client, plugin->option.respons_topic, 0, result); // Remove context of timeout pthread_mutex_lock(&plugin->list_mutex); ctx = neu_list_first(&plugin->context_list); if (NULL != ctx) { if (!ctx->ready && (0 == context_timeout(ctx))) { neu_list_remove(&plugin->context_list, ctx); context_destroy(ctx); } } pthread_mutex_unlock(&plugin->list_mutex); // If list empty -> delay(INTERVAL) int rc = 0; pthread_mutex_lock(&plugin->list_mutex); rc = neu_list_empty(&plugin->context_list); pthread_mutex_unlock(&plugin->list_mutex); if (rc) { usleep(INTERVAL); } } // Cleanup on quit pthread_mutex_lock(&plugin->list_mutex); while (!neu_list_empty(&plugin->context_list)) { struct context *ctx = neu_list_first(&plugin->context_list); neu_list_remove(&plugin->context_list, ctx); context_destroy(ctx); } pthread_mutex_unlock(&plugin->list_mutex); return NULL; } static neu_plugin_t *mqtt_plugin_open(neu_adapter_t * adapter, const adapter_callbacks_t *callbacks) { if (NULL == adapter || NULL == callbacks) { log_error("Open plugin with NULL adapter or callbacks"); return NULL; } neu_plugin_t *plugin = NULL; plugin = (neu_plugin_t *) malloc(sizeof(neu_plugin_t)); if (NULL == plugin) { log_error("Failed to allocate plugin %s", neu_plugin_module.module_name); return NULL; } memset(plugin, 0, sizeof(neu_plugin_t)); neu_plugin_common_init(&plugin->common); plugin->common.adapter = adapter; plugin->common.adapter_callbacks = callbacks; plugin->common.link_state = NEU_PLUGIN_LINK_STATE_DISCONNECTED; log_info("Success to create plugin: %s", neu_plugin_module.module_name); return plugin; } static int mqtt_plugin_close(neu_plugin_t *plugin) { free(plugin); log_info("Success to free plugin:%s", neu_plugin_module.module_name); return 0; } static int mqtt_plugin_init(neu_plugin_t *plugin) { // Context list init NEU_LIST_INIT(&plugin->context_list, struct context, node); // Publish thread init plugin->running = true; pthread_mutex_init(&plugin->running_mutex, NULL); pthread_mutex_init(&plugin->list_mutex, NULL); if (0 != pthread_create(&plugin->daemon, NULL, mqtt_send_loop, plugin)) { log_error("Failed to start thread daemon."); return -1; } log_info("Initialize plugin: %s", neu_plugin_module.module_name); return 0; } static int mqtt_plugin_uninit(neu_plugin_t *plugin) { // Close MQTT-C client mqttc_client_close(plugin->mqtt_client); plugin->mqtt_client = NULL; mqtt_option_uninit(&plugin->option); plugin->common.link_state = NEU_PLUGIN_LINK_STATE_DISCONNECTED; // Quit publish thread pthread_mutex_lock(&plugin->running_mutex); plugin->running = false; pthread_mutex_unlock(&plugin->running_mutex); pthread_join(plugin->daemon, NULL); pthread_mutex_destroy(&plugin->list_mutex); pthread_mutex_destroy(&plugin->running_mutex); log_info("Uninitialize plugin: %s", neu_plugin_module.module_name); return 0; } static int mqtt_plugin_config(neu_plugin_t *plugin, neu_config_t *configs) { if (NULL == configs || NULL == configs->buf) { return -1; } plugin->common.link_state = NEU_PLUGIN_LINK_STATE_CONNECTING; // Try close MQTT-C client mqttc_client_close(plugin->mqtt_client); plugin->mqtt_client = NULL; mqtt_option_uninit(&plugin->option); // Use new config set MQTT option instance int rc = mqtt_option_init_by_config(configs, &plugin->option); if (0 != rc) { log_error("MQTT option init fail:%d, initialize plugin failed: %s", rc, neu_plugin_module.module_name); mqtt_option_uninit(&plugin->option); return -1; } // MQTT-C client setup client_error_e error = mqttc_client_open( &plugin->option, plugin, (mqttc_client_t **) &plugin->mqtt_client); error = mqttc_client_subscribe(plugin->mqtt_client, plugin->option.topic, 0, mqtt_response_handle); if (MQTTC_IS_NULL == error) { log_error("Can not create mqtt client instance, initialize plugin " "failed: %s", neu_plugin_module.module_name); return -1; } plugin->common.link_state = NEU_PLUGIN_LINK_STATE_CONNECTED; log_info("Config plugin: %s", neu_plugin_module.module_name); return 0; } static int mqtt_plugin_request(neu_plugin_t *plugin, neu_request_t *req) { int rv = 0; if (plugin == NULL || req == NULL) { log_warn("The plugin pointer or request is NULL"); return (-1); } log_info_node(plugin, "send request to plugin: %s, type:%d", neu_plugin_module.module_name, req->req_type); switch (req->req_type) { case NEU_REQRESP_READ_RESP: { neu_reqresp_read_resp_t *read_resp = NULL; read_resp = (neu_reqresp_read_resp_t *) req->buf; pthread_mutex_lock(&plugin->list_mutex); struct context *ctx = NULL; ctx = context_list_find(plugin, req->req_id); if (NULL != ctx) { char *json_str = command_read_once_response( plugin, &ctx->parse_header, read_resp->data_val); ctx->result = json_str; ctx->ready = true; } pthread_mutex_unlock(&plugin->list_mutex); break; } case NEU_REQRESP_WRITE_RESP: { neu_reqresp_write_resp_t *write_resp = NULL; write_resp = (neu_reqresp_write_resp_t *) req->buf; pthread_mutex_lock(&plugin->list_mutex); struct context *ctx = NULL; ctx = context_list_find(plugin, req->req_id); if (NULL != ctx) { char *json_str = command_write_response(plugin, &ctx->parse_header, write_resp->data_val); ctx->result = json_str; ctx->ready = true; } pthread_mutex_unlock(&plugin->list_mutex); break; } case NEU_REQRESP_TRANS_DATA: { neu_reqresp_data_t *neu_data = NULL; neu_data = (neu_reqresp_data_t *) req->buf; char *json_str = command_read_cycle_response(plugin, neu_data->data_val); mqtt_context_add(plugin, 0, NULL, json_str, true); break; } case NEU_REQRESP_WRITE_DATA: { break; } default: break; } return rv; } static int mqtt_plugin_event_reply(neu_plugin_t * plugin, neu_event_reply_t *reply) { UNUSED(plugin); UNUSED(reply); return 0; } static int mqtt_plugin_start(neu_plugin_t *plugin) { (void) plugin; return 0; } static int mqtt_plugin_stop(neu_plugin_t *plugin) { (void) plugin; return 0; } static const neu_plugin_intf_funs_t plugin_intf_funs = { .open = mqtt_plugin_open, .close = mqtt_plugin_close, .init = mqtt_plugin_init, .uninit = mqtt_plugin_uninit, .start = mqtt_plugin_start, .stop = mqtt_plugin_stop, .config = mqtt_plugin_config, .request = mqtt_plugin_request, .event_reply = mqtt_plugin_event_reply }; const neu_plugin_module_t neu_plugin_module = { .version = NEURON_PLUGIN_VER_1_0, .module_name = "neuron-mqtt-plugin", .module_descr = "Neuron northbound MQTT communication plugin", .intf_funs = &plugin_intf_funs };