#include <IRremote.h>
#include <DHT.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// 电机控制引脚
#define MOTOR_PIN1 13
#define MOTOR_PIN2 12
#define MOTOR_PIN3 14
#define MOTOR_PIN4 27
#define IR_RECEIVE_PIN 5
// DHT22温湿度传感器
#define DHT_PIN 32
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);
// OLED显示屏设置
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// 红外遥控器按钮定义
#define IR_BUTTON_1 0x30
#define IR_BUTTON_2 0x18
#define IR_BUTTON_3 0x7A
#define IR_BUTTON_4 0x10
#define IR_BUTTON_5 0x38
#define IR_BUTTON_6 0x5A
#define IR_BUTTON_7 0x42
#define IR_BUTTON_8 0x4A
#define IR_BUTTON_9 0x52
#define IR_BUTTON_0 0x68
#define IR_BUTTON_UP 0x02
#define IR_BUTTON_DOWN 0x98
#define IR_BUTTON_LEFT 0xE0
#define IR_BUTTON_RIGHT 0x90
#define IR_BUTTON_OK 0xA8
// 窗帘参数设置
const int MAX_STEPS = 600;
const int MICRO_ADJUST = 50;
int currentPosition = 0;
// 温湿度自动控制参数
const float MAX_TEMP = 28.0;
const float MIN_TEMP = 18.0;
const float MAX_HUMIDITY = 70.0;
bool autoMode = false;
// 时间设置相关
typedef struct {
uint8_t hour;
uint8_t minute;
uint8_t second;
} CustomTime;
CustomTime currentTime = {12, 0, 0};
// 定时开关设置(可自定义)
uint8_t openTimeHour = 7; // 默认早上7点打开
uint8_t openTimeMinute = 0; // 默认0分
uint8_t closeTimeHour = 20; // 默认晚上20点关闭
uint8_t closeTimeMinute = 0; // 默认0分
bool openScheduled = false;
bool closeScheduled = false;
// 设置模式枚举
enum SettingMode {
MODE_NORMAL,
MODE_TIME_SETTING,
MODE_OPEN_TIME_SETTING,
MODE_CLOSE_TIME_SETTING
};
SettingMode currentSettingMode = MODE_NORMAL;
uint8_t timeSettingField = 0; // 时间设置字段(0=小时,1=分钟,2=秒钟)
uint8_t scheduleSettingField = 0; // 定时设置字段(0=小时,1=分钟)
// 夜间模式控制
const uint8_t NIGHT_START_HOUR = 20;
const uint8_t NIGHT_END_HOUR = 7;
bool nightMode = false;
// 电机参数
const int stepDelay = 3;
const int stepSequence[4][4] = {
{1, 0, 0, 1},
{1, 0, 1, 0},
{0, 1, 1, 0},
{0, 1, 0, 1}
};
// 状态变量
int targetPosition = 0;
int stepsToTake = 0;
bool isMoving = false;
int moveDirection = 0;
unsigned long lastStepTime = 0;
unsigned long lastSensorReadTime = 0;
unsigned long lastDisplayUpdate = 0;
unsigned long lastSecondTick = 0;
unsigned long lastIRTime = 0;
const unsigned long SENSOR_READ_INTERVAL = 5000;
const unsigned long DISPLAY_UPDATE_INTERVAL = 500;
const unsigned long IR_DEBOUNCE = 300;
// 电机控制状态机
int currentStep = 0;
void setup() {
Serial.begin(115200);
Serial.println("Smart Curtain Control System - Customizable Schedule");
// 设置电机引脚
pinMode(MOTOR_PIN1, OUTPUT);
pinMode(MOTOR_PIN2, OUTPUT);
pinMode(MOTOR_PIN3, OUTPUT);
pinMode(MOTOR_PIN4, OUTPUT);
// 初始化红外接收
Serial.println("Initializing IR receiver...");
IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
Serial.println("IR receiver started");
// 初始化温湿度传感器
dht.begin();
// 初始化OLED
Wire.begin(21, 22);
if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
Serial.println("SSD1306 allocation failed");
while(1);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.println("Smart Curtain");
display.println("System Ready");
display.println("Waiting...");
display.display();
delay(2000);
// 停止电机
stopMotor();
// 初始化时间
lastSensorReadTime = millis();
lastDisplayUpdate = millis();
lastSecondTick = millis();
lastIRTime = millis();
Serial.println("System ready! Button functions:");
Serial.println("1: Fully Open, 2: Fully Close, 3: Stop");
Serial.println("4: Half Open, 5: Micro Open, 6: Micro Close");
Serial.println("7: Toggle Auto, 8: Time Setting, 9: Schedule Setting");
Serial.println("0: Exit Setting, Arrows: Adjust, OK: Confirm");
Serial.print("Current Schedule: Open at ");
Serial.print(openTimeHour);
Serial.print(":");
if (openTimeMinute < 10) Serial.print("0");
Serial.print(openTimeMinute);
Serial.print(", Close at ");
Serial.print(closeTimeHour);
Serial.print(":");
if (closeTimeMinute < 10) Serial.print("0");
Serial.println(closeTimeMinute);
}
void loop() {
unsigned long currentMillis = millis();
// 1. 处理电机运动
if (isMoving && currentMillis - lastStepTime >= stepDelay) {
takeStep();
lastStepTime = currentMillis;
if (stepsToTake <= 0) {
completeMovement();
}
}
// 2. 更新时间(每秒)
if (currentMillis - lastSecondTick >= 1000) {
updateTime();
checkScheduledTasks();
lastSecondTick = currentMillis;
}
// 3. 读取传感器数据(每5秒)
if (currentMillis - lastSensorReadTime >= SENSOR_READ_INTERVAL) {
readSensorData();
lastSensorReadTime = currentMillis;
if (autoMode && !isMoving) {
checkAutoControl();
}
}
// 4. 更新显示(每500ms)
if (currentMillis - lastDisplayUpdate >= DISPLAY_UPDATE_INTERVAL) {
updateDisplay();
lastDisplayUpdate = currentMillis;
}
// 5. 处理红外接收(带防抖)
if (IrReceiver.decode()) {
if (currentMillis - lastIRTime >= IR_DEBOUNCE) {
handleIRCommand();
lastIRTime = currentMillis;
}
IrReceiver.resume();
}
delay(10);
}
void checkNightMode() {
bool wasNightMode = nightMode;
nightMode = (currentTime.hour >= NIGHT_START_HOUR || currentTime.hour < NIGHT_END_HOUR);
if (nightMode != wasNightMode) {
Serial.print("Night mode: ");
Serial.println(nightMode ? "ON" : "OFF");
}
}
void checkScheduledTasks() {
checkNightMode();
// 检查打开时间
if (currentTime.hour == openTimeHour && currentTime.minute == openTimeMinute && currentTime.second == 0) {
if (!openScheduled && !nightMode) {
Serial.print("Scheduled opening: ");
Serial.print(openTimeHour);
Serial.print(":");
if (openTimeMinute < 10) Serial.print("0");
Serial.println(openTimeMinute);
moveToPosition(MAX_STEPS);
openScheduled = true;
closeScheduled = false;
}
}
// 检查关闭时间
else if (currentTime.hour == closeTimeHour && currentTime.minute == closeTimeMinute && currentTime.second == 0) {
if (!closeScheduled) {
Serial.print("Scheduled closing: ");
Serial.print(closeTimeHour);
Serial.print(":");
if (closeTimeMinute < 10) Serial.print("0");
Serial.println(closeTimeMinute);
moveToPosition(0);
closeScheduled = true;
openScheduled = false;
}
}
// 重置标记
else if (currentTime.hour != openTimeHour || currentTime.minute != openTimeMinute) {
if (currentTime.hour != closeTimeHour || currentTime.minute != closeTimeMinute) {
openScheduled = false;
closeScheduled = false;
}
}
}
void updateTime() {
currentTime.second++;
if (currentTime.second >= 60) {
currentTime.second = 0;
currentTime.minute++;
if (currentTime.minute >= 60) {
currentTime.minute = 0;
currentTime.hour++;
if (currentTime.hour >= 24) {
currentTime.hour = 0;
openScheduled = false;
closeScheduled = false;
}
}
}
}
void updateDisplay() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
if (currentSettingMode == MODE_TIME_SETTING) {
// 时间设置模式
display.setTextSize(2);
display.setCursor(0, 0);
display.print("SET TIME");
display.setTextSize(3);
display.setCursor(10, 20);
// 小时
if (timeSettingField == 0) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
else display.setTextColor(SSD1306_WHITE);
if (currentTime.hour < 10) display.print("0");
display.print(currentTime.hour);
display.print(":");
// 分钟
if (timeSettingField == 1) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
else display.setTextColor(SSD1306_WHITE);
if (currentTime.minute < 10) display.print("0");
display.print(currentTime.minute);
display.print(":");
// 秒钟
if (timeSettingField == 2) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
else display.setTextColor(SSD1306_WHITE);
if (currentTime.second < 10) display.print("0");
display.print(currentTime.second);
display.setTextSize(1);
display.setCursor(0, 56);
display.print("Arrows:Adjust OK:Confirm 0:Exit");
} else if (currentSettingMode == MODE_OPEN_TIME_SETTING) {
// 打开时间设置模式
display.setTextSize(2);
display.setCursor(0, 0);
display.print("SET OPEN");
display.setTextSize(3);
display.setCursor(10, 20);
// 小时
if (scheduleSettingField == 0) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
else display.setTextColor(SSD1306_WHITE);
if (openTimeHour < 10) display.print("0");
display.print(openTimeHour);
display.print(":");
// 分钟
if (scheduleSettingField == 1) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
else display.setTextColor(SSD1306_WHITE);
if (openTimeMinute < 10) display.print("0");
display.print(openTimeMinute);
display.setTextSize(1);
display.setCursor(0, 56);
display.print("Arrows:Adjust OK:Next 0:Exit");
} else if (currentSettingMode == MODE_CLOSE_TIME_SETTING) {
// 关闭时间设置模式
display.setTextSize(2);
display.setCursor(0, 0);
display.print("SET CLOSE");
display.setTextSize(3);
display.setCursor(10, 20);
// 小时
if (scheduleSettingField == 0) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
else display.setTextColor(SSD1306_WHITE);
if (closeTimeHour < 10) display.print("0");
display.print(closeTimeHour);
display.print(":");
// 分钟
if (scheduleSettingField == 1) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
else display.setTextColor(SSD1306_WHITE);
if (closeTimeMinute < 10) display.print("0");
display.print(closeTimeMinute);
display.setTextSize(1);
display.setCursor(0, 56);
display.print("Arrows:Adjust OK:Confirm 0:Exit");
} else {
// 正常显示模式
display.setCursor(0, 0);
display.print("Time:");
if (currentTime.hour < 10) display.print("0");
display.print(currentTime.hour);
display.print(":");
if (currentTime.minute < 10) display.print("0");
display.print(currentTime.minute);
display.print(":");
if (currentTime.second < 10) display.print("0");
display.print(currentTime.second);
display.setCursor(80, 0);
display.print("Mode:");
display.print(autoMode ? "Auto" : "Manu");
// 温湿度显示
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
display.setCursor(0, 12);
if (!isnan(temperature)) {
display.print("Temp:");
display.print(temperature, 1);
display.print("C");
} else {
display.print("Temp:Error");
}
display.setCursor(64, 12);
if (!isnan(humidity)) {
display.print("Humid:");
display.print(humidity, 1);
display.print("%");
} else {
display.print("Humid:Error");
}
// 窗帘位置
int curtainPercent = (currentPosition * 100) / MAX_STEPS;
display.setCursor(0, 24);
display.print("Curtain:");
display.print(curtainPercent);
display.print("%");
// 具体位置
display.setCursor(0, 36);
display.print("Pos:");
display.print(currentPosition);
display.print("/");
display.print(MAX_STEPS);
// 移动状态
display.setCursor(0, 48);
display.print("Status:");
if (isMoving) {
display.print(moveDirection > 0 ? "Opening" : "Closing");
} else {
display.print("Stopped");
}
// 定时信息
display.setCursor(0, 56);
display.print("S:");
display.print(openTimeHour);
display.print(":");
if (openTimeMinute < 10) display.print("0");
display.print(openTimeMinute);
display.print("-");
display.print(closeTimeHour);
display.print(":");
if (closeTimeMinute < 10) display.print("0");
display.print(closeTimeMinute);
}
display.display();
}
void readSensorData() {
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
if (isnan(humidity) || isnan(temperature)) {
return;
}
Serial.print("Time: ");
Serial.print(currentTime.hour);
Serial.print(":");
Serial.print(currentTime.minute);
Serial.print(":");
Serial.print(currentTime.second);
Serial.print(" | Temp: ");
Serial.print(temperature);
Serial.print("C, Humidity: ");
Serial.print(humidity);
Serial.print("%, Position: ");
Serial.print(currentPosition);
Serial.print(" steps, Auto: ");
Serial.print(autoMode ? "ON" : "OFF");
Serial.print(", Schedule: ");
Serial.print(openTimeHour);
Serial.print(":");
if (openTimeMinute < 10) Serial.print("0");
Serial.print(openTimeMinute);
Serial.print("-");
Serial.print(closeTimeHour);
Serial.print(":");
if (closeTimeMinute < 10) Serial.print("0");
Serial.println(closeTimeMinute);
}
void checkAutoControl() {
if (nightMode) {
float temperature = dht.readTemperature();
if (!isnan(temperature) && temperature < MIN_TEMP && currentPosition > 0) {
Serial.println("Night mode - Auto closing curtain for insulation");
moveToPosition(0);
}
return;
}
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
if (isnan(humidity) || isnan(temperature)) {
return;
}
if (temperature > MAX_TEMP || humidity > MAX_HUMIDITY) {
if (currentPosition < MAX_STEPS) {
Serial.println("Auto opening curtain");
moveToPosition(MAX_STEPS);
}
} else if (temperature < MIN_TEMP) {
if (currentPosition > 0) {
Serial.println("Auto closing curtain");
moveToPosition(0);
}
}
}
void handleIRCommand() {
if (IrReceiver.decodedIRData.flags) {
return;
}
uint32_t command = IrReceiver.decodedIRData.command;
Serial.print("IR Command: 0x");
Serial.println(command, HEX);
// 设置模式下的处理
if (currentSettingMode != MODE_NORMAL) {
handleSettingCommand(command);
return;
}
// 正常模式下的处理
switch (command) {
case IR_BUTTON_1:
Serial.println("Button 1 - Fully Open (100%)");
moveToPosition(MAX_STEPS);
break;
case IR_BUTTON_2:
Serial.println("Button 2 - Fully Close (0%)");
moveToPosition(0);
break;
case IR_BUTTON_3:
Serial.println("Button 3 - Stop Immediately");
stopMovement();
break;
case IR_BUTTON_4:
Serial.println("Button 4 - Half Open (50%)");
moveToPosition(MAX_STEPS / 2);
break;
case IR_BUTTON_5:
Serial.println("Button 5 - Micro Adjust Open (+5%)");
moveToPosition(min(currentPosition + MICRO_ADJUST, MAX_STEPS));
break;
case IR_BUTTON_6:
Serial.println("Button 6 - Micro Adjust Close (-5%)");
moveToPosition(max(currentPosition - MICRO_ADJUST, 0));
break;
case IR_BUTTON_7:
Serial.println("Button 7 - Toggle Auto/Manual Mode");
autoMode = !autoMode;
Serial.print("Auto Mode: ");
Serial.println(autoMode ? "ON" : "OFF");
break;
case IR_BUTTON_8:
Serial.println("Button 8 - Enter Time Setting Mode");
currentSettingMode = MODE_TIME_SETTING;
timeSettingField = 0;
break;
case IR_BUTTON_9:
Serial.println("Button 9 - Enter Schedule Setting Mode");
currentSettingMode = MODE_OPEN_TIME_SETTING;
scheduleSettingField = 0;
break;
case IR_BUTTON_0:
// 正常模式下按钮0无功能
break;
default:
Serial.print("Unknown button: 0x");
Serial.println(command, HEX);
break;
}
}
void handleSettingCommand(uint32_t command) {
switch (command) {
case IR_BUTTON_UP:
adjustSetting(1);
break;
case IR_BUTTON_DOWN:
adjustSetting(-1);
break;
case IR_BUTTON_LEFT:
moveToPreviousField();
break;
case IR_BUTTON_RIGHT:
moveToNextField();
break;
case IR_BUTTON_OK:
confirmSetting();
break;
case IR_BUTTON_0:
exitSetting();
break;
default:
Serial.print("Unknown setting button: 0x");
Serial.println(command, HEX);
break;
}
}
void adjustSetting(int delta) {
switch (currentSettingMode) {
case MODE_TIME_SETTING:
if (timeSettingField == 0) {
currentTime.hour = (currentTime.hour + delta + 24) % 24;
} else if (timeSettingField == 1) {
currentTime.minute = (currentTime.minute + delta + 60) % 60;
} else {
currentTime.second = (currentTime.second + delta + 60) % 60;
}
break;
case MODE_OPEN_TIME_SETTING:
if (scheduleSettingField == 0) {
openTimeHour = (openTimeHour + delta + 24) % 24;
} else {
openTimeMinute = (openTimeMinute + delta + 60) % 60;
}
break;
case MODE_CLOSE_TIME_SETTING:
if (scheduleSettingField == 0) {
closeTimeHour = (closeTimeHour + delta + 24) % 24;
} else {
closeTimeMinute = (closeTimeMinute + delta + 60) % 60;
}
break;
default:
break;
}
}
void moveToNextField() {
switch (currentSettingMode) {
case MODE_TIME_SETTING:
timeSettingField = (timeSettingField + 1) % 3;
break;
case MODE_OPEN_TIME_SETTING:
case MODE_CLOSE_TIME_SETTING:
scheduleSettingField = (scheduleSettingField + 1) % 2;
break;
default:
break;
}
}
void moveToPreviousField() {
switch (currentSettingMode) {
case MODE_TIME_SETTING:
timeSettingField = (timeSettingField - 1 + 3) % 3;
break;
case MODE_OPEN_TIME_SETTING:
case MODE_CLOSE_TIME_SETTING:
scheduleSettingField = (scheduleSettingField - 1 + 2) % 2;
break;
default:
break;
}
}
void confirmSetting() {
switch (currentSettingMode) {
case MODE_TIME_SETTING:
currentSettingMode = MODE_NORMAL;
Serial.println("Time setting confirmed");
break;
case MODE_OPEN_TIME_SETTING:
currentSettingMode = MODE_CLOSE_TIME_SETTING;
scheduleSettingField = 0;
break;
case MODE_CLOSE_TIME_SETTING:
currentSettingMode = MODE_NORMAL;
Serial.print("Schedule set: Open at ");
Serial.print(openTimeHour);
Serial.print(":");
if (openTimeMinute < 10) Serial.print("0");
Serial.print(openTimeMinute);
Serial.print(", Close at ");
Serial.print(closeTimeHour);
Serial.print(":");
if (closeTimeMinute < 10) Serial.print("0");
Serial.println(closeTimeMinute);
break;
default:
break;
}
}
void exitSetting() {
currentSettingMode = MODE_NORMAL;
Serial.println("Setting mode exited");
}
void moveToPosition(int newPosition) {
newPosition = constrain(newPosition, 0, MAX_STEPS);
if (newPosition == currentPosition) return;
targetPosition = newPosition;
stepsToTake = abs(newPosition - currentPosition);
moveDirection = (newPosition > currentPosition) ? 1 : -1;
isMoving = true;
Serial.print("Moving to: ");
Serial.print(newPosition);
Serial.print(" (");
Serial.print((newPosition * 100) / MAX_STEPS);
Serial.print("%), Direction: ");
Serial.println(moveDirection > 0 ? "Opening" : "Closing");
}
void takeStep() {
digitalWrite(MOTOR_PIN1, stepSequence[currentStep][0]);
digitalWrite(MOTOR_PIN2, stepSequence[currentStep][1]);
digitalWrite(MOTOR_PIN3, stepSequence[currentStep][2]);
digitalWrite(MOTOR_PIN4, stepSequence[currentStep][3]);
if (moveDirection > 0) {
currentStep = (currentStep + 1) % 4;
} else {
currentStep = (currentStep - 1 + 4) % 4;
}
stepsToTake--;
currentPosition += moveDirection;
currentPosition = constrain(currentPosition, 0, MAX_STEPS);
}
void completeMovement() {
stopMotor();
Serial.print("Movement completed. Position: ");
Serial.print(currentPosition);
Serial.print(" (");
Serial.print((currentPosition * 100) / MAX_STEPS);
Serial.println("%)");
}
void stopMovement() {
if (isMoving) {
stopMotor();
Serial.print("Movement stopped. Position: ");
Serial.print(currentPosition);
Serial.print(" (");
Serial.print((currentPosition * 100) / MAX_STEPS);
Serial.println("%)");
}
}
void stopMotor() {
isMoving = false;
digitalWrite(MOTOR_PIN1, LOW);
digitalWrite(MOTOR_PIN2, LOW);
digitalWrite(MOTOR_PIN3, LOW);
digitalWrite(MOTOR_PIN4, LOW);
}