#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <HX711.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <NewPing.h>
// LCD settings
LiquidCrystal_I2C lcd(0x27, 20, 4);
// HX711 settings
HX711 dryScale;
HX711 wetScale;
// DS18B20 settings
#define ONE_WIRE_BUS 7
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// HC-SR04 settings
#define TRIG_PIN 13
#define ECHO_PIN 12
NewPing sonar(TRIG_PIN, ECHO_PIN);
// Pin settings
const int TARE_BUTTON_PIN = 6;
const int UP_BUTTON_PIN = 4;
const int DOWN_BUTTON_PIN = 3;
const int TEST_BUTTON_PIN = 5;
const int START_BUTTON_PIN = 2;
// Calibration weights
const float dryCalWeight = 20.0; // kg
const float wetCalWeight = 5.0; // kg
// Calibration factors and zero offsets
float dryCalFactor = 0;
float wetCalFactor = 0;
long dryZeroOffset = 0;
long wetZeroOffset = 0;
// EEPROM addresses
const int dryZeroOffsetAddress = 0;
const int wetZeroOffsetAddress = sizeof(long);
const int dryCalFactorAddress = sizeof(long) * 2;
const int wetCalFactorAddress = sizeof(long) * 2 + sizeof(float);
// States
enum State {
MAIN_SCREEN,
TARE_SCREEN,
CALIBRATION_DRY_SCREEN,
CALIBRATION_WET_SCREEN,
MEASUREMENT_SCREEN,
WIZARD_START,
WIZARD_GENDER,
WIZARD_AGE,
WIZARD_LUNG_CAPACITY,
WIZARD_DRY_WEIGHT,
WIZARD_WET_WEIGHT,
WIZARD_SUMMARY,
CALCULATION_SCREEN1,
CALCULATION_SCREEN2
};
State currentState = MAIN_SCREEN;
// Wizard data
enum Gender { MALE, FEMALE };
Gender clientGender = MALE;
int clientAge = 30;
float clientLungCapacity = 3.5;
float clientDryWeight = 0;
float clientWetWeight = 0;
float clientTemperature = 0;
int clientHeight = 170; // Height will now be measured automatically
float waterDensity = 0;
float correctedWetWeight = 0;
float bodyVolume = 0;
float bodyDensity = 0;
float siriBodyFat = 0;
float brozekBodyFat = 0;
float bmi = 0;
float leanMass = 0;
float fatMass = 0;
// Button debounce timing
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 200;
// Lookup table for water density
float waterDensityTable[46] = {
0.999964, 0.999942, 0.999922, 0.999901, 0.999879, 0.999700, 0.999710, 0.999715, 0.999710, 0.999700,
0.999675, 0.999645, 0.999610, 0.999570, 0.999530, 0.998207, 0.998189, 0.998154, 0.998112, 0.998068,
0.997047, 0.996997, 0.996942, 0.996883, 0.996822, 0.995650, 0.995584, 0.995516, 0.995444, 0.995370,
0.994030, 0.993951, 0.993871, 0.993789, 0.993705, 0.992214, 0.992126, 0.992037, 0.991946, 0.991854,
0.990215, 0.990120, 0.990024, 0.989926, 0.989826, 0.988045
};
// Helper function to print state messages on the LCD
void printStateMessage(const char* message) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(message);
}
void setup() {
Serial.begin(9600);
// Initialize LCD
lcd.init();
lcd.backlight();
// Initialize HX711
dryScale.begin(8, 9);
wetScale.begin(10, 11);
// Initialize DS18B20
sensors.begin();
// Initialize buttons
pinMode(TARE_BUTTON_PIN, INPUT_PULLUP);
pinMode(UP_BUTTON_PIN, INPUT_PULLUP);
pinMode(DOWN_BUTTON_PIN, INPUT_PULLUP);
pinMode(TEST_BUTTON_PIN, INPUT_PULLUP);
pinMode(START_BUTTON_PIN, INPUT_PULLUP);
// Load values from EEPROM
EEPROM.get(dryZeroOffsetAddress, dryZeroOffset);
EEPROM.get(wetZeroOffsetAddress, wetZeroOffset);
EEPROM.get(dryCalFactorAddress, dryCalFactor);
EEPROM.get(wetCalFactorAddress, wetCalFactor);
Serial.print("Loaded dry zero offset: ");
Serial.println(dryZeroOffset);
Serial.print("Loaded wet zero offset: ");
Serial.println(wetZeroOffset);
Serial.print("Loaded dry calibration factor: ");
Serial.println(dryCalFactor, 6);
Serial.print("Loaded wet calibration factor: ");
Serial.println(wetCalFactor, 6);
// Display main screen with requested text
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BY DR. ANAS ALFAKEER");
lcd.setCursor(0, 1);
lcd.print(" 078-70-70-078");
lcd.setCursor(0, 2);
lcd.print("Accurate hydrostatic");
lcd.setCursor(0, 3);
lcd.print(" weighing scale");
}
void loop() {
if (millis() - lastDebounceTime > debounceDelay) {
switch (currentState) {
case MAIN_SCREEN:
if (digitalRead(TARE_BUTTON_PIN) == LOW) {
currentState = TARE_SCREEN;
tareScales();
} else if (digitalRead(UP_BUTTON_PIN) == LOW) {
currentState = CALIBRATION_DRY_SCREEN;
calibrateDryScale();
} else if (digitalRead(DOWN_BUTTON_PIN) == LOW) {
currentState = CALIBRATION_WET_SCREEN;
calibrateWetScale();
} else if (digitalRead(TEST_BUTTON_PIN) == LOW) {
currentState = MEASUREMENT_SCREEN;
measureScales();
} else if (digitalRead(START_BUTTON_PIN) == LOW) {
startWizard();
}
break;
case TARE_SCREEN:
case CALIBRATION_DRY_SCREEN:
case CALIBRATION_WET_SCREEN:
case MEASUREMENT_SCREEN:
case WIZARD_GENDER:
case WIZARD_AGE:
case WIZARD_LUNG_CAPACITY:
case WIZARD_DRY_WEIGHT:
case WIZARD_WET_WEIGHT:
case WIZARD_SUMMARY:
case CALCULATION_SCREEN1:
case CALCULATION_SCREEN2:
handleWizard();
break;
}
lastDebounceTime = millis();
}
}
void tareScales() {
printStateMessage("Taring scales...");
dryScale.tare();
wetScale.tare();
dryZeroOffset = dryScale.read_average();
wetZeroOffset = wetScale.read_average();
EEPROM.put(dryZeroOffsetAddress, dryZeroOffset);
EEPROM.put(wetZeroOffsetAddress, wetZeroOffset);
Serial.print("Dry zero offset: ");
Serial.println(dryZeroOffset);
Serial.print("Wet zero offset: ");
Serial.println(wetZeroOffset);
Serial.println("Zero points saved to EEPROM.");
printStateMessage("Tare complete");
delay(1000);
currentState = MAIN_SCREEN;
// Display main screen with requested text
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BY DR. ANAS ALFAKEER");
lcd.setCursor(0, 1);
lcd.print(" 078-70-70-078");
lcd.setCursor(0, 2);
lcd.print("Accurate hydrostatic");
lcd.setCursor(0, 3);
lcd.print(" weighing scale");
}
void calibrateDryScale() {
printStateMessage("Calibrating dry...");
dryScale.set_scale();
dryScale.set_offset(dryZeroOffset);
delay(1000); // Wait for stable reading
float rawReading = dryScale.get_units(10);
dryCalFactor = rawReading / dryCalWeight;
EEPROM.put(dryCalFactorAddress, dryCalFactor);
Serial.print("Dry calibration factor: ");
Serial.println(dryCalFactor, 6);
Serial.println("Dry calibration factor saved to EEPROM.");
printStateMessage("Dry calibration");
lcd.setCursor(0, 1);
lcd.print("Factor: ");
lcd.print(dryCalFactor, 6);
delay(2000);
currentState = MAIN_SCREEN;
// Display main screen with requested text
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BY DR. ANAS ALFAKEER");
lcd.setCursor(0, 1);
lcd.print(" 078-70-70-078");
lcd.setCursor(0, 2);
lcd.print("Accurate hydrostatic");
lcd.setCursor(0, 3);
lcd.print(" weighing scale");
}
void calibrateWetScale() {
printStateMessage("Calibrating wet...");
wetScale.set_scale();
wetScale.set_offset(wetZeroOffset);
delay(1000); // Wait for stable reading
float rawReading = wetScale.get_units(10);
wetCalFactor = rawReading / wetCalWeight;
EEPROM.put(wetCalFactorAddress, wetCalFactor);
Serial.print("Wet calibration factor: ");
Serial.println(wetCalFactor, 6);
Serial.println("Wet calibration factor saved to EEPROM.");
printStateMessage("Wet calibration");
lcd.setCursor(0, 1);
lcd.print("Factor: ");
lcd.print(wetCalFactor, 6);
delay(2000);
currentState = MAIN_SCREEN;
// Display main screen with requested text
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BY DR. ANAS ALFAKEER");
lcd.setCursor(0, 1);
lcd.print(" 078-70-70-078");
lcd.setCursor(0, 2);
lcd.print("Accurate hydrostatic");
lcd.setCursor(0, 3);
lcd.print(" weighing scale");
}
void measureScales() {
printStateMessage("Measuring...");
dryScale.set_scale(dryCalFactor);
wetScale.set_scale(wetCalFactor);
float dryWeight = dryScale.get_units(10);
float wetWeight = wetScale.get_units(10);
sensors.requestTemperatures();
float temperature = sensors.getTempCByIndex(0);
// Measure height using HC-SR04
unsigned int distance = sonar.ping_cm();
clientHeight = 210 - distance; // Calculate height
Serial.print("Dry weight: ");
Serial.print(dryWeight, 3);
Serial.println(" kg");
Serial.print("Wet weight: ");
Serial.print(wetWeight, 3);
Serial.println(" kg");
Serial.print("Temperature: ");
Serial.print(temperature, 2);
Serial.println(" C");
Serial.print("Height: ");
Serial.print(clientHeight);
Serial.println(" cm");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Dry: ");
lcd.print(dryWeight, 3);
lcd.print(" kg");
lcd.setCursor(0, 1);
lcd.print("Wet: ");
lcd.print(wetWeight, 3);
lcd.print(" kg");
lcd.setCursor(0, 2);
lcd.print("Temp: ");
lcd.print(temperature, 2);
lcd.print(" C");
lcd.setCursor(0, 3);
lcd.print("Height: ");
lcd.print(clientHeight);
lcd.print(" cm");
delay(5000);
currentState = MAIN_SCREEN;
// Display main screen with requested text
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BY DR. ANAS ALFAKEER");
lcd.setCursor(0, 1);
lcd.print(" 078-70-70-078");
lcd.setCursor(0, 2);
lcd.print("Accurate hydrostatic");
lcd.setCursor(0, 3);
lcd.print(" weighing scale");
}
void startWizard() {
// Reset wizard data
clientGender = MALE;
clientAge = 30;
clientLungCapacity = 3.5;
clientDryWeight = 0;
clientWetWeight = 0;
clientTemperature = 0;
waterDensity = 0;
correctedWetWeight = 0;
bodyVolume = 0;
bodyDensity = 0;
siriBodyFat = 0;
brozekBodyFat = 0;
bmi = 0;
leanMass = 0;
fatMass = 0;
currentState = WIZARD_GENDER;
printStateMessage("Select Gender");
lcd.setCursor(0, 1);
lcd.print("Male or Female?");
lcd.setCursor(0, 2);
lcd.print("UP/DOWN to select");
lcd.setCursor(0, 3);
lcd.print("START to confirm");
}
void handleWizard() {
switch (currentState) {
case WIZARD_GENDER:
if (digitalRead(UP_BUTTON_PIN) == LOW || digitalRead(DOWN_BUTTON_PIN) == LOW) {
clientGender = (clientGender == MALE) ? FEMALE : MALE;
printStateMessage(clientGender == MALE ? "Gender: Male" : "Gender: Female");
} else if (digitalRead(START_BUTTON_PIN) == LOW) {
currentState = WIZARD_AGE;
printStateMessage("Set Age");
lcd.setCursor(0, 1);
lcd.print("Age: ");
lcd.print(clientAge);
lcd.print(" yrs");
lcd.setCursor(0, 2);
lcd.print("UP/DOWN to change");
lcd.setCursor(0, 3);
lcd.print("START to confirm");
}
break;
case WIZARD_AGE:
if (digitalRead(UP_BUTTON_PIN) == LOW) {
clientAge++;
printStateMessage("Set Age");
lcd.setCursor(0, 1);
lcd.print("Age: ");
lcd.print(clientAge);
lcd.print(" yrs");
} else if (digitalRead(DOWN_BUTTON_PIN) == LOW) {
clientAge--;
printStateMessage("Set Age");
lcd.setCursor(0, 1);
lcd.print("Age: ");
lcd.print(clientAge);
lcd.print(" yrs");
} else if (digitalRead(START_BUTTON_PIN) == LOW) {
currentState = WIZARD_LUNG_CAPACITY;
printStateMessage("Lung Capacity");
lcd.setCursor(0, 1);
lcd.print("Capacity: ");
lcd.print(clientLungCapacity, 1);
lcd.print(" L");
lcd.setCursor(0, 2);
lcd.print("UP/DOWN to change");
lcd.setCursor(0, 3);
lcd.print("START to confirm");
} else if (digitalRead(TARE_BUTTON_PIN) == LOW) {
currentState = WIZARD_GENDER;
printStateMessage(clientGender == MALE ? "Gender: Male" : "Gender: Female");
lcd.setCursor(0, 1);
lcd.print("Male or Female?");
lcd.setCursor(0, 2);
lcd.print("UP/DOWN to select");
lcd.setCursor(0, 3);
lcd.print("START to confirm");
}
break;
case WIZARD_LUNG_CAPACITY:
if (digitalRead(UP_BUTTON_PIN) == LOW) {
clientLungCapacity += 0.1;
printStateMessage("Lung Capacity");
lcd.setCursor(0, 1);
lcd.print("Capacity: ");
lcd.print(clientLungCapacity, 1);
lcd.print(" L");
} else if (digitalRead(DOWN_BUTTON_PIN) == LOW) {
clientLungCapacity -= 0.1;
printStateMessage("Lung Capacity");
lcd.setCursor(0, 1);
lcd.print("Capacity: ");
lcd.print(clientLungCapacity, 1);
lcd.print(" L");
} else if (digitalRead(START_BUTTON_PIN) == LOW) {
currentState = WIZARD_DRY_WEIGHT;
measureDryWeight();
} else if (digitalRead(TARE_BUTTON_PIN) == LOW) {
currentState = WIZARD_AGE;
printStateMessage("Set Age");
lcd.setCursor(0, 1);
lcd.print("Age: ");
lcd.print(clientAge);
lcd.print(" yrs");
lcd.setCursor(0, 2);
lcd.print("UP/DOWN to change");
lcd.setCursor(0, 3);
lcd.print("START to confirm");
}
break;
case WIZARD_DRY_WEIGHT:
if (digitalRead(START_BUTTON_PIN) == LOW) {
currentState = WIZARD_WET_WEIGHT;
measureWetWeight();
} else if (digitalRead(TEST_BUTTON_PIN) == LOW) {
measureDryWeight();
} else if (digitalRead(TARE_BUTTON_PIN) == LOW) {
currentState = WIZARD_LUNG_CAPACITY;
printStateMessage("Lung Capacity");
lcd.setCursor(0, 1);
lcd.print("Capacity: ");
lcd.print(clientLungCapacity, 1);
lcd.print(" L");
lcd.setCursor(0, 2);
lcd.print("UP/DOWN to change");
lcd.setCursor(0, 3);
lcd.print("START to confirm");
}
break;
case WIZARD_WET_WEIGHT:
if (digitalRead(START_BUTTON_PIN) == LOW) {
currentState = WIZARD_SUMMARY;
showSummary();
} else if (digitalRead(TEST_BUTTON_PIN) == LOW) {
measureWetWeight();
} else if (digitalRead(TARE_BUTTON_PIN) == LOW) {
currentState = WIZARD_DRY_WEIGHT;
measureDryWeight();
}
break;
case WIZARD_SUMMARY:
if (digitalRead(START_BUTTON_PIN) == LOW) {
currentState = CALCULATION_SCREEN1;
calculateBodyFatPercentage();
}
break;
case CALCULATION_SCREEN1:
if (digitalRead(START_BUTTON_PIN) == LOW) {
currentState = CALCULATION_SCREEN2;
calculateBMILeanAndFatMass();
}
break;
case CALCULATION_SCREEN2:
if (digitalRead(START_BUTTON_PIN) == LOW) {
printWizardDataToSerial();
currentState = MAIN_SCREEN;
// Display main screen with requested text
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BY DR. ANAS ALFAKEER");
lcd.setCursor(0, 1);
lcd.print(" 078-70-70-078");
lcd.setCursor(0, 2);
lcd.print("Accurate hydrostatic");
lcd.setCursor(0, 3);
lcd.print(" weighing scale");
}
break;
}
}
void measureDryWeight() {
printStateMessage("Measuring Dry Wt");
dryScale.set_scale(dryCalFactor);
float sum = 0;
int count = 0;
unsigned long startTime = millis();
while (millis() - startTime < 1000) {
float weight = dryScale.get_units();
lcd.setCursor(0, 1);
lcd.print("Weight: ");
lcd.print(weight, 3);
lcd.print(" kg");
if (count > 0 && abs(weight - (sum / count)) > 0.1) {
sum = 0;
count = 0;
startTime = millis();
}
sum += weight;
count++;
delay(10);
}
clientDryWeight = sum / count;
sensors.requestTemperatures();
clientTemperature = sensors.getTempCByIndex(0);
// Measure height using HC-SR04
unsigned int distance = sonar.ping_cm();
clientHeight = 210 - distance; // Calculate height
printStateMessage("Dry Weight:");
lcd.setCursor(0, 1);
lcd.print(clientDryWeight, 3);
lcd.print(" kg");
lcd.setCursor(0, 2);
lcd.print("Height: ");
lcd.print(clientHeight);
lcd.print(" cm");
lcd.setCursor(0, 3);
lcd.print("START to confirm");
}
void measureWetWeight() {
printStateMessage("Measuring Wet Wt");
wetScale.set_scale(wetCalFactor);
float sum = 0;
int count = 0;
unsigned long startTime = millis();
while (millis() - startTime < 2500) {
float weight = wetScale.get_units();
lcd.setCursor(0, 1);
lcd.print("Weight: ");
lcd.print(weight, 3);
lcd.print(" kg");
if (count > 0 && abs(weight - (sum / count)) > 0.4) {
sum = 0;
count = 0;
startTime = millis();
}
sum += weight;
count++;
delay(10);
}
clientWetWeight = sum / count;
printStateMessage("Wet Weight:");
lcd.setCursor(0, 1);
lcd.print(clientWetWeight, 3);
lcd.print(" kg");
lcd.setCursor(0, 2);
lcd.print("START to confirm");
lcd.setCursor(0, 3);
lcd.print("TEST to retry");
}
void showSummary() {
printStateMessage("Summary:");
lcd.setCursor(0, 1);
lcd.print("Gender: ");
lcd.print(clientGender == MALE ? "Male" : "Female");
lcd.setCursor(0, 2);
lcd.print("Age: ");
lcd.print(clientAge);
lcd.print(" yrs");
delay(3000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Height: ");
lcd.print(clientHeight);
lcd.print(" cm");
lcd.setCursor(0, 1);
lcd.print("Lung: ");
lcd.print(clientLungCapacity, 1);
lcd.print(" L");
delay(3000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Dry Weight: ");
lcd.print(clientDryWeight, 3);
lcd.print(" kg");
lcd.setCursor(0, 1);
lcd.print("Wet Weight: ");
lcd.print(clientWetWeight, 3);
lcd.print(" kg");
lcd.setCursor(0, 2);
lcd.print("Temp: ");
lcd.print(clientTemperature, 2);
lcd.print(" C");
lcd.setCursor(0, 3);
lcd.print("START for results");
}
void calculateBodyFatPercentage() {
float residualLungVolume;
if (clientGender == MALE) {
residualLungVolume = (0.019 * clientHeight) + (0.013 * clientAge) - 2.24;
} else {
residualLungVolume = (0.022 * clientHeight) + (0.009 * clientAge) - 2.24;
}
waterDensity = getWaterDensity(clientTemperature);
correctedWetWeight = clientWetWeight + (residualLungVolume * waterDensity);
bodyVolume = clientDryWeight - correctedWetWeight;
bodyDensity = clientDryWeight / bodyVolume;
siriBodyFat = (495 / bodyDensity) - 450;
brozekBodyFat = (457 / bodyDensity) - 414;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Body Fat %:");
lcd.setCursor(0, 1);
lcd.print("Siri: ");
lcd.print(siriBodyFat, 2);
lcd.setCursor(0, 2);
lcd.print("Brozek: ");
lcd.print(brozekBodyFat, 2);
lcd.setCursor(0, 3);
lcd.print("START for BMI");
delay(5000);
}
void calculateBMILeanAndFatMass() {
bmi = clientDryWeight / ((clientHeight / 100.0) * (clientHeight / 100.0));
fatMass = clientDryWeight * (brozekBodyFat / 100.0);
leanMass = clientDryWeight - fatMass;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("BMI: ");
lcd.print(bmi, 2);
lcd.setCursor(0, 1);
lcd.print("Lean Mass: ");
lcd.print(leanMass, 3);
lcd.setCursor(0, 2);
lcd.print("Fat Mass: ");
lcd.print(fatMass, 3);
lcd.setCursor(0, 3);
lcd.print("START to exit");
delay(5000);
}
void printWizardDataToSerial() {
Serial.print("Gender: ");
Serial.println(clientGender == MALE ? "Male" : "Female");
Serial.print("Age: ");
Serial.print(clientAge);
Serial.println(" Years");
Serial.print("Height: ");
Serial.print(clientHeight);
Serial.println("cm");
Serial.print("Residual Lung Volume: ");
Serial.print((clientGender == MALE) ? (0.019 * clientHeight) + (0.013 * clientAge) - 2.24 : (0.022 * clientHeight) + (0.009 * clientAge) - 2.24, 3);
Serial.println(" L");
Serial.print("Dry Weight: ");
Serial.print(clientDryWeight, 3);
Serial.println(" kg");
Serial.print("Wet Weight: ");
Serial.print(clientWetWeight, 3);
Serial.println(" kg");
Serial.print("Water Temperature: ");
Serial.print(clientTemperature, 2);
Serial.println(" C");
Serial.print("Water Density: ");
Serial.print(waterDensity, 6);
Serial.println(" kg/l");
Serial.print("Corrected Wet Weight: ");
Serial.print(correctedWetWeight, 3);
Serial.println(" kg");
Serial.print("Body Volume: ");
Serial.print(bodyVolume, 3);
Serial.println(" l");
Serial.print("Body Density: ");
Serial.print(bodyDensity, 3);
Serial.println(" kg/l");
Serial.print("Brozek Fat Percentage: ");
Serial.print(brozekBodyFat, 2);
Serial.println(" %");
Serial.print("Siri Fat Percentage: ");
Serial.print(siriBodyFat, 2);
Serial.println(" %");
Serial.print("BMI: ");
Serial.print(bmi, 2);
Serial.println(" kg/m^2");
Serial.print("Lean Mass: ");
Serial.print(leanMass, 3);
Serial.println(" kg");
Serial.print("Fat Mass: ");
Serial.print(fatMass, 3);
Serial.println(" kg");
}
float getWaterDensity(float temperature) {
int index = (int)temperature - 5;
if (index < 0) index = 0;
if (index >= 46) index = 45;
return waterDensityTable[index];
}