/*
 * Copyright (c) 2023, Erich Styger
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Useful read: https://github.com/particle-iot/lwip/blob/master/doc/mqtt_client.txt
 */

#include "platform.h"
#if PL_CONFIG_USE_MQTT_CLIENT

#include "pico/cyw43_arch.h"
#include "lwip/apps/mqtt.h"
#include "lwip/dns.h"
#include "mqtt_client.h"
#include "dns_resolver.h"
#include "McuLog.h"
#include "McuUtility.h"
#include "McuRTOS.h"
#if PL_CONFIG_USE_MINI
  #include "minIni/McuMinINI.h"
  #include "MinIniKeys.h"
#endif
#if MQTT_CLIENT_IS_EV_CHARGER
  #include "Modbus/McuHeidelberg.h"
#endif

#if LWIP_TCP

#define MQTT_EXTRA_LOGS       (0)  /* set to 1 to produce extra log output */

#if MQTT_CLIENT_IS_EV_CHARGER
  /* HomeAssistant Tesla Powerwall topics */
  #define TOPIC_NAME_SOLAR_POWER            "homeassistant/sensor/powerwall_solar_now/state"
  #define TOPIC_NAME_SITE_POWER             "homeassistant/sensor/powerwall_load_now/state"
  #define TOPIC_NAME_GRID_POWER             "homeassistant/sensor/powerwall_site_now/state"
  #define TOPIC_NAME_BATTERY_POWER          "homeassistant/sensor/powerwall_battery_now/state"
  #define TOPIC_NAME_BATTERY_PERCENTAGE     "homeassistant/sensor/powerwall_charge/state"
  #define TOPIC_NAME_CHARGER_CHARGING_POWER "home/charger/power"
#endif

typedef enum topic_ID_e {
  Topic_ID_None,
#if MQTT_CLIENT_IS_EV_CHARGER
  Topic_ID_Solar_Power,       /* power from PV panels */
  Topic_ID_Site_Power,        /* power to the house/site */
  Topic_ID_Grid_Power,        /* power from/to grid */
  Topic_ID_Battery_Power,     /* power from/to battery */
  Topic_ID_Battery_Percentage,/* battery level percentage */
  Topic_ID_Charging_Power,     /* actual charging power */
#elif MQTT_CLIENT_IS_SENSOR
  Topic_ID_Sensor_Update,
#endif
} topic_ID_e;

/* default entries for the MQTT broker connection */
#define MQTT_DEFAULT_BROKER   "homeassistant"
#define MQTT_DEFAULT_CLIENT   "client"
#define MQTT_DEFAULT_USER     "user"
#define MQTT_DEFAULT_PASS     "password"
#define MQTT_DEFAULT_PUBLISH  true
#define DEFAULT_TOPIC_NAME_SENSORS_UPDATE         "user/room/device/update" /*<user>/<room>/<device>/update*/

typedef struct mqtt_t {
  mqtt_client_t *mqtt_client;       /* lwIP MQTT client handle */
  DnsResolver_info_t addr;          /* broker lwip address, resolved by DNS if hostname is used */
  unsigned char broker[32];         /* broker IP or hostname string. For hostname, DNS will be used */
  unsigned char client_id[32];      /* client ID used for connection: each client should have a unique ID */
  unsigned char client_user[32];    /* client user name used for connection */
  unsigned char client_pass[96];    /* client user password */
  unsigned char sensors_update_topic[64]; /* topic name for sensor updates */
  topic_ID_e in_pub_ID;             /* incoming published ID, set in the incoming_publish_cb and used in the incoming_data_cb */
  /* configuration settings */
  bool doLogging; /* if it shall write log messages */
  bool doPublishing; /* if it publish messages */
} mqtt_t;

static mqtt_t mqtt; /* information used for MQTT connection */

static const struct mqtt_connect_client_info_t mqtt_client_info = {
  mqtt.client_id, /* client ID */
  mqtt.client_user, /* client user name */
  mqtt.client_pass, /* client password */
  100,  /* keep alive timeout in seconds */
  NULL, /* will_topic */
  NULL, /* will_msg */
  0,    /* will_qos */
  0     /* will_retain */
#if LWIP_ALTCP && LWIP_ALTCP_TLS
  , NULL
#endif
};

