// MPINO-16A8R8T Slave Control Board Code R1.1, 2024-11-05
// 수동모드, AI 모드에서 펌프 On 시 밸브가 닫혀있는 상태에서 펌프가 On 되는 현상 수정
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <ModbusMaster.h>

// 송신 주기 및 타이밍 변수 선언
unsigned long lastSendTime = 0;
const unsigned long sendInterval = 50;  // 송신 간격 (100ms)
bool sendSensorNext = true;  // SensorData 전송 플래그 초기값

#define ONE_WIRE_BUS 12  // DS18B20 온도 센서 핀
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// 온도 측정값 저장 변수
float soilTemp[2];  // 2개의 온도 값을 저장하는 배열

// RS485 통신 모듈 및 센서 설정
ModbusMaster node1;  // ID 1: 토양 센서
ModbusMaster node2;  // ID 2: 대기 센서
unsigned long lastRequestTime = 0;
const unsigned long requestInterval = 1000;  // 각 센서 데이터 요청 간격 (1초)

// 데이터 변수 선언
int moisture = 0;
float soiltemperature = 0.0;
int ec = 0;
float ph = 0.0;
float airtemperature = 0.0;
float airhumidity = 0.0;

// 타이머 설정
unsigned long previousMillis = 0;
const long interval = 1000;  // 데이터 읽기 간격 (1초)

// 핀 정의
const int flowSensorPin = 2;        // 유량 센서 핀
const int pumpPin = 64;             // 펌프 핀
const int valvePin = 67;            // 밸브 핀
const int control_PumpOn = 36;
const int control_PumpOff = 37;
const int waterLevelPin = 31;       // 워터 레벨 핀
const int rainSensor = 29;          // 강우 센서 핀
const int manualModePin = 33;       // 수동 모드 선택 핀
const int autoModePin = 34;         // 자동 모드 선택 핀
const int aiModePin = 35;           // AI 모드 선택 핀
const int adcPins[3] = {A0, A1, A2}; // 토양 수분 센서 핀들

// 토양 수분 센서 값
int ADC0 = 0;
int ADC1 = 0;
int ADC2 = 0;

// 필터링된 ADC 값
float filtered_ADC[3] = {0, 0, 0};
const float alpha = 0.1;  // LPF 강도 설정 (0.1 ~ 0.3 정도 추천)

// 설정 값
int smStart = 850;                  // 펌프 ON 수분 기준값
int smStop = 600;                   // 펌프 OFF 수분 기준값
unsigned long valveDelay = 5000;    // 밸브 열림 대기 시간 (5초)
unsigned long cycleTime = 60000;    // 자동 모드 사이클 시간 (10초)
unsigned long holdDuration = 10000;  // 조건 유지 시간 (10초)
// 필요한 변수 추가
unsigned long pumpStartTime = 0;
unsigned long restartDelayStartTime = 0;
unsigned long cycleStartTime = 0; // 초기화 추가, 필요에 따라 사용
unsigned long cycleRestartDelay = 60000;

unsigned long debounceDelay = 200; // 디바운스 딜레이 (1초)
unsigned long lastSensorPrintTime = 0; // 마지막 센서 출력 시간
const unsigned long sensorPrintInterval = 2000; // 센서 출력 간격 (2초)

unsigned long holdStartTime = 0;
bool holdConditionMet = false;

// 유량계 관련 변수
volatile int flow_frequency = 0;    // 펄스 카운터
unsigned long cloopTime = 0;        // 마지막 유량 측정 시간
float l_minute = 0.0, l_hour = 0.0, l_second = 0.0; // 유량 계산 변수
float minFlow = 0.5; // 초기 값 설정. 필요에 따라 수정 가능

// 상태 변수
bool control_pumpState = false;     // 펌프 플래그
bool pumpIsOn = false;
bool valveOpening = false;
bool waterLevelHigh = false;
unsigned long valveOpenStartTime = 0;
unsigned long lastDebounceTime = 0;
bool inCycle = false;
bool autoModeAlerted = false; // 자동 모드에서 연속 메시지 방지

// 전역 변수 선언
bool delayAfterPumpOff = false;  // 펌프 종료 후 대기 상태 플래그
unsigned long pumpOffTime = 0;   // 펌프가 꺼진 시각 저장
const unsigned long pumpRestartDelay = 5000;  // 펌프 재시작 지연 시간 (5초)
int currentStep = 1; // 시퀀스의 시작 단계

// 모드 상태 정의
enum Mode { MANUAL, AUTO, AI };
Mode currentMode = MANUAL;

// 이전 핀 상태 추적 변수
int lastManualPinState = LOW;
int lastAutoPinState = LOW;
int lastAiPinState = LOW;

// 제어 명령 플래그
//bool manualModeRequested = false;
//bool autoModeRequested = false;
bool aiModeRequested = false;

