// MPINO-16A8R8T Slave Control Board Code R1.1, 2024-11-05 // 수동모드, AI 모드에서 펌프 On 시 밸브가 닫혀있는 상태에서 펌프가 On 되는 현상 수정 #include #include #include #include // 송신 주기 및 타이밍 변수 선언 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(); // 센서 및 상태 출력 }