static void mqtt_publish_request_cb(void *arg, err_t err) {
#if 0 && MQTT_EXTRA_LOGS
  const struct mqtt_connect_client_info_t *client_info = (const struct mqtt_connect_client_info_t*)arg;
  McuLog_trace("MQTT client \"%s\" publish request cb: err %d", client_info->client_id, (int)err);
#endif
}

#if MQTT_CLIENT_IS_SENSOR
int MqttClient_Publish_SensorValues(float temperature, float humidity) {
  err_t res;
  uint8_t buf[64];
  const uint8_t qos = 0; /* quos: 0: fire&forget, 1: at least once */
  const uint8_t retain = 0;

  if (!MqttClient_GetDoPublish()) {
    return ERR_DISABLED;
  }

  if (mqtt.mqtt_client != NULL) { /* connected? */
    if (mqtt.doLogging) {
      McuLog_trace("publish t:%f h:%f", temperature, humidity);
    }
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"{\"values\": {\"temperature\": ");
    McuUtility_strcatNumFloat(buf, sizeof(buf), temperature, 2);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)", \"humidity\": ");
    McuUtility_strcatNumFloat(buf, sizeof(buf), humidity, 2);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"}}");

    res = mqtt_publish(mqtt.mqtt_client, mqtt.sensors_update_topic, buf, strlen(buf), qos, retain, mqtt_publish_request_cb, NULL);
    if (res != ERR_OK) {
      McuLog_error("Failed sensor values mqtt_publish: %d", res);
      (void)MqttClient_Disconnect(); /* try disconnect and connect again */
      (void)MqttClient_Connect();
      return res;
    }
    return ERR_OK;
  } else {
    return ERR_FAILED;
  }
}
#endif

#if MQTT_CLIENT_IS_EV_CHARGER
int MqttClient_Publish_ChargingPower(uint32_t powerW) {
  err_t res;
  uint8_t buf[64];
  const uint8_t qos = 0; /* quos: 0: fire&forget, 1: at least once */
  const uint8_t retain = 0;

  if (!MqttClient_GetDoPublish()) {
    return ERR_DISABLED;
  }

  if (mqtt.mqtt_client!=NULL) { /* connected? */
    if (mqtt.doLogging) {
      McuLog_trace("publish P: %d W", powerW);
    }
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"{\"chargeP\": ");
    McuUtility_strcatNum32u(buf, sizeof(buf), powerW);
    McuUtility_strcat(buf, sizeof(buf), (unsigned char*)", \"unit\": \"W\"}");
    res = mqtt_publish(mqtt.mqtt_client, TOPIC_NAME_CHARGER_CHARGING_POWER, buf, strlen(buf), qos, retain, mqtt_publish_request_cb, NULL);
    if (res!=ERR_OK) {
      McuLog_error("Failed charging power mqtt_publish: %d", res);
      (void)MqttClient_Disconnect(); /* try disconnect and connect again */
      (void)MqttClient_Connect();
      return res;
    }
    return ERR_OK;
  } else {
    return ERR_FAILED;
  }
}
#endif

bool MqttClient_GetDoPublish(void) {
  return mqtt.doPublishing && mqtt.mqtt_client!=NULL;
}

void MqttClient_SetDoPublish(bool publish) {
#if PL_CONFIG_USE_MINI
  McuMinINI_ini_putl(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_PUBLISH, publish, NVMC_MININI_FILE_NAME);
#endif
  mqtt.doPublishing = publish;
}

int MqttClient_Publish(const unsigned char *topic, const unsigned char *value) {
  err_t res;
  uint8_t buf[64];
  const uint8_t qos = 0; /* quos: 0: fire&forget, 1: at least once */
  const uint8_t retain = 0;

  if (!MqttClient_GetDoPublish()) {
    return ERR_DISABLED;
  }

  if (mqtt.mqtt_client!=NULL) { /* connected? */
    if (mqtt.doLogging) {
      McuLog_trace("publish topic: \"%s\" value: \"%s\"", topic, value);
    }
    res = mqtt_publish(mqtt.mqtt_client, topic, value, strlen(value), qos, retain, mqtt_publish_request_cb, NULL);
    if (res!=ERR_OK) {
      McuLog_error("Failed mqtt_publish: %d", res);
      (void)MqttClient_Disconnect(); /* try disconnect and connect again */
      (void)MqttClient_Connect();
      return res;
    }
    return ERR_OK;
  } else {
    return ERR_FAILED;
  }
}

