/* WaterTimer input test */
/* CC BY-NC-SA Jeroen Brinkman */
/* Version 1.2 - 10/6/2026 */
/* This version solves */
/* V2: moisture sensor mapping inverted (high raw = dry 1%, low raw = wet 99%) */
  
#include <TM1637TinyDisplay.h>

/* Standard definitions */
#define BAUD           57600              // Baud rate serial port
#define VERSION        "1.2"             // Software release
#define date         __DATE__            // Compile date
#define time         __TIME__            // Compile time
#define SOFTWARE  "WaterTimer input test" // Product name
#define MAKER     "Jeroen Brinkman"      // Maker name

/* Pin definitions */
#define IO_GREENLED         2 
#define IO_VALVE            3             // Goes HIGH when button is pressed
#define IO_BLUELED          4 
#define IO_REDLED           5 
#define IO_SWITCH           6 
#define IO_PUSH             7 
#define IO_ROT1             9 
#define IO_ROT2            10 
#define IO_ROT3            11 
#define IO_ROT4            12 

/* Menu buttons */
#define SEL_DOWN           A0
#define SEL_UP             A1

/* Sensor pins */
#define PIN_LDR            A3
#define PIN_MOISTURE       A6
#define PIN_AMPERAGE       A7

/* Display pins */
#define DIO                A4
#define CLK                A5

#define REFRESH_RATE      1000            // Sensor row print interval (ms)
#define HEADER_EVERY        10            // Print header row every N data rows

TM1637TinyDisplay display(CLK, DIO);

int           lastRotaryPos   = -1;
bool          lastPushState   = HIGH;     // INPUT_PULLUP: idle = HIGH
bool          lastSwitchState = HIGH;     // INPUT_PULLUP: idle = HIGH
unsigned long lastRefreshMs   = 0;
int           rowCount        = 0;        // Counts printed data rows; resets after HEADER_EVERY
char          eventBuf[12]    = "";       // Pending event label, printed on next sensor row

/* Returns the active rotary position (1-4),
   or 0 when no pin or more than one pin is active. */
int getRotaryPos() {
  int pos = 0, count = 0;
  if (digitalRead(IO_ROT1) == HIGH) { pos = 1; count++; }
  if (digitalRead(IO_ROT2) == HIGH) { pos = 2; count++; }
  if (digitalRead(IO_ROT3) == HIGH) { pos = 3; count++; }
  if (digitalRead(IO_ROT4) == HIGH) { pos = 4; count++; }
  return (count == 1) ? pos : 0;
}

/* Maps a raw 10-bit ADC value (0-1023) to a percentage (0-100). */
int toPercent(int value) {
  return (int)constrain(map(value, 0, 1023, 0, 100), 0, 100);
}

/* Maps a raw moisture ADC value to a soil-wetness percentage (1-99).
   Capacitive sensors output HIGH when dry and LOW when wet, so the
   mapping is inverted: high raw = 1% (dry/air), low raw = 99% (wet/water).
   Calibration endpoints match SOIL_DRY and SOIL_WET in WaterTimerR1V6. */
#define SOIL_DRY_RAW     560              // Raw ADC for fully dry soil
#define SOIL_WET_RAW     200              // Raw ADC for fully wet soil
int toMoistPct(int value) {
  return (int)constrain(map(value, SOIL_DRY_RAW, SOIL_WET_RAW, 1, 99), 1, 99);
}

/* Prints a fixed-width integer: up to 4 digits right-aligned, space-padded. */
void printW4(int v) {
  if (v <  10) Serial.print(F("   "));
  else if (v < 100) Serial.print(F("  "));
  else if (v < 1000) Serial.print(F(" "));
  Serial.print(v);
}

/* Prints a fixed-width percentage: 2 digits right-aligned + '%'. */
void printPct(int v) {
  if (v < 10) Serial.print(F(" "));
  Serial.print(v);
  Serial.print(F("%"));
}

void printHeader() {
  Serial.println(F("  #    LDR        Moisture   Amperage   Event"));
  Serial.println(F("-----  ---------  ---------  ---------  ---------------"));
}

