/* * *
 *                _____________________________________
 *               /        __          __              /
 *              /   ___  / /   ____ _/ /_  ____      /
 *             /   / _ \/ /   / __ `/ __ \/_  /     /
 *            /   /  __/ /___/ /_/ / /_/ / / /_    /
 *           /    \___/_____/\__,_/_.___/ /___/   /
 *          /____________________________________/
 *              ~ Embedded Labz :: eLabz.Net ~
 *
 * Project:       ProjectName
 *
 * Author:        Designed by Andre Santana [eLabz.Net/Santana]
 * 
 * Sketch:        Sketch.ino
 *                [github.com/e-Labz/xxxxxx]
 *
 * Description:   Simple implementation of vsRTOS to
 *                demostrate library resouces
 *
 * Version:       00.10
 * Release Date:  2023/04/18
 *
 * License:       GPL-3.0-or-later
 *
 * This is free software, please consider supporting to
 * help keep our coffee or beer @ [eLabz.Net/donate] ;-)
 * 
 * This program comes with ABSOLUTELY NO WARRANTY. This is
 * free software, and you are welcome to redistribute it
 * under certain conditions.
 * 
 * See [www.gnu.org/licenses] for more details.
 * 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* ------------------ Identify MPU Archeture  -------------------- */
#ifdef ESP8266                                  // check for ESP8266 MCU
  #include <ESP8266WiFi.h>                      // ESP8266 WiFi Library
  #define MPU_Arch  "ESP8266"                   // define ESP8266 Arch
#elif defined(ESP32)                            // check for ESP32 MCU
  #include <WiFi.h>                             // ESP32 WiFi Library
  #define MPU_Arch  "ESP32"                     // define ESP32 Arch
#else                                           // if not supported
  #error "Supported MCUs ESP8266 or ESP32"      // publish error msg
#endif

/* ----------------------- Library Imports ---------------------- */
#include "vsRTOS.h"                             // vsRTOS Library
#include <PubSubClient.h>                       // MQTT broker Library

/* ----------------------- Global Variables --------------------- */
#define         DevID           "ESP32C3-Teste"
#define         SerialBaudRate  9600UL
#define         failRetries     5

const char*     ssid        =   "Wokwi-GUEST";
const char*     password    =   "";

const char*     mqtt_server =   "broker.hivemq.com";
const uint16_t  mqtt_port   =   1883;
const char*     mqtt_id     =   DevID;

const char*     inTopic     =   "casa-teste/topic-teste/sub";
const char*     outTopic    =   "casa-teste/topic-teste/pub";

//char* ipAddr[16];
String ipAddr;
String serialData = "";

struct tskFlag {                                // flag struct:
  bool firstBoot;                               // 1st boot
  bool netOn;                                   // network online
  bool brokerOn;                                // broker online
} tskFlag = {true, false, false};               // set initial runlevel

static tsk tskWiFi, tskBroker,tskMQTT,
           tskSerialRead, tskSerialData;        // task thread identifier

/* -------------------- Initialize Libraries -------------------- */
WiFiClient tcpSocket;                           // WiFi Client
PubSubClient broker(tcpSocket);                 // MQTT Broker

/* ----------------------- Setup Function ----------------------- */
void setup() {
  Serial.begin(SerialBaudRate);                 // init uart interface

  broker.setServer(mqtt_server, mqtt_port);     // define mqtt broker
  broker.setCallback(brokerCallback);           // set callback function

  TSK_INIT ( &tskWiFi       );                  // network handle
  TSK_INIT ( &tskBroker     );                  // broker handle
  TSK_INIT ( &tskMQTT       );                  // mqttPub handle
  TSK_INIT ( &tskSerialRead );                  // serialRead handle
  TSK_INIT ( &tskSerialData );                  // serialData handle
}

/* --------------- Main (Infinite) Loop Function ---------------- */
void loop() { 
  TSK_RUN ( thWiFi       ( &tskWiFi        ) ); // thread WiFi Handle
  TSK_RUN ( thBroker     ( &tskBroker      ) );
  TSK_RUN ( thMQTTpub    ( &tskMQTT, 60    ) ); // task, delay interval
  TSK_RUN ( thSerialRead ( &tskSerialRead  ) );
  TSK_RUN ( thSerialData ( &tskSerialData  ) );
}

