// SPDX-FileCopyrightText: 2025  Amal Mazrah <mazrah@ruc.dk>
// SPDX-FileCopyrightText: 2025  Jonas Smedegaard <dr@jones.dk>
// SPDX-FileCopyrightText: 2025  Mennatullah Hatim Kassim <stud-mennatulla@ruc.dk>
// SPDX-FileCopyrightText: 2025  Noor Ahmad <noora@ruc.dk>
// SPDX-FileCopyrightText: 2025  Tanishka Suwalka <tanishkas@ruc.dk>
// SPDX-License-Identifier: GPL-3.0-or-later

/// Sensor mussel - an Arduino sketch to emulate a mussel biosensor
///
/// @version 0.0.3
/// @see <https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z2tFBF4gN7ziG9oXtUytVQNYe3VhQ>
/// @see <https://moodle.ruc.dk/course/view.php?id=23504>

// arduino-esp32 Logging system
// activate in Arduino IDE: Tools -> Core Debug Level
// special: set Core Debug Level to Error for plot-friendly output
#define CONFIG_ARDUHAL_ESP_LOG 1
#define LOG_LOCAL_LEVEL CORE_DEBUG_LEVEL
#include <esp32-hal-log.h>
#undef ARDUHAL_LOG_FORMAT
#define ARDUHAL_LOG_FORMAT(letter, format) \
 ARDUHAL_LOG_COLOR_##letter "[" #letter "] %s(): " format \
 ARDUHAL_LOG_RESET_COLOR "\r\n", __FUNCTION__

// arduino-esp32 Bluetooth Low Energy (BLE) networking stack
#include "BLEDevice.h"
#include "BLEBeacon.h"
#include "BLEAdvertising.h"
#include "BLEEddystoneTLM.h"

// Adjust these for production use
//
// * BEACON_NAME must be unique within deployment
// * BEACON_UUID should be unique for each deployment
//
// @see https://www.uuidgenerator.net/
#define BEACON_NAME   "Dummy mussel sensor"
#define BEACON_UUID   "00000000-0000-0000-0000-000000000000"

// maximum accumulated stress
#define STRESS_MAX 50

// light sensor
#define LIGHT_PIN    34
#define DARKNESS_MAX 1000

// arduino-esp32 Touch sensor
#define TOUCH_PIN           T0  // T0 is GPIO4
#define TOUCH_THRESHOLD     40

// arduino-esp32 LED PWM Controller (LEDC) as pacemaker for gaping rhythm
#define LED_PIN             LED_BUILTIN
#define LEDC_BITS           7
#define LEDC_FREQ           500
#define LEDC_START_DUTY     0
#define LEDC_TARGET_DUTY    90
#define LEDC_CALM_PACE      3000
#define LEDC_STRESSED_PACE  400

// pacemaker variables
int stress = 0;
bool touch_detected = false;
int pace = LEDC_STRESSED_PACE;
bool fade_ended = false;
bool fade_in = true;

// pointer to control Bluetooth networking
BLEAdvertising *pAdvertising;

// Touch sensor callback
void gotTouch() {
//  keepPace();
  touch_detected = true;
  pace = LEDC_STRESSED_PACE;
}

// pacemaker end-of-fade Interrupt Service Routine (ISR) a.k.a. callback
void ARDUINO_ISR_ATTR LED_FADE_ISR() {
  fade_ended = true;
  keepPace();
}

// stress-inducing touch callback
void beginTouchDetection() {
  touchAttachInterrupt(TOUCH_PIN, gotTouch, TOUCH_THRESHOLD);
  log_d("touch detected");
}

// pacemaker initialization
void beginPace() {

  // Setup pacemaker timer
  ledcAttach(LED_PIN, LEDC_FREQ, LEDC_BITS);

  // fade in once uncontrolled and then begin fade out with ISR
  ledcFade(LED_PIN, LEDC_START_DUTY, LEDC_TARGET_DUTY, pace);
  delay(pace);
  ledcFadeWithInterrupt(LED_PIN, LEDC_TARGET_DUTY, LEDC_START_DUTY,
    pace, LED_FADE_ISR);
}

// pacemaker maintenance
void keepPace() {

//  if (fade_ended || touch_detected) {
  if (fade_ended) {
    fade_ended = false;

    // stress management
    if (touch_detected) {
      touch_detected = false;
      log_i("Stressed by touch!");
      if (stress < STRESS_MAX) {
        stress = stress + 10;
      }
    } else if (stress > 0) {
      stress--;
      if (stress <= 0) {
        pace = LEDC_CALM_PACE;
        log_i("Calmed down...");
      } else {
        log_i("Still stressed...");
      }
    } else {
      pace = LEDC_CALM_PACE;
    }

    // begin fade at decided direction and pace
    ledcFadeWithInterrupt(LED_PIN,
      fade_in ? LEDC_START_DUTY : LEDC_TARGET_DUTY,
      fade_in ? LEDC_TARGET_DUTY : LEDC_START_DUTY,
      pace, LED_FADE_ISR);

    // remember next fade direction
    fade_in = !fade_in;
  }
}

// read light intensity and return its non-zero capped value
int getLightIntensity() {
  int value = analogRead(LIGHT_PIN);
  if (value > DARKNESS_MAX)
    value = DARKNESS_MAX;
  log_i("light intensity: %d", value);

  return DARKNESS_MAX - value;
}

// fake gape angle as pacemaker position dampened by light intensity
int resolveGapeAngle() {
  int paceAngle = ledcRead(LED_PIN);
  log_i("pacemaker value: %d", value);

  int lightIntensity = getLightIntensity();
  log_i("light intensity: %d", value);

  int gapeAngle = paceAngle * lightIntensity / DARKNESS_MAX;

  // misuse error-only log level for plot-friendly output
#if ARDUHAL_LOG_LEVEL == ARDUHAL_LOG_LEVEL_ERROR
  Serial.printf("pace_angle:%d light/10:%d gape_angle:%d\n",
    paceAngle, lightIntensity/10, gapeAngle);
#endif

  return gapeAngle;
}

// Encode static Bluetooth beacon advertisement data
void setBeaconAdvertisement() {
  BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
  oAdvertisementData.setName(BEACON_NAME);
  pAdvertising->setAdvertisementData(oAdvertisementData);
}

// Encode variable Bluetooth beacon service data
void setBeaconServiceData(int angle) {
  BLEEddystoneTLM EddystoneTLM;
  EddystoneTLM.setTemp(angle);
  log_i("Gape angle: %.2f°", EddystoneTLM.getTemp());

  BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
  oScanResponseData.setServiceData(
    BLEUUID((uint16_t)0xFEAA),
    String(
      EddystoneTLM.getData().c_str(),
      EddystoneTLM.getData().length()));
  pAdvertising->setScanResponseData(oScanResponseData);
}

void setup() {

  // enable logging to serial
  Serial.begin(115200);
  esp_log_level_set("*", ESP_LOG_DEBUG);
  if (BEACON_UUID == "00000000-0000-0000-0000-000000000000")
    Serial.println("Please set a deployment-wide unique BEACON_UUID");

  beginPace();
  beginTouchDetection();

  // setup Bluetooth
  BLEDevice::init(BEACON_NAME);
  pAdvertising = BLEDevice::getAdvertising();
  setBeaconAdvertisement();
  setBeaconServiceData(resolveGapeAngle());
  pAdvertising->start();
}

void loop() {

  // update Bluetooth beacon service data
  setBeaconServiceData(resolveGapeAngle());

  delay(500);
}