SS Sensor Hub · Wave Simulator · downstairs bench
The downstairs ESP32-WROOM-32 (38-pin DevKit) that reads all 7 KY-003 Hall sensors and the MPU-6050, reporting to Blynk (quota-safe firmware, patched 2026-08-03). Pin map matches wave_sim_hall_sensors_v1.ino.
Only the pins in use are highlighted. All hall inputs sit on the top half of the left column; I²C for the MPU is on the right column.
INPUT.| Axis | GPIO | Signal wire | Blynk pin | Notes |
|---|---|---|---|---|
| X | 36 (VP) | white | V0 | input-only pin |
| Y | 39 (VN) | white | V1 | input-only pin |
| Z1 | 34 | white | V2 | input-only pin |
| Z2 | 35 | white | V3 | input-only pin |
| Yaw | 32 | white | V4 | |
| Pitch | 33 | white | V5 | |
| Roll | 27 | white | V6 |
Every hall on the moving frame runs through a coiled USB cable with a USB breakout board at each end. The coil absorbs frame vibration mid-span; the breakout keeps strain off the ESP32 pins. Native USB wire colors already match the bench convention.
| MPU pin | ESP32 | Wire | Notes |
|---|---|---|---|
| VCC | 3V3 | red | 3.3 V bus |
| GND | GND | black | ground bus |
| SDA | 21 | yellow | I²C data |
| SCL | 22 | green | I²C clock |
wave_sim_hall_sensors_v1.ino · OTA hostname wavesim-downstairs · Serial 115200
| Behavior | Setting | Blynk cost |
|---|---|---|
| Hall updates | on-change + 500 ms/channel cap | ~2 msgs per magnet event |
| Heartbeat (V7) | every 5 min (was 5 s) | ~9k/month |
| Sensor scan | 20 Hz | free (local) |
The exact firmware on the board. Credentials live in a separate secrets.h tab (gitignored, never published). Server copy: ~/waveforge/firmware/diagnostics/wave_sim_hall_sensors_v1/
/*
* WaveForge Wave Simulator — Hall Sensor + OTA Sketch
*
* Reads 7 Hall sensors and reports state to Blynk dashboard.
* Supports ArduinoOTA for wireless firmware updates after first USB flash.
*
* Board: ESP32-WROOM-32 (38-pin DevKit)
* Blynk: WaveForge Wave Simulator template
*
* SETUP: Copy secrets.h.template to secrets.h and fill in real values.
* secrets.h is gitignored and stays local.
*/
#include "secrets.h"
#define BLYNK_PRINT Serial
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <ArduinoOTA.h>
// Hall sensor pin assignments — matches physical wiring
const int PIN_HALL_X = 36; // VP — X axis
const int PIN_HALL_Y = 39; // VN — Y axis
const int PIN_HALL_Z1 = 34; // D34 — Z axis sensor 1
const int PIN_HALL_Z2 = 35; // D35 — Z axis sensor 2
const int PIN_HALL_YAW = 32; // D32 — Yaw
const int PIN_HALL_PITCH = 33; // D33 — Pitch
const int PIN_HALL_ROLL = 27; // D27 — Roll
// Blynk virtual pins (datastreams)
#define V_HALL_X V0
#define V_HALL_Y V1
#define V_HALL_Z1 V2
#define V_HALL_Z2 V3
#define V_HALL_YAW V4
#define V_HALL_PITCH V5
#define V_HALL_ROLL V6
#define V_STATUS V7
// Track previous sensor states so we only send updates on change
int prevX = -1, prevY = -1, prevZ1 = -1, prevZ2 = -1;
int prevYaw = -1, prevPitch = -1, prevRoll = -1;
// Rate cap: during vibration a hall can chatter at the 20 Hz scan rate.
// Cap each channel to one update per RATE_CAP_MS so quota survives long runs.
const unsigned long RATE_CAP_MS = 500;
unsigned long lastSent[7] = {0,0,0,0,0,0,0};
bool canSend(int ch) {
unsigned long now = millis();
if (now - lastSent[ch] < RATE_CAP_MS) return false;
lastSent[ch] = now;
return true;
}
BlynkTimer timer;
void sendSensorReadings() {
// Invert each reading — sensors are active LOW
int x = !digitalRead(PIN_HALL_X);
int y = !digitalRead(PIN_HALL_Y);
int z1 = !digitalRead(PIN_HALL_Z1);
int z2 = !digitalRead(PIN_HALL_Z2);
int yaw = !digitalRead(PIN_HALL_YAW);
int pitch = !digitalRead(PIN_HALL_PITCH);
int roll = !digitalRead(PIN_HALL_ROLL);
if (x != prevX && canSend(0)) { Blynk.virtualWrite(V_HALL_X, x); prevX = x; }
if (y != prevY && canSend(1)) { Blynk.virtualWrite(V_HALL_Y, y); prevY = y; }
if (z1 != prevZ1 && canSend(2)) { Blynk.virtualWrite(V_HALL_Z1, z1); prevZ1 = z1; }
if (z2 != prevZ2 && canSend(3)) { Blynk.virtualWrite(V_HALL_Z2, z2); prevZ2 = z2; }
if (yaw != prevYaw && canSend(4)) { Blynk.virtualWrite(V_HALL_YAW, yaw); prevYaw = yaw; }
if (pitch != prevPitch && canSend(5)) { Blynk.virtualWrite(V_HALL_PITCH, pitch); prevPitch = pitch; }
if (roll != prevRoll && canSend(6)) { Blynk.virtualWrite(V_HALL_ROLL, roll); prevRoll = roll; }
}
void sendHeartbeat() {
String msg = "Online — up " + String(millis() / 60000) + " min";
Blynk.virtualWrite(V_STATUS, msg);
}
void setupOTA() {
ArduinoOTA.setHostname("wavesim-downstairs");
ArduinoOTA.onStart([]() {
Serial.println("OTA update starting");
Blynk.virtualWrite(V_STATUS, "OTA update starting");
});
ArduinoOTA.onEnd([]() {
Serial.println("OTA complete");
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("OTA error[%u]\n", error);
});
ArduinoOTA.begin();
Serial.println("OTA ready");
}
void setup() {
Serial.begin(115200);
Serial.println("WaveForge Wave Simulator starting");
// GPIO 34, 35, 36, 39 are input-only — no INPUT_PULLUP available
// Hall modules have onboard pull-ups, so INPUT is correct
pinMode(PIN_HALL_X, INPUT);
pinMode(PIN_HALL_Y, INPUT);
pinMode(PIN_HALL_Z1, INPUT);
pinMode(PIN_HALL_Z2, INPUT);
pinMode(PIN_HALL_YAW, INPUT);
pinMode(PIN_HALL_PITCH, INPUT);
pinMode(PIN_HALL_ROLL, INPUT);
Blynk.begin(BLYNK_AUTH_TOKEN, WIFI_SSID, WIFI_PASS);
setupOTA();
timer.setInterval(50L, sendSensorReadings); // 20 Hz sensor scan
timer.setInterval(300000L, sendHeartbeat); // 5 min heartbeat (was 5 s — Blynk quota!)
Blynk.virtualWrite(V_STATUS, "Wave Sim online");
}
void loop() {
Blynk.run();
ArduinoOTA.handle();
timer.run();
}