- // 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);
- }
|