Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!

Human-microenvironment interaction

Exploring interspecies interaction via a heart beat sensor triggering water movement, mapped to sound and visuals

BeginnerFull instructions provided4 hours143
Human-microenvironment interaction

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
Solar Cockroach Vibrating Disc Motor
Brown Dog Gadgets Solar Cockroach Vibrating Disc Motor
×1
Gravity: Heart Rate Monitor Sensor for Arduino
DFRobot Gravity: Heart Rate Monitor Sensor for Arduino
×1
Microscope
×1
Petri dish
×1

Software apps and online services

TouchDesigner
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Biomaterials
Laser cutter (generic)
Laser cutter (generic)

Story

Read more

Schematics

Circuit Diagram

Code

Heartbeat to Vibration motor

C/C++
const int PULSE_SENSOR_PIN = 18; // Replace with the ESP32-S3 pin connected to the v1706
const int VIBRATION_PIN_1 = 12;
const int VIBRATION_PIN_2 = 14;

const int THRESHOLD = 2100; // Adjust this threshold after testing
const int BEAT_DURATION = 200;
const int PAUSE_DURATION = 200;
const int VIBRATION_INTENSITY = 150; // Fixed vibration intensity (0-255)

unsigned long previousBeatTime = 0;
bool beatDetected = false;

// --- Variables for filtering ---
const int SAMPLE_SIZE = 10; // Number of samples for averaging
int pulseReadings[SAMPLE_SIZE];
int readingIndex = 0;
int averagePulse = 0;

// --- Variables for BPM calculation ---
unsigned long lastBeatTime = 0;
unsigned long interBeatInterval = 0;
int bpm = 0;

void setup() {
    Serial.begin(115200);
    pinMode(VIBRATION_PIN_1, OUTPUT);
    pinMode(VIBRATION_PIN_2, OUTPUT);

    // --- Initialize pulseReadings array ---
    for (int i = 0; i < SAMPLE_SIZE; i++) {
        pulseReadings[i] = 0;
    }
}

void loop() {
    // Read sensor value
    int pulseValue = analogRead(PULSE_SENSOR_PIN);

    // --- Simple Moving Average Filtering ---
    pulseReadings[readingIndex] = pulseValue;
    readingIndex = (readingIndex + 1) % SAMPLE_SIZE; // Wrap around
    int sum = 0;
    for (int i = 0; i < SAMPLE_SIZE; i++) {
        sum += pulseReadings[i];
    }
    averagePulse = sum / SAMPLE_SIZE;

    // --- Beat Detection ---
    if (averagePulse > THRESHOLD && !beatDetected) {
        beatDetected = true;
        unsigned long currentTime = millis();

        // --- Calculate inter-beat interval and BPM ---
        interBeatInterval = currentTime - lastBeatTime;
        if (interBeatInterval > 0) {
            bpm = 60000 / interBeatInterval; // 60000 milliseconds in a minute
        }
        lastBeatTime = currentTime;

        previousBeatTime = millis();

        // --- Trigger vibration (using analogWrite for PWM)---
        analogWrite(VIBRATION_PIN_1, VIBRATION_INTENSITY);
        analogWrite(VIBRATION_PIN_2, VIBRATION_INTENSITY);
    } else if (millis() - previousBeatTime > BEAT_DURATION) {
        // --- Stop vibration after BEAT_DURATION ---
        analogWrite(VIBRATION_PIN_1, 0);
        analogWrite(VIBRATION_PIN_2, 0);

        if (millis() - previousBeatTime > BEAT_DURATION + PAUSE_DURATION) {
            // --- Reset beat detection after PAUSE_DURATION ---
            beatDetected = false;
        }
    }

    // --- Print Data to Serial Monitor ---
    Serial.print("Raw: ");
    Serial.print(pulseValue);
    Serial.print(" | Filtered: ");
    Serial.print(averagePulse);
    Serial.print(" | BPM: ");
    Serial.println(bpm);

    delay(10);
}

TouchDesigner visual interface

Python
No preview (download only).

Open CV corner detection

Python
# me - this DAT
# scriptOp - the OP which is cooking

import numpy as np
import cv2 as cv

# press 'Setup Parameters' in the OP to call this function to re-create the parameters.
def onSetupParameters(scriptOp):

	return

# called whenever custom pulse parameter is pushed
def onPulse(par):
	return


def onCook(scriptOp):
    img = op('null2').numpyArray(delayed=True)
    
    
    gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
     
    # find Harris corners
    gray = np.float32(gray)
    dst = cv.cornerHarris(gray,2,3,0.04)
    dst = cv.dilate(dst,None)
    ret, dst = cv.threshold(dst,0.01*dst.max(),255,0)
    dst = np.uint8(dst)
     
    # find centroids
    ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)
     
    # define the criteria to stop and refine the corners
    criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
    corners = cv.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)

    scriptOp.store('corners', corners)

    #op('table1').clear()

    #for i in corners:
     #   op('table1').appendRow(i)

    img[dst>0.01*dst.max()]=[0,0,255,255]
    scriptOp.copyNumpyArray(img)
    #print(corners)
    return

Credits

Maria Vittoria Colombo
2 projects • 6 followers
Contact
belll
2 projects • 5 followers
Working on biofabrication and fast prototyping at IAAC Barcelona
Contact
ziming shang
1 project • 5 followers
Contact
Kevin Enriquez Ambrocio
2 projects • 5 followers
Contact

Comments

Please log in or sign up to comment.