#include "esp_camera.h"
#include <WiFi.h>
#include <PubSubClient.h> // PubSubClient 2.8.0
#include <ArduinoJson.h> // ArduinoJson 7.1.0
// Camera GPIO Setup
#define Y2_GPIO_NUM 5
#define Y3_GPIO_NUM 18
#define Y4_GPIO_NUM 19
#define Y5_GPIO_NUM 21
#define Y6_GPIO_NUM 36
#define Y7_GPIO_NUM 39
#define Y8_GPIO_NUM 34
#define Y9_GPIO_NUM 35
#define XCLK_GPIO_NUM 0
#define PCLK_GPIO_NUM 22
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
/*
<< 센서 GPIO 설정 >>
- GPIO00 : 부팅 시 LOW 상태이면 업로드 모드
- GPIO04 : 카메라 플래시가 사용
- GPIO16 : 부팅 시 HIGH 상태이면 무한 재부팅, 입력 모드로 사용 시 카메라 사용 불가
*/
#define PERSON 13 // 인체 감지
#define DOOR 2 // 현관 열림
#define WINDOW 4 // 창문 열림
#define RAIN 12 // 빗물 감지
#define DIR 14 // 스텝모터 드라이버 DIR (창문 센서 GPIO와 병행 사용)
#define STEP 15 // 스텝모터 드라이버 STEP
#define LED_BUILTIN 33 // Built in LED
#define STEP_DELAY 1000 // 스텝모터 펄스 간격(1ms)
#define STEP_COUNT 360 // 스텝모터 펄스 갯수(회전각 = 360 * 1.8 = 648)
// WiFi Setup
String ssid[] = { "KT_GiGA_45B7", "MICS1", "SRC" };
String password[] = { "cfccdb4460", "mics13579@", "!src@5146#" };
const int wifi_count = 3; // WiFi 연결할 갯수(위 3개 중)
// MQTT Broker Setup
const char* mqttServer = "ict.nanum.info";
const int mqttPort = 18883;
const char* HostName = "ESP32CAM_MQTT";
const char* mqttUser = "ict";
const char* mqttPassword = "qhdkscjfwj0!";
const int MAX_PAYLOAD = 60000;
// MQTT Topic Setup
char topic_DEVICE[20] = { 0 }; // "00:00:00:00:00:00/#"
char topic_PHOTO[29] = { 0 }; // "00:00:00:00:00:00/PhotoShoot";
char topic_VIDEO[32] = { 0 }; // "00:00:00:00:00:00/RealTimeVideo";
char topic_DEBUG[24] = { 0 }; // "00:00:00:00:00:00/Debug";
char topic_WINDOW[25] = { 0 }; // "00:00:00:00:00:00/WINDOW"
char topic_SECURITY[27] = { 0 }; // "00:00:00:00:00:00/SECURITY"
char topic_PHOTOPUB[27] = { 0 }; // "00:00:00:00:00:00/PhotoPub"
char topic_SENSORPUB[28] = { 0 }; // "00:00:00:00:00:00/SENSORPub"
byte mac[6]; // MAC Address
char DeviceID[18] = { 0 }; // 장치 ID (00:00:00:00:00:00)
unsigned long previousMillis = 0; // 이전시간
const long delayTime = 500; // 500ms 대기
boolean Video_OnOff = false; // 실시간 영상 ON/OFF
boolean Debug_OnOff = true; // 디버그 모드 ON/OFF
boolean SecurityMode = true; // 보안 모드 ON/OFF
// 센서 데이터 저장 변수
boolean previousPerson = false; // 인체 감지(이전)
boolean previousDoor = false; // 현관 열림(이전)
boolean previousWindow = false; // 창문 열림(이전)
boolean previousRain = false; // 빗물 감지(이전)
boolean currentPerson = false; // 인체 감지(현재)
boolean currentDoor = false; // 현관 열림(현재)
boolean currentWindow = false; // 창문 열림(현재)
boolean currentRain = false; // 빗물 감지(현재)
WiFiClient espClient;
PubSubClient mqtt_client(espClient);
DynamicJsonDocument CONFIG(100); // MQTT 수신
DynamicJsonDocument SENSOR(100); // MQTT 광고
// Program Init
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("\nInitialization in progress...");
Serial.setDebugOutput(true);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PERSON, INPUT);
pinMode(DOOR, INPUT_PULLUP);
//pinMode(WINDOW, INPUT_PULLUP);
pinMode(RAIN, INPUT);
pinMode(DIR, OUTPUT);
pinMode(STEP, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
camera_init();
wifi_init();
mqtt_init();
delay(1000);
for (int i=0;i<3;i++) {
digitalWrite(LED_BUILTIN, LOW);
delay(500);
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
}
if (Debug_OnOff) Serial.begin(115200);
else Serial.end();
}
// Camera Init
void camera_init() {
delay(100);
// OV2640 Camera
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.frame_size = FRAMESIZE_CIF; // VGA(640x480,37), CIF(400x296,47), QVGA(320x240,50), HQVGA(240x176,50), QQVGA(160x120,52)
config.pixel_format = PIXFORMAT_JPEG;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.jpeg_quality = 12; // 값이 낮을수록 고화질
config.fb_count = 1;
// 카메라 초기화
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera initialization failed : 0x%x", err);
return;
} else {
sensor_t* s = esp_camera_sensor_get();
s->set_vflip(s, 1); // 상하 반전
s->set_hmirror(s, 1); // 좌우 반전
Serial.println("Camera initialization successful");
}
}
// WiFi Init
void wifi_init() {
int i = 0;
WiFi.mode(WIFI_STA); // WiFi_STA, WiFi_AP, WiFi_STA+WiFi_AP
WiFi.setHostname(HostName);
WiFi.setSleep(false);
while (WiFi.status() != WL_CONNECTED) {
Serial.print("\nConnecting to ");
Serial.print(ssid[i]);
Serial.print(" ");
WiFi.begin(ssid[i], password[i]);
for (int j = 0; j < 5; j++) {
if (WiFi.status() == WL_CONNECTED) break;
Serial.print(".");
delay(1000);
}
if (wifi_count-1 == i++) i = 0;
}
// WiFi 연결 IP 출력
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP : ");
Serial.println(WiFi.localIP());
// MAC Address 출력
WiFi.macAddress(mac);
sprintf(DeviceID, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
Serial.print("MAC : ");
Serial.println(DeviceID);
Serial.println("------------------------------");
// MQTT Topic 설정
sprintf(topic_DEVICE, "%02X:%02X:%02X:%02X:%02X:%02X/#", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
sprintf(topic_PHOTO, "%02X:%02X:%02X:%02X:%02X:%02X/PhotoShoot", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
sprintf(topic_VIDEO, "%02X:%02X:%02X:%02X:%02X:%02X/RealTimeVideo", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
sprintf(topic_DEBUG, "%02X:%02X:%02X:%02X:%02X:%02X/Debug", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
sprintf(topic_WINDOW, "%02X:%02X:%02X:%02X:%02X:%02X/WINDOW", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
sprintf(topic_SECURITY, "%02X:%02X:%02X:%02X:%02X:%02X/SECURITY", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
sprintf(topic_PHOTOPUB, "%02X:%02X:%02X:%02X:%02X:%02X/PhotoPub", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
sprintf(topic_SENSORPUB, "%02X:%02X:%02X:%02X:%02X:%02X/SENSORPub", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
// MQTT Init
void mqtt_init() {
mqtt_client.setServer(mqttServer, mqttPort);
mqtt_client.setBufferSize(MAX_PAYLOAD);
mqtt_client.setCallback(callback);
}
// MQTT Callback
void callback(String topic, byte* message, unsigned int length) {
String messageTemp;
if (topic != topic_PHOTOPUB) {
for (int i = 0; i < length; i++) {
messageTemp += (char)message[i];
}
Serial.printf("Topic : %s\n", topic.c_str());
Serial.printf("Message : %s\n", messageTemp.c_str());
if (topic == topic_PHOTO) {
take_picture();
} else if (topic == topic_VIDEO) {
if (messageTemp == "true") Video_OnOff = true;
else Video_OnOff = false;
} else if (topic == topic_DEBUG) {
if (messageTemp == "true") Debug_OnOff = true;
else Debug_OnOff = false;
if (Debug_OnOff) Serial.begin(115200);
else Serial.end();
} else if (topic == topic_WINDOW) {
if (messageTemp == "true") {
windowControl(1);
currentWindow = true;
} else {
windowControl(0);
currentWindow = false;
}
sensorRead();
} else if (topic == topic_SECURITY) {
if (messageTemp == "true") SecurityMode = true;
else SecurityMode = false;
sensorRead();
}
}
}
// Photo shoot
void take_picture() {
if (SecurityMode) {
camera_fb_t* fb = NULL; // 촬영한 사진을 저장할 공간
fb = esp_camera_fb_get(); // 스냅샷
if (!fb) {
Serial.println("Photo shoot failed");
return;
} else {
if (MQTT_MAX_PACKET_SIZE == 128) {
// SLOW MODE (increase MQTT_MAX_PACKET_SIZE)
mqtt_client.publish_P(topic_PHOTOPUB, fb->buf, fb->len, false);
} else {
// FAST MODE (increase MQTT_MAX_PACKET_SIZE)
mqtt_client.publish(topic_PHOTOPUB, fb->buf, fb->len, false);
}
}
esp_camera_fb_return(fb);
}
}
// MQTT Connect
void reconnect() {
while (!mqtt_client.connected()) {
Serial.println("\nAttempting MQTT connection...");
if (mqtt_client.connect(DeviceID, mqttUser, mqttPassword)) {
Serial.println("Connected\n");
mqtt_client.subscribe(topic_DEVICE);
} else {
Serial.print("MQTT connection failed, rc=");
Serial.print(mqtt_client.state());
Serial.println("Try Again in 5 Seconds");
delay(5000);
}
}
}
// 센서 상태 확인
void sensorRead() {
currentPerson = digitalRead(PERSON);
currentDoor = digitalRead(DOOR);
//currentWindow = digitalRead(WINDOW);
currentRain = digitalRead(RAIN);
if (currentPerson || currentDoor) Video_OnOff = true;
else Video_OnOff = false;
if (previousPerson != currentPerson || previousDoor != currentDoor || previousWindow != currentWindow || previousRain != currentRain) {
previousPerson = currentPerson;
previousDoor = currentDoor;
previousWindow = currentWindow;
previousRain = currentRain;
SENSOR["Person"] = currentPerson;
SENSOR["Door"] = currentDoor;
SENSOR["Window"] = currentWindow;
SENSOR["Rain"] = currentRain;
SENSOR["Security"]= SecurityMode;
char buffer[200];
serializeJson(SENSOR, buffer, sizeof(buffer));
mqtt_client.publish(topic_SENSORPUB, buffer);
}
}
// 창문 제어
void windowControl(boolean dirCon) {
// 현재 창문 열림/닫힘 상태와 창문 제어 요청 상태를 비교하여 다른 경우에만 창문 제어
if (currentWindow != dirCon) {
if (dirCon) Serial.println("창문 열기");
else Serial.println("창문 닫기");
for (int i=0; i<STEP_COUNT; i++) {
digitalWrite(STEP, HIGH);
delayMicroseconds(STEP_DELAY);
digitalWrite(STEP, LOW);
delayMicroseconds(STEP_DELAY);
}
}
}
// Program Start
void loop() {
unsigned long currentMillis = millis(); // 현재 시간(아두이노 가동 시간)
if (!mqtt_client.connected()) {
reconnect();
}
mqtt_client.loop();
if (currentMillis - previousMillis > delayTime) {
previousMillis = currentMillis;
sensorRead();
}
if (Video_OnOff) {
take_picture();
}
}