Files
neuron/plugins/mqtt/plugin_mqtt.c
T

447 lines
14 KiB
C

/**
* 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 <assert.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/time.h>
#include "command/command.h"
#include "mqttc_client.h"
#include "mqttc_util.h"
#include <neuron.h>
#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
};