// 모드 전환을 위한 타이머 및 디바운스 변수
unsigned long lastModeChangeTime = 0;
const unsigned long modeChangeDebounceDelay = 1000; // 모드 변경 디바운스 시간 (500ms)

// 버튼을 일정 시간 동안 누를 때만 모드 전환
unsigned long holdTime = 1000;       // 모드 전환을 위한 홀드 시간
unsigned long manualHoldStart = 0;
unsigned long autoHoldStart = 0;
unsigned long aiHoldStart = 0;

// 펌프 시퀀스 상태
enum PumpState { IDLE, OPENING_VALVE, PUMP_ON };
PumpState pumpState = IDLE;

//I2C 통신 송수신 설정
const uint8_t SENSOR_DATA_HEADER = 0x01;
const uint8_t CONFIG_DATA_HEADER = 0x02;

#pragma pack(push, 1)
struct SensorData {
  uint8_t header = SENSOR_DATA_HEADER;
  uint16_t ec;
  uint16_t ph;
  int16_t airTemperature;
  int16_t airHumidity;
  uint16_t ADC0;
  uint16_t ADC1;
  uint16_t ADC2;
  int16_t soilTemp1;         // 첫 번째 DS18B20 센서 온도
  int16_t soilTemp2;         // 두 번째 DS18B20 센서 온도
  uint16_t l_minute;
  uint8_t pumpPin;
  uint8_t valvePin;
  uint8_t waterLevelPin;
  uint8_t rainSensor;
  uint16_t crc;
};
#pragma pack(pop)
SensorData sensorData;


// ConfigData 구조체 정의
#pragma pack(push, 1)
struct ConfigData {
  uint8_t header = CONFIG_DATA_HEADER;  // 1 byte
  uint8_t currentMode;                  // 1 byte (0: Manual, 1: Auto, 2: AI)
  uint32_t cycleTime;                   // 4 bytes
  uint32_t cycleRestartDelay;           // 4 bytes
  uint32_t valveDelay;                  // 4 bytes
  uint32_t smStart;                     // 4 bytes
  uint32_t smStop;                      // 4 bytes
  //uint32_t holdDuration;                // 4 bytes
  uint16_t crc;                         // 2 bytes
};
#pragma pack(pop)

ConfigData configData;


uint16_t calculateCRC(uint8_t* data, size_t length) {
  uint16_t crc = 0xFFFF;
  for (size_t i = 0; i < length; i++) {
    crc ^= data[i];
    for (int j = 0; j < 8; j++) {
      if (crc & 1)
        crc = (crc >> 1) ^ 0xA001;
      else
        crc >>= 1;
    }
  }
  return crc;
}


void updateSoilMoistureValues() {
    for (int i = 0; i < 3; i++) {
        int rawADC = analogRead(adcPins[i]);
      //  if (i == 2) {
      //      rawADC += 65; // ADC2에만 65 추가
      //  }
        // LPF 적용
        filtered_ADC[i] = alpha * rawADC + (1 - alpha) * filtered_ADC[i];
    }

    // 필터링된 ADC 값을 ADC0, ADC1, ADC2 변수에 할당
    ADC0 = filtered_ADC[0];
    ADC1 = filtered_ADC[1];
    ADC2 = filtered_ADC[2];
}
// 슬레이브 ID 1의 데이터 읽기 함수
void readSensorDataID1() {
    uint8_t result = node1.readHoldingRegisters(0x00, 4);
    if (result == node1.ku8MBSuccess) {
        moisture = node1.getResponseBuffer(0);
        soiltemperature = node1.getResponseBuffer(1) / 10.0;
        ec = node1.getResponseBuffer(2);
        ph = node1.getResponseBuffer(3) / 10.0;

        Serial.println("ID 1 - Soil Sensor Data:");
        Serial.print("Moisture: "); Serial.println(moisture);
        Serial.print("Temperature: "); Serial.println(soiltemperature, 1);
        Serial.print("EC: "); Serial.print(ec); Serial.println(" us/cm");
        Serial.println("PH: "); Serial.println(ph, 2);
    } else {
        Serial.print("Error reading from sensor ID 1. Error code: ");
        Serial.println(result);
    }
}

// 슬레이브 ID 2의 데이터 읽기 함수
void readSensorDataID2() {
    uint8_t result = node2.readInputRegisters(0x0001, 2); // 온도와 습도 데이터 요청
    if (result == node2.ku8MBSuccess) {
        // Modbus에서 수신된 온도 값
        int16_t rawTemperature = node2.getResponseBuffer(0); // 온도 값 (16비트 부호 있는 값)
        int16_t rawHumidity = node2.getResponseBuffer(1);    // 습도 값 (16비트 부호 없는 값)

        // 온도 변환 (Modbus 데이터는 1/10 단위로 제공됨)
        airtemperature = rawTemperature / 10.0;

        // 습도 변환 (습도는 항상 양수)
        airhumidity = rawHumidity / 10.0;

        // 디버깅용 출력
        Serial.print("ID 2 - Temperature and Humidity Data:");
        Serial.print(" Temperature: ");
        Serial.print(airtemperature);
        Serial.print(" °C, Humidity: ");
        Serial.print(airhumidity);
        Serial.println(" %");
    } else {
        Serial.print("Error reading from sensor ID 2. Error code: ");
        Serial.println(result);
    }
}

