#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <math.h>
// ============================================================
// WIFI
// ============================================================
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASSWORD = "";
// ============================================================
// SUPABASE
// ============================================================
const char* SUPABASE_URL =
"https://ypisqngghzppqolacaau.supabase.co";
const char* SUPABASE_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlwaXNxbmdnaHpwcHFvbGFjYWF1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODc3MjA2NTUsImV4cCI6MjEwMzI5NjY1NX0.nOvgRV8AVuetbwdpFL6gyYwxqwjR4qGaAVDW4l4HLos";
// ============================================================
// GPIO
// ============================================================
#define RED_LED 2
#define BLUE_LED 5
#define YELLOW_LED 18
#define BUTTON_PIN 4
#define SDA_PIN 21
#define SCL_PIN 22
// ============================================================
// POSTURE SETTINGS
// ============================================================
const float POSTURE_THRESHOLD = 5.0;
// Bad posture correction time
const unsigned long WARNING_TIME = 10000;
// ============================================================
// DATABASE TIMING
// ============================================================
// Therapy commands read every 400ms
const unsigned long THERAPY_READ_INTERVAL = 400;
// History is not continuously pushed
const unsigned long HISTORY_INTERVAL = 10000;
// Device state is saved continuously
const unsigned long STATE_SYNC_INTERVAL = 1000;
// ============================================================
// NETWORK
// ============================================================
// Short timeout so network failure doesn't freeze posture system
const uint16_t HTTP_TIMEOUT = 1200;
// ============================================================
// OBJECTS
// ============================================================
Adafruit_MPU6050 mpu;
// ============================================================
// KEEP-ALIVE HTTPS CLIENT
// ============================================================
//
// One HTTPS client is reused for Supabase.
// HTTPClient is also configured for connection reuse.
//
WiFiClientSecure supabaseClient;
HTTPClient therapyHttp;
HTTPClient stateHttp;
HTTPClient historyHttp;
// ============================================================
// DEVICE ID
// ============================================================
String deviceId;
// ============================================================
// POSTURE VARIABLES
// ============================================================
float neutralAngle = 0.0;
float currentAngle = 0.0;
bool trackingEnabled = false;
bool badPosture = false;
bool waitingForCorrection = false;
unsigned long badPostureStart = 0;
unsigned long lastBadSecondTick = 0;
unsigned long badPostureSeconds = 0;
// ============================================================
// THERAPY STATE
// ============================================================
bool heatingEnabled = false;
bool tensEnabled = false;
// CUMULATIVE THERAPY SESSION COUNT
unsigned long therapySessionsCount = 0;
int heatLevel = 1;
int tensLevel = 1;
bool vibrationAlert = false;
// ============================================================
// PREVIOUS THERAPY STATE
// Used to print logs ONLY when state changes
// ============================================================
bool previousHeatingEnabled = false;
bool previousTensEnabled = false;
int previousHeatLevel = 1;
int previousTensLevel = 1;
// First successful therapy read
bool therapyStateInitialized = false;
// ============================================================
// DATABASE TIMERS
// ============================================================
unsigned long lastTherapyRead = 0;
unsigned long lastHistoryUpdate = 0;
unsigned long lastStateSync = 0;
unsigned long lastTrackingSync = 0;
// ============================================================
// BUTTON
// ============================================================
bool lastButtonState = HIGH;
unsigned long lastButtonPress = 0;
// ============================================================
// YELLOW LED STATE
// ============================================================
bool yellowState = false;
int yellowBlinkCount = 0;
unsigned long yellowLastChange = 0;
unsigned long yellowPauseUntil = 0;
int yellowPreviousLevel = -1;
// ============================================================
// BLUE LED STATE
// ============================================================
bool blueState = false;
int blueBlinkCount = 0;
unsigned long blueLastChange = 0;
unsigned long bluePauseUntil = 0;
int bluePreviousLevel = -1;
// ============================================================
// FUNCTION DECLARATIONS
// ============================================================
void connectWiFi();
void ensureWiFi();
void generateDeviceId();
void calibrateMPU();
float getPostureAngle();
void handlePosture();
void blinkRedAlert();
void readTherapyState();
void updateTherapyLED(
int ledPin,
bool enabled,
int level,
bool &ledState,
int &blinkCount,
unsigned long &lastChange,
unsigned long &pauseUntil,
int &previousLevel,
const char* therapyName
);
void updateDeviceState();
void syncTrackingFromDatabase();
void updatePostureHistory();
bool readJsonBool(
const String& json,
const char* key,
bool fallback
);
int readJsonInt(
const String& json,
const char* key,
int fallback
);
String getTodayDate();
void printTherapyChangeLogs();
void resetTherapyLEDState(
int ledPin,
bool &ledState,
int &blinkCount,
unsigned long &lastChange,
unsigned long &pauseUntil
);
// ============================================================
// SETUP
// ============================================================
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println();
Serial.println("========================================");
Serial.println(" SMART BIOPATCH ESP32");
Serial.println("========================================");
// ==========================================================
// GPIO
// ==========================================================
pinMode(RED_LED, OUTPUT);
pinMode(BLUE_LED, OUTPUT);
pinMode(YELLOW_LED, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
digitalWrite(RED_LED, LOW);
digitalWrite(BLUE_LED, LOW);
digitalWrite(YELLOW_LED, LOW);
// ==========================================================
// I2C
// ==========================================================
Wire.begin(
SDA_PIN,
SCL_PIN
);
Serial.println("Initializing MPU6050...");
if (!mpu.begin()) {
Serial.println("ERROR: MPU6050 not found!");
while (true) {
digitalWrite(
RED_LED,
HIGH
);
delay(300);
digitalWrite(
RED_LED,
LOW
);
delay(300);
}
}
Serial.println("MPU6050 connected.");
// ==========================================================
// WIFI
// ==========================================================
connectWiFi();
// ==========================================================
// DEVICE ID
// ==========================================================
generateDeviceId();
// ==========================================================
// SUPABASE HTTPS CLIENT
// ==========================================================
supabaseClient.setInsecure();
// ==========================================================
// CALIBRATION
// ==========================================================
calibrateMPU();
// ==========================================================
// INITIAL DEVICE STATE
// ==========================================================
Serial.println();
Serial.println("Checking device in Supabase...");
syncTrackingFromDatabase();
updateDeviceState();
updatePostureHistory();
// ==========================================================
// SYSTEM READY
// ONLY ONCE
// ==========================================================
Serial.println();
Serial.println("========================================");
Serial.println("SYSTEM READY");
Serial.println("========================================");
Serial.print("Device ID: ");
Serial.println(deviceId);
Serial.print("Tracking: ");
Serial.println(trackingEnabled ? "ON" : "OFF");
Serial.println("Posture threshold: 5 degrees");
Serial.println("Warning timer: 10 seconds");
Serial.println("Therapy polling: 400ms");
Serial.println("HTTPS Keep-Alive: ENABLED");
Serial.println("========================================");
}
// ============================================================
// LOOP
// ============================================================
void loop() {
// ==========================================================
// WIFI
// ==========================================================
ensureWiFi();
// ==========================================================
// BUTTON
// ==========================================================
bool buttonState =
digitalRead(BUTTON_PIN);
if (
lastButtonState == HIGH &&
buttonState == LOW
) {
if (
millis() -
lastButtonPress >
500
) {
trackingEnabled =
!trackingEnabled;
lastButtonPress =
millis();
// ======================================================
// TRACKING START
// ======================================================
if (trackingEnabled) {
badPosture = false;
waitingForCorrection = false;
badPostureStart = 0;
lastBadSecondTick = 0;
digitalWrite(
RED_LED,
LOW
);
Serial.println();
Serial.println(
">>> TRACKING STARTED"
);
// Sync immediately
updateDeviceState();
}
// ======================================================
// TRACKING STOP
// ======================================================
else {
badPosture = false;
waitingForCorrection = false;
badPostureStart = 0;
lastBadSecondTick = 0;
digitalWrite(
RED_LED,
LOW
);
Serial.println();
Serial.println(
">>> TRACKING STOPPED"
);
// Sync immediately
updateDeviceState();
}
}
}
lastButtonState =
buttonState;
// ==========================================================
// THERAPY
//
// EVERY 400ms
// ==========================================================
if (
millis() -
lastTherapyRead >=
THERAPY_READ_INTERVAL
) {
lastTherapyRead =
millis();
readTherapyState();
}
// ==========================================================
// TRACKING STATE FROM SUPABASE
// App/Supabase controls tracking ON/OFF
// ==========================================================
if (
millis() -
lastTrackingSync >=
1000
) {
lastTrackingSync =
millis();
syncTrackingFromDatabase();
}
// ==========================================================
// CONTINUOUS DEVICE STATE SYNC
// ==========================================================
if (
millis() -
lastStateSync >=
STATE_SYNC_INTERVAL
) {
lastStateSync =
millis();
updateDeviceState();
}
// ==========================================================
// YELLOW = HEATING
// ==========================================================
updateTherapyLED(
YELLOW_LED,
heatingEnabled,
heatLevel,
yellowState,
yellowBlinkCount,
yellowLastChange,
yellowPauseUntil,
yellowPreviousLevel,
"HEATING"
);
// ==========================================================
// BLUE = TENS
// ==========================================================
updateTherapyLED(
BLUE_LED,
tensEnabled,
tensLevel,
blueState,
blueBlinkCount,
blueLastChange,
bluePauseUntil,
bluePreviousLevel,
"TENS"
);
// ==========================================================
// POSTURE
// ==========================================================
if (trackingEnabled) {
handlePosture();
}
// ==========================================================
// PERIODIC HISTORY
// ==========================================================
if (
millis() -
lastHistoryUpdate >=
HISTORY_INTERVAL
) {
lastHistoryUpdate =
millis();
updatePostureHistory();
}
// ==========================================================
// VERY SMALL LOOP DELAY
// ==========================================================
delay(5);
}
// ============================================================
// WIFI CONNECT
// ============================================================
void connectWiFi() {
Serial.println();
Serial.println("Connecting to WiFi...");
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(
WIFI_SSID,
WIFI_PASSWORD
);
int attempts = 0;
while (
WiFi.status() != WL_CONNECTED &&
attempts < 40
) {
delay(500);
Serial.print(".");
attempts++;
}
Serial.println();
if (
WiFi.status() ==
WL_CONNECTED
) {
Serial.println(
"WiFi connected!"
);
Serial.print("IP: ");
Serial.println(
WiFi.localIP()
);
Serial.print("MAC: ");
Serial.println(
WiFi.macAddress()
);
delay(500);
} else {
Serial.println(
"WiFi connection failed!"
);
}
}
// ============================================================
// ENSURE WIFI
// ============================================================
void ensureWiFi() {
if (
WiFi.status() ==
WL_CONNECTED
) {
return;
}
Serial.println();
Serial.println(
"WiFi disconnected. Reconnecting..."
);
WiFi.disconnect();
delay(100);
WiFi.begin(
WIFI_SSID,
WIFI_PASSWORD
);
int attempts = 0;
while (
WiFi.status() != WL_CONNECTED &&
attempts < 10
) {
delay(300);
attempts++;
}
if (
WiFi.status() ==
WL_CONNECTED
) {
Serial.println(
"WiFi reconnected!"
);
} else {
Serial.println(
"WiFi reconnect failed."
);
}
}
// ============================================================
// DEVICE ID
// ============================================================
void generateDeviceId() {
String mac =
WiFi.macAddress();
mac.replace(
":",
""
);
mac.toUpperCase();
deviceId =
mac;
Serial.println();
Serial.println(
"========================================"
);
Serial.print(
"ESP32 MAC: "
);
Serial.println(
WiFi.macAddress()
);
Serial.print(
"DEVICE ID: "
);
Serial.println(
deviceId
);
Serial.println(
"========================================"
);
}
// ============================================================
// MPU CALIBRATION
// ============================================================
void calibrateMPU() {
Serial.println();
Serial.println(
"Calibrating MPU6050..."
);
Serial.println(
"Keep sensor in normal posture."
);
delay(1500);
float total =
0;
const int samples =
100;
for (
int i = 0;
i < samples;
i++
) {
sensors_event_t a;
sensors_event_t g;
sensors_event_t temp;
mpu.getEvent(
&a,
&g,
&temp
);
float angle =
atan2(
a.acceleration.y,
sqrt(
a.acceleration.x *
a.acceleration.x +
a.acceleration.z *
a.acceleration.z
)
)
*
180.0 /
PI;
total +=
angle;
delay(10);
}
neutralAngle =
total /
samples;
Serial.print(
"Neutral angle: "
);
Serial.println(
neutralAngle,
2
);
Serial.println(
"Calibration complete."
);
}
// ============================================================
// GET POSTURE ANGLE
// ============================================================
float getPostureAngle() {
sensors_event_t a;
sensors_event_t g;
sensors_event_t temp;
mpu.getEvent(
&a,
&g,
&temp
);
float angle =
atan2(
a.acceleration.y,
sqrt(
a.acceleration.x *
a.acceleration.x +
a.acceleration.z *
a.acceleration.z
)
)
*
180.0 /
PI;
return angle -
neutralAngle;
}
// ============================================================
// POSTURE HANDLER
// ============================================================
void handlePosture() {
currentAngle =
getPostureAngle();
float deviation =
fabs(currentAngle);
// ==========================================================
// NORMAL POSTURE
// ==========================================================
if (
deviation <=
POSTURE_THRESHOLD
) {
if (
badPosture ||
waitingForCorrection
) {
badPosture =
false;
waitingForCorrection =
false;
badPostureStart =
0;
lastBadSecondTick =
0;
digitalWrite(
RED_LED,
LOW
);
Serial.println();
Serial.println(
">>> POSTURE CORRECTED"
);
}
return;
}
// ==========================================================
// BAD POSTURE FIRST DETECTION
// ==========================================================
if (!badPosture) {
badPosture =
true;
waitingForCorrection =
true;
badPostureStart =
millis();
lastBadSecondTick =
millis();
Serial.println();
Serial.print(
">>> BAD POSTURE DETECTED | "
);
Serial.print(
deviation,
1
);
Serial.println(
" deg"
);
return;
}
// ==========================================================
// COUNT BAD POSTURE TIME
// ==========================================================
if (
millis() -
lastBadSecondTick >=
1000
) {
unsigned long secondsPassed =
(
millis() -
lastBadSecondTick
) /
1000;
badPostureSeconds +=
secondsPassed;
lastBadSecondTick +=
secondsPassed *
1000;
}
// ==========================================================
// WAIT 10 SEC
// ==========================================================
unsigned long elapsed =
millis() -
badPostureStart;
if (
elapsed <
WARNING_TIME
) {
return;
}
// ==========================================================
// ALERT
// ==========================================================
Serial.println();
Serial.println(
">>> POSTURE ALERT"
);
Serial.println(
"10 seconds completed - RED 3 BLINKS"
);
vibrationAlert =
true;
blinkRedAlert();
vibrationAlert =
false;
// ==========================================================
// SAVE HISTORY
// ==========================================================
updatePostureHistory();
// ==========================================================
// NEXT 10 SEC PERIOD
// ==========================================================
badPostureStart =
millis();
lastBadSecondTick =
millis();
}
// ============================================================
// RED LED 3 BLINK
// ============================================================
void blinkRedAlert() {
for (
int i = 0;
i < 3;
i++
) {
digitalWrite(
RED_LED,
HIGH
);
delay(150);
digitalWrite(
RED_LED,
LOW
);
delay(150);
}
}
// ============================================================
// READ THERAPY STATE
// ============================================================
//
// IMPORTANT:
//
// No repeated logs.
// Only state changes are printed.
//
// ============================================================
void readTherapyState() {
if (
WiFi.status() !=
WL_CONNECTED
) {
return;
}
String url =
String(SUPABASE_URL) +
"/rest/v1/device_state?device_id=eq." +
deviceId +
"&select=heating_enabled,heat_level,tens_enabled,tens_level,vibration_alert";
// ==========================================================
// KEEP-ALIVE HTTP
// ==========================================================
if (
!therapyHttp.begin(
supabaseClient,
url
)
) {
return;
}
therapyHttp.setReuse(
true
);
therapyHttp.setTimeout(
HTTP_TIMEOUT
);
therapyHttp.addHeader(
"apikey",
SUPABASE_KEY
);
therapyHttp.addHeader(
"Authorization",
String("Bearer ") +
SUPABASE_KEY
);
therapyHttp.addHeader(
"Connection",
"keep-alive"
);
int httpCode =
therapyHttp.GET();
// ==========================================================
// SUCCESS
// ==========================================================
if (
httpCode == 200
) {
String response =
therapyHttp.getString();
bool newHeating =
readJsonBool(
response,
"heating_enabled",
heatingEnabled
);
bool newTens =
readJsonBool(
response,
"tens_enabled",
tensEnabled
);
bool newVibration =
readJsonBool(
response,
"vibration_alert",
vibrationAlert
);
int newHeatLevel =
constrain(
readJsonInt(
response,
"heat_level",
heatLevel
),
1,
10
);
int newTensLevel =
constrain(
readJsonInt(
response,
"tens_level",
tensLevel
),
1,
10
);
// ========================================================
// FIRST VALID STATE
// Don't spam logs during startup.
// ========================================================
if (!therapyStateInitialized) {
heatingEnabled =
newHeating;
tensEnabled =
newTens;
vibrationAlert =
newVibration;
heatLevel =
newHeatLevel;
tensLevel =
newTensLevel;
previousHeatingEnabled =
newHeating;
previousTensEnabled =
newTens;
previousHeatLevel =
newHeatLevel;
previousTensLevel =
newTensLevel;
therapyStateInitialized =
true;
}
// ========================================================
// HEATING START / STOP
// ========================================================
if (
newHeating !=
heatingEnabled
) {
heatingEnabled =
newHeating;
Serial.println();
if (heatingEnabled) {
Serial.println(
">>> HEATING STARTED"
);
therapySessionsCount++;
updatePostureHistory();
} else {
Serial.println(
">>> HEATING STOPPED"
);
}
resetTherapyLEDState(
YELLOW_LED,
yellowState,
yellowBlinkCount,
yellowLastChange,
yellowPauseUntil
);
}
// ========================================================
// TENS START / STOP
// ========================================================
if (
newTens !=
tensEnabled
) {
tensEnabled =
newTens;
Serial.println();
if (tensEnabled) {
Serial.println(
">>> TENS STARTED"
);
therapySessionsCount++;
updatePostureHistory();
} else {
Serial.println(
">>> TENS STOPPED"
);
}
resetTherapyLEDState(
BLUE_LED,
blueState,
blueBlinkCount,
blueLastChange,
bluePauseUntil
);
}
// ========================================================
// HEAT LEVEL
// ========================================================
if (
newHeatLevel !=
heatLevel
) {
heatLevel =
newHeatLevel;
Serial.print(
">>> HEAT LEVEL -> "
);
Serial.println(
heatLevel
);
resetTherapyLEDState(
YELLOW_LED,
yellowState,
yellowBlinkCount,
yellowLastChange,
yellowPauseUntil
);
}
// ========================================================
// TENS LEVEL
// ========================================================
if (
newTensLevel !=
tensLevel
) {
tensLevel =
newTensLevel;
Serial.print(
">>> TENS LEVEL -> "
);
Serial.println(
tensLevel
);
resetTherapyLEDState(
BLUE_LED,
blueState,
blueBlinkCount,
blueLastChange,
bluePauseUntil
);
}
// ========================================================
// VIBRATION
// ========================================================
vibrationAlert =
newVibration;
}
// ==========================================================
// IMPORTANT:
// DO NOT PRINT HTTP ERRORS EVERY 400ms
// ==========================================================
therapyHttp.end();
}
// ============================================================
// RESET THERAPY LED
// ============================================================
void resetTherapyLEDState(
int ledPin,
bool &ledState,
int &blinkCount,
unsigned long &lastChange,
unsigned long &pauseUntil
) {
digitalWrite(
ledPin,
LOW
);
ledState =
false;
blinkCount =
0;
lastChange =
millis();
pauseUntil =
0;
}
// ============================================================
// JSON BOOL READER
// ============================================================
bool readJsonBool(
const String& json,
const char* key,
bool fallback
) {
String searchKey =
"\"" +
String(key) +
"\"";
int pos =
json.indexOf(
searchKey
);
if (
pos < 0
) {
return fallback;
}
int colon =
json.indexOf(
":",
pos
);
if (
colon < 0
) {
return fallback;
}
String value =
json.substring(
colon + 1,
colon + 15
);
value.trim();
if (
value.startsWith(
"true"
)
) {
return true;
}
if (
value.startsWith(
"false"
)
) {
return false;
}
return fallback;
}
// ============================================================
// JSON INT READER
// ============================================================
int readJsonInt(
const String& json,
const char* key,
int fallback
) {
String searchKey =
"\"" +
String(key) +
"\"";
int pos =
json.indexOf(
searchKey
);
if (
pos < 0
) {
return fallback;
}
int colon =
json.indexOf(
":",
pos
);
if (
colon < 0
) {
return fallback;
}
int start =
colon + 1;
while (
start <
json.length()
) {
char c =
json[start];
if (
c == ' ' ||
c == '\t' ||
c == '\n' ||
c == '\r'
) {
start++;
} else {
break;
}
}
int end =
start;
while (
end <
json.length()
) {
if (
isDigit(
json[end]
)
) {
end++;
} else {
break;
}
}
if (
end ==
start
) {
return fallback;
}
return json.substring(
start,
end
).toInt();
}
// ============================================================
// THERAPY LED CONTROLLER
// ============================================================
void updateTherapyLED(
int ledPin,
bool enabled,
int level,
bool &ledState,
int &blinkCount,
unsigned long &lastChange,
unsigned long &pauseUntil,
int &previousLevel,
const char* therapyName
) {
unsigned long now =
millis();
level =
constrain(
level,
1,
10
);
// ==========================================================
// DISABLED
// ==========================================================
if (!enabled) {
digitalWrite(
ledPin,
LOW
);
ledState =
false;
blinkCount =
0;
pauseUntil =
0;
previousLevel =
level;
return;
}
// ==========================================================
// LEVEL CHANGE
// ==========================================================
if (
previousLevel !=
level
) {
previousLevel =
level;
digitalWrite(
ledPin,
LOW
);
ledState =
false;
blinkCount =
0;
pauseUntil =
0;
lastChange =
now;
}
// ==========================================================
// LEVEL 1 = STEADY ON
// ==========================================================
if (
level == 1
) {
digitalWrite(
ledPin,
HIGH
);
ledState =
true;
blinkCount =
0;
pauseUntil =
0;
return;
}
// ==========================================================
// LEVEL 2 = CONTINUOUS BLINK
// ==========================================================
if (
level == 2
) {
if (
now -
lastChange >=
300
) {
lastChange =
now;
ledState =
!ledState;
digitalWrite(
ledPin,
ledState
? HIGH
: LOW
);
}
return;
}
// ==========================================================
// LEVEL 3-10
// ==========================================================
if (
now <
pauseUntil
) {
digitalWrite(
ledPin,
LOW
);
return;
}
if (
now -
lastChange >=
250
) {
lastChange =
now;
ledState =
!ledState;
digitalWrite(
ledPin,
ledState
? HIGH
: LOW
);
// ========================================================
// BLINK COMPLETED
// ========================================================
if (!ledState) {
blinkCount++;
if (
blinkCount >=
level
) {
blinkCount =
0;
digitalWrite(
ledPin,
LOW
);
ledState =
false;
pauseUntil =
now + 1200;
}
}
}
}
// ============================================================
// SYNC TRACKING FROM SUPABASE
// ============================================================
void syncTrackingFromDatabase() {
if (
WiFi.status() !=
WL_CONNECTED
) {
return;
}
String url =
String(SUPABASE_URL) +
"/rest/v1/device_state?device_id=eq." +
deviceId +
"&select=tracking_enabled";
if (
!stateHttp.begin(
supabaseClient,
url
)
) {
return;
}
stateHttp.setReuse(
true
);
stateHttp.setTimeout(
HTTP_TIMEOUT
);
stateHttp.addHeader(
"apikey",
SUPABASE_KEY
);
stateHttp.addHeader(
"Authorization",
String("Bearer ") +
SUPABASE_KEY
);
stateHttp.addHeader(
"Connection",
"keep-alive"
);
int httpCode =
stateHttp.GET();
if (
httpCode == 200
) {
String response =
stateHttp.getString();
int pos =
response.indexOf(
"\"tracking_enabled\""
);
if (
pos >= 0
) {
bool newTracking =
readJsonBool(
response,
"tracking_enabled",
trackingEnabled
);
if (
newTracking !=
trackingEnabled
) {
trackingEnabled =
newTracking;
badPosture = false;
waitingForCorrection = false;
badPostureStart = 0;
lastBadSecondTick = 0;
digitalWrite(
RED_LED,
LOW
);
Serial.println();
if (trackingEnabled) {
Serial.println(
">>> TRACKING STARTED"
);
} else {
Serial.println(
">>> TRACKING STOPPED"
);
}
}
}
}
stateHttp.end();
}
// ============================================================
// UPDATE DEVICE STATE
// ============================================================
//
// Called:
// 1. At startup
// 2. When tracking starts
// 3. When tracking stops
//
// NOT every 5 seconds.
//
// ============================================================
void updateDeviceState() {
if (
WiFi.status() !=
WL_CONNECTED
) {
return;
}
String checkURL =
String(SUPABASE_URL) +
"/rest/v1/device_state" +
"?device_id=eq." +
deviceId +
"&select=device_id";
// ==========================================================
// CHECK DEVICE
// ==========================================================
if (
!stateHttp.begin(
supabaseClient,
checkURL
)
) {
return;
}
stateHttp.setReuse(
true
);
stateHttp.setTimeout(
HTTP_TIMEOUT
);
stateHttp.addHeader(
"apikey",
SUPABASE_KEY
);
stateHttp.addHeader(
"Authorization",
String("Bearer ") +
SUPABASE_KEY
);
stateHttp.addHeader(
"Connection",
"keep-alive"
);
int checkCode =
stateHttp.GET();
String response =
"";
if (
checkCode == 200
) {
response =
stateHttp.getString();
}
stateHttp.end();
// ==========================================================
// DEVICE EXISTS
// ==========================================================
if (
checkCode == 200 &&
response.indexOf(deviceId) >= 0
) {
String updateURL =
String(SUPABASE_URL) +
"/rest/v1/device_state" +
"?device_id=eq." +
deviceId;
if (
!stateHttp.begin(
supabaseClient,
updateURL
)
) {
return;
}
stateHttp.setReuse(
true
);
stateHttp.setTimeout(
HTTP_TIMEOUT
);
stateHttp.addHeader(
"Content-Type",
"application/json"
);
stateHttp.addHeader(
"apikey",
SUPABASE_KEY
);
stateHttp.addHeader(
"Authorization",
String("Bearer ") +
SUPABASE_KEY
);
stateHttp.addHeader(
"Prefer",
"return=minimal"
);
stateHttp.addHeader(
"Connection",
"keep-alive"
);
String json =
"{";
json +=
"\"battery_level\":100,";
json +=
"\"today_bad_posture_sec\":" +
String(
badPostureSeconds
) +
",";
json +=
"\"tracking_enabled\":" +
String(
trackingEnabled
? "true"
: "false"
);
json +=
"}";
stateHttp.PATCH(
json
);
stateHttp.end();
return;
}
// ==========================================================
// DEVICE DOES NOT EXIST
// ==========================================================
if (
checkCode == 200 &&
response.indexOf(deviceId) < 0
) {
String insertURL =
String(SUPABASE_URL) +
"/rest/v1/device_state";
if (
!stateHttp.begin(
supabaseClient,
insertURL
)
) {
return;
}
stateHttp.setReuse(
true
);
stateHttp.setTimeout(
HTTP_TIMEOUT
);
stateHttp.addHeader(
"Content-Type",
"application/json"
);
stateHttp.addHeader(
"apikey",
SUPABASE_KEY
);
stateHttp.addHeader(
"Authorization",
String("Bearer ") +
SUPABASE_KEY
);
stateHttp.addHeader(
"Prefer",
"return=minimal"
);
stateHttp.addHeader(
"Connection",
"keep-alive"
);
String json =
"{";
json +=
"\"device_id\":\"" +
deviceId +
"\",";
json +=
"\"battery_level\":100,";
json +=
"\"today_bad_posture_sec\":0,";
json +=
"\"tracking_enabled\":false,";
json +=
"\"heating_enabled\":false,";
json +=
"\"heat_level\":1,";
json +=
"\"tens_enabled\":false,";
json +=
"\"tens_level\":1,";
json +=
"\"vibration_alert\":false";
json +=
"}";
int code =
stateHttp.POST(
json
);
if (
code >= 200 &&
code < 300
) {
Serial.println(">>> NEW DEVICE CREATED");
Serial.println(
">>> NEW DEVICE CREATED"
);
}
stateHttp.end();
}
}
// ============================================================
// POSTURE HISTORY
// ============================================================
void updatePostureHistory() {
if (
WiFi.status() !=
WL_CONNECTED
) {
return;
}
String today =
getTodayDate();
int badMinutes =
(
badPostureSeconds +
59
) /
60;
if (
badPostureSeconds > 0 &&
badMinutes < 1
) {
badMinutes =
1;
}
// ==========================================================
// SELECT TODAY
// ==========================================================
String selectURL =
String(SUPABASE_URL) +
"/rest/v1/posture_history" +
"?device_id=eq." +
deviceId +
"&record_date=eq." +
today +
"&select=bad_posture_min,therapy_sessions";
if (
!historyHttp.begin(
supabaseClient,
selectURL
)
) {
return;
}
historyHttp.setReuse(
true
);
historyHttp.setTimeout(
HTTP_TIMEOUT
);
historyHttp.addHeader(
"apikey",
SUPABASE_KEY
);
historyHttp.addHeader(
"Authorization",
String("Bearer ") +
SUPABASE_KEY
);
historyHttp.addHeader(
"Connection",
"keep-alive"
);
int getCode =
historyHttp.GET();
String response =
"";
if (
getCode == 200
) {
response =
historyHttp.getString();
}
historyHttp.end();
// ==========================================================
// EXISTING RECORD
// ==========================================================
if (
response.length() > 5 &&
response.indexOf(
"\"bad_posture_min\""
) >= 0
) {
int oldMinutes =
readJsonInt(
response,
"bad_posture_min",
0
);
int newMinutes =
max(
oldMinutes,
badMinutes
);
// ========================================================
// KEEP CUMULATIVE THERAPY SESSION COUNT
// ========================================================
int oldTherapySessions =
readJsonInt(
response,
"therapy_sessions",
0
);
// Agar database mein count zyada hai,
// to local counter ko database wale count ke barabar rakho.
if (
oldTherapySessions >
(int)therapySessionsCount
) {
therapySessionsCount =
(unsigned long)oldTherapySessions;
}
int therapySessions =
(int)therapySessionsCount;
// ========================================================
// PATCH TODAY'S RECORD
// ========================================================
String patchURL =
String(SUPABASE_URL) +
"/rest/v1/posture_history" +
"?device_id=eq." +
deviceId +
"&record_date=eq." +
today;
if (
!historyHttp.begin(
supabaseClient,
patchURL
)
) {
return;
}
historyHttp.setReuse(
true
);
historyHttp.setTimeout(
HTTP_TIMEOUT
);
historyHttp.addHeader(
"Content-Type",
"application/json"
);
historyHttp.addHeader(
"apikey",
SUPABASE_KEY
);
historyHttp.addHeader(
"Authorization",
String("Bearer ") +
SUPABASE_KEY
);
historyHttp.addHeader(
"Prefer",
"return=minimal"
);
String json =
"{\"bad_posture_min\":" +
String(newMinutes) +
",\"therapy_sessions\":" +
String(therapySessions) +
"}";
historyHttp.PATCH(
json
);
historyHttp.end();
}
// ==========================================================
// NEW RECORD
// ==========================================================
else {
String insertURL =
String(SUPABASE_URL) +
"/rest/v1/posture_history";
if (
!historyHttp.begin(
supabaseClient,
insertURL
)
) {
return;
}
historyHttp.setReuse(
true
);
historyHttp.setTimeout(
HTTP_TIMEOUT
);
historyHttp.addHeader(
"Content-Type",
"application/json"
);
historyHttp.addHeader(
"apikey",
SUPABASE_KEY
);
historyHttp.addHeader(
"Authorization",
String("Bearer ") +
SUPABASE_KEY
);
historyHttp.addHeader(
"Prefer",
"return=minimal"
);
String json =
"{";
json +=
"\"device_id\":\"" +
deviceId +
"\",";
json +=
"\"record_date\":\"" +
today +
"\",";
json +=
"\"bad_posture_min\":" +
String(badMinutes) +
",";
// ========================================================
// CUMULATIVE THERAPY SESSION COUNT
// ========================================================
int therapySessions =
(int)therapySessionsCount;
json +=
"\"therapy_sessions\":" +
String(therapySessions);
json +=
"}";
historyHttp.POST(
json
);
historyHttp.end();
}
}
// ============================================================
// TODAY DATE
// ============================================================
String getTodayDate() {
return "2026-09-03";
}