//--------------------------------------------------------------------------------------------
// Program: Mikro Funkstrecken - Swimming Pixel (ESP32)
// Karlsruhe University of Applied Science
// Authors: Jonas Felhauer, Matthias Essig
// Current Status: 14.11.2020
// Description: This code is programmed for the project "Mikro Funkstrecken - Swimming Pixel"
// for the ESP32. In this program the audio recording via an I2S microphone is realized. The 
// seperated class gets incude to this file. The ESP32 needs to get connected to your local 
// WiFi network to use the MQTT protocoll for communicaion between the ESP32 and the PC. 
//--------------------------------------------------------------------------------------------

//-------------------- CHANGES -------------------
// 09.11.20 changing HTTP to MQTT
//------------------------------------------------

#include <Arduino.h>
#include "driver/i2s.h"
#include "I2SSampler.h"
#include <PubSubClient.h>
#include <WiFi.h>

#define MQTT_BROKER_ADDRESS "YOUR IP ADRESS"// place your ip in here
#define SSID "YOUR WIFI NAME" // place your Wifi name in here
#define PASSWORD "YOUR WIFI PASSWORD" //place your Wifi password in here

// MQTT Topics
const char TOPIC_PIXEL_START_FLAG[] = "pixel/Start_Flag";
const char TOPIC_DATA_P1[] = "pixel/Data_P1";
const char TOPIC_ECHO[] = "pixel/Echo";

int i = 0;
bool messageTemp = false;
unsigned long period = 2000;
unsigned long curent_time = 0;
bool Reset_Flag = false;


//-------------------------------- I2S Config --------------------------------

/*
I2S Interface is used for communication of serial digital audio files between integradet circuits. The interface at least consists
out of three signal wires, the Clock (SCK), Data (SD) and the Word-Select-Wire (WS). The Word-Select-Wire defines the timespan of a
serial sended data word. The Clock defines the sampling rate or frequency. The communication always takes place in one direction. 
*/

// i2s config - this is set up to read from the left channel
i2s_config_t i2s_config = {
    // I2S work mode 
    // Master Mode, RX Mode 
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    // I2S sample rate
    .sample_rate = 88200, // 44,1kHz
    // I2S bits per sample
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 16 Bit
    // I2S channel format type
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // only left channel used
    // I2S communication format
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S),
    // flags used to allocate the interrupt
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    // I2S DMA Buffer Count
    .dma_buf_count = 4, // 4 Buffer used
    // I2S DMA Buffer Length
    .dma_buf_len = 1024, // length of one Buffer
    // I2S using APLL as main I2S clock, enable it to get accurate clock
    .use_apll = false,
    // I2S auto clear tx descriptor if there is underflow condition (helps in avoiding noise in case of data unavailability)
    .tx_desc_auto_clear = false,
    // I2S using fixed MCLK output. If use_apll = true and fixed_mclk > 0, then clock output for i2s is fixed and equa to the fixed_mclk value
    .fixed_mclk = 0};

// i2s pins
i2s_pin_config_t i2s_pins = {
    // BCK in out pin
    .bck_io_num = GPIO_NUM_32, // Seriel Clock pin --> has the frequency of (sample rate * bits per channel * number of channels)
    // WS in out pin
    .ws_io_num = GPIO_NUM_25, // Word Select pin --> 1 for the right sound channel and 0 for the left sound channel
    // DATA out pin
    .data_out_num = I2S_PIN_NO_CHANGE, // no data output used
    // DATA in pin
    .data_in_num = GPIO_NUM_33}; // Serial Data pin --> on this pin data gets transmitted



// create an object pointer
I2SSampler *i2s_sampler = NULL;

// Setup MQTT client
WiFiClient wifi_client;
PubSubClient client(wifi_client);