static void GetDataString(unsigned char *buf, size_t bufSize, const u8_t *data, u16_t len) {
  buf[0] = '\0';
  for(int i=0; i<len; i++){
    McuUtility_chcat(buf, bufSize, data[i]);
  }
}

#if MQTT_CLIENT_IS_EV_CHARGER
static int32_t scanWattValue(const unsigned char *str) {
  /* string in in kW and it returns the number in Watt, so for example:
   * "1" => 1000, * "1.2" => 1200, "3.025" = 3025, "-1.002" = -1002
   */
  int32_t watt;
  float f;

  if (sscanf(str, "%f", &f)!=1) { /* check number of conversions, shall be 1 */
    McuLog_error("failed conversion of %s", str);
    return 0; /* conversion failed, return 0 */
  }
  watt = (int)(f*1000.0f);
  return watt;
}
#endif

static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags) {
  const struct mqtt_connect_client_info_t *client_info = (const struct mqtt_connect_client_info_t*)arg;
  LWIP_UNUSED_ARG(data);
  unsigned char buf[32];
  int32_t watt;

#if MQTT_EXTRA_LOGS
  McuLog_trace("MQTT client \"%s\" data cb: len %d, flags %d", client_info->client_id, (int)len, (int)flags);
#endif
  if(flags & MQTT_DATA_FLAG_LAST) {
    /* Last fragment of payload received (or whole part if payload fits receive buffer. See MQTT_VAR_HEADER_BUFFER_LEN)  */
#if MQTT_CLIENT_IS_EV_CHARGER
    if (mqtt.in_pub_ID == Topic_ID_Solar_Power) {
      GetDataString(buf, sizeof(buf), data, len);
      if (mqtt.doLogging) {
        McuLog_trace("solarP: %s kW", buf);
      }
      watt = scanWattValue(buf);
      if (watt>=0) { /* can only be positive */
        McuHeidelberg_SetSolarPowerWatt(watt);
      }
    } else if(mqtt.in_pub_ID == Topic_ID_Site_Power) {
      GetDataString(buf, sizeof(buf), data, len);
      if (mqtt.doLogging) {
        McuLog_trace("siteP: %s kW", buf);
      }
      watt = scanWattValue(buf);
      if (watt>=0) { /* can only be positive */
        McuHeidelberg_SetSitePowerWatt(watt);
      }
    } else if(mqtt.in_pub_ID == Topic_ID_Grid_Power) {
      GetDataString(buf, sizeof(buf), data, len);
      if (mqtt.doLogging) {
        McuLog_trace("gridP: %s kW", buf);
      }
      watt = scanWattValue(buf);
      McuHeidelberg_SetGridPowerWatt(watt);
    } else if(mqtt.in_pub_ID == Topic_ID_Battery_Power) {
      GetDataString(buf, sizeof(buf), data, len);
      if (mqtt.doLogging) {
        McuLog_trace("battP: %s, kW", buf);
      }
    } else if(mqtt.in_pub_ID == Topic_ID_Battery_Percentage) {
      GetDataString(buf, sizeof(buf), data, len);
      if (mqtt.doLogging) {
        McuLog_trace("bat%%: %s%%", buf);
      }
#elif MQTT_CLIENT_IS_SENSOR
    if (mqtt.in_pub_ID == Topic_ID_Sensor_Update) {
      McuLog_trace("Sensor update");
#endif
    } else {
      McuLog_trace("mqtt_incoming_data_cb: Ignoring payload...");
    }
  } else {
    McuLog_trace("mqtt_incoming_data_cb: fragmented payload ...");
    /* Handle fragmented payload, store in buffer, write to file or whatever */
  }
}

static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len) {
  const struct mqtt_connect_client_info_t *client_info = (const struct mqtt_connect_client_info_t*)arg;

#if MQTT_CLIENT_IS_EV_CHARGER
  if (McuUtility_strcmp(topic, TOPIC_NAME_SOLAR_POWER)==0) {
    mqtt.in_pub_ID = Topic_ID_Solar_Power;
  } else if (McuUtility_strcmp(topic, TOPIC_NAME_SITE_POWER)==0) {
    mqtt.in_pub_ID = Topic_ID_Site_Power;
  } else if (McuUtility_strcmp(topic, TOPIC_NAME_GRID_POWER)==0) {
    mqtt.in_pub_ID = Topic_ID_Grid_Power;
  } else if (McuUtility_strcmp(topic, TOPIC_NAME_BATTERY_POWER)==0) {
    mqtt.in_pub_ID = Topic_ID_Battery_Power;
  } else if (McuUtility_strcmp(topic, TOPIC_NAME_BATTERY_PERCENTAGE)==0) {
    mqtt.in_pub_ID = Topic_ID_Battery_Percentage;
  } else if (McuUtility_strcmp(topic, TOPIC_NAME_CHARGER_CHARGING_POWER)==0) {
    mqtt.in_pub_ID = Topic_ID_Charging_Power;
#elif MQTT_CLIENT_IS_SENSOR
  if (McuUtility_strcmp(topic, mqtt.sensors_update_topic)==0) {
    mqtt.in_pub_ID = Topic_ID_Sensor_Update;
#endif
  } else { /* unknown */
    McuLog_trace("MQTT client \"%s\" publish cb: topic %s, len %d", client_info->client_id, topic, (int)tot_len);
    mqtt.in_pub_ID = Topic_ID_None;
  }
}

static void mqtt_request_cb(void *arg, err_t err) {
#if MQTT_EXTRA_LOGS
  const struct mqtt_connect_client_info_t *client_info = (const struct mqtt_connect_client_info_t*)arg;
  McuLog_trace("MQTT client \"%s\" request cb: err %d", client_info->client_id, (int)err);
#endif
}

static void mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status) {
  const struct mqtt_connect_client_info_t *client_info = (const struct mqtt_connect_client_info_t*)arg;
  LWIP_UNUSED_ARG(client);
  err_t err;

#if MQTT_EXTRA_LOGS
  McuLog_trace("MQTT client \"%s\" connection cb: status %d", client_info->client_id, (int)status);
#endif
  if (status!=MQTT_CONNECT_ACCEPTED) {
    McuLog_error("MQTT client \"%s\" connection cb: FAILED status %d", client_info->client_id, (int)status);
  }
  /* subscribe to topics */
  if (status == MQTT_CONNECT_ACCEPTED) {
    McuLog_trace("MQTT connect accepted");
#if MQTT_CLIENT_IS_EV_CHARGER
    err = mqtt_sub_unsub(client,
            TOPIC_NAME_SOLAR_POWER, /* solar P in kW */
            1, /* quos: 0: fire&forget, 1: at least once */
            mqtt_request_cb, /* callback */
            LWIP_CONST_CAST(void*, client_info),
            1 /* subscribe */
            );
    if (err!=ERR_OK) {
      McuLog_error("failed subscribing, err %d", err);
    }
    err = mqtt_sub_unsub(client,
            TOPIC_NAME_SITE_POWER, /* site/house P in kW */
            1, /* quos */
            mqtt_request_cb,
            LWIP_CONST_CAST(void*, client_info),
            1);
    if (err!=ERR_OK) {
      McuLog_error("failed subscribing, err %d", err);
    }
    err = mqtt_sub_unsub(client,
            TOPIC_NAME_GRID_POWER, /* grid P in kW */
            1, /* quos */
            mqtt_request_cb,
            LWIP_CONST_CAST(void*, client_info),
            1);
    if (err!=ERR_OK) {
      McuLog_error("failed subscribing, err %d", err);
    }
    err = mqtt_sub_unsub(client,
            TOPIC_NAME_BATTERY_POWER, /* battery P in kW */
            1, /* quos */
            mqtt_request_cb,
            LWIP_CONST_CAST(void*, client_info),
            1);
    if (err!=ERR_OK) {
      McuLog_error("failed subscribing, err %d", err);
    }
    err = mqtt_sub_unsub(client,
        TOPIC_NAME_BATTERY_PERCENTAGE,  /* topic: percentage of battery charge */
            1,  /* quos */
            mqtt_request_cb, LWIP_CONST_CAST(void*, client_info),
            1);
    if (err!=ERR_OK) {
      McuLog_error("failed subscribing, err %d", err);
    }
#elif MQTT_CLIENT_IS_SENSOR
  	/* no subscriptions */
#endif
  } else if (status==MQTT_CONNECT_DISCONNECTED) {
    McuLog_trace("MQTT connect disconnect");
  }
}
#endif /* LWIP_TCP */

