aboutsummaryrefslogtreecommitdiff
path: root/Arduino/sensor
diff options
context:
space:
mode:
Diffstat (limited to 'Arduino/sensor')
-rw-r--r--Arduino/sensor/Mussel_Sensor_Beacon.md35
-rw-r--r--Arduino/sensor/sensor.ino215
-rw-r--r--Arduino/sensor/sensor.puml26
3 files changed, 276 insertions, 0 deletions
diff --git a/Arduino/sensor/Mussel_Sensor_Beacon.md b/Arduino/sensor/Mussel_Sensor_Beacon.md
new file mode 100644
index 0000000..89beeec
--- /dev/null
+++ b/Arduino/sensor/Mussel_Sensor_Beacon.md
@@ -0,0 +1,35 @@
+# Mussel Sensor Beacon
+
+1. Reads sensors.
+
+2. Normalises sensor data as a gape angle in the range of 0°-90°.
+
+3. Broadcasts normalised sensor data as a beacon on a bluetooth network.
+
+## Scanner apps
+
+These Android apps have been found usable
+for monitoring this type of sensor:
+
+ * FeasyBeacon on Play store
+ * NanoBeacon BLE Scanner on Play store
+ * decodes Eddystone TLM temperature as Fahrenheit
+ * does not decode Eddystone URL
+ * SemBeacon on Play store
+ * detects URL-only but not TLM-only Eddystone Beacon
+ * too aggressive caching misses changing data
+ * UI optimized for SemBeacon
+ * nRF Connect for Mobile on Play store
+ * misses changing data
+ * UI not specific to beacons
+
+These Android apps are potentially interesting as well:
+
+ * Beacon Locator on F-droid
+ * unreliable detection
+ * active development
+ * BLE Radar on F-droid
+ * detects URL-only but not TLM-only Eddystone Beacon
+ * UI not specific to beacons
+ * AltBeacon Loc on Play store
+ * detects URL-only but not TLM-only Eddystone Beacon
diff --git a/Arduino/sensor/sensor.ino b/Arduino/sensor/sensor.ino
new file mode 100644
index 0000000..8d43b22
--- /dev/null
+++ b/Arduino/sensor/sensor.ino
@@ -0,0 +1,215 @@
+// 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);
+}
diff --git a/Arduino/sensor/sensor.puml b/Arduino/sensor/sensor.puml
new file mode 100644
index 0000000..3f033d1
--- /dev/null
+++ b/Arduino/sensor/sensor.puml
@@ -0,0 +1,26 @@
+@startuml
+'start
+:instantiate mussel object;
+:instantiate bluetooth object;
+group init
+:setup mussel sensors;
+:setup bluetooth beacon;
+end group
+split
+while (each 500ms)
+group loop {
+:read sensors;
+:normalize sensor data
+as a gape angle;
+:add gape angle to beacon;
+end group
+endwhile
+-[hidden]->
+kill
+split again
+while (each 100ms)
+:broadcast beacon;
+endwhile
+-[hidden]->
+kill
+@enduml