// DS18B20 온도 센서 값 읽기
void readDS18B20Data() {
    sensors.requestTemperatures();  // 모든 센서로부터 온도 요청
    
    // 첫 번째 센서의 온도 읽기
    float temp1 = sensors.getTempCByIndex(0);  
    soilTemp[0] = (temp1 == DEVICE_DISCONNECTED_C) ? -127.0 : temp1; // 센서 연결 실패 시 -127.0 저장

    // 두 번째 센서의 온도 읽기
    float temp2 = sensors.getTempCByIndex(1);  
    soilTemp[1] = (temp2 == DEVICE_DISCONNECTED_C) ? -127.0 : temp2; // 센서 연결 실패 시 -127.0 저장

    // 시리얼 출력 (디버깅용)
    Serial.print("SoilTemp 1: ");
    Serial.print(soilTemp[0]);
    Serial.print(" °C, SoilTemp 2: ");
    Serial.println(soilTemp[1]);
}

// 유량 센서의 펄스를 처리하는 인터럽트 핸들러
void flow() {
    flow_frequency++;
}

// 1초마다 유량을 계산하여 출력하는 함수
void calculateFlowRate() {
    unsigned long currentTime = millis();
    if (currentTime - cloopTime >= 1000) { // 1초마다 유량 계산
        cloopTime = currentTime; // 마지막 계산 시간 갱신

        if (flow_frequency == 0) {
            l_minute = 0.0;
            l_hour = 0.0;
            Serial.println("Flow frequency is zero. Setting flow rates to 0.");
        } else {
            // L/min 및 L/hour 계산
            l_minute = (flow_frequency / 7.5);   // L/min 단위로 변환 (유량계 사양에 따라 조정 필요)
            l_hour = l_minute * 60;              // L/hr 계산

            // 출력
            Serial.print("Flow rate: ");
            Serial.print(l_minute);
            Serial.print(" L/min, ");
            Serial.print(l_hour);
            Serial.println(" L/hour");
        }

        // 펄스 카운터 초기화
        flow_frequency = 0;
    }
}

void checkMode() {
    unsigned long currentMillis = millis();

    if (currentMillis - lastModeChangeTime < modeChangeDebounceDelay) {
        return;
    }

    int manualPinState = digitalRead(manualModePin);
    int autoPinState = digitalRead(autoModePin);
    int aiPinState = digitalRead(aiModePin);

    if (manualPinState == HIGH && lastManualPinState == LOW && currentMode != MANUAL) {
        currentMode = MANUAL;
        Serial.println("Switched to Manual Mode.");
        lastModeChangeTime = currentMillis;
    } else if (autoPinState == HIGH && lastAutoPinState == LOW && currentMode != AUTO) {
        currentMode = AUTO;
        Serial.println("Switched to Auto Mode.");
        lastModeChangeTime = currentMillis;

        // 확인용: 자동 모드 전환 시 cycleTime과 cycleRestartDelay 값 출력
        Serial.print("Auto Mode - cycleTime: ");
        Serial.println(cycleTime);
        Serial.print("Auto Mode - cycleRestartDelay: ");
        Serial.println(cycleRestartDelay);

        // ConfigData의 값도 출력
        Serial.print("ConfigData cycleTime: ");
        Serial.println(configData.cycleTime);
        Serial.print("ConfigData cycleRestartDelay: ");
        Serial.println(configData.cycleRestartDelay);
    } else if (aiPinState == HIGH && lastAiPinState == LOW && currentMode != AI) {
        currentMode = AI;
        Serial.println("Switched to AI Mode.");
        lastModeChangeTime = currentMillis;
    }

    lastManualPinState = manualPinState;
    lastAutoPinState = autoPinState;
    lastAiPinState = aiPinState;
}


void checkWaterLevel() {
    if (digitalRead(waterLevelPin) == HIGH) { // 높은 수위일 때
        if (!waterLevelHigh) { // 처음 감지된 경우에만 실행
            Serial.println("Water level is high, stopping pump and returning to idle mode.");
            stopPumpSequence();
            waterLevelHigh = true;
        }
    } else {
        waterLevelHigh = false;
    }
}