uint8_t MqttClient_Connect(void) {
#if LWIP_TCP
  int nofRetry;
  mqtt.mqtt_client = mqtt_client_new(); /* create client handle */
  err_t err;

  if (mqtt.mqtt_client==NULL) {
    McuLog_fatal("new mqtt client is NULL");
  }

  /* setup connection information */
#if PL_CONFIG_USE_MINI
  McuMinINI_ini_gets(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_BROKER, MQTT_DEFAULT_BROKER, mqtt.broker, sizeof(mqtt.broker), NVMC_MININI_FILE_NAME);
  McuMinINI_ini_gets(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_CLIENT, MQTT_DEFAULT_CLIENT, mqtt.client_id, sizeof(mqtt.client_id), NVMC_MININI_FILE_NAME);
  McuMinINI_ini_gets(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_USER, MQTT_DEFAULT_USER, mqtt.client_user, sizeof(mqtt.client_user), NVMC_MININI_FILE_NAME);
  McuMinINI_ini_gets(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_PASS, MQTT_DEFAULT_PASS, mqtt.client_pass, sizeof(mqtt.client_pass), NVMC_MININI_FILE_NAME);
  McuMinINI_ini_gets(NVMC_MININI_SECTION_MQTT, NVMC_TOPIC_NAME_SENSORS_UPDATE, DEFAULT_TOPIC_NAME_SENSORS_UPDATE, mqtt.sensors_update_topic, sizeof(mqtt.sensors_update_topic), NVMC_MININI_FILE_NAME);
  mqtt.doPublishing = McuMinINI_ini_getbool(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_PASS, MQTT_DEFAULT_PUBLISH, NVMC_MININI_FILE_NAME);
#else
  McuUtility_strcpy(mqtt.broker, sizeof(mqtt.broker), MQTT_DEFAULT_BROKER);
  McuUtility_strcpy(mqtt.client_id, sizeof(mqtt.client_id), MQTT_DEFAULT_CLIENT);
  McuUtility_strcpy(mqtt.client_user, sizeof(mqtt.client_user), MQTT_DEFAULT_USER);
  McuUtility_strcpy(mqtt.client_pass, sizeof(mqtt.client_pass), MQTT_DEFAULT_PASS);
  mqtt.doPublishing = MQTT_DEFAULT_PUBLISH;
#endif

  /* resolve host name to IP address: */
  for (nofRetry=60; nofRetry>=0; nofRetry--) {
    if (DnsResolver_ResolveName(mqtt.broker, &mqtt.addr, 5*1000)!=0) { /* use DNS to resolve name to IP address */
      McuLog_error("failed to resolve broker name %s, retry ...", mqtt.broker);
      vTaskDelay(pdMS_TO_TICKS(5000));
    } else {
      break; /* success! leaving loop */
    }
  }
  if (nofRetry<0) {
    McuLog_fatal("failed to resolve broker name %s, giving up", mqtt.broker);
    return ERR_FAILED;
  }
  /* setup callbacks for incoming data: */
  mqtt_set_inpub_callback(
      mqtt.mqtt_client, /* client handle */
      mqtt_incoming_publish_cb, /* callback for incoming publish messages */
      mqtt_incoming_data_cb, /* callback for incoming data */
      LWIP_CONST_CAST(void*, &mqtt_client_info) /* argument for callbacks */
      );
  /* connect to broker */
  cyw43_arch_lwip_begin(); /* start section for to lwIP access */
  err = mqtt_client_connect(
      mqtt.mqtt_client, /* client handle */
      &mqtt.addr.resolved_addr, /* broker IP address */
      MQTT_PORT, /* port to be used */
      mqtt_connection_cb, LWIP_CONST_CAST(void*, &mqtt_client_info), /* connection callback with argument */
      &mqtt_client_info /* client information */
      );
  cyw43_arch_lwip_end(); /* end section accessing lwIP */
  if (err!=ERR_OK) {
    McuLog_error("failed connecting client to server");
  } else {
    McuLog_trace("client connecting");
  }
  return err;
#else
  return ERR_FAILED;
#endif /* LWIP_TCP */
}

