/*|-----------------------------------------------------------------------------------|*/
/*|Project: Ultrasonic sensor node ESP32 with HTTP connectivity (FreeRTOS)            |*/
/*|ESP32 (DevKit, Generic) - ESP-IDF v4.2 (4.0 compatible)                            |*/
/*|Compatible sensors: HC-SR04 / JSN-SR04T / HY-SRF05 / Parallax PING)))™, DYP-ME007..|*/
/*|Author: martinius96                                                                |*/
/*|E-mail: [email protected]                                                      |*/
/*|Project info: https://martinius96.github.io/hladinomer-studna-scripty/en/          |*/
/*|Test web interface: http://arduino.clanweb.eu/studna_s_prekladom/?lang=en          |*/
/*|On this web interface there is ESP32 sending datas each 5 minututes                |*/
/*|Buy me a coffee at: paypal.me/chlebovec                                            |*/
/*|-----------------------------------------------------------------------------------|*/

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

#include "ultrasonic.h"
#include "driver/dac.h"

/* Constants that aren't configurable in menuconfig */

#define MAX_DISTANCE_CM 450 // 5m max
#define GPIO_TRIGGER	22
#define GPIO_ECHO	23
// Webserver
#define WEB_SERVER "arduino.clanweb.eu"
#define WEB_PORT "80"


static const char *TAG = "http_request";
static const char *TAG2 = "ultrasonic_measurement";

// WiFi parameters
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASS ""


// Event group
static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;

QueueHandle_t  q = NULL;
static void ultrasonic(void *pvParamters)
{
  ultrasonic_sensor_t sensor = {
    .trigger_pin = GPIO_TRIGGER,
    .echo_pin = GPIO_ECHO
  };

  ultrasonic_init(&sensor);
  uint32_t distance = 0;
  if (q == NULL) {
    printf("Queue is not ready \n");
    return;
  }
  while (true) {
    uint32_t avg_distance = 0;
    int index_loop = 1;
    while (index_loop <= 10) {
      esp_err_t res = ultrasonic_measure_cm(&sensor, MAX_DISTANCE_CM, &distance);
      if (res != ESP_OK) {
        printf("Error: ");
        switch (res) {
          case ESP_ERR_ULTRASONIC_PING:
            printf("Cannot ping (device is in invalid state)\n");
            break;
          case ESP_ERR_ULTRASONIC_PING_TIMEOUT:
            printf("Ping timeout (no device found)\n");
            break;
          case ESP_ERR_ULTRASONIC_ECHO_TIMEOUT:
            printf("Echo timeout (i.e. distance too big)\n");
            break;
          default:
            printf("%d\n", res);
        }
      } else {
        printf("Measurement %d: %d cm\n", index_loop, distance);
        avg_distance +=  distance;
        index_loop++;
      }
    }
    avg_distance = avg_distance / 10;
    distance  = avg_distance;
    xQueueSend(q, (void *)&distance, (TickType_t )0); // add the counter value to the queue
    for (int countdown = 300; countdown >= 0; countdown--) {
      printf("Next measurement in %d seconds\n", countdown);
      vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
  }
}

static void http_get_task(void *pvParameters)
{
  const struct addrinfo hints = {
    .ai_family = AF_INET,
    .ai_socktype = SOCK_STREAM,
  };
  struct addrinfo *res;
  struct in_addr *addr;
  int s, r;
  char recv_buf[64];
  uint32_t distance;
  if (q == NULL) {
    printf("Queue is not ready \n");
    return;
  }
  while (1) {
    xQueueReceive(q, &distance, portMAX_DELAY);
    char REQUEST [1000];
    char values [250];
    sprintf(values, "hodnota=%d&token=123456789", distance);
    sprintf (REQUEST, "POST /studna_s_prekladom/data.php HTTP/1.0\r\nHost: "WEB_SERVER"\r\nUser-Agent: ESP32\r\nConnection: close\r\nContent-Type: application/x-www-form-urlencoded;\r\nContent-Length:%d\r\n\r\n%s\r\n",strlen(values),values);
    int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);

    if (err != 0 || res == NULL) {
      ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res);
      vTaskDelay(1000 / portTICK_PERIOD_MS);
      continue;
    }

    /* Code to print the resolved IP.

       Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */
    addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
    ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));

    s = socket(res->ai_family, res->ai_socktype, 0);
    if (s < 0) {
      ESP_LOGE(TAG, "... Failed to allocate socket.");
      freeaddrinfo(res);
      vTaskDelay(1000 / portTICK_PERIOD_MS);
      continue;
    }
    ESP_LOGI(TAG, "... allocated socket");

    if (connect(s, res->ai_addr, res->ai_addrlen) != 0) {
      ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
      close(s);
      freeaddrinfo(res);
      vTaskDelay(4000 / portTICK_PERIOD_MS);
      continue;
    }

    ESP_LOGI(TAG, "... connected");
    freeaddrinfo(res);

    if (write(s, REQUEST, strlen(REQUEST)) < 0) {
      ESP_LOGE(TAG, "... socket send failed");
      close(s);
      vTaskDelay(4000 / portTICK_PERIOD_MS);
      continue;
    }
    ESP_LOGI(TAG, "... socket send success");

    struct timeval receiving_timeout;
    receiving_timeout.tv_sec = 5;
    receiving_timeout.tv_usec = 0;
    if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout,
                   sizeof(receiving_timeout)) < 0) {
      ESP_LOGE(TAG, "... failed to set socket receiving timeout");
      close(s);
      vTaskDelay(4000 / portTICK_PERIOD_MS);
      continue;
    }
    ESP_LOGI(TAG, "... set socket receiving timeout success");

    /* Read HTTP response */
    do {
      bzero(recv_buf, sizeof(recv_buf));
      r = read(s, recv_buf, sizeof(recv_buf) - 1);
      for (int i = 0; i < r; i++) {
        putchar(recv_buf[i]);
      }
    } while (r > 0);

    ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);
    close(s);
    ESP_LOGI(TAG, "Starting again!");
  }
}