// checkPumpControl 함수에서 수정
void checkPumpControl() {
    unsigned long currentTime = millis();
    int smCountHigh = 0;
    int smCountLow = 0;

    // waterLevelHigh가 true일 경우 제어 상태를 종료합니다
    if (waterLevelHigh) {
        control_pumpState = false;
        return;
    }

    if (currentMode == MANUAL) {
        // 수동 모드에서 control_PumpOn 핀이 HIGH일 때 펌프 작동
        if (digitalRead(control_PumpOn) == HIGH && !control_pumpState) {
            Serial.println("Manual mode: Turning on the pump");
            control_pumpState = true;
        } else if (digitalRead(control_PumpOff) == HIGH && control_pumpState) {
            Serial.println("Manual mode: Turning off the pump");
            control_pumpState = false;
            stopPumpSequence();
        }
    } else if (currentMode == AUTO) {
        // ADC0, ADC1, ADC2 변수를 사용해 조건 체크
        if (ADC0 > smStart) smCountHigh++;
        if (ADC1 > smStart) smCountHigh++;
        if (ADC2 > smStart) smCountHigh++;

        if (ADC0 < smStop) smCountLow++;
        if (ADC1 < smStop) smCountLow++;
        if (ADC2 < smStop) smCountLow++;

        if (smCountHigh >= 2 && !control_pumpState) {
            if (!holdConditionMet) {
                holdStartTime = currentTime;
                holdConditionMet = true;
            }
            if (currentTime - holdStartTime >= holdDuration) {
                control_pumpState = true;
                pumpStartTime = currentTime; // cycleTime 체크 시작
            }
        } else if (smCountLow >= 2 || digitalRead(rainSensor) == HIGH) {
            if (!holdConditionMet) {
                holdStartTime = currentTime;
                holdConditionMet = true;
            }
            if (currentTime - holdStartTime >= holdDuration && control_pumpState) {
                control_pumpState = false;
                stopPumpSequence();
                restartDelayStartTime = currentTime; // cycleRestartDelay 체크 시작
            }
        } else {
            holdConditionMet = false;
            holdStartTime = 0;
        }
    }
}

void managePumpSequence() {
    unsigned long currentTime = millis();

    // 물이 높은 경우 펌프를 즉시 중지하고 리턴
    if (digitalRead(waterLevelPin) == HIGH) {
        stopPumpSequence();
        return;
    }

    // 펌프 초기화 후 빠르게 재시작하지 않도록 지연
    if (delayAfterPumpOff && (currentTime - pumpOffTime < valveDelay)) {
        return;
    } else {
        delayAfterPumpOff = false;
    }

    // 자동 모드에서 강우 센서가 HIGH인 경우 펌프를 중지
    if (currentMode == AUTO && digitalRead(rainSensor) == HIGH) {
        stopPumpSequence();
        return;
    }

    // 현재 모드에 따른 동작 처리
    switch (currentMode) {
        case MANUAL:
            handleManualMode();
            break;
        case AUTO:
            handleAutoMode();
            break;
        case AI:
            handleAIMode();
            break;
    }
}

void handleManualMode() {
    // 수동 모드에서 펌프 ON 명령이 들어왔을 때
    if (digitalRead(control_PumpOn) == HIGH && !pumpIsOn && !valveOpening) {
        Serial.println("Manual mode: Opening valve and waiting for delay");
        digitalWrite(valvePin, HIGH);          // 밸브를 먼저 열기
        valveOpening = true;                   // 밸브 열림 상태 플래그 설정
        valveOpenStartTime = millis();         // 밸브 열림 시작 시간 기록
    }

    // 밸브가 열리고 대기 시간이 지난 후에 펌프를 켭니다
    if (valveOpening && (millis() - valveOpenStartTime >= valveDelay) && digitalRead(valvePin) == HIGH && !pumpIsOn) {
        Serial.println("Manual mode: Valve delay complete, turning on pump");
        digitalWrite(pumpPin, HIGH);           // 펌프 켜기
        pumpIsOn = true;                       // 펌프 상태 플래그 설정
        valveOpening = false;                  // 밸브 상태 초기화
    }

    // 수동 모드에서 펌프 OFF 명령이 들어왔을 때
    if (digitalRead(control_PumpOff) == HIGH && pumpIsOn) {
        Serial.println("Manual mode: Turning off the pump and closing valve");
        stopPumpSequence();                    // 펌프와 밸브 즉시 종료
    }
}