uint8_t MqttClient_Disconnect(void) {
  if (mqtt.mqtt_client!=NULL) {
    McuLog_trace("disconnecting client");
    cyw43_arch_lwip_begin(); /* start section for to lwIP access */
    mqtt_disconnect(mqtt.mqtt_client);
    mqtt_client_free(mqtt.mqtt_client);
    cyw43_arch_lwip_end(); /* end section accessing lwIP */
    mqtt.mqtt_client = NULL;
  }
  return ERR_OK;
}

static uint8_t SetBroker(const unsigned char *broker) {
  unsigned char buf[64];

  McuUtility_ScanDoubleQuotedString(&broker, buf, sizeof(buf));
  McuUtility_strcpy(mqtt.broker, sizeof(mqtt.broker), buf);
#if PL_CONFIG_USE_MINI
  McuMinINI_ini_puts(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_BROKER, mqtt.broker, NVMC_MININI_FILE_NAME);
#endif
  return ERR_OK;
}

static uint8_t SetID(const unsigned char *id) {
  unsigned char buf[64];

  McuUtility_ScanDoubleQuotedString(&id, buf, sizeof(buf));
  McuUtility_strcpy(mqtt.client_id, sizeof(mqtt.client_id), buf);
#if PL_CONFIG_USE_MINI
  McuMinINI_ini_puts(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_CLIENT, mqtt.client_id, NVMC_MININI_FILE_NAME);
#endif
  return ERR_OK;
}