void setup() {
  Serial.begin(BAUD);

  String year = date;
  year = year.substring(7, 11);
  Serial.println(F(""));
  Serial.println(F("----------------------- Begin -----------------------"));
  Serial.println(F(""));
  Serial.println((String)"         Software: " + SOFTWARE);
  Serial.println((String)"        Copyright: CC BY-NC-SA " + year + " " + MAKER + ". All rights reserved.");
  Serial.println((String)" Software Version: " + VERSION);
  Serial.println((String)"Software Compiled: " + date + " - " + time);
  Serial.println(F(""));

  /* Output pins */
  pinMode(IO_GREENLED, OUTPUT);
  pinMode(IO_BLUELED,  OUTPUT);
  pinMode(IO_REDLED,   OUTPUT);
  pinMode(IO_VALVE,    OUTPUT);

  /* Input pins with internal pull-up (active LOW) */
  pinMode(IO_PUSH,   INPUT_PULLUP);
  pinMode(IO_SWITCH, INPUT_PULLUP);
  pinMode(SEL_DOWN,  INPUT_PULLUP);
  pinMode(SEL_UP,    INPUT_PULLUP);

  /* Rotary switch pins (active HIGH — external pull-downs required) */
  pinMode(IO_ROT1, INPUT);
  pinMode(IO_ROT2, INPUT);
  pinMode(IO_ROT3, INPUT);
  pinMode(IO_ROT4, INPUT);

  display.begin();
  display.setBrightness(7);
  display.showString("SET");

  /* LED lamp test */
  digitalWrite(IO_GREENLED, HIGH);
  digitalWrite(IO_BLUELED,  HIGH);
  digitalWrite(IO_REDLED,   HIGH);
  delay(2000);
  digitalWrite(IO_GREENLED, LOW);
  digitalWrite(IO_BLUELED,  LOW);
  digitalWrite(IO_REDLED,   LOW);

  printHeader();
}

void loop() {

  /* --- SEL buttons: display feedback --- */
  bool currentDown = digitalRead(SEL_DOWN);
  bool currentUp   = digitalRead(SEL_UP);
  if      (currentDown == LOW) display.showString("DOWN");
  else if (currentUp   == LOW) display.showString("UP  ");
  else                         display.showString("LOOP");

  /* --- PUSH: valve + blue LED; latch event on change --- */
  bool currentPush = digitalRead(IO_PUSH);
  if (currentPush != lastPushState) {
    lastPushState = currentPush;
    strncpy(eventBuf, currentPush == LOW ? "PUSH ON" : "PUSH OFF", sizeof(eventBuf) - 1);
  }
  digitalWrite(IO_VALVE,   currentPush == LOW ? HIGH : LOW);
  digitalWrite(IO_BLUELED, currentPush == LOW ? HIGH : LOW);

  /* --- Auto switch: green LED; latch event on change --- */
  bool currentSwitch = digitalRead(IO_SWITCH);
  if (currentSwitch != lastSwitchState) {
    lastSwitchState = currentSwitch;
    strncpy(eventBuf, currentSwitch == LOW ? "SWITCH ON" : "SWITCH OFF", sizeof(eventBuf) - 1);
  }
  digitalWrite(IO_GREENLED, currentSwitch == LOW ? HIGH : LOW);

  /* --- Rotary: red LED flash; latch event on change --- */
  int currentRotaryPos = getRotaryPos();
  if (currentRotaryPos != lastRotaryPos) {
    if (currentRotaryPos == 0) {
      strncpy(eventBuf, "ROTARY 0", sizeof(eventBuf) - 1);
    } else {
      /* Flash red LED briefly to confirm rotary change */
      digitalWrite(IO_REDLED, HIGH);
      delay(200);
      digitalWrite(IO_REDLED, LOW);
      display.showNumberDec(currentRotaryPos);
      char tmp[12];
      snprintf(tmp, sizeof(tmp), "ROTARY %d", currentRotaryPos);
      strncpy(eventBuf, tmp, sizeof(eventBuf) - 1);
    }
    lastRotaryPos = currentRotaryPos;
  }

  /* --- Sensor row: print at REFRESH_RATE interval --- */
  unsigned long now = millis();
  if (now - lastRefreshMs >= REFRESH_RATE) {
    lastRefreshMs = now;

    /* Reprint header every HEADER_EVERY rows */
    if (rowCount % HEADER_EVERY == 0) printHeader();
    rowCount++;

    int ldrRaw   = analogRead(PIN_LDR);
    int moistRaw = analogRead(PIN_MOISTURE);
    int ampRaw   = analogRead(PIN_AMPERAGE);

    /* Row number, right-aligned in 3 chars */
    if (rowCount < 10)       Serial.print(F("  "));
    else if (rowCount < 100) Serial.print(F(" "));
    Serial.print(rowCount);
    Serial.print(F("   "));

    /* LDR column */
    printW4(ldrRaw);  Serial.print(F("  ")); printPct(toPercent(ldrRaw));  Serial.print(F("   "));
    /* Moisture column */
    printW4(moistRaw); Serial.print(F("  ")); printPct(toMoistPct(moistRaw)); Serial.print(F("   "));
    /* Amperage column */
    printW4(ampRaw);  Serial.print(F("  ")); printPct(toPercent(ampRaw));  Serial.print(F("   "));

    /* Event column: print and clear */
    Serial.println(eventBuf);
    eventBuf[0] = '\0';
  }

  delay(10);
}