void handleAutoMode() {
    unsigned long currentMillis = millis();

    // 특정 조건이 충족되면 시퀀스를 중단
    if (checkExitConditions()) {
        Serial.println("Exit condition met, stopping sequence.");
        stopPumpSequence(); // 시퀀스를 종료하고 초기 상태로 복귀
        currentStep = 1;    // 초기 단계로 복귀하여 시퀀스 재시작 준비
        return;
    }

    switch (currentStep) {
        case 1:
            Serial.println("Step 1: Opening valve");
            digitalWrite(valvePin, HIGH); // 밸브 열기
            valveOpenStartTime = currentMillis; // 밸브 열림 시작 시간 기록
            currentStep = 2; // 다음 단계로 이동
            break;

        case 2:
            if (currentMillis - valveOpenStartTime >= valveDelay) {
                Serial.println("Step 2: Valve Delay complete, turning on pump");
                digitalWrite(pumpPin, HIGH); // 펌프 켜기
                pumpStartTime = currentMillis; // 펌프 시작 시간 기록
                pumpIsOn = true;              // 펌프 상태 설정
                currentStep = 3; // 다음 단계로 이동
            }
            break;

        case 3:
            if (currentMillis - pumpStartTime >= cycleTime) {
                Serial.println("Step 3: cycleTime complete, turning off pump");
                digitalWrite(pumpPin, LOW); // 펌프 끄기
                pumpIsOn = false;           // 펌프 상태 업데이트
                currentStep = 4; // 다음 단계로 이동
            }
            break;

        case 4:
            Serial.println("Step 4: Closing valve");
            digitalWrite(valvePin, LOW); // 밸브 닫기
            valveOpenStartTime = 0;
            restartDelayStartTime = currentMillis; // 재시작 대기 시간 기록
            currentStep = 5; // 다음 단계로 이동
            break;

        case 5:
            if (currentMillis - restartDelayStartTime >= cycleRestartDelay) {
                Serial.println("Step 5: cycleRestartDelay complete, ready to start new sequence");
                currentStep = 1; // 시퀀스를 처음 단계로 복귀
            }
            break;
    }
}

// checkExitConditions 함수에서 수정
bool checkExitConditions() {
    int smStopCount = 0;

    // ADC0, ADC1, ADC2 변수를 사용하여 smStop 조건 확인
    if (ADC0 < smStop) smStopCount++;
    if (ADC1 < smStop) smStopCount++;
    if (ADC2 < smStop) smStopCount++;

    bool waterLevelHigh = digitalRead(waterLevelPin) == HIGH;
    bool rainDetected = digitalRead(rainSensor) == HIGH;

    return (smStopCount >= 2 || waterLevelHigh || rainDetected);
}

void handleAIMode() {
    unsigned long currentMillis = millis();

    // AI 모드에서 펌프를 켜야 하는 경우
    if (aiModeRequested && !pumpIsOn) {
        // 밸브가 열리는 상태가 아니라면 밸브 열기
        if (!valveOpening) {
            Serial.println("AI Mode: Opening valve and starting delay.");
            digitalWrite(valvePin, HIGH); // 밸브 열기
            valveOpening = true;          // 밸브 열림 상태 플래그 설정
            valveOpenStartTime = currentMillis; // 밸브 열림 시간 기록
        } 
        // 밸브가 열린 후 지연 시간이 지나면 펌프를 켜기
        else if (currentMillis - valveOpenStartTime >= valveDelay) {
            Serial.println("AI Mode: Turning on the pump after valve delay.");
            digitalWrite(pumpPin, HIGH); // 펌프 켜기
            pumpIsOn = true;             // 펌프 상태 업데이트
            valveOpening = false;        // 밸브 열림 상태 플래그 해제
            valveOpenStartTime = 0;      // 밸브 지연 시간 초기화
        }
    } 
    // AI 모드가 중단되면 (제어 명령 2,0)
    else if (!aiModeRequested && (pumpIsOn || valveOpening)) {
        Serial.println("AI Mode: Turning off the pump and closing valve.");
        stopPumpSequence();  // 펌프와 밸브를 즉시 중지
    }
}

void handleControlCommand(int command, int value) {
    if (command == 1) {  // 모드 제어 명령
        if (value == 0) {
            currentMode = MANUAL;
            Serial.println("Control Command: Manual Mode activated.");
        } else if (value == 1) {
            currentMode = AUTO;
            Serial.println("Control Command: Auto Mode activated.");
        } else if (value == 2) {
            currentMode = AI;
            Serial.println("Control Command: AI Mode activated.");
        }
        stopPumpSequence(); // 모드 전환 시 펌프 초기화
    } else if (command == 2) {  // 펌프 제어 명령
        if (value == 0) {
            aiModeRequested = false;
            stopPumpSequence();
            Serial.println("Control Command: Pump off.");
        } else if (value == 1) {
            aiModeRequested = true;
            if (currentMode == AI) {
                handleAIMode();
            } else if (currentMode == AUTO) {
                handleAutoMode();
            }
        }
    }
}