void connect(){
  
  // Connect to WiFi
  Serial.println("\nConnecting to WiFi...");
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PASSWORD);
  if (WiFi.waitForConnectResult() != WL_CONNECTED)
  {
    Serial.println("\nConnection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  Serial.print("Connected to: ");
  Serial.println(WiFi.localIP());

  // Connect to MQTT Broker
  Serial.println("\nConnecting to Broker...");
  while(!client.connect(WiFi.macAddress().c_str(), "try", "try")){
    Serial.print(".");
    i++;
    if(i==20)
    {
      i=0;
    }
  }
  Serial.print("Connected to Broker: ");
  Serial.println(MQTT_BROKER_ADDRESS);
  delay(2000);

  // Subscribe to Topics
  client.subscribe(TOPIC_PIXEL_START_FLAG);

  //define callback function
  client.setCallback(callback);
}

// set up the i2s sample writer task
// TaskHandle_t is a type by which taks are referenced. A call to xTaskCreate returns an TaskHandke_t
// variable as a pointer that can than be used as an parameter to delete the task
TaskHandle_t writer_task_handle;


// Setup Function
void setup() {
  Serial.begin(115200);

  // setup the MQTT Client
  client.setServer(MQTT_BROKER_ADDRESS, 1883);  
  connect(); // connect to WiFi and MQTT
  
  // set up i2s to read from microphone
  i2s_sampler = new I2SSampler(); // allocate dynamic memory //DELETE????

  BaseType_t xReturned;
  // Create a new task and add it to the list of tasks that are ready to run. If a task is created using xTaskCreate()
  // then the required RAM gets automatically allocated from HEAP.
  xReturned = xTaskCreate(i2sWriterTask, "I2S Writer Task", 4096, i2s_sampler, 1, &writer_task_handle);
  // i2sWriterTask       = Function that implements the task
  // "I2S Writer Task"   = Text name for the task
  // 4096                = Stack size in words, not byte fo use as the task's stack // BIG ENOUGH???
  // i2s_sampler         = Parameter passed into the task
  // 1                   = Priority at which the task is created
  // &writer_task_handle = Used to pass out the created task's handle

  if(xReturned == pdPASS)
  {
    // the task was created
    //Serial.println("Writer Task got created!");
  }

}

// Task to write samples to 
void i2sWriterTask(void *param)
{
  I2SSampler *sampler = (I2SSampler *)param;
  // Time is measured in 'ticks' - which is the number of times the tick interrupt has executed since the kernel 
  // was started. The tick count is held in the variable of type TickType_t
  // pdMS_TO_TICKS converts a time in milliseconds to a time in ticks.
  const TickType_t xMaxBlockTime = pdMS_TO_TICKS(100);
  while (true)
  {
    // wait for some samples to save
    // ulTaskNotifyTake() is a macro intended for use when a task notification is used as a faster and lighter
    // binary sempahore alternative
    // gets notification from task when audio buffer is full (xTaskNotify) to save data
    // it returns the value of the notification value 
    uint32_t ulNotificationValue = ulTaskNotifyTake(pdTRUE, xMaxBlockTime);
    // pdTrue        = clear the notofication value (set to 0) before exiting
    // xMaxBlockTime = the maximum time to wait in the Blocked state for a notification to be received
    // A task can only take the semaphore if it is available, and the semaphore is only available if its count is 1
    // The ulTaskNotifyTake() function's xClearOnExit parameter is set to pdTRUE so the count value is returned to zero
    // each time the notification is taken (emulating a binary semaphore)                
    if (ulNotificationValue > 0)
    {
       // Do something with the sample (has to be done in 100ms)

       // Buffer where Data is located, current type is int16_t, check for casting!!! 
       (uint8_t *)sampler->getCapturedAudioBuffer();

        // HTTP Post Requests
        // URL of the website where the Post request should be done
        // send the HTTP request with POST --> sending information from buffer
        client.publish(TOPIC_DATA_P1, (uint8_t *)sampler->getCapturedAudioBuffer(), sampler->getBufferSizeInBytes());
        //int httpResponseCode = mqtt_client->POST((uint8_t *)sampler->getCapturedAudioBuffer(), sampler->getBufferSizeInBytes());
        //Serial.println("HTTP response code: " + httpResponseCode);
        //http_client->end();

    }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
    messageTemp = payload;

    if (String(topic) == TOPIC_PIXEL_START_FLAG) {
      if(messageTemp == true){
        client.publish(TOPIC_ECHO, "Echo"); 
        // start sampling from i2s device
        i2s_sampler->start(I2S_NUM_1, i2s_pins, i2s_config, 32768, writer_task_handle);
        // I2S_NUM_1  = I2S Peripherial 1
        // i2s_pins   = i2s_pin_config_t
        // i2s_config = i2s_config_t
        // 32768      = Buffer size in bytes
        // writer_task_handle
        Reset_Flag = true;
        curent_time = millis();
      }
    }
}


void loop(){

  if (!client.connected()){ // reconnect after connection loss
    connect();
  }
  client.loop(); // checks over MQTT for new msgs

  // sending for 2sec after Flag received
  if(Reset_Flag == true && millis() >= curent_time + period){
  // Use the handle to delete the task.
  vTaskDelete(writer_task_handle); 
  Reset_Flag = false; 
  }

}

