#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <HX711.h>
#include <Servo.h>
#include <RotaryEncoder.h>
#include <math.h>
#include <EEPROM.h>
#define LB2KG 0.45352
#define CALWEIGHT 1.00 // Set known value for calibration (In Kg 1.0 = 1000 gram)
#define DEFAULT_CALIFACTOR -7050
// Pins
#define ENCODER_CLK 2
#define ENCODER_DT 3
#define BUTTON_CALIBRATE 4
#define BUTTON_POUR 5
#define SERVO_PIN 6
#define HX711_DOUT 7
#define HX711_SCK 8
#define LONG_PRESS_DURATION 1000 // 1 second for long press
#define CALIBRATION_WEIGHT 100 // 100 grams calibration weight
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
HX711 scale;
Servo gateServo;
// Setup a RotaryEncoder with 2 steps per latch for the 2 signal input pins:
RotaryEncoder encoder(ENCODER_CLK, ENCODER_DT, RotaryEncoder::LatchMode::FOUR3 );
double totalAmount = 1000; // Total coffee in grams
long selectedAmount = 200; // Amount to pour in grams
long consumedAmount = 0;
long currentWeight = 0;
bool inMenu = false;
unsigned long buttonPressTime = 0;
long currentOffset;
float calibration_factor;
unsigned long lastValueTime = millis(); // Variable to hold last time when value was taken
// Creatign a menu list
enum MenuState { IDLE,
MAIN_MENU,
SET_TOTAL,
SET_AMOUNT,
BACK
};
MenuState menuState = IDLE;
void setup() {
Serial.begin(9600);
// Initialize components
pinMode(BUTTON_CALIBRATE, INPUT_PULLUP);
pinMode(BUTTON_POUR, INPUT_PULLUP);
scale.begin(HX711_DOUT, HX711_SCK);
gateServo.attach(SERVO_PIN);
gateServo.write(0); // Close gate initially
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
// Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(WHITE, BLACK); // Draw white text
display.display();
// eeprom
if (EEPROM.read(0x00) != 0x01) {
// Serial.println("NOT INIT !!!!");
currentOffset = 0;
calibration_factor = DEFAULT_CALIFACTOR;
// show instructions
showMessage(0, 0, "First Calibrate Scale");
delay(1000);
} else {
EEPROM.get(0x01, currentOffset);
EEPROM.get(0x01 + sizeof(long), calibration_factor);
// Serial.println("currentOffset = " + String(currentOffset));
// Serial.println("calibration_factor = " + String(calibration_factor));
}
//scale
delay(100);
// Serial.println("calibration_factor = " + String(calibration_factor));
scale.set_scale(calibration_factor / LB2KG);
scale.set_offset(currentOffset); // Apply saved offset value
Serial.println("setup done ...");
delay(1000); // Wait for 1 second to stabilize
totalAmount = abs(scale.get_units()); // Get current scale value
// Show main screen
showMainScreen();
}
void loop() {
// Check if user has pressed Calibrate button and we are not in menu
if (digitalRead(BUTTON_CALIBRATE) == LOW) {
if (menuState == IDLE)
menuState = SET_TOTAL; // Set the menu state to main menu
showMenu();
delay(300);
}
// Handle menu navigation with encoder
if (inMenu) {
handleMenu();
}
// Pour coffee and check for scale calibration
if (digitalRead(BUTTON_POUR) == LOW && !inMenu) {
unsigned long cTime = millis(); // Note current button press time
// Serial.println("Button Pressed");
// showMessage(0, 0, "Button Prssed", HIGH);
while (digitalRead(BUTTON_POUR) == LOW) {
;
}
// showMessage(0, 15, "Button Released");
Serial.println("Button Released");
Serial.println(millis() - cTime);
pourCoffee();
}
}
void showMessage(byte x, byte y, char *message) {
display.setCursor(x, y);
display.print(message);
display.display();
}
void showMessage(byte x, byte y, char *message, bool clearDisplay) {
if (clearDisplay)
display.clearDisplay();
display.setCursor(x, y);
display.print(message);
display.display();
}
void showMultiMessage(byte x, byte y, char *message1, int value, char *message2, bool clearDisplay) {
if (clearDisplay)
display.clearDisplay();
display.setCursor(x, y);
display.print(message1);
display.print(value);
display.print(message2);
display.display();
}
void showMainScreen() {
showMultiMessage(0, 0, "Total: ", totalAmount, " g ", HIGH);
showMultiMessage(0, 15, "Selected: ", selectedAmount, " g ", LOW);
showMultiMessage(0, 30, "Weight: ", currentWeight, " g ", LOW);
}
void updateMainScreen(long currentWeight) {
display.setCursor(0, 30);
display.print("Weight: ");
display.print(currentWeight);
display.print(" g ");
display.display();
}
void pourCoffee() {
//scale.tare(); // clear scale
long measuredWeight = 0; // Variable to hold value to measure weight
long currentWeight = scale.get_units(); // Note current scale measurement
gateServo.write(90); // Open the gate
while (abs(scale.get_units() - currentWeight) < selectedAmount) {
measuredWeight = abs(scale.get_units() - currentWeight);
updateMainScreen(measuredWeight);
delay(10); // Introduce some small delays
}
gateServo.write(0); // Close the gate
measuredWeight = abs(scale.get_units() - currentWeight);
updateMainScreen(measuredWeight);
totalAmount = abs(scale.get_units());
updateMainScreen(measuredWeight);
showMultiMessage(0, 0, "Total: ", totalAmount, " g ", LOW);
showMultiMessage(0, 30, "Weight: ", currentWeight, " g ", LOW);
}
void displayCursor(byte x, byte y) {
display.setCursor(x, y); // Set curosor position
display.print(">");
display.display();
}
void showMenu() {
inMenu = false;
// menuState = SET_TOTAL; // Set the menu state to set total
// Reset encoder position value
// encoder.setPosition(0);
display.clearDisplay();
display.setCursor(3, 5);
display.print("1. Set Amount");
display.setCursor(3, 20);
display.print("2. Back");
display.display();
switch (menuState) {
case SET_TOTAL:
inMenu = true;
encoder.setPosition(selectedAmount); // Set the encoder value to selected amount
delay(500);
display.clearDisplay();
menuState = SET_AMOUNT;
displayCursor(0, 10);
break;
case SET_AMOUNT:
menuState = BACK;
displayCursor(0, 20);
break;
case BACK:
menuState = IDLE;
showMainScreen();
break;
default:
break;
}
}
void handleMenu() {
long encoderPosition = encoder.getPosition();
switch (menuState) {
case SET_AMOUNT:
selectedAmount = encoderPosition; // Adjust the amount with encoder
showMultiMessage(0, 10, "Pour Amount: ", selectedAmount, " g ", LOW);
break;
case BACK:
inMenu = false;
showMainScreen();
break;
}
display.display();
}