void stopPumpSequence() {
    Serial.println("Stopping pump sequence. Turning off pump and closing valve.");
    digitalWrite(pumpPin, LOW);     // 펌프 끄기
    digitalWrite(valvePin, LOW);    // 밸브 닫기
    pumpIsOn = false;               // 펌프 상태 플래그 설정
    valveOpening = false;           // 밸브 열림 상태 플래그 해제
    aiModeRequested = false;        // AI 모드 요청 플래그 해제
    control_pumpState = false;      // 제어 플래그 해제
    valveOpenStartTime = 0;         // 밸브 열림 시간 초기화
    currentStep = 1;                // 시퀀스를 초기 단계로 복귀
}

void sendSensorDataToMaster() {
    // SensorData 업데이트
    updateSoilMoistureValues();  // 먼저 토양 수분 값 업데이트

    sensorData.ec = ec;  // EC 값
    sensorData.ph = ph * 100;  // PH 값 (곱하기 100을 통해 정밀도 조정)
    sensorData.airTemperature = airtemperature * 100;  // 대기 온도 값 (곱하기 100을 통해 정밀도 조정)
    sensorData.airHumidity = airhumidity * 100;  // 대기 습도 값 (곱하기 100을 통해 정밀도 조정)
    sensorData.ADC0 = ADC0;  // 업데이트된 토양 수분 센서 값 (ADC0)
    sensorData.ADC1 = ADC1;  // 업데이트된 토양 수분 센서 값 (ADC1)
    sensorData.ADC2 = ADC2;  // 업데이트된 토양 수분 센서 값 (ADC2, 85 추가 적용)
    sensorData.soilTemp1 = soilTemp[0] * 100;  // 첫 번째 DS18B20 센서 값 (곱하기 100)
    sensorData.soilTemp2 = soilTemp[1] * 100;  // 두 번째 DS18B20 센서 값 (곱하기 100)
    sensorData.l_minute = l_minute;  // 유량 센서 값 (L/min 단위)
    sensorData.pumpPin = digitalRead(pumpPin);  // 펌프 핀의 현재 상태 (HIGH 또는 LOW)
    sensorData.valvePin = digitalRead(valvePin);  // 밸브 핀의 현재 상태 (HIGH 또는 LOW)
    sensorData.waterLevelPin = digitalRead(waterLevelPin);  // 수위 센서 핀의 상태 (HIGH 또는 LOW)
    sensorData.rainSensor = digitalRead(rainSensor);  // 강우 센서 핀의 상태 (HIGH 또는 LOW)
    
    /// CRC 계산
    sensorData.crc = calculateCRC((uint8_t*)&sensorData, sizeof(SensorData) - sizeof(sensorData.crc));

    // 데이터 송신
    Wire.write((uint8_t*)&sensorData, sizeof(SensorData));
}


void sendConfigDataToMaster() {
    // currentMode 업데이트
	/*
	// configData 구조체의 크기를 출력하여 확인
    Serial.print("Size of ConfigData structure: ");
    Serial.println(sizeof(ConfigData));
	
	Serial.print( "Current Mode: " );
	Serial.println( currentMode );
	*/
    configData.currentMode = currentMode == MANUAL ? 0 : (currentMode == AUTO ? 1 : 2);

    // 시간 관련 설정 값들을 초 단위로 변환하여 업데이트
	
	configData.cycleTime = cycleTime;
	configData.cycleRestartDelay = cycleRestartDelay;
	configData.valveDelay = valveDelay;
	
    // 토양 수분 설정 값들 업데이트
    configData.smStart = smStart;
    configData.smStop = smStop;
    //configData.holdDuration = holdDuration / 1000;

    // CRC 계산
    configData.crc = calculateCRC((uint8_t*)&configData, sizeof(ConfigData) - sizeof(configData.crc));
/*
    // 디버깅: 전송 전에 ConfigData 내용을 시리얼로 출력 (선택사항)
    Serial.println("Sending ConfigData to Master:");
    Serial.print("Current Mode: "); Serial.println(configData.currentMode);
    Serial.print("Cycle Time (s): "); Serial.println(configData.cycleTime);
    Serial.print("Cycle Restart Delay (s): "); Serial.println(configData.cycleRestartDelay);
    Serial.print("Valve Delay (s): "); Serial.println(configData.valveDelay);
    Serial.print("Soil Moisture Start Threshold: "); Serial.println(configData.smStart);
    Serial.print("Soil Moisture Stop Threshold: "); Serial.println(configData.smStop);
    //Serial.print("Hold Duration (s): "); Serial.println(configData.holdDuration);
    Serial.print("CRC: "); Serial.println(configData.crc, HEX);
*/    
    // I2C를 통해 ConfigData 전송
    Wire.write((uint8_t*)&configData, sizeof(ConfigData));
}

void onRequestEvent() {
    unsigned long currentMillis = millis();

    // 데이터 전송 간격이 충분히 지났는지 확인
    if ((currentMillis - lastSendTime >= sendInterval) || lastSendTime == 0) {
        if (sendSensorNext) {
            sendSensorDataToMaster();
            sendSensorNext = false;
        } else {
            sendConfigDataToMaster();
            sendSensorNext = true;
        }
        lastSendTime = currentMillis;  // 전송 후 시간 갱신
    } else {
        Serial.println("Not enough time has passed to send data.");
    }
}

// receiveData 함수에서 업데이트
// receiveData 함수에서 업데이트
void receiveData(int numBytes) {
    int settingID = Wire.read();
    int lowerByte = Wire.read();
    int upperByte = Wire.read();
    uint32_t settingValue = (upperByte << 8) | lowerByte;

    Serial.print("Received setting ID: ");
    Serial.println(settingID);
    Serial.print("Setting Value: ");
    Serial.println(settingValue);

    switch (settingID) {
        case 1: // currentMode 설정 0 == 수동, 1 == 자동, 2 == AI
            if (settingValue == 0) {
                currentMode = MANUAL;
                Serial.println("Switched to Manual Mode.");
            } else if (settingValue == 1) {
                currentMode = AUTO;
                Serial.println("Switched to Auto Mode.");
            } else if (settingValue == 2) {
                currentMode = AI;
                Serial.println("Switched to AI Mode.");
            } else {
                Serial.println("Invalid mode value received for setting ID 1.");
            }
            stopPumpSequence(); // 모드 전환 시 펌프 초기화
            break;

       case 2: // AI 모드에서 펌프 제어
            if (currentMode == AI) {
                aiModeRequested = (settingValue == 1);
                Serial.print("AI Mode: Received command to turn ");
                Serial.println(aiModeRequested ? "ON" : "OFF");
                handleAIMode();
            } else {
                Serial.println("Pump control command received, but system is not in AI mode.");
            }
            break;

        case 3:  // cycleTime 설정 (펌프 ON 지속시간)
            {
                unsigned long calculatedCycleTime = settingValue * 1000;
                cycleTime = (calculatedCycleTime > 86400000UL) ? 86400000UL : calculatedCycleTime;
                configData.cycleTime = cycleTime;

                Serial.print("Updated cycleTime: ");
                Serial.println(cycleTime);
            }
            break;

        case 4:  // cycleRestartDelay 설정 (펌프 OFF 지속시간)
            {
                unsigned long calculatedRestartDelay = settingValue * 1000UL; // 밀리초 단위 변환
                cycleRestartDelay = (calculatedRestartDelay > 86400000UL) ? 86400000UL : calculatedRestartDelay;
                configData.cycleRestartDelay = cycleRestartDelay;

                Serial.print("Updated cycleRestartDelay: ");
                Serial.println(cycleRestartDelay);
            }
            break;

        case 5:  // valveDelay 설정
            {
                unsigned long calculatedDelay = settingValue * 1000;
                valveDelay = (calculatedDelay > 86400000UL) ? 86400000UL : calculatedDelay;
                configData.valveDelay = valveDelay;

                Serial.print("Updated valveDelay: ");
                Serial.println(valveDelay);
            }
            break;

        case 6:  // smStart 설정
            smStart = (settingValue > 1023) ? 1023 : settingValue;
            configData.smStart = smStart;

            Serial.print("Updated smStart: ");
            Serial.println(smStart);
            break;

        case 7:  // smStop 설정
            smStop = (settingValue > 1023) ? 1023 : settingValue;
            configData.smStop = smStop;

            Serial.print("Updated smStop: ");
            Serial.println(smStop);
            break;

        case 8:  // holdDuration 설정
            {
                unsigned long calculatedHoldDuration = settingValue * 1000;
                holdDuration = (calculatedHoldDuration > 86400000UL) ? 86400000UL : calculatedHoldDuration;

                Serial.print("Updated holdDuration: ");
                Serial.println(holdDuration);
            }
            break;

        default:
            Serial.println("Unknown setting ID received");
            break;
    }

    // CRC 업데이트 후 설정 데이터 전송
    configData.crc = calculateCRC((uint8_t*)&configData, sizeof(ConfigData) - sizeof(configData.crc));
    //Serial.print("Updated CRC: ");
    //Serial.println(configData.crc, HEX);

    // ConfigData를 마스터로 전송
    sendConfigDataToMaster();
}



void printSensorData() {
    unsigned long currentTime = millis();
    
    // 2초 간격으로 센서 및 플래그 상태를 출력
    if (currentTime - lastSensorPrintTime >= sensorPrintInterval) {
        lastSensorPrintTime = currentTime;

        // 토양 수분 센서 값 출력
        Serial.print("Soil Moisture Sensor Values: ");
		Serial.print("ADC0: "); Serial.print(ADC0);
		Serial.print(", ADC1: "); Serial.print(ADC1);
		Serial.print(", ADC2: "); Serial.println(ADC2);
		Serial.println();
		
		Serial.print("SoilTemp 1: ");
		Serial.print(soilTemp[0]);
		Serial.print(", ");

		Serial.print("SoilTemp 2: ");
		Serial.println(soilTemp[1]);
		    
        // water level, rain sensor 상태 출력
        Serial.print("Water Level: ");
        Serial.print(digitalRead(waterLevelPin) == HIGH ? "High" : "Low");
        Serial.print(", Rain Sensor: ");
        Serial.println(digitalRead(rainSensor) == HIGH ? "Detected" : "Not Detected");

        // 유량 정보 출력
        calculateFlowRate(); // 1초마다 유량 계산하여 출력

        // 각 플래그 및 상태 변수 출력
        Serial.print("Current Mode: ");
		switch (currentMode) {
			case MANUAL:
				Serial.println("Manual");
				break;
			case AUTO:
				Serial.println("Auto");
				break;
			case AI:
				Serial.println("AI");
				break;
		}
        Serial.print(",Pump: ");
        Serial.print(pumpIsOn ? "ON" : "OFF");
        Serial.print(",Valve: ");
        Serial.print(valveOpening ? "Opening" : "Closed");
        Serial.print(",ControlPump: ");
        Serial.print(control_pumpState ? "ON" : "OFF");
        Serial.print(",Water High: ");
        Serial.print(waterLevelHigh ? "True" : "False");
        Serial.print(",In Cycle: ");
        Serial.print(inCycle ? "True" : "False");
        Serial.print(",AutoModeAlerted: ");
        Serial.println(autoModeAlerted ? "True" : "False");

        Serial.println("-------------------------------------------------");
    }
}


void setup() {
    Serial.begin(115200);
	Wire.begin(8);
	Wire.setClock(100000);  // I2C 속도를 400kHz로 설정 (고속 모드)  예를 들어, 100000은 100kHz (기본), 400000은 400kHz (고속)
    Serial2.begin(9600);      // RS485 통신 포트 초기화
    node1.begin(1, Serial2);  // RS485 슬레이브 ID 1
    node2.begin(2, Serial2);  // RS485 슬레이브 ID 2
    Wire.onRequest(onRequestEvent);  // 요청 시 이벤트 처리
    Wire.onReceive(receiveData);
    
    sensors.begin();  // DS18B20 센서 초기화
        
    pinMode(pumpPin, OUTPUT);
    pinMode(valvePin, OUTPUT);
    pinMode(control_PumpOn, INPUT);
    pinMode(control_PumpOff, INPUT);
    pinMode(waterLevelPin, INPUT);
    pinMode(rainSensor, INPUT);
    pinMode(flowSensorPin, INPUT);
    
    pinMode(manualModePin, INPUT);
    pinMode(autoModePin, INPUT);
    pinMode(aiModePin, INPUT);
    
    digitalWrite(flowSensorPin, HIGH); // 내장 풀업 설정
    attachInterrupt(digitalPinToInterrupt(flowSensorPin), flow, RISING); // 유량 센서 인터럽트 설정
    
    analogReference(EXTERNAL);

    digitalWrite(pumpPin, LOW);    // 초기 펌프 OFF
    digitalWrite(valvePin, LOW);   // 초기 밸브 CLOSE
	
	// lastSendTime을 현재 시간에서 초기화하여 첫 전송이 즉시 발생하게 함
    lastSendTime = millis() - sendInterval;  // sendInterval 간격을 맞춰 첫 전송 가능
    // 초기 설정 값 출력
    Serial.println("System Initialization:");
    Serial.print("Initial cycleTime: ");
    Serial.println(cycleTime);
    Serial.print("Initial cycleRestartDelay: ");
    Serial.println(cycleRestartDelay);
    
    Serial.println("System initialized in Manual mode.");
}

void loop() {
	unsigned long currentMillis = millis();
    
    // 주기적으로 센서 데이터를 읽습니다
    if (currentMillis - lastRequestTime >= requestInterval) {
        lastRequestTime = currentMillis;

        // ID 1 데이터 읽기
        readSensorDataID1();
        
        // ID 1 요청 후 짧은 대기 후 ID 2 데이터 읽기
        delay(50);  // 50ms 대기 (필요한 최소한의 대기 시간)
        readSensorDataID2();
    }
    updateSoilMoistureValues();   // 토양 수분 값 업데이트
	checkMode();           // 모드 확인 및 전환
	checkWaterLevel();     // 워터 레벨 상태 확인
    checkPumpControl();    // 펌프 제어 상태 확인
    managePumpSequence();  // 펌프 시퀀스 관리
    readDS18B20Data();  // 온도 센서 데이터 읽기
	calculateFlowRate();
    //printSensorData();     // 센서 및 상태 출력
}
