2025-11-08 21:48:22 +09:00

945 lines
34 KiB
C++

// 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(); // 센서 및 상태 출력
}