// Wifi event handler
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
  switch (event->event_id) {

    case SYSTEM_EVENT_STA_START:
      esp_wifi_connect();
      break;

    case SYSTEM_EVENT_STA_GOT_IP:
      xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
      break;

    case SYSTEM_EVENT_STA_DISCONNECTED:
      xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
      break;

    default:
      break;
  }

  return ESP_OK;
}

// Main task
void main_task(void *pvParameter)
{
  tcpip_adapter_ip_info_t ip_info;
  // wait for connection
  printf("Waiting for connection to the wifi network... \n");
  xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
  printf("Connected!\n");

  // print the local IP address
  ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info));
  printf("IP Address:  %s\n", ip4addr_ntoa(&ip_info.ip));
  printf("Subnet mask: %s\n", ip4addr_ntoa(&ip_info.netmask));
  printf("Gateway:     %s\n", ip4addr_ntoa(&ip_info.gw));
  if (q != NULL) {
    printf("Queue is created\n");
    vTaskDelay(1000 / portTICK_PERIOD_MS); //wait for a second
    xTaskCreate(&ultrasonic, "ultrasonic", 2048, NULL, 5, NULL);
    printf("producer task  started\n");
    xTaskCreate(&http_get_task, "http_get_task", 4096, NULL, 5, NULL);
    printf("consumer task  started\n");
  } else {
    printf("Queue creation failed");
  }
  while (1) {
    vTaskDelay(1000 / portTICK_RATE_MS);
  }
}


// Main application
void app_main()
{

  printf("ESP-IDF version used: ");
  printf(IDF_VER"\n");
  // disable the default wifi logging
  esp_log_level_set("wifi", ESP_LOG_NONE);

  // initialize NVS
  ESP_ERROR_CHECK(nvs_flash_init());

  // create the event group to handle wifi events
  wifi_event_group = xEventGroupCreate();

  // initialize the tcp stack
  tcpip_adapter_init();

  // initialize the wifi event handler
  ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));

  // initialize the wifi stack in STAtion mode with config in RAM
  wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
  ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config));
  ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

  // configure the wifi connection and start the interface
  wifi_config_t wifi_config = {
    .sta = {
      .ssid = WIFI_SSID,
      .password = WIFI_PASS,
    },
  };
  // start the main task
  xTaskCreate(&main_task, "main_task", 2048, NULL, 5, NULL);
  ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
  ESP_ERROR_CHECK(esp_wifi_start());
  printf("Connecting to %s\n", WIFI_SSID);
  q = xQueueCreate(20, sizeof(unsigned long));

}