/* --------------- MQTT Broker Callback Function ---------------- */
void brokerCallback(const char* topic, byte* payload, const unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

/* --------------- Check / Connects to WiFi network ------------- */
TSK_THREAD( thWiFi(struct tsk *tsk) ) {
  TSK_BEGIN(tsk);
  while(true) {
    static unsigned char failCount = 0;
    if (WiFi.status() != WL_CONNECTED) {
      TSK_FLAG_SET(tskFlag, netOn, false);
      TSK_LOGI(DevID, "Conecting to WiFi network...");
      WiFi.begin(ssid, password);
      TSK_DELAY(tsk, 3000);
      if(WiFi.status() == WL_CONNECTED) {
        ipAddr = WiFi.localIP().toString();
        TSK_LOGI(DevID, "Connected to " + String(ssid));
        TSK_FLAG_SET(tskFlag, netOn, true);
        failCount = 0;
      } else {
        failCount++;
        TSK_LOGW(DevID, "Fail to connect to WiFi network!");
        WiFi.disconnect();
        if(failCount > failRetries) {
          TSK_LOGE(DevID, "System restart...");
          ESP.restart();  // if error persists, restart device
          }
        TSK_DELAY(tsk, 10000);
      }
    }
    TSK_YIELD(tsk);
  }
  TSK_END(tsk);
}

/* --------------- Check / Connect to Broker --------------- */
TSK_THREAD( thBroker(struct tsk *tsk) ) {
  TSK_BEGIN(tsk);
  while(true) {
    TSK_WAIT_UNTIL(tsk, TSK_FLAG_GET(tskFlag, netOn));  // wait wifi connection
    if (!broker.connected()) {
      TSK_LOGI(DevID, "Conecting to MQTT broker...");
      if (broker.connect(mqtt_id)) {
        TSK_LOGI(DevID, "Connected to broker " + String(mqtt_server));
        broker.subscribe(inTopic);
        TSK_FLAG_SET(tskFlag, brokerOn, true);
      } else {
        TSK_LOGW(DevID, "Fail to connect to MQTT broker!");
        static unsigned char failCount = 0; failCount++;
        TSK_FLAG_SET(tskFlag, brokerOn, false);
        if(failCount > failRetries) {
          broker.disconnect();
          WiFi.disconnect();
          TSK_FLAG_SET(tskFlag, netOn, false);
          failCount = 0;
        }
        TSK_DELAY(tsk, 30000)
      }
    }
    broker.loop();
    TSK_YIELD(tsk);
  }
  TSK_END(tsk);
}

/* ----------------- MQTT Broker Publish Tread ----------------- */
TSK_THREAD( thMQTTpub(struct tsk *tsk, int interval) ) {
  TSK_BEGIN(tsk);
  while(true) {
    TSK_WAIT_UNTIL(tsk, TSK_FLAG_GET(tskFlag, brokerOn)); // wait broker connection
    if(TSK_FLAG_GET(tskFlag, firstBoot)) {          // check for boot flag

      String bootLog = "DevID: "                    // booLog payload
                 + String(DevID)
                 + " IP Addr: "
                 + ipAddr;

      TSK_LOGI(DevID, bootLog);                      // print payload to serial
      broker.publish(outTopic, bootLog.c_str());     // initial MQTT pub
      TSK_FLAG_SET(tskFlag, firstBoot, false);
    }
    TSK_DELAY(tsk, interval * 1000);
    int RSSI = WiFi.RSSI();
    char payload[25];
    sprintf(payload, "Signal Strength: %d dBm", RSSI);
    TSK_LOGI(DevID, payload);
    broker.publish(outTopic, payload);
  //TSK_YIELD(tsk);
  }
  TSK_END(tsk);
}

/* --------------------- Serial Read Thread -------------------- */
TSK_THREAD( thSerialRead(struct tsk *tsk) ) {
  TSK_BEGIN(tsk);
  while (true) {
    TSK_WAIT_UNTIL(tsk, Serial.available() > 0);
    serialData = Serial.readStringUntil('\n');
    TSK_LOGI(DevID, "Task 4 run: " + String(millis()));
    TSK_YIELD(tsk);
  }
  TSK_END(tsk);
}

/* --------------------- Serial Data Thread -------------------- */
TSK_THREAD( thSerialData(struct tsk *tsk) ) {
  TSK_BEGIN(tsk);
  while (TSK_FLAG_GET(tskFlag, brokerOn)) {
    TSK_WAIT_UNTIL(tsk, serialData.length() > 0);
    if (serialData == "broker_off") {             // simulate broker down
      broker.disconnect();
      serialData = "";
    } else if (serialData == "wifi_off") {        // simulate wifi down
      WiFi.disconnect();
      serialData = "";
    } else {
      TSK_LOGI(DevID, "Serial Data: " + serialData);
      broker.publish(outTopic, serialData.c_str());
      serialData = "";
    }
  }
  TSK_END(tsk);
}

/* * *
 *  This is free software, please consider supporting to
 *  help keep our coffee or beer @ [eLabz.Net/donate] ;-)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */