/**
 * @file 01_무게에다른led변화.ino
 * @brief Control 3 LEDs based on weight value from HX711 load cell module.
 *
 * Green LED: weight < 600
 * Yellow LED: 600 <= weight < 900
 * Red LED: weight >= 900
 * Only one LED is on at a time.
 *
 * @author
 * @date 2025-05-19
 */
#include <HX711.h>
// Pin assignments (use ALL_CAPS for pin constants by convention)
constexpr uint8_t HX711_DOUT_PIN = 6;      ///< HX711 data pin
constexpr uint8_t HX711_SCK_PIN = 7;       ///< HX711 clock pin
constexpr uint8_t GREEN_LED_PIN = 10;      ///< Green LED pin
constexpr uint8_t YELLOW_LED_PIN = 9;      ///< Yellow LED pin
constexpr uint8_t RED_LED_PIN = 8;         ///< Red LED pin
// Weight thresholds
constexpr long GREEN_THRESHOLD = 600;
constexpr long YELLOW_THRESHOLD = 900;
// Debug macro
#define DEBUG 1
#if DEBUG
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif
HX711 scale;
long scaleZeroOffset = 0; ///< HX711 zero offset (tare)
float scaleCalibrationFactor = 0.0f; ///< Calibration factor (adjust for your load cell)
long lastStableWeight = 0;
unsigned long lastStableTime = 0;
constexpr unsigned long STABLE_INTERVAL_MS = 200; ///< ms to consider value stable
constexpr long WEIGHT_HYSTERESIS = 10; ///< Ignore changes smaller than this
/**
 * @brief Initialize pins and HX711 module.
 */
/**
 * @brief Initialize pins, serial, and HX711 module with calibration.
 */
void setup()
{
    pinMode(GREEN_LED_PIN, OUTPUT);
    pinMode(YELLOW_LED_PIN, OUTPUT);
    pinMode(RED_LED_PIN, OUTPUT);
#if DEBUG
    Serial.begin(9600);
    Serial.println("HX710B Demo with HX711 Library");
    Serial.println("Initializing the scale");
#endif
    scale.begin(HX711_DOUT_PIN, HX711_SCK_PIN);
    delay(100); // Allow HX711 to power up
    // Check if HX711 is ready
    if (!scale.is_ready()) {
#if DEBUG
        Serial.println("HX711 is not ready!");
#endif
        while (true); // Halt if not ready
    }
#if DEBUG
    Serial.println("Before setting up the scale:");
    Serial.print("read: \t\t");
    Serial.println(scale.read());
    Serial.print("read average: \t\t");
    Serial.println(scale.read_average(20));
    Serial.print("get value: \t\t");
    Serial.println(scale.get_value(5));
    Serial.print("get units: \t\t");
    Serial.println(scale.get_units(5), 1);
#endif
    scale.set_scale(scaleCalibrationFactor); // Set calibration factor
    scale.tare(); // Zero the scale (remove all weight)
    scaleZeroOffset = scale.get_offset();
#if DEBUG
    Serial.println("After setting up the scale:");
    Serial.print("read: \t\t");
    Serial.println(scale.read());
    Serial.print("read average: \t\t");
    Serial.println(scale.read_average(20));
    Serial.print("get value: \t\t");
    Serial.println(scale.get_value(5));
    Serial.print("get units: \t\t");
    Serial.println(scale.get_units(5), 1);
    Serial.println("Readings:");
#endif
}
/**
 * @brief Set the LED state. Only one LED is on at a time.
 * @param greenOn Green LED state
 * @param yellowOn Yellow LED state
 * @param redOn Red LED state
 */
/**
 * @brief Set the LED state. Only one LED is on at a time.
 * @param greenOn Green LED state
 * @param yellowOn Yellow LED state
 * @param redOn Red LED state
 */
void setLedState(bool greenOn, bool yellowOn, bool redOn)
{
    // For clarity, use array and loop
    const uint8_t ledPins[3] = {GREEN_LED_PIN, YELLOW_LED_PIN, RED_LED_PIN};
    const bool ledStates[3] = {greenOn, yellowOn, redOn};
    for (uint8_t i = 0; i < 3; ++i)
    {
        digitalWrite(ledPins[i], ledStates[i] ? HIGH : LOW);
    }
}
/**
 * @brief Update LEDs based on the measured weight.
 *        Uses non-blocking logic (no delay in loop).
 */
/**
 * @brief Get a stable weight value using simple hysteresis and time filtering.
 * @return long Stable weight value
 */
unsigned long lastSerialPrintTime = 0;
/**
 * @brief Get a stable weight value using simple hysteresis and time filtering.
 *        Serial output is printed every 1 second.
 * @return long Stable weight value
 */
long getStableWeight()
{
    long currentWeight = scale.get_units();
    unsigned long now = millis();
#if DEBUG
    if (now - lastSerialPrintTime >= 1000) {
        Serial.print("currentWeight: ");
        Serial.println(currentWeight);
        lastSerialPrintTime = now;
    }
#endif
    if (abs(currentWeight - lastStableWeight) > WEIGHT_HYSTERESIS)
    {
        lastStableTime = now;
        lastStableWeight = currentWeight;
    }
    else if (now - lastStableTime > STABLE_INTERVAL_MS)
    {
        // Value is stable
        return lastStableWeight;
    }
    return lastStableWeight;
}
/**
 * @brief Update LEDs based on the measured weight (with stability filtering).
 *        Uses non-blocking logic (no delay in loop).
 */
unsigned long lastWeightPrintTime = 0;
/**
 * @brief Update LEDs based on the measured weight (with stability filtering).
 *        Serial output for weight is printed every 1 second.
 *        Uses non-blocking logic (no delay in loop).
 */
void updateLedsByWeight()
{
    long weight = getStableWeight();
    unsigned long now = millis();
#if DEBUG
    if (now - lastWeightPrintTime >= 1000) {
        Serial.print("Weight: ");
        Serial.println(weight);
        lastWeightPrintTime = now;
    }
#endif
    if (weight < GREEN_THRESHOLD)
    {
        setLedState(true, false, false);
    }
    else if (weight < YELLOW_THRESHOLD)
    {
        setLedState(false, true, false);
    }
    else
    {
        setLedState(false, false, true);
    }
}
/**
 * @brief Main loop. Only calls feature functions for readability.
 */
void loop()
{
    updateLedsByWeight();
}