static uint8_t SetUser(const unsigned char *user) {
  unsigned char buf[64];

  McuUtility_ScanDoubleQuotedString(&user, buf, sizeof(buf));
  McuUtility_strcpy(mqtt.client_user, sizeof(mqtt.client_user), buf);
#if PL_CONFIG_USE_MINI
  McuMinINI_ini_puts(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_USER, mqtt.client_user, NVMC_MININI_FILE_NAME);
#endif
  return ERR_OK;
}

static uint8_t SetPassword(const unsigned char *pass) {
  unsigned char buf[96];

  McuUtility_ScanDoubleQuotedString(&pass, buf, sizeof(buf));
  McuUtility_strcpy(mqtt.client_pass, sizeof(mqtt.client_pass), buf);
#if PL_CONFIG_USE_MINI
  McuMinINI_ini_puts(NVMC_MININI_SECTION_MQTT, NVMC_MININI_KEY_MQTT_PASS, mqtt.client_pass, NVMC_MININI_FILE_NAME);
#endif
  return ERR_OK;
}

static uint8_t PrintStatus(const McuShell_StdIOType *io) {
  McuShell_SendStatusStr((unsigned char*)"mqttclient", (unsigned char*)"mqttclient status\r\n", io->stdOut);
  McuShell_SendStatusStr((unsigned char*)"  log", mqtt.doLogging?(unsigned char*)"on\r\n":(unsigned char*)"off\r\n", io->stdOut);
  McuShell_SendStatusStr((unsigned char*)"  publish", MqttClient_GetDoPublish()?(unsigned char*)"on\r\n":(unsigned char*)"off\r\n", io->stdOut);
  McuShell_SendStatusStr((unsigned char*)"  client", mqtt.mqtt_client==NULL?(unsigned char*)"diconnected\r\n":(unsigned char*)"connected\r\n", io->stdOut);
  McuShell_SendStatusStr((unsigned char*)"  broker", mqtt.broker, io->stdOut);
  McuShell_SendStr((unsigned char*)"\r\n", io->stdOut);
  McuShell_SendStatusStr((unsigned char*)"  client ID", mqtt.client_id, io->stdOut);
  McuShell_SendStr((unsigned char*)"\r\n", io->stdOut);
  McuShell_SendStatusStr((unsigned char*)"  client user", mqtt.client_user, io->stdOut);
  McuShell_SendStr((unsigned char*)"\r\n", io->stdOut);
  McuShell_SendStatusStr((unsigned char*)"  client password", mqtt.client_pass, io->stdOut);
  McuShell_SendStr((unsigned char*)"\r\n", io->stdOut);
  return ERR_OK;
}

static uint8_t PrintHelp(const McuShell_StdIOType *io) {
  McuShell_SendHelpStr((unsigned char*)"mqttclient", (unsigned char*)"Group of mqttclient commands\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  help|status", (unsigned char*)"Print help or status information\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  log on|off", (unsigned char*)"Turn logging on or off\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  publish on|off", (unsigned char*)"Publishing on or off\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  connect|disconnect", (unsigned char*)"Connect or disconnect from server\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  set broker \"<broker>\"", (unsigned char*)"Set broker name\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  set id \"<id>\"", (unsigned char*)"Set client ID\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  set user \"<user>\"", (unsigned char*)"Set client user name\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  set pass \"<password>\"", (unsigned char*)"Set client password\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  pub \"<topic>\" \"<txt>\"", (unsigned char*)"Publish text to a topic\r\n", io->stdOut);
  return ERR_OK;
}

uint8_t MqttClient_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) {
  const unsigned char *p;
  uint16_t val16u;

  if (McuUtility_strcmp((char*)cmd, McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, "mqttclient help")==0) {
    *handled = true;
    return PrintHelp(io);
  } else if ((McuUtility_strcmp((char*)cmd, McuShell_CMD_STATUS)==0) || (McuUtility_strcmp((char*)cmd, "mqttclient status")==0)) {
    *handled = true;
    return PrintStatus(io);
  } else if (McuUtility_strcmp((char*)cmd, "mqttclient log on")==0) {
    *handled = true;
    mqtt.doLogging = true;
  } else if (McuUtility_strcmp((char*)cmd, "mqttclient log off")==0) {
    *handled = true;
    mqtt.doLogging = false;
  } else if (McuUtility_strcmp((char*)cmd, "mqttclient publish on")==0) {
    *handled = true;
    MqttClient_SetDoPublish(true);
  } else if (McuUtility_strcmp((char*)cmd, "mqttclient publish off")==0) {
    *handled = true;
    MqttClient_SetDoPublish(false);
  } else if (McuUtility_strcmp((char*)cmd, "mqttclient connect")==0) {
    *handled = true;
    MqttClient_Connect();
  } else if (McuUtility_strcmp((char*)cmd, "mqttclient disconnect")==0) {
    *handled = true;
    MqttClient_Disconnect();
  } else if (McuUtility_strncmp((char*)cmd, "mqttclient set broker ", sizeof("mqttclient set broker ")-1)==0) {
    *handled = TRUE;
    p = cmd + sizeof("mqttclient set broker ")-1;
    return SetBroker(p);
  } else if (McuUtility_strncmp((char*)cmd, "mqttclient set id ", sizeof("mqttclient set id ")-1)==0) {
    *handled = TRUE;
    p = cmd + sizeof("mqttclient set id ")-1;
    return SetID(p);
  } else if (McuUtility_strncmp((char*)cmd, "mqttclient set user ", sizeof("mqttclient set user ")-1)==0) {
    *handled = TRUE;
    p = cmd + sizeof("mqttclient set user ")-1;
    return SetUser(p);
  } else if (McuUtility_strncmp((char*)cmd, "mqttclient set pass ", sizeof("mqttclient set pass ")-1)==0) {
    *handled = TRUE;
    p = cmd + sizeof("mqttclient set pass ")-1;
    return SetPassword(p);
  } else if (McuUtility_strncmp((char*)cmd, "mqttclient pub ", sizeof("mqttclient pub ")-1)==0) {
    unsigned char topic[128], text[128];
    *handled = TRUE;
    p = cmd + sizeof("mqttclient pub ")-1;
    if (McuUtility_ScanDoubleQuotedString(&p, topic, sizeof(topic))!=ERR_OK) {
      return ERR_FAILED;
    }
    McuUtility_SkipSpaces(&p);
    if (McuUtility_ScanDoubleQuotedString(&p, text, sizeof(text))!=ERR_OK) {
      return ERR_FAILED;
    }
    return MqttClient_Publish(topic, text);
  }
  return ERR_OK;
}

void MqttClient_Deinit(void) {
  MqttClient_Disconnect();
}

void MqttClient_Init(void) {
  mqtt.doLogging = true;
  mqtt.doPublishing = MQTT_DEFAULT_PUBLISH;
}

#endif /* PL_CONFIG_USE_MQTT_CLIENT */
