์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŒ C์–ธ์–ด๋กœ ์‹œ์ž‘ํ•˜๋Š” IoT ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์—ฌํ–‰

๐ŸŒ C์–ธ์–ด๋กœ ์‹œ์ž‘ํ•˜๋Š” IoT ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์—ฌํ–‰

์„ผ์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘๋ถ€ํ„ฐ ์ฒ˜๋ฆฌ๊นŒ์ง€, ์‹ค์ „ ์‹œ์Šคํ…œ ๊ตฌ์ถ• ์™„๋ฒฝ ๊ฐ€์ด๋“œ ๐Ÿš€

๐ŸŽฏ IoT์™€ C์–ธ์–ด, ์™œ ์ด ์กฐํ•ฉ์ด ์ตœ๊ณ ์ผ๊นŒ?

์•ˆ๋…•! ์˜ค๋Š˜์€ ์ •๋ง ์žฌ๋ฏธ์žˆ๋Š” ์ฃผ์ œ๋กœ ์ด์•ผ๊ธฐ๋ฅผ ๋‚˜๋ˆ ๋ณผ ๊ฑฐ์•ผ. ๋ฐ”๋กœ **C์–ธ์–ด๋ฅผ ํ™œ์šฉํ•œ IoT ํ”„๋กœ๊ทธ๋ž˜๋ฐ**์ด์•ผ. ์š”์ฆ˜ ์Šค๋งˆํŠธํ™ˆ, ์›จ์–ด๋Ÿฌ๋ธ” ๊ธฐ๊ธฐ, ์ž์œจ์ฃผํ–‰์ฐจ ๋“ฑ IoT ๊ธฐ์ˆ ์ด ์šฐ๋ฆฌ ์ƒํ™œ ๊ณณ๊ณณ์— ์Šค๋ฉฐ๋“ค๊ณ  ์žˆ์ž–์•„? ๐Ÿ โœจ

๊ทธ๋Ÿฐ๋ฐ ๋ง์ด์•ผ, ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด IoT ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด๋ผ๊ณ  ํ•˜๋ฉด ํŒŒ์ด์ฌ์ด๋‚˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ™์€ ์–ธ์–ด๋ฅผ ๋จผ์ € ๋– ์˜ฌ๋ ค. ๋ฌผ๋ก  ๊ทธ๊ฒƒ๋“ค๋„ ์ข‹์ง€๋งŒ, ์ง„์งœ ํ•˜๋“œ์›จ์–ด์™€ ์ง์ ‘ ๋Œ€ํ™”ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด C์–ธ์–ด๋งŒํ•œ ๊ฒŒ ์—†์–ด. ์™œ๋ƒ๊ณ ? C์–ธ์–ด๋Š” ํ•˜๋“œ์›จ์–ด์— ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ณ ๊ธ‰ ์–ธ์–ด๊ฑฐ๋“ ! ๐Ÿ’ช

์„ผ์„œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์˜ค๊ณ , ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , ๋‹ค์‹œ ์•ก์ถ”์—์ดํ„ฐ๋ฅผ ์ œ์–ดํ•˜๋Š” ๋ชจ๋“  ๊ณผ์ •์—์„œ C์–ธ์–ด๋Š” ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ด์•ผ. ํŠนํžˆ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์ œํ•œ์ ์ธ ์ž„๋ฒ ๋””๋“œ ์‹œ์Šคํ…œ์—์„œ๋Š” C์–ธ์–ด์˜ ์ง„๊ฐ€๊ฐ€ ๋ฐœํœ˜๋˜์ง€. ์•„๋‘์ด๋…ธ, ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด, ESP32 ๊ฐ™์€ ๋ณด๋“œ๋“ค๋„ ๋ชจ๋‘ C/C++ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๊ณ  ์žˆ์–ด!
๐Ÿ’ก IoT ์‹œ์Šคํ…œ์˜ ํ•ต์‹ฌ ๊ตฌ์„ฑ์š”์†Œ

์„ผ์„œ(Sensor): ์˜จ๋„, ์Šต๋„, ๋น›, ์›€์ง์ž„ ๋“ฑ ๋ฌผ๋ฆฌ์  ๋ฐ์ดํ„ฐ๋ฅผ ์ „๊ธฐ ์‹ ํ˜ธ๋กœ ๋ณ€ํ™˜
๋งˆ์ดํฌ๋กœ์ปจํŠธ๋กค๋Ÿฌ(MCU): ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋‘๋‡Œ ์—ญํ• 
ํ†ต์‹  ๋ชจ๋“ˆ: WiFi, Bluetooth, LoRa ๋“ฑ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ „์†ก
์•ก์ถ”์—์ดํ„ฐ: LED, ๋ชจํ„ฐ, ๋ฆด๋ ˆ์ด ๋“ฑ ๋ฌผ๋ฆฌ์  ๋™์ž‘ ์ˆ˜ํ–‰
ํด๋ผ์šฐ๋“œ/์„œ๋ฒ„: ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐ ๋ถ„์„

๐Ÿ”ง ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์ถ•ํ•˜๊ธฐ

์ž, ๊ทธ๋Ÿผ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ๋จผ์ € ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๋ถ€ํ„ฐ ์„ธํŒ…ํ•ด์•ผ๊ฒ ์ง€? ๊ฑฑ์ • ๋งˆ, ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ต์ง€ ์•Š์•„! ๐Ÿ˜Š
ํ•„์ˆ˜ ํ•˜๋“œ์›จ์–ด ์ค€๋น„๋ฌผ
IoT ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ๋ช‡ ๊ฐ€์ง€ ํ•˜๋“œ์›จ์–ด๊ฐ€ ํ•„์š”ํ•ด. ์ดˆ๋ณด์ž๋ผ๋ฉด **์•„๋‘์ด๋…ธ ์šฐ๋…ธ**๋‚˜ **ESP32** ๋ณด๋“œ๋ฅผ ์ถ”์ฒœํ•ด. ๊ฐ€๊ฒฉ๋„ ์ €๋ ดํ•˜๊ณ  ์ปค๋ฎค๋‹ˆํ‹ฐ๋„ ํ™œ๋ฐœํ•ด์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒจ๋„ ํ•ด๊ฒฐํ•˜๊ธฐ ์‰ฝ๊ฑฐ๋“ ! ๐ŸŽฎ

๋ณด๋“œ ์ข…๋ฅ˜ ํŠน์ง• ์ถ”์ฒœ ์šฉ๋„
Arduino Uno ์ดˆ๋ณด์ž ์นœํ™”์ , ํ’๋ถ€ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธฐ๋ณธ ์„ผ์„œ ์‹ค์Šต
ESP32 WiFi/Bluetooth ๋‚ด์žฅ, ์ €๋ ดํ•œ ๊ฐ€๊ฒฉ ๋ฌด์„  IoT ํ”„๋กœ์ ํŠธ
Raspberry Pi ๋ฆฌ๋ˆ…์Šค OS, ๊ฐ•๋ ฅํ•œ ์„ฑ๋Šฅ ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
STM32 ์‚ฐ์—…์šฉ ์ˆ˜์ค€, ๋‹ค์–‘ํ•œ ์‹œ๋ฆฌ์ฆˆ ์ „๋ฌธ์ ์ธ ์ž„๋ฒ ๋””๋“œ ๊ฐœ๋ฐœ
์„ผ์„œ๋„ ๋ช‡ ๊ฐœ ์ค€๋น„ํ•ด์•ผ๊ฒ ์ง€? ์ฒ˜์Œ์—๋Š” **DHT11**(์˜จ์Šต๋„ ์„ผ์„œ), **HC-SR04**(์ดˆ์ŒํŒŒ ๊ฑฐ๋ฆฌ ์„ผ์„œ), **LDR**(์กฐ๋„ ์„ผ์„œ) ์ •๋„๋ฉด ์ถฉ๋ถ„ํ•ด. ์ด๊ฒƒ๋“ค๋กœ ์ •๋ง ๋‹ค์–‘ํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐ŸŒก๏ธ๐Ÿ“๐Ÿ’ก
์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ ๋„๊ตฌ
ํ•˜๋“œ์›จ์–ด ์ค€๋น„๊ฐ€ ๋๋‚ฌ๋‹ค๋ฉด ์ด์ œ ์ฝ”๋”ฉ ํ™˜๊ฒฝ์„ ๋งŒ๋“ค์–ด์•ผ์ง€. ์•„๋‘์ด๋…ธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด **Arduino IDE**๊ฐ€ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•ด. ํ•˜์ง€๋งŒ ์ข€ ๋” ์ „๋ฌธ์ ์ธ ๊ฐœ๋ฐœ์„ ์›ํ•œ๋‹ค๋ฉด **PlatformIO**๋‚˜ **VS Code**๋ฅผ ์ถ”์ฒœํ•ด! ๐Ÿ–ฅ๏ธ

ESP32๋‚˜ STM32 ๊ฐ™์€ ๋ณด๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๊ฐ ์ œ์กฐ์‚ฌ์—์„œ ์ œ๊ณตํ•˜๋Š” SDK๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•ด. ESP32๋Š” **ESP-IDF**, STM32๋Š” **STM32CubeIDE**๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ. ์ฒ˜์Œ์—” ๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ, ํ•œ๋ฒˆ ์ต์ˆ™ํ•ด์ง€๋ฉด ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋“ค์ด์•ผ! ๐Ÿ’ช
๐ŸŽ“ ์ดˆ๋ณด์ž ํŒ

์ฒ˜์Œ ์‹œ์ž‘ํ•œ๋‹ค๋ฉด Arduino IDE๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฑธ ์ถ”์ฒœํ•ด! ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ ์ฝ”๋“œ๋“ค์ด ๋งŽ์•„์„œ ํ•™์Šตํ•˜๊ธฐ ์ข‹๊ฑฐ๋“ . ๋‚˜์ค‘์— ์‹ค๋ ฅ์ด ๋Š˜๋ฉด ๋” ์ „๋ฌธ์ ์ธ ๋„๊ตฌ๋กœ ๋„˜์–ด๊ฐ€๋ฉด ๋ผ. ์žฌ๋Šฅ๋„ท์—์„œ IoT ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฉ˜ํ† ๋ฅผ ์ฐพ์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ!

๐Ÿ“ก ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ์˜ ๊ธฐ์ดˆ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์„ผ์„œ์™€ ๋Œ€ํ™”ํ•ด๋ณผ ์‹œ๊ฐ„์ด์•ผ! ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š” ๊ฑด ์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•ด. ๊ธฐ๋ณธ์ ์œผ๋กœ **๋””์ง€ํ„ธ ์ž…๋ ฅ**๊ณผ **์•„๋‚ ๋กœ๊ทธ ์ž…๋ ฅ** ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์ด ์žˆ์–ด. ๐ŸŽ›๏ธ
๋””์ง€ํ„ธ ์„ผ์„œ ๋‹ค๋ฃจ๊ธฐ
๋””์ง€ํ„ธ ์„ผ์„œ๋Š” 0 ๋˜๋Š” 1, ์ฆ‰ HIGH๋‚˜ LOW ๊ฐ’๋งŒ ๋ฐ˜ํ™˜ํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฒ„ํŠผ์ด๋‚˜ PIR ๋ชจ์…˜ ์„ผ์„œ ๊ฐ™์€ ๊ฑฐ์ง€. ์ฝ”๋“œ๋กœ ๋ณด๋ฉด ์ด๋ ‡๊ฒŒ ์ƒ๊ฒจ๋จน์—ˆ์–ด:
#define BUTTON_PIN 2

void setup() {
    pinMode(BUTTON_PIN, INPUT);
    Serial.begin(9600);
}

void loop() {
    int buttonState = digitalRead(BUTTON_PIN);
    
    if (buttonState == HIGH) {
        Serial.println("๋ฒ„ํŠผ์ด ๋ˆŒ๋ ธ์–ด์š”! ๐ŸŽ‰");
    }
    
    delay(100);
}
๊ฐ„๋‹จํ•˜์ง€? pinMode()๋กœ ํ•€์„ ์ž…๋ ฅ ๋ชจ๋“œ๋กœ ์„ค์ •ํ•˜๊ณ , digitalRead()๋กœ ๊ฐ’์„ ์ฝ์–ด์˜ค๋ฉด ๋์ด์•ผ! ๐Ÿ˜Ž
์•„๋‚ ๋กœ๊ทธ ์„ผ์„œ์˜ ์„ธ๊ณ„
์•„๋‚ ๋กœ๊ทธ ์„ผ์„œ๋Š” ์ข€ ๋” ์žฌ๋ฏธ์žˆ์–ด. 0๋ถ€ํ„ฐ 1023๊นŒ์ง€(10๋น„ํŠธ ADC ๊ธฐ์ค€) ์—ฐ์†์ ์ธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋“ . ์˜จ๋„ ์„ผ์„œ, ์กฐ๋„ ์„ผ์„œ, ๊ฐ€๋ณ€์ €ํ•ญ ๋“ฑ์ด ์—ฌ๊ธฐ ์†ํ•ด. ๐ŸŒก๏ธ

์˜ˆ๋ฅผ ๋“ค์–ด ์กฐ๋„ ์„ผ์„œ๋กœ ๋น›์˜ ๋ฐ๊ธฐ๋ฅผ ์ธก์ •ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ณผ๊นŒ?
#define LDR_PIN A0

void setup() {
    Serial.begin(9600);
}

void loop() {
    int lightValue = analogRead(LDR_PIN);
    
    // 0-1023 ๊ฐ’์„ 0-100 ํผ์„ผํŠธ๋กœ ๋ณ€ํ™˜
    int brightness = map(lightValue, 0, 1023, 0, 100);
    
    Serial.print("๋ฐ๊ธฐ: ");
    Serial.print(brightness);
    Serial.println("%");
    
    // ์–ด๋‘์šฐ๋ฉด ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€
    if (brightness < 30) {
        Serial.println("โš ๏ธ ๋„ˆ๋ฌด ์–ด๋‘์›Œ์š”!");
    }
    
    delay(500);
}
์—ฌ๊ธฐ์„œ map() ํ•จ์ˆ˜๊ฐ€ ์ •๋ง ์œ ์šฉํ•ด! ์„ผ์„œ ๊ฐ’์˜ ๋ฒ”์œ„๋ฅผ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๋ฒ”์œ„๋กœ ์‰ฝ๊ฒŒ ๋ณ€ํ™˜ํ•ด์ฃผ๊ฑฐ๋“ . ์ด๋Ÿฐ ์ž‘์€ ํŒ๋“ค์ด ๋ชจ์—ฌ์„œ ๋ฉ‹์ง„ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋˜๋Š” ๊ฑฐ์•ผ! โœจ IoT ๋ฐ์ดํ„ฐ ํ๋ฆ„๋„ ์„ผ์„œ ์˜จ๋„/์Šต๋„/๋น› MCU ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ†ต์‹  WiFi/BLE ํด๋ผ์šฐ๋“œ ์ €์žฅ/๋ถ„์„ ์•„๋‚ ๋กœ๊ทธ/๋””์ง€ํ„ธ ์‹ ํ˜ธ ์ž…๋ ฅ ADC ๋ณ€ํ™˜ ํ•„ํ„ฐ๋ง/์—ฐ์‚ฐ ํ”„๋กœํ† ์ฝœ ๋ณ€ํ™˜ ํŒจํ‚ท ์ „์†ก ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง C์–ธ์–ด์˜ ์—ญํ•  ์„ผ์„œ ๋“œ๋ผ์ด๋ฒ„ ํ•˜๋“œ์›จ์–ด ์ œ์–ด ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„ ํ†ต์‹  ํ”„๋กœํ† ์ฝœ UART/SPI/I2C ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ํšจ์œจ์  ์ž์› ํ™œ์šฉ

๐ŸŒก๏ธ ์‹ค์ „! DHT11 ์˜จ์Šต๋„ ์„ผ์„œ ํ”„๋กœ์ ํŠธ

์ด๋ก ๋งŒ ๋ฐฐ์šฐ๋ฉด ์žฌ๋ฏธ์—†์ž–์•„? ์ด์ œ ์‹ค์ œ๋กœ DHT11 ์˜จ์Šต๋„ ์„ผ์„œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ์™„์ „ํ•œ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณผ ๊ฑฐ์•ผ! ์ด ํ”„๋กœ์ ํŠธ๋Š” ์ •๋ง ์‹ค์šฉ์ ์ด๋ผ์„œ ์Šค๋งˆํŠธํ™ˆ ์‹œ์Šคํ…œ์˜ ๊ธฐ์ดˆ๊ฐ€ ๋  ์ˆ˜ ์žˆ์–ด. ๐Ÿก
ํ•˜๋“œ์›จ์–ด ์—ฐ๊ฒฐํ•˜๊ธฐ
DHT11 ์„ผ์„œ๋Š” ๋ณดํ†ต 3๊ฐœ์˜ ํ•€์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด. VCC(์ „์›), GND(์ ‘์ง€), DATA(๋ฐ์ดํ„ฐ) ํ•€์ด์ง€. ์—ฐ๊ฒฐ์€ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ผ:

โ€ข DHT11 VCC โ†’ Arduino 5V
โ€ข DHT11 GND โ†’ Arduino GND
โ€ข DHT11 DATA โ†’ Arduino Digital Pin 2
โ€ข DATA ํ•€๊ณผ VCC ์‚ฌ์ด์— 10kฮฉ ํ’€์—… ์ €ํ•ญ ์—ฐ๊ฒฐ (์ค‘์š”!) ๐Ÿ”Œ
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ

ํ’€์—… ์ €ํ•ญ์„ ๊ผญ ์—ฐ๊ฒฐํ•ด์•ผ ํ•ด! ์•ˆ ๊ทธ๋Ÿฌ๋ฉด ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ๊ฐ€ ๋ถˆ์•ˆ์ •ํ•ด์งˆ ์ˆ˜ ์žˆ์–ด. ์ผ๋ถ€ DHT11 ๋ชจ๋“ˆ์€ ์ด๋ฏธ ์ €ํ•ญ์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์œผ๋‹ˆ ํ™•์ธํ•ด๋ณด์ž.
DHT11 ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ฐ ๊ธฐ๋ณธ ์ฝ”๋“œ
Arduino IDE์—์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋งค๋‹ˆ์ €๋ฅผ ์—ด๊ณ  "DHT sensor library"๋ฅผ ๊ฒ€์ƒ‰ํ•ด์„œ ์„ค์น˜ํ•˜์ž. Adafruit์—์„œ ๋งŒ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๊ฐ€์žฅ ์•ˆ์ •์ ์ด์•ผ! ๐Ÿ“š

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์ธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณผ๊นŒ?
#include "DHT.h"

#define DHTPIN 2        // DHT11 ๋ฐ์ดํ„ฐ ํ•€
#define DHTTYPE DHT11   // ์„ผ์„œ ํƒ€์ž… ์ง€์ •

DHT dht(DHTPIN, DHTTYPE);

// ๋ฐ์ดํ„ฐ ์ €์žฅ์„ ์œ„ํ•œ ๊ตฌ์กฐ์ฒด
typedef struct {
    float temperature;
    float humidity;
    unsigned long timestamp;
    bool isValid;
} SensorData;

SensorData currentData;

void setup() {
    Serial.begin(9600);
    Serial.println("๐ŸŒก๏ธ DHT11 ์„ผ์„œ ์‹œ์Šคํ…œ ์‹œ์ž‘!");
    
    dht.begin();
    
    // ์ดˆ๊ธฐํ™”
    currentData.isValid = false;
}

void loop() {
    // ์„ผ์„œ ์ฝ๊ธฐ (2์ดˆ๋งˆ๋‹ค)
    delay(2000);
    
    // ์˜จ์Šต๋„ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ
    float h = dht.readHumidity();
    float t = dht.readTemperature();
    
    // ์ฝ๊ธฐ ์‹คํŒจ ์ฒดํฌ
    if (isnan(h) || isnan(t)) {
        Serial.println("โŒ ์„ผ์„œ ์ฝ๊ธฐ ์‹คํŒจ!");
        currentData.isValid = false;
        return;
    }
    
    // ๋ฐ์ดํ„ฐ ์ €์žฅ
    currentData.temperature = t;
    currentData.humidity = h;
    currentData.timestamp = millis();
    currentData.isValid = true;
    
    // ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ
    printSensorData();
    
    // ๊ฒฝ๊ณ  ์‹œ์Šคํ…œ
    checkAlerts();
}

void printSensorData() {
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    Serial.print("๐ŸŒก๏ธ  ์˜จ๋„: ");
    Serial.print(currentData.temperature);
    Serial.println("ยฐC");
    
    Serial.print("๐Ÿ’ง ์Šต๋„: ");
    Serial.print(currentData.humidity);
    Serial.println("%");
    
    // ์ฒด๊ฐ์˜จ๋„ ๊ณ„์‚ฐ (์—ด์ง€์ˆ˜)
    float heatIndex = calculateHeatIndex(
        currentData.temperature, 
        currentData.humidity
    );
    
    Serial.print("๐Ÿ”ฅ ์ฒด๊ฐ์˜จ๋„: ");
    Serial.print(heatIndex);
    Serial.println("ยฐC");
    
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
}

// ์—ด์ง€์ˆ˜(Heat Index) ๊ณ„์‚ฐ ํ•จ์ˆ˜
float calculateHeatIndex(float temp, float humidity) {
    // ๊ฐ„๋‹จํ•œ ์—ด์ง€์ˆ˜ ๊ณต์‹
    float hi = temp + 0.5555 * (6.11 * exp(5417.7530 * 
               ((1/273.16) - (1/(273.15+temp)))) * 
               (humidity/100.0) - 10.0);
    return hi;
}

void checkAlerts() {
    // ์˜จ๋„ ๊ฒฝ๊ณ 
    if (currentData.temperature > 30.0) {
        Serial.println("๐Ÿ”ด ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค!");
    } else if (currentData.temperature < 15.0) {
        Serial.println("๐Ÿ”ต ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์Šต๋‹ˆ๋‹ค!");
    }
    
    // ์Šต๋„ ๊ฒฝ๊ณ 
    if (currentData.humidity > 70.0) {
        Serial.println("๐Ÿ’ฆ ๊ฒฝ๊ณ : ์Šต๋„๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค!");
    } else if (currentData.humidity < 30.0) {
        Serial.println("๐Ÿœ๏ธ ๊ฒฝ๊ณ : ์Šต๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์Šต๋‹ˆ๋‹ค!");
    }
}
์™€! ์ฝ”๋“œ๊ฐ€ ์ข€ ๊ธธ์–ด ๋ณด์ด์ง€? ํ•˜์ง€๋งŒ ํ•˜๋‚˜ํ•˜๋‚˜ ๋œฏ์–ด๋ณด๋ฉด ์ „ํ˜€ ์–ด๋ ต์ง€ ์•Š์•„. ๊ตฌ์กฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ , ํ•จ์ˆ˜๋กœ ๊ธฐ๋Šฅ์„ ๋ถ„๋ฆฌํ•ด์„œ ์ฝ”๋“œ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ์–ด. ์ด๊ฒŒ ๋ฐ”๋กœ ์ข‹์€ C ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์Šต๊ด€์ด์•ผ! ๐Ÿ‘

๐Ÿ“Š ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๋ฐ ํ•„ํ„ฐ๋ง

์„ผ์„œ์—์„œ ์ฝ์–ด์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋ผ! ์™œ๋ƒ๊ณ ? ์„ผ์„œ ๋ฐ์ดํ„ฐ์—๋Š” ํ•ญ์ƒ **๋…ธ์ด์ฆˆ**๊ฐ€ ์„ž์—ฌ ์žˆ๊ฑฐ๋“ . ์ „๊ธฐ์  ๊ฐ„์„ญ, ์„ผ์„œ ์ž์ฒด์˜ ์˜ค์ฐจ, ํ™˜๊ฒฝ์  ์š”์ธ ๋“ฑ์œผ๋กœ ์ธํ•ด ๊ฐ’์ด ํŠ€๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„. ๐Ÿ˜ฐ

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ **ํ•„ํ„ฐ๋ง**ํ•˜๊ณ  **ํ‰ํ™œํ™”**ํ•ด์•ผ ํ•ด. ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ, ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š” ๊ธฐ๋ฒ•๋“ค์„ ์•Œ์•„๋ณด์ž!
์ด๋™ ํ‰๊ท  ํ•„ํ„ฐ (Moving Average Filter)
๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๋ฉด์„œ๋„ ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•์ด์•ผ. ์ตœ๊ทผ N๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ‰๊ท ๋‚ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์ง€. ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ์ด๋ ‡๊ฒŒ ๋ผ:
#define WINDOW_SIZE 10

typedef struct {
    float buffer[WINDOW_SIZE];
    int index;
    int count;
    float sum;
} MovingAverage;

MovingAverage tempFilter;
MovingAverage humidFilter;

void initMovingAverage(MovingAverage *ma) {
    ma->index = 0;
    ma->count = 0;
    ma->sum = 0.0;
    
    for (int i = 0; i < WINDOW_SIZE; i++) {
        ma->buffer[i] = 0.0;
    }
}

float addValue(MovingAverage *ma, float value) {
    // ์ด์ „ ๊ฐ’์„ ํ•ฉ๊ณ„์—์„œ ๋นผ๊ธฐ
    ma->sum -= ma->buffer[ma->index];
    
    // ์ƒˆ ๊ฐ’์„ ๋ฒ„ํผ์— ์ €์žฅ
    ma->buffer[ma->index] = value;
    ma->sum += value;
    
    // ์ธ๋ฑ์Šค ์—…๋ฐ์ดํŠธ (์ˆœํ™˜ ๋ฒ„ํผ)
    ma->index = (ma->index + 1) % WINDOW_SIZE;
    
    // ์นด์šดํŠธ ์—…๋ฐ์ดํŠธ
    if (ma->count < WINDOW_SIZE) {
        ma->count++;
    }
    
    // ํ‰๊ท  ๋ฐ˜ํ™˜
    return ma->sum / ma->count;
}

void setup() {
    Serial.begin(9600);
    dht.begin();
    
    // ํ•„ํ„ฐ ์ดˆ๊ธฐํ™”
    initMovingAverage(&tempFilter);
    initMovingAverage(&humidFilter);
}

void loop() {
    delay(2000);
    
    float rawTemp = dht.readTemperature();
    float rawHumid = dht.readHumidity();
    
    if (!isnan(rawTemp) && !isnan(rawHumid)) {
        // ํ•„ํ„ฐ๋ง๋œ ๊ฐ’ ์–ป๊ธฐ
        float filteredTemp = addValue(&tempFilter, rawTemp);
        float filteredHumid = addValue(&humidFilter, rawHumid);
        
        Serial.print("์›๋ณธ ์˜จ๋„: ");
        Serial.print(rawTemp);
        Serial.print("ยฐC โ†’ ํ•„ํ„ฐ๋ง: ");
        Serial.print(filteredTemp);
        Serial.println("ยฐC");
        
        Serial.print("์›๋ณธ ์Šต๋„: ");
        Serial.print(rawHumid);
        Serial.print("% โ†’ ํ•„ํ„ฐ๋ง: ");
        Serial.print(filteredHumid);
        Serial.println("%");
    }
}
์ด ์ฝ”๋“œ์˜ ํ•ต์‹ฌ์€ **์ˆœํ™˜ ๋ฒ„ํผ(Circular Buffer)**๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฑฐ์•ผ. ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ๋„ ์ตœ๊ทผ ๋ฐ์ดํ„ฐ๋งŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์ง€! ๐Ÿ”„
์นผ๋งŒ ํ•„ํ„ฐ (Kalman Filter)
์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•์„ ์›ํ•œ๋‹ค๋ฉด ์นผ๋งŒ ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด๋ณผ ์ˆ˜ ์žˆ์–ด. ์ด๊ฑด NASA์—์„œ ์•„ํด๋กœ ์šฐ์ฃผ์„  ํ•ญ๋ฒ•์— ์‚ฌ์šฉํ–ˆ๋˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด์•ผ! ๐Ÿš€ ๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ, 1์ฐจ์› ์นผ๋งŒ ํ•„ํ„ฐ๋Š” ์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•ด:
typedef struct {
    float q;  // ํ”„๋กœ์„ธ์Šค ๋…ธ์ด์ฆˆ
    float r;  // ์ธก์ • ๋…ธ์ด์ฆˆ
    float x;  // ์ถ”์ •๊ฐ’
    float p;  // ์ถ”์ • ์˜ค์ฐจ
    float k;  // ์นผ๋งŒ ๊ฒŒ์ธ
} KalmanFilter;

void initKalman(KalmanFilter *kf, float q, float r, float initial) {
    kf->q = q;
    kf->r = r;
    kf->x = initial;
    kf->p = 1.0;
}

float updateKalman(KalmanFilter *kf, float measurement) {
    // ์˜ˆ์ธก ๋‹จ๊ณ„
    kf->p = kf->p + kf->q;
    
    // ์นผ๋งŒ ๊ฒŒ์ธ ๊ณ„์‚ฐ
    kf->k = kf->p / (kf->p + kf->r);
    
    // ์—…๋ฐ์ดํŠธ ๋‹จ๊ณ„
    kf->x = kf->x + kf->k * (measurement - kf->x);
    kf->p = (1 - kf->k) * kf->p;
    
    return kf->x;
}

KalmanFilter tempKalman;
KalmanFilter humidKalman;

void setup() {
    Serial.begin(9600);
    dht.begin();
    
    // ์นผ๋งŒ ํ•„ํ„ฐ ์ดˆ๊ธฐํ™”
    // (ํ”„๋กœ์„ธ์Šค ๋…ธ์ด์ฆˆ, ์ธก์ • ๋…ธ์ด์ฆˆ, ์ดˆ๊ธฐ๊ฐ’)
    initKalman(&tempKalman, 0.01, 0.1, 25.0);
    initKalman(&humidKalman, 0.01, 0.1, 50.0);
}

void loop() {
    delay(2000);
    
    float rawTemp = dht.readTemperature();
    float rawHumid = dht.readHumidity();
    
    if (!isnan(rawTemp) && !isnan(rawHumid)) {
        float kalmanTemp = updateKalman(&tempKalman, rawTemp);
        float kalmanHumid = updateKalman(&humidKalman, rawHumid);
        
        Serial.print("์นผ๋งŒ ์˜จ๋„: ");
        Serial.print(kalmanTemp);
        Serial.println("ยฐC");
        
        Serial.print("์นผ๋งŒ ์Šต๋„: ");
        Serial.print(kalmanHumid);
        Serial.println("%");
    }
}
์นผ๋งŒ ํ•„ํ„ฐ๋Š” ์ด๋™ ํ‰๊ท ๋ณด๋‹ค ๋ฐ˜์‘์ด ๋น ๋ฅด๋ฉด์„œ๋„ ๋…ธ์ด์ฆˆ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ œ๊ฑฐํ•ด์ค˜. ํŠนํžˆ ๋น ๋ฅด๊ฒŒ ๋ณ€ํ•˜๋Š” ์„ผ์„œ ๊ฐ’์„ ์ถ”์ ํ•  ๋•Œ ์œ ์šฉํ•ด! ๐ŸŽฏ
๐Ÿค” ์–ด๋–ค ํ•„ํ„ฐ๋ฅผ ์„ ํƒํ•ด์•ผ ํ• ๊นŒ?

์ด๋™ ํ‰๊ท  ํ•„ํ„ฐ: ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜๊ณ  ์•ˆ์ •์ . ์ฒœ์ฒœํžˆ ๋ณ€ํ•˜๋Š” ๊ฐ’์— ์ ํ•ฉ
์นผ๋งŒ ํ•„ํ„ฐ: ๋น ๋ฅธ ๋ฐ˜์‘๊ณผ ์ •ํ™•๋„๊ฐ€ ํ•„์š”ํ•  ๋•Œ. ์•ฝ๊ฐ„ ๋ณต์žกํ•จ
์ค‘๊ฐ„๊ฐ’ ํ•„ํ„ฐ: ๊ฐ‘์ž‘์Šค๋Ÿฌ์šด ์ŠคํŒŒ์ดํฌ ๋…ธ์ด์ฆˆ ์ œ๊ฑฐ์— ํšจ๊ณผ์ 
์ง€์ˆ˜ ์ด๋™ ํ‰๊ท : ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ด๊ณ  ์ตœ๊ทผ ๊ฐ’์— ๋” ํฐ ๊ฐ€์ค‘์น˜

๐Ÿ”— ๋‹ค์ค‘ ์„ผ์„œ ํ†ตํ•ฉ ์‹œ์Šคํ…œ

์‹ค์ œ IoT ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ํ•˜๋‚˜์˜ ์„ผ์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๊ฑฐ์˜ ์—†์–ด. ์—ฌ๋Ÿฌ ์„ผ์„œ๋ฅผ ๋™์‹œ์— ์ฝ๊ณ  ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜์ง€! ์ด์ œ DHT11, ์กฐ๋„ ์„ผ์„œ, ์ดˆ์ŒํŒŒ ์„ผ์„œ๋ฅผ ๋ชจ๋‘ ํ†ตํ•ฉํ•œ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž. ๐ŸŽจ
์„ผ์„œ ๋งค๋‹ˆ์ € ๊ตฌ์กฐ ์„ค๊ณ„
์—ฌ๋Ÿฌ ์„ผ์„œ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋ ค๋ฉด ์ฒด๊ณ„์ ์ธ ๊ตฌ์กฐ๊ฐ€ ํ•„์š”ํ•ด. ๊ฐ์ฒด์ง€ํ–ฅ์  ์‚ฌ๊ณ ๋ฅผ C์–ธ์–ด๋กœ ๊ตฌํ˜„ํ•ด๋ณด์ž!
#include "DHT.h"

// ์„ผ์„œ ํƒ€์ž… ์ •์˜
typedef enum {
    SENSOR_DHT11,
    SENSOR_LDR,
    SENSOR_ULTRASONIC,
    SENSOR_COUNT
} SensorType;

// ์„ผ์„œ ์ƒํƒœ
typedef enum {
    SENSOR_OK,
    SENSOR_ERROR,
    SENSOR_NOT_READY
} SensorStatus;

// ์„ผ์„œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ฒด
typedef struct {
    SensorType type;
    SensorStatus status;
    float value1;      // ์˜จ๋„ or ์กฐ๋„ or ๊ฑฐ๋ฆฌ
    float value2;      // ์Šต๋„ (DHT11๋งŒ ์‚ฌ์šฉ)
    unsigned long lastRead;
    unsigned int readInterval;  // ms ๋‹จ์œ„
} Sensor;

// ์„ผ์„œ ๋ฐฐ์—ด
Sensor sensors[SENSOR_COUNT];

// DHT11 ์„ค์ •
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

// ์กฐ๋„ ์„ผ์„œ ์„ค์ •
#define LDR_PIN A0

// ์ดˆ์ŒํŒŒ ์„ผ์„œ ์„ค์ •
#define TRIG_PIN 9
#define ECHO_PIN 10

// ์„ผ์„œ ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜
void initSensors() {
    // DHT11 ์ดˆ๊ธฐํ™”
    sensors[SENSOR_DHT11].type = SENSOR_DHT11;
    sensors[SENSOR_DHT11].readInterval = 2000;  // 2์ดˆ
    sensors[SENSOR_DHT11].lastRead = 0;
    sensors[SENSOR_DHT11].status = SENSOR_NOT_READY;
    dht.begin();
    
    // ์กฐ๋„ ์„ผ์„œ ์ดˆ๊ธฐํ™”
    sensors[SENSOR_LDR].type = SENSOR_LDR;
    sensors[SENSOR_LDR].readInterval = 500;  // 0.5์ดˆ
    sensors[SENSOR_LDR].lastRead = 0;
    sensors[SENSOR_LDR].status = SENSOR_OK;
    pinMode(LDR_PIN, INPUT);
    
    // ์ดˆ์ŒํŒŒ ์„ผ์„œ ์ดˆ๊ธฐํ™”
    sensors[SENSOR_ULTRASONIC].type = SENSOR_ULTRASONIC;
    sensors[SENSOR_ULTRASONIC].readInterval = 100;  // 0.1์ดˆ
    sensors[SENSOR_ULTRASONIC].lastRead = 0;
    sensors[SENSOR_ULTRASONIC].status = SENSOR_OK;
    pinMode(TRIG_PIN, OUTPUT);
    pinMode(ECHO_PIN, INPUT);
    
    Serial.println("โœ… ๋ชจ๋“  ์„ผ์„œ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ!");
}

// DHT11 ์ฝ๊ธฐ
bool readDHT11(Sensor *sensor) {
    float h = dht.readHumidity();
    float t = dht.readTemperature();
    
    if (isnan(h) || isnan(t)) {
        sensor->status = SENSOR_ERROR;
        return false;
    }
    
    sensor->value1 = t;
    sensor->value2 = h;
    sensor->status = SENSOR_OK;
    return true;
}

// ์กฐ๋„ ์„ผ์„œ ์ฝ๊ธฐ
bool readLDR(Sensor *sensor) {
    int rawValue = analogRead(LDR_PIN);
    // 0-1023์„ 0-100%๋กœ ๋ณ€ํ™˜
    sensor->value1 = map(rawValue, 0, 1023, 0, 100);
    sensor->status = SENSOR_OK;
    return true;
}

// ์ดˆ์ŒํŒŒ ์„ผ์„œ ์ฝ๊ธฐ
bool readUltrasonic(Sensor *sensor) {
    // ํŠธ๋ฆฌ๊ฑฐ ํŽ„์Šค ์ƒ์„ฑ
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    
    // ์—์ฝ” ํŽ„์Šค ์ธก์ •
    long duration = pulseIn(ECHO_PIN, HIGH, 30000);  // ํƒ€์ž„์•„์›ƒ 30ms
    
    if (duration == 0) {
        sensor->status = SENSOR_ERROR;
        return false;
    }
    
    // ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ (cm)
    float distance = duration * 0.034 / 2;
    sensor->value1 = distance;
    sensor->status = SENSOR_OK;
    return true;
}

// ์„ผ์„œ ์—…๋ฐ์ดํŠธ (๋…ผ๋ธ”๋กœํ‚น ๋ฐฉ์‹)
void updateSensors() {
    unsigned long currentTime = millis();
    
    for (int i = 0; i < SENSOR_COUNT; i++) {
        // ์ฝ๊ธฐ ๊ฐ„๊ฒฉ ์ฒดํฌ
        if (currentTime - sensors[i].lastRead >= sensors[i].readInterval) {
            bool success = false;
            
            switch (sensors[i].type) {
                case SENSOR_DHT11:
                    success = readDHT11(&sensors[i]);
                    break;
                case SENSOR_LDR:
                    success = readLDR(&sensors[i]);
                    break;
                case SENSOR_ULTRASONIC:
                    success = readUltrasonic(&sensors[i]);
                    break;
            }
            
            sensors[i].lastRead = currentTime;
            
            if (success) {
                printSensorData(&sensors[i]);
            }
        }
    }
}

// ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ
void printSensorData(Sensor *sensor) {
    Serial.print("[");
    Serial.print(millis());
    Serial.print("ms] ");
    
    switch (sensor->type) {
        case SENSOR_DHT11:
            Serial.print("๐ŸŒก๏ธ ์˜จ๋„: ");
            Serial.print(sensor->value1);
            Serial.print("ยฐC, ๐Ÿ’ง ์Šต๋„: ");
            Serial.print(sensor->value2);
            Serial.println("%");
            break;
            
        case SENSOR_LDR:
            Serial.print("๐Ÿ’ก ์กฐ๋„: ");
            Serial.print(sensor->value1);
            Serial.println("%");
            break;
            
        case SENSOR_ULTRASONIC:
            Serial.print("๐Ÿ“ ๊ฑฐ๋ฆฌ: ");
            Serial.print(sensor->value1);
            Serial.println("cm");
            break;
    }
}

void setup() {
    Serial.begin(9600);
    Serial.println("๐Ÿš€ ๋‹ค์ค‘ ์„ผ์„œ ์‹œ์Šคํ…œ ์‹œ์ž‘!");
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    
    initSensors();
}

void loop() {
    updateSensors();
    
    // ๋‹ค๋ฅธ ์ž‘์—…๋„ ์—ฌ๊ธฐ์„œ ์ˆ˜ํ–‰ ๊ฐ€๋Šฅ
    // ๋…ผ๋ธ”๋กœํ‚น ๋ฐฉ์‹์ด๋ผ ์‹œ์Šคํ…œ์ด ๋ฉˆ์ถ”์ง€ ์•Š์Œ!
}
์ด ์ฝ”๋“œ์˜ ํ•ต์‹ฌ์€ ๋…ผ๋ธ”๋กœํ‚น(Non-blocking) ๋ฐฉ์‹์ด์•ผ! ๊ฐ ์„ผ์„œ๊ฐ€ ์ž์‹ ์˜ ์ฝ๊ธฐ ๊ฐ„๊ฒฉ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ, ํ•˜๋‚˜์˜ ์„ผ์„œ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋А๋ผ ๋‹ค๋ฅธ ์„ผ์„œ๊ฐ€ ๋ฉˆ์ถ”๋Š” ์ผ์ด ์—†์–ด. ์ด๊ฒŒ ๋ฐ”๋กœ ์‹ค์ „ IoT ์‹œ์Šคํ…œ์˜ ํ•ต์‹ฌ์ด์ง€! โšก
๐Ÿ’ก Pro Tip

์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ด๋Ÿฐ ์„ผ์„œ ๋งค๋‹ˆ์ € ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ •๋ง ํŽธํ•ด! ์ƒˆ๋กœ์šด ์„ผ์„œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ๋„ ์‰ฝ๊ณ , ๊ฐ ์„ผ์„œ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ์žฌ๋Šฅ๋„ท์—์„œ ์ž„๋ฒ ๋””๋“œ ์‹œ์Šคํ…œ ์ „๋ฌธ๊ฐ€๋“ค์˜ ์กฐ์–ธ์„ ๋ฐ›์œผ๋ฉด ๋” ํšจ์œจ์ ์ธ ๊ตฌ์กฐ๋ฅผ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด!

๐Ÿ“ก ๋ฐ์ดํ„ฐ ํ†ต์‹  ํ”„๋กœํ† ์ฝœ

์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ–ˆ์œผ๋ฉด ์ด์ œ ์–ด๋”˜๊ฐ€๋กœ ๋ณด๋‚ด์•ผ๊ฒ ์ง€? IoT์˜ ํ•ต์‹ฌ์€ ๋ฐ”๋กœ **ํ†ต์‹ **์ด์•ผ! ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ํ†ต์‹  ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ, ํ•˜๋‚˜์”ฉ ์•Œ์•„๋ณด์ž. ๐Ÿ“ถ
์‹œ๋ฆฌ์–ผ ํ†ต์‹  (UART)
๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ํ†ต์‹  ๋ฐฉ๋ฒ•์ด์•ผ. ์šฐ๋ฆฌ๊ฐ€ ์ง€๊ธˆ๊นŒ์ง€ ์‚ฌ์šฉํ•œ Serial.print()๊ฐ€ ๋ฐ”๋กœ UART ํ†ต์‹ ์ด์ง€! ๊ฐ„๋‹จํ•˜์ง€๋งŒ ๊ฐ•๋ ฅํ•ด:
// JSON ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ ์ „์†ก
void sendSensorDataJSON() {
    Serial.print("{");
    Serial.print("\"temperature\":");
    Serial.print(sensors[SENSOR_DHT11].value1);
    Serial.print(",\"humidity\":");
    Serial.print(sensors[SENSOR_DHT11].value2);
    Serial.print(",\"light\":");
    Serial.print(sensors[SENSOR_LDR].value1);
    Serial.print(",\"distance\":");
    Serial.print(sensors[SENSOR_ULTRASONIC].value1);
    Serial.print(",\"timestamp\":");
    Serial.print(millis());
    Serial.println("}");
}

// ๋ฐ”์ด๋„ˆ๋ฆฌ ํ”„๋กœํ† ์ฝœ (๋” ํšจ์œจ์ )
typedef struct {
    uint8_t header;      // 0xAA (์‹œ์ž‘ ๋งˆ์ปค)
    float temperature;
    float humidity;
    float light;
    float distance;
    uint32_t timestamp;
    uint8_t checksum;
} __attribute__((packed)) DataPacket;

void sendBinaryData() {
    DataPacket packet;
    packet.header = 0xAA;
    packet.temperature = sensors[SENSOR_DHT11].value1;
    packet.humidity = sensors[SENSOR_DHT11].value2;
    packet.light = sensors[SENSOR_LDR].value1;
    packet.distance = sensors[SENSOR_ULTRASONIC].value1;
    packet.timestamp = millis();
    
    // ์ฒดํฌ์„ฌ ๊ณ„์‚ฐ (๊ฐ„๋‹จํ•œ XOR)
    uint8_t *data = (uint8_t*)&packet;
    packet.checksum = 0;
    for (int i = 0; i < sizeof(DataPacket) - 1; i++) {
        packet.checksum ^= data[i];
    }
    
    // ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ ์ „์†ก
    Serial.write((uint8_t*)&packet, sizeof(DataPacket));
}
JSON์€ ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์‰ฝ์ง€๋งŒ ๋ฐ์ดํ„ฐ ํฌ๊ธฐ๊ฐ€ ์ปค. ๋ฐ˜๋ฉด ๋ฐ”์ด๋„ˆ๋ฆฌ ํ”„๋กœํ† ์ฝœ์€ ํšจ์œจ์ ์ด์ง€๋งŒ ํŒŒ์‹ฑ์ด ๋ณต์žกํ•ด. ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•˜๋ฉด ๋ผ! ๐ŸŽฏ
WiFi ํ†ต์‹  (ESP32 ์˜ˆ์ œ)
ESP32๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด WiFi๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•  ์ˆ˜ ์žˆ์–ด. ์ง„์งœ IoT์˜ ์‹œ์ž‘์ด์ง€! ๐ŸŒ
#include <WiFi.h>
#include <HTTPClient.h>

const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";
const char* serverUrl = "http://your-server.com/api/sensor-data";

void setupWiFi() {
    Serial.print("WiFi ์—ฐ๊ฒฐ ์ค‘");
    WiFi.begin(ssid, password);
    
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    
    Serial.println("\nโœ… WiFi ์—ฐ๊ฒฐ ์™„๋ฃŒ!");
    Serial.print("IP ์ฃผ์†Œ: ");
    Serial.println(WiFi.localIP());
}

void sendDataToServer() {
    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        http.begin(serverUrl);
        http.addHeader("Content-Type", "application/json");
        
        // JSON ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
        String jsonData = "{";
        jsonData += "\"temperature\":" + String(sensors[SENSOR_DHT11].value1) + ",";
        jsonData += "\"humidity\":" + String(sensors[SENSOR_DHT11].value2) + ",";
        jsonData += "\"light\":" + String(sensors[SENSOR_LDR].value1) + ",";
        jsonData += "\"distance\":" + String(sensors[SENSOR_ULTRASONIC].value1) + ",";
        jsonData += "\"timestamp\":" + String(millis());
        jsonData += "}";
        
        // POST ์š”์ฒญ
        int httpResponseCode = http.POST(jsonData);
        
        if (httpResponseCode > 0) {
            String response = http.getString();
            Serial.print("โœ… ์„œ๋ฒ„ ์‘๋‹ต: ");
            Serial.println(response);
        } else {
            Serial.print("โŒ ์ „์†ก ์‹คํŒจ: ");
            Serial.println(httpResponseCode);
        }
        
        http.end();
    } else {
        Serial.println("โš ๏ธ WiFi ์—ฐ๊ฒฐ ๋Š๊น€!");
    }
}

// MQTT ํ”„๋กœํ† ์ฝœ (๋” ํšจ์œจ์ ์ธ IoT ํ†ต์‹ )
#include <PubSubClient.h>

WiFiClient espClient;
PubSubClient mqttClient(espClient);

const char* mqttServer = "broker.hivemq.com";
const int mqttPort = 1883;
const char* mqttTopic = "myiot/sensors";

void setupMQTT() {
    mqttClient.setServer(mqttServer, mqttPort);
    mqttClient.setCallback(mqttCallback);
}

void mqttCallback(char* topic, byte* payload, unsigned int length) {
    Serial.print("๐Ÿ“จ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ : ");
    for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);
    }
    Serial.println();
}

void reconnectMQTT() {
    while (!mqttClient.connected()) {
        Serial.print("MQTT ์—ฐ๊ฒฐ ์‹œ๋„...");
        
        if (mqttClient.connect("ESP32Client")) {
            Serial.println("โœ… ์—ฐ๊ฒฐ ์„ฑ๊ณต!");
            mqttClient.subscribe("myiot/commands");
        } else {
            Serial.print("โŒ ์‹คํŒจ, rc=");
            Serial.print(mqttClient.state());
            Serial.println(" 5์ดˆ ํ›„ ์žฌ์‹œ๋„");
            delay(5000);
        }
    }
}

void publishSensorData() {
    if (!mqttClient.connected()) {
        reconnectMQTT();
    }
    mqttClient.loop();
    
    String payload = "{";
    payload += "\"temp\":" + String(sensors[SENSOR_DHT11].value1) + ",";
    payload += "\"humid\":" + String(sensors[SENSOR_DHT11].value2);
    payload += "}";
    
    mqttClient.publish(mqttTopic, payload.c_str());
    Serial.println("๐Ÿ“ค ๋ฐ์ดํ„ฐ ๋ฐœํ–‰ ์™„๋ฃŒ!");
}
MQTT๋Š” IoT์— ์ตœ์ ํ™”๋œ ํ”„๋กœํ† ์ฝœ์ด์•ผ. HTTP๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ€๋ณ๊ณ  ํšจ์œจ์ ์ด์ง€! ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ๋„ ๊ฐ€๋Šฅํ•ด์„œ ๋ช…๋ น์„ ๋ฐ›์•„ ์•ก์ถ”์—์ดํ„ฐ๋ฅผ ์ œ์–ดํ•  ์ˆ˜๋„ ์žˆ์–ด. ๐ŸŽฎ IoT ํ†ต์‹  ํ”„๋กœํ† ์ฝœ ๋น„๊ต UART/Serial ์†๋„: 9600-115200 bps ๊ฑฐ๋ฆฌ: ์งง์Œ (์ˆ˜ ๋ฏธํ„ฐ) ์šฉ๋„: ๋””๋ฒ„๊น…, ๋กœ์ปฌ ํ†ต์‹  1 WiFi/HTTP ์†๋„: ์ˆ˜์‹ญ Mbps ๊ฑฐ๋ฆฌ: ์ค‘๊ฐ„ (์ˆ˜์‹ญ ๋ฏธํ„ฐ) ์šฉ๋„: ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ, ์›น API 2 MQTT ์†๋„: ๊ฐ€๋ณ€์  ๊ฑฐ๋ฆฌ: ์ธํ„ฐ๋„ท ์ „์—ญ ์šฉ๋„: IoT ์ตœ์ ํ™”, ์‹ค์‹œ๊ฐ„ 3 ๋ฐ์ดํ„ฐ ์ „์†ก ํšจ์œจ์„ฑ JSON 100% Binary 40% MQTT 60% CoAP 35% ์ „๋ ฅ ์†Œ๋น„ ๋น„๊ต BLE WiFi LoRa ์ €์ „๋ ฅ ๊ณ ์ „๋ ฅ ์ดˆ์ €์ „๋ ฅ

๐Ÿ’พ ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐ ๋กœ๊น… ์‹œ์Šคํ…œ

์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ํ†ต์‹ ๊นŒ์ง€ ํ–ˆ๋‹ค๋ฉด, ์ด์ œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ด์•ผ๊ฒ ์ง€? ๋‚˜์ค‘์— ๋ถ„์„ํ•˜๊ฑฐ๋‚˜ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ๋””๋ฒ„๊น…ํ•˜๋ ค๋ฉด ๋กœ๊ทธ๊ฐ€ ํ•„์ˆ˜์•ผ! ๐Ÿ“
SD ์นด๋“œ ๋ฐ์ดํ„ฐ ๋กœ๊น…
์•„๋‘์ด๋…ธ๋‚˜ ESP32์— SD ์นด๋“œ ๋ชจ๋“ˆ์„ ์—ฐ๊ฒฐํ•˜๋ฉด ๋กœ์ปฌ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์–ด. ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์ด ๋Š๊ฒจ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์žƒ์–ด๋ฒ„๋ฆฌ์ง€ ์•Š์ง€!
#include <SD.h>
#include <SPI.h>

#define SD_CS_PIN 5  // SD ์นด๋“œ ์นฉ ์…€๋ ‰ํŠธ ํ•€

File dataFile;
unsigned long lastLogTime = 0;
const unsigned long LOG_INTERVAL = 60000;  // 1๋ถ„๋งˆ๋‹ค ๋กœ๊ทธ

bool initSDCard() {
    Serial.print("SD ์นด๋“œ ์ดˆ๊ธฐํ™” ์ค‘...");
    
    if (!SD.begin(SD_CS_PIN)) {
        Serial.println("โŒ SD ์นด๋“œ ์ดˆ๊ธฐํ™” ์‹คํŒจ!");
        return false;
    }
    
    Serial.println("โœ… SD ์นด๋“œ ์ค€๋น„ ์™„๋ฃŒ!");
    
    // ๋กœ๊ทธ ํŒŒ์ผ ํ—ค๋” ์ž‘์„ฑ (ํŒŒ์ผ์ด ์—†์„ ๋•Œ๋งŒ)
    if (!SD.exists("/sensor_log.csv")) {
        dataFile = SD.open("/sensor_log.csv", FILE_WRITE);
        if (dataFile) {
            dataFile.println("Timestamp,Temperature,Humidity,Light,Distance");
            dataFile.close();
            Serial.println("๐Ÿ“„ ์ƒˆ ๋กœ๊ทธ ํŒŒ์ผ ์ƒ์„ฑ");
        }
    }
    
    return true;
}

void logSensorData() {
    unsigned long currentTime = millis();
    
    // ๋กœ๊ทธ ๊ฐ„๊ฒฉ ์ฒดํฌ
    if (currentTime - lastLogTime < LOG_INTERVAL) {
        return;
    }
    
    lastLogTime = currentTime;
    
    // CSV ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ ์ €์žฅ
    dataFile = SD.open("/sensor_log.csv", FILE_APPEND);
    
    if (dataFile) {
        dataFile.print(currentTime);
        dataFile.print(",");
        dataFile.print(sensors[SENSOR_DHT11].value1);
        dataFile.print(",");
        dataFile.print(sensors[SENSOR_DHT11].value2);
        dataFile.print(",");
        dataFile.print(sensors[SENSOR_LDR].value1);
        dataFile.print(",");
        dataFile.println(sensors[SENSOR_ULTRASONIC].value1);
        
        dataFile.close();
        
        Serial.println("๐Ÿ’พ ๋ฐ์ดํ„ฐ ์ €์žฅ ์™„๋ฃŒ!");
    } else {
        Serial.println("โŒ ํŒŒ์ผ ์—ด๊ธฐ ์‹คํŒจ!");
    }
}

// ๋กœ๊ทธ ํŒŒ์ผ ์ฝ๊ธฐ ๋ฐ ์ถœ๋ ฅ
void printLogFile() {
    dataFile = SD.open("/sensor_log.csv", FILE_READ);
    
    if (dataFile) {
        Serial.println("๐Ÿ“– ๋กœ๊ทธ ํŒŒ์ผ ๋‚ด์šฉ:");
        Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
        
        while (dataFile.available()) {
            Serial.write(dataFile.read());
        }
        
        dataFile.close();
        Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    } else {
        Serial.println("โŒ ๋กœ๊ทธ ํŒŒ์ผ ์—†์Œ!");
    }
}

// ๋กœ๊ทธ ํŒŒ์ผ ํฌ๊ธฐ ๊ด€๋ฆฌ (์ˆœํ™˜ ๋กœ๊น…)
void manageLogSize() {
    File logFile = SD.open("/sensor_log.csv", FILE_READ);
    
    if (logFile) {
        unsigned long fileSize = logFile.size();
        logFile.close();
        
        // ํŒŒ์ผ์ด 1MB๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ๋ฐฑ์—…ํ•˜๊ณ  ์ƒˆ๋กœ ์‹œ์ž‘
        if (fileSize > 1048576) {
            Serial.println("๐Ÿ“ฆ ๋กœ๊ทธ ํŒŒ์ผ ๋ฐฑ์—… ์ค‘...");
            
            // ๊ธฐ์กด ํŒŒ์ผ์„ ๋ฐฑ์—…์œผ๋กœ ์ด๋ฆ„ ๋ณ€๊ฒฝ
            SD.remove("/sensor_log_old.csv");
            SD.rename("/sensor_log.csv", "/sensor_log_old.csv");
            
            // ์ƒˆ ๋กœ๊ทธ ํŒŒ์ผ ์ƒ์„ฑ
            dataFile = SD.open("/sensor_log.csv", FILE_WRITE);
            if (dataFile) {
                dataFile.println("Timestamp,Temperature,Humidity,Light,Distance");
                dataFile.close();
                Serial.println("โœ… ์ƒˆ ๋กœ๊ทธ ํŒŒ์ผ ์‹œ์ž‘!");
            }
        }
    }
}
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์žฅ๊ธฐ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•  ์ˆ˜ ์žˆ์–ด! CSV ํ˜•์‹์ด๋ผ ์—‘์…€์ด๋‚˜ ํŒŒ์ด์ฌ์œผ๋กœ ๋ถ„์„ํ•˜๊ธฐ๋„ ์‰ฝ์ง€. ๐Ÿ“Š
EEPROM ํ™œ์šฉํ•˜๊ธฐ
์„ค์ •๊ฐ’์ด๋‚˜ ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” EEPROM์— ์ €์žฅํ•˜๋ฉด ์ „์›์ด ๊บผ์ ธ๋„ ์œ ์ง€๋ผ. ํ•˜์ง€๋งŒ ์“ฐ๊ธฐ ํšŸ์ˆ˜ ์ œํ•œ์ด ์žˆ์œผ๋‹ˆ ์ฃผ์˜ํ•ด์•ผ ํ•ด!
#include <EEPROM.h>

// EEPROM ์ฃผ์†Œ ์ •์˜
#define EEPROM_SIZE 512
#define ADDR_TEMP_THRESHOLD 0
#define ADDR_HUMID_THRESHOLD 4
#define ADDR_LOG_INTERVAL 8
#define ADDR_WIFI_SSID 12
#define ADDR_WIFI_PASS 44

// ์„ค์ • ๊ตฌ์กฐ์ฒด
typedef struct {
    float tempThreshold;
    float humidThreshold;
    unsigned long logInterval;
    char wifiSSID[32];
    char wifiPassword[32];
} SystemConfig;

SystemConfig config;

void initEEPROM() {
    EEPROM.begin(EEPROM_SIZE);
    Serial.println("๐Ÿ’พ EEPROM ์ดˆ๊ธฐํ™” ์™„๋ฃŒ");
}

// ์„ค์ • ์ €์žฅ
void saveConfig() {
    EEPROM.put(ADDR_TEMP_THRESHOLD, config.tempThreshold);
    EEPROM.put(ADDR_HUMID_THRESHOLD, config.humidThreshold);
    EEPROM.put(ADDR_LOG_INTERVAL, config.logInterval);
    
    // ๋ฌธ์ž์—ด ์ €์žฅ
    for (int i = 0; i < 32; i++) {
        EEPROM.write(ADDR_WIFI_SSID + i, config.wifiSSID[i]);
        EEPROM.write(ADDR_WIFI_PASS + i, config.wifiPassword[i]);
    }
    
    EEPROM.commit();  // ESP32์—์„œ๋Š” commit ํ•„์š”
    Serial.println("โœ… ์„ค์ • ์ €์žฅ ์™„๋ฃŒ!");
}

// ์„ค์ • ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
void loadConfig() {
    EEPROM.get(ADDR_TEMP_THRESHOLD, config.tempThreshold);
    EEPROM.get(ADDR_HUMID_THRESHOLD, config.humidThreshold);
    EEPROM.get(ADDR_LOG_INTERVAL, config.logInterval);
    
    // ๋ฌธ์ž์—ด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
    for (int i = 0; i < 32; i++) {
        config.wifiSSID[i] = EEPROM.read(ADDR_WIFI_SSID + i);
        config.wifiPassword[i] = EEPROM.read(ADDR_WIFI_PASS + i);
    }
    
    Serial.println("โœ… ์„ค์ • ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์™„๋ฃŒ!");
    printConfig();
}

void printConfig() {
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    Serial.println("โš™๏ธ ํ˜„์žฌ ์„ค์ •:");
    Serial.print("์˜จ๋„ ์ž„๊ณ„๊ฐ’: ");
    Serial.print(config.tempThreshold);
    Serial.println("ยฐC");
    Serial.print("์Šต๋„ ์ž„๊ณ„๊ฐ’: ");
    Serial.print(config.humidThreshold);
    Serial.println("%");
    Serial.print("๋กœ๊ทธ ๊ฐ„๊ฒฉ: ");
    Serial.print(config.logInterval / 1000);
    Serial.println("์ดˆ");
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
}
โš ๏ธ EEPROM ์ฃผ์˜์‚ฌํ•ญ

EEPROM์€ ์“ฐ๊ธฐ ํšŸ์ˆ˜๊ฐ€ ์ œํ•œ๋˜์–ด ์žˆ์–ด (๋ณดํ†ต 10๋งŒ~100๋งŒ ํšŒ). ์ž์ฃผ ๋ณ€ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋Š” RAM์— ์ €์žฅํ•˜๊ณ , ์ •๋ง ํ•„์š”ํ•  ๋•Œ๋งŒ EEPROM์— ์“ฐ์ž!

๐ŸŽจ ๊ณ ๊ธ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ธฐ๋ฒ•

์ด์ œ ์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ์ˆ ์„ ๋ฐฐ์›Œ๋ณผ๊นŒ? ์‹ค์ œ ์‚ฐ์—… ํ˜„์žฅ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ธฐ๋ฒ•๋“ค์ด์•ผ! ๐Ÿš€
์ด์ƒ์น˜ ํƒ์ง€ (Outlier Detection)
์„ผ์„œ๊ฐ€ ๊ฐ€๋” ์ด์ƒํ•œ ๊ฐ’์„ ๋ฑ‰์„ ๋•Œ๊ฐ€ ์žˆ์–ด. ์ด๋Ÿฐ ์ด์ƒ์น˜๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ์ œ๊ฑฐํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๋งŒ๋“ค์–ด๋ณด์ž!
#define HISTORY_SIZE 20

typedef struct {
    float values[HISTORY_SIZE];
    int count;
    int index;
} DataHistory;

DataHistory tempHistory;

void initHistory(DataHistory *hist) {
    hist->count = 0;
    hist->index = 0;
    for (int i = 0; i < HISTORY_SIZE; i++) {
        hist->values[i] = 0.0;
    }
}

void addToHistory(DataHistory *hist, float value) {
    hist->values[hist->index] = value;
    hist->index = (hist->index + 1) % HISTORY_SIZE;
    if (hist->count < HISTORY_SIZE) {
        hist->count++;
    }
}

// ํ‰๊ท  ๊ณ„์‚ฐ
float calculateMean(DataHistory *hist) {
    if (hist->count == 0) return 0.0;
    
    float sum = 0.0;
    for (int i = 0; i < hist->count; i++) {
        sum += hist->values[i];
    }
    return sum / hist->count;
}

// ํ‘œ์ค€ํŽธ์ฐจ ๊ณ„์‚ฐ
float calculateStdDev(DataHistory *hist) {
    if (hist->count < 2) return 0.0;
    
    float mean = calculateMean(hist);
    float sumSquares = 0.0;
    
    for (int i = 0; i < hist->count; i++) {
        float diff = hist->values[i] - mean;
        sumSquares += diff * diff;
    }
    
    return sqrt(sumSquares / (hist->count - 1));
}

// ์ด์ƒ์น˜ ํŒ๋‹จ (Z-score ๋ฐฉ๋ฒ•)
bool isOutlier(DataHistory *hist, float newValue, float threshold) {
    if (hist->count < 3) return false;  // ๋ฐ์ดํ„ฐ๊ฐ€ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Œ
    
    float mean = calculateMean(hist);
    float stdDev = calculateStdDev(hist);
    
    if (stdDev < 0.01) return false;  // ํ‘œ์ค€ํŽธ์ฐจ๊ฐ€ ๋„ˆ๋ฌด ์ž‘์Œ
    
    // Z-score ๊ณ„์‚ฐ
    float zScore = abs(newValue - mean) / stdDev;
    
    // ์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ ์‹œ ์ด์ƒ์น˜๋กœ ํŒ๋‹จ
    if (zScore > threshold) {
        Serial.print("๐Ÿšจ ์ด์ƒ์น˜ ๊ฐ์ง€! ๊ฐ’: ");
        Serial.print(newValue);
        Serial.print(", Z-score: ");
        Serial.println(zScore);
        return true;
    }
    
    return false;
}

// ์ด์ƒ์น˜ ์ œ๊ฑฐ ํ›„ ๊ฐ’ ๋ฐ˜ํ™˜
float getFilteredValue(DataHistory *hist, float newValue) {
    // ์ด์ƒ์น˜ ํŒ๋‹จ (Z-score > 3์ด๋ฉด ์ด์ƒ์น˜)
    if (isOutlier(hist, newValue, 3.0)) {
        // ์ด์ƒ์น˜๋ฉด ์ด์ „ ํ‰๊ท ๊ฐ’ ๋ฐ˜ํ™˜
        Serial.println("โš ๏ธ ์ด์ƒ์น˜ ์ œ๊ฑฐ, ํ‰๊ท ๊ฐ’ ์‚ฌ์šฉ");
        return calculateMean(hist);
    }
    
    // ์ •์ƒ ๊ฐ’์ด๋ฉด ํžˆ์Šคํ† ๋ฆฌ์— ์ถ”๊ฐ€ํ•˜๊ณ  ๋ฐ˜ํ™˜
    addToHistory(hist, newValue);
    return newValue;
}

void loop() {
    delay(2000);
    
    float rawTemp = dht.readTemperature();
    
    if (!isnan(rawTemp)) {
        // ์ด์ƒ์น˜ ํ•„ํ„ฐ๋ง ์ ์šฉ
        float filteredTemp = getFilteredValue(&tempHistory, rawTemp);
        
        Serial.print("์›๋ณธ: ");
        Serial.print(rawTemp);
        Serial.print("ยฐC โ†’ ํ•„ํ„ฐ๋ง: ");
        Serial.print(filteredTemp);
        Serial.println("ยฐC");
    }
}
์ด ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ํ†ต๊ณ„์  ๋ฐฉ๋ฒ•์œผ๋กœ ์ด์ƒ์น˜๋ฅผ ๊ฐ์ง€ํ•ด. Z-score๊ฐ€ 3์„ ๋„˜์œผ๋ฉด 99.7% ํ™•๋ฅ ๋กœ ์ด์ƒ์น˜๋ผ๊ณ  ํŒ๋‹จํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ“ˆ
์˜ˆ์ธก ์•Œ๊ณ ๋ฆฌ์ฆ˜ (Simple Linear Regression)
๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋ฏธ๋ž˜ ๊ฐ’์„ ์˜ˆ์ธกํ•  ์ˆ˜๋„ ์žˆ์–ด! ๊ฐ„๋‹จํ•œ ์„ ํ˜• ํšŒ๊ท€๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž:
typedef struct {
    float slope;
    float intercept;
    float correlation;
} RegressionModel;

// ์„ ํ˜• ํšŒ๊ท€ ๊ณ„์‚ฐ
RegressionModel calculateRegression(DataHistory *hist) {
    RegressionModel model = {0.0, 0.0, 0.0};
    
    if (hist->count < 2) return model;
    
    float sumX = 0.0, sumY = 0.0, sumXY = 0.0, sumX2 = 0.0;
    
    for (int i = 0; i < hist->count; i++) {
        float x = (float)i;
        float y = hist->values[i];
        
        sumX += x;
        sumY += y;
        sumXY += x * y;
        sumX2 += x * x;
    }
    
    int n = hist->count;
    
    // ๊ธฐ์šธ๊ธฐ ๊ณ„์‚ฐ
    model.slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
    
    // ์ ˆํŽธ ๊ณ„์‚ฐ
    model.intercept = (sumY - model.slope * sumX) / n;
    
    // ์ƒ๊ด€๊ณ„์ˆ˜ ๊ณ„์‚ฐ
    float meanX = sumX / n;
    float meanY = sumY / n;
    float sumXDiff2 = 0.0, sumYDiff2 = 0.0, sumXYDiff = 0.0;
    
    for (int i = 0; i < n; i++) {
        float xDiff = i - meanX;
        float yDiff = hist->values[i] - meanY;
        sumXDiff2 += xDiff * xDiff;
        sumYDiff2 += yDiff * yDiff;
        sumXYDiff += xDiff * yDiff;
    }
    
    model.correlation = sumXYDiff / sqrt(sumXDiff2 * sumYDiff2);
    
    return model;
}

// ๋ฏธ๋ž˜ ๊ฐ’ ์˜ˆ์ธก
float predictValue(RegressionModel *model, int stepsAhead) {
    int futureX = HISTORY_SIZE + stepsAhead;
    return model->slope * futureX + model->intercept;
}

void analyzeTemperatureTrend() {
    RegressionModel model = calculateRegression(&tempHistory);
    
    Serial.println("๐Ÿ“Š ์˜จ๋„ ํŠธ๋ Œ๋“œ ๋ถ„์„:");
    Serial.print("๊ธฐ์šธ๊ธฐ: ");
    Serial.println(model.slope, 4);
    Serial.print("์ƒ๊ด€๊ณ„์ˆ˜: ");
    Serial.println(model.correlation, 4);
    
    if (model.slope > 0.1) {
        Serial.println("๐Ÿ“ˆ ์˜จ๋„ ์ƒ์Šน ์ถ”์„ธ!");
    } else if (model.slope < -0.1) {
        Serial.println("๐Ÿ“‰ ์˜จ๋„ ํ•˜๊ฐ• ์ถ”์„ธ!");
    } else {
        Serial.println("โžก๏ธ ์˜จ๋„ ์•ˆ์ •์ ");
    }
    
    // 10๋ถ„ ํ›„ ์˜ˆ์ธก
    float prediction = predictValue(&model, 5);  // 5๊ฐœ ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ ํ›„
    Serial.print("๐Ÿ”ฎ ์˜ˆ์ƒ ์˜จ๋„ (10๋ถ„ ํ›„): ");
    Serial.print(prediction);
    Serial.println("ยฐC");
}
์ด๋Ÿฐ ์˜ˆ์ธก ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์˜จ๋„๊ฐ€ ์ž„๊ณ„๊ฐ’์„ ๋„˜๊ธฐ ์ „์— ๋ฏธ๋ฆฌ ๊ฒฝ๊ณ ๋ฅผ ์ค„ ์ˆ˜ ์žˆ์–ด! ์ •๋ง ์Šค๋งˆํŠธํ•˜์ง€? ๐Ÿง 

โšก ์ „๋ ฅ ๊ด€๋ฆฌ ๋ฐ ์ตœ์ ํ™”

IoT ๊ธฐ๊ธฐ๋Š” ๋Œ€๋ถ€๋ถ„ ๋ฐฐํ„ฐ๋ฆฌ๋กœ ๋™์ž‘ํ•˜์ž–์•„? ๊ทธ๋ž˜์„œ ์ „๋ ฅ ๊ด€๋ฆฌ๊ฐ€ ์ •๋ง ์ค‘์š”ํ•ด! ํšจ์œจ์ ์ธ ์ „๋ ฅ ๊ด€๋ฆฌ ๊ธฐ๋ฒ•์„ ์•Œ์•„๋ณด์ž. ๐Ÿ”‹
์Šฌ๋ฆฝ ๋ชจ๋“œ ํ™œ์šฉํ•˜๊ธฐ
์„ผ์„œ๋ฅผ ๊ณ„์† ์ฝ์„ ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด ๋งˆ์ดํฌ๋กœ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์Šฌ๋ฆฝ ๋ชจ๋“œ๋กœ ๋ณด๋‚ด์ž. ์ „๋ ฅ ์†Œ๋น„๋ฅผ 90% ์ด์ƒ ์ค„์ผ ์ˆ˜ ์žˆ์–ด!
#include <esp_sleep.h>  // ESP32์šฉ

#define SLEEP_DURATION 60  // ์ดˆ ๋‹จ์œ„

// ๋”ฅ ์Šฌ๋ฆฝ ์„ค์ •
void setupDeepSleep() {
    // ํƒ€์ด๋จธ๋กœ ๊นจ์–ด๋‚˜๊ธฐ ์„ค์ •
    esp_sleep_enable_timer_wakeup(SLEEP_DURATION * 1000000ULL);
    
    Serial.println("๐Ÿ’ค ๋”ฅ ์Šฌ๋ฆฝ ๋ชจ๋“œ ์ง„์ž…...");
    Serial.flush();  // ์‹œ๋ฆฌ์–ผ ๋ฒ„ํผ ๋น„์šฐ๊ธฐ
    
    // ๋”ฅ ์Šฌ๋ฆฝ ์ง„์ž…
    esp_deep_sleep_start();
}

// ๋ผ์ดํŠธ ์Šฌ๋ฆฝ (๋” ๋น ๋ฅธ ๊นจ์–ด๋‚จ)
void lightSleep(int seconds) {
    Serial.println("๐Ÿ˜ด ๋ผ์ดํŠธ ์Šฌ๋ฆฝ ๋ชจ๋“œ...");
    
    esp_sleep_enable_timer_wakeup(seconds * 1000000ULL);
    esp_light_sleep_start();
    
    Serial.println("๐Ÿ‘€ ๊นจ์–ด๋‚จ!");
}

// ์„ผ์„œ ์ฝ๊ธฐ โ†’ ์ „์†ก โ†’ ์Šฌ๋ฆฝ ์‚ฌ์ดํด
void efficientDataCollection() {
    // 1. ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ
    float temp = dht.readTemperature();
    float humid = dht.readHumidity();
    
    // 2. ๋ฐ์ดํ„ฐ ์ „์†ก
    if (!isnan(temp) && !isnan(humid)) {
        sendDataToServer();  // WiFi๋กœ ์ „์†ก
    }
    
    // 3. WiFi ๋„๊ธฐ (์ „๋ ฅ ์ ˆ์•ฝ)
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    
    // 4. ๋”ฅ ์Šฌ๋ฆฝ (1๋ถ„๊ฐ„)
    setupDeepSleep();
}

// ๊นจ์–ด๋‚ฌ์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ
void setup() {
    Serial.begin(115200);
    
    // ๊นจ์–ด๋‚œ ์ด์œ  ํ™•์ธ
    esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
    
    switch(wakeup_reason) {
        case ESP_SLEEP_WAKEUP_TIMER:
            Serial.println("โฐ ํƒ€์ด๋จธ๋กœ ๊นจ์–ด๋‚จ");
            break;
        case ESP_SLEEP_WAKEUP_EXT0:
            Serial.println("๐Ÿ”” ์™ธ๋ถ€ ์ธํ„ฐ๋ŸฝํŠธ๋กœ ๊นจ์–ด๋‚จ");
            break;
        default:
            Serial.println("๐Ÿ”Œ ์ „์› ์ผœ์ง");
            break;
    }
    
    // WiFi ์—ฐ๊ฒฐ
    setupWiFi();
    
    // ์„ผ์„œ ์ดˆ๊ธฐํ™”
    dht.begin();
}

void loop() {
    efficientDataCollection();
    // ์ด ์ฝ”๋“œ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š์Œ (๋”ฅ ์Šฌ๋ฆฝ ํ›„ ์žฌ๋ถ€ํŒ…)
}
๋”ฅ ์Šฌ๋ฆฝ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐฐํ„ฐ๋ฆฌ ์ˆ˜๋ช…์ด ๋ช‡ ์ฃผ์—์„œ ๋ช‡ ๋‹ฌ๋กœ ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ์–ด! ํ•˜์ง€๋งŒ ๊นจ์–ด๋‚  ๋•Œ๋งˆ๋‹ค ์žฌ๋ถ€ํŒ…๋˜๋‹ˆ ์ฃผ์˜ํ•ด์•ผ ํ•ด. ๐Ÿ’ก
๋™์  ์ „๋ ฅ ๊ด€๋ฆฌ
์ƒํ™ฉ์— ๋”ฐ๋ผ ์„ผ์„œ ์ฝ๊ธฐ ์ฃผ๊ธฐ๋ฅผ ์กฐ์ ˆํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ:
typedef enum {
    POWER_MODE_NORMAL,
    POWER_MODE_ECO,
    POWER_MODE_ULTRA_ECO
} PowerMode;

PowerMode currentPowerMode = POWER_MODE_NORMAL;
float batteryVoltage = 4.2;  // ๋ฆฌํŠฌ ๋ฐฐํ„ฐ๋ฆฌ ์ „์••

// ๋ฐฐํ„ฐ๋ฆฌ ์ „์•• ์ฝ๊ธฐ (ESP32 ADC ์‚ฌ์šฉ)
float readBatteryVoltage() {
    int adcValue = analogRead(35);  // ๋ฐฐํ„ฐ๋ฆฌ ์ „์•• ๋ถ„๋ฐฐ ํšŒ๋กœ ์—ฐ๊ฒฐ
    // ADC ๊ฐ’์„ ์ „์••์œผ๋กœ ๋ณ€ํ™˜ (๋ณด์ • ํ•„์š”)
    return (adcValue / 4095.0) * 3.3 * 2.0;  // ์ „์•• ๋ถ„๋ฐฐ ๋น„์œจ 2:1
}

// ์ „๋ ฅ ๋ชจ๋“œ ๊ฒฐ์ •
void updatePowerMode() {
    batteryVoltage = readBatteryVoltage();
    
    if (batteryVoltage > 3.8) {
        currentPowerMode = POWER_MODE_NORMAL;
        Serial.println("๐Ÿ”‹ ๋ฐฐํ„ฐ๋ฆฌ ์ถฉ๋ถ„ - ์ผ๋ฐ˜ ๋ชจ๋“œ");
    } else if (batteryVoltage > 3.5) {
        currentPowerMode = POWER_MODE_ECO;
        Serial.println("๐Ÿ”‹ ๋ฐฐํ„ฐ๋ฆฌ ๋ณดํ†ต - ์ ˆ์•ฝ ๋ชจ๋“œ");
    } else {
        currentPowerMode = POWER_MODE_ULTRA_ECO;
        Serial.println("๐Ÿ”‹ ๋ฐฐํ„ฐ๋ฆฌ ๋ถ€์กฑ - ์ดˆ์ ˆ์•ฝ ๋ชจ๋“œ");
    }
}

// ๋ชจ๋“œ์— ๋”ฐ๋ฅธ ์„ผ์„œ ์ฝ๊ธฐ ๊ฐ„๊ฒฉ
unsigned long getSensorInterval() {
    switch (currentPowerMode) {
        case POWER_MODE_NORMAL:
            return 10000;   // 10์ดˆ
        case POWER_MODE_ECO:
            return 60000;   // 1๋ถ„
        case POWER_MODE_ULTRA_ECO:
            return 300000;  // 5๋ถ„
        default:
            return 10000;
    }
}

// ๋ชจ๋“œ์— ๋”ฐ๋ฅธ WiFi ์‚ฌ์šฉ ๊ฒฐ์ •
bool shouldUseWiFi() {
    switch (currentPowerMode) {
        case POWER_MODE_NORMAL:
            return true;    // ํ•ญ์ƒ ์ „์†ก
        case POWER_MODE_ECO:
            return (millis() % 300000 < 10000);  // 5๋ถ„๋งˆ๋‹ค ์ „์†ก
        case POWER_MODE_ULTRA_ECO:
            return false;   // SD ์นด๋“œ์—๋งŒ ์ €์žฅ
        default:
            return true;
    }
}

void smartDataCollection() {
    static unsigned long lastSensorRead = 0;
    unsigned long currentTime = millis();
    
    // ์ „๋ ฅ ๋ชจ๋“œ ์—…๋ฐ์ดํŠธ (10๋ถ„๋งˆ๋‹ค)
    if (currentTime % 600000 < 1000) {
        updatePowerMode();
    }
    
    // ์„ผ์„œ ์ฝ๊ธฐ ๊ฐ„๊ฒฉ ์ฒดํฌ
    if (currentTime - lastSensorRead >= getSensorInterval()) {
        lastSensorRead = currentTime;
        
        // ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ
        float temp = dht.readTemperature();
        float humid = dht.readHumidity();
        
        // SD ์นด๋“œ์— ์ €์žฅ (ํ•ญ์ƒ)
        logSensorData();
        
        // WiFi ์ „์†ก (๋ชจ๋“œ์— ๋”ฐ๋ผ)
        if (shouldUseWiFi()) {
            sendDataToServer();
        }
    }
}
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฐฐํ„ฐ๋ฆฌ ์ƒํƒœ์— ๋”ฐ๋ผ ์ž๋™์œผ๋กœ ์ „๋ ฅ ์†Œ๋น„๋ฅผ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ์–ด! ์ •๋ง ์Šค๋งˆํŠธํ•˜์ง€? ๐Ÿค–
๐Ÿ’ก ์ „๋ ฅ ์ ˆ์•ฝ ํŒ ๋ชจ์Œ

โ€ข LED ์‚ฌ์šฉ ์ตœ์†Œํ™” (์˜์™ธ๋กœ ์ „๋ ฅ ๋งŽ์ด ๋จน์Œ)
โ€ข ์„ผ์„œ ์ „์›์„ ํŠธ๋žœ์ง€์Šคํ„ฐ๋กœ ์ œ์–ด (ํ•„์š”ํ•  ๋•Œ๋งŒ ์ผœ๊ธฐ)
โ€ข WiFi ์ „์†ก ํšŸ์ˆ˜ ์ค„์ด๊ธฐ (๋ฐฐ์น˜ ์ „์†ก)
โ€ข ๋‚ฎ์€ ํด๋Ÿญ ์†๋„ ์‚ฌ์šฉ (์„ฑ๋Šฅ vs ์ „๋ ฅ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„)
โ€ข ํšจ์œจ์ ์ธ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‚ฌ์šฉ (CPU ์‚ฌ์šฉ ์‹œ๊ฐ„ ๋‹จ์ถ•)

๐Ÿ›ก๏ธ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ

์‹ค์ „ IoT ์‹œ์Šคํ…œ์—์„œ๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๊ฐ€ ํ•ญ์ƒ ๋ฐœ์ƒํ•ด. ๊ฒฌ๊ณ ํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์ˆ˜์•ผ! ๐Ÿ› ๏ธ
Watchdog Timer ๊ตฌํ˜„
์‹œ์Šคํ…œ์ด ๋ฉˆ์ท„์„ ๋•Œ ์ž๋™์œผ๋กœ ์žฌ์‹œ์ž‘ํ•˜๋Š” ์›Œ์น˜๋… ํƒ€์ด๋จธ๋ฅผ ์„ค์ •ํ•˜์ž:
#include <esp_task_wdt.h>

#define WDT_TIMEOUT 30  // 30์ดˆ

void setupWatchdog() {
    Serial.println("๐Ÿ• ์›Œ์น˜๋… ํƒ€์ด๋จธ ์„ค์ •");
    
    // ์›Œ์น˜๋… ์ดˆ๊ธฐํ™”
    esp_task_wdt_init(WDT_TIMEOUT, true);
    esp_task_wdt_add(NULL);  // ํ˜„์žฌ ํƒœ์Šคํฌ ์ถ”๊ฐ€
}

void feedWatchdog() {
    // ์›Œ์น˜๋…์—๊ฒŒ "๋‚˜ ์‚ด์•„์žˆ์–ด!" ์‹ ํ˜ธ ๋ณด๋‚ด๊ธฐ
    esp_task_wdt_reset();
}

void loop() {
    // ์„ผ์„œ ์ฝ๊ธฐ
    updateSensors();
    
    // ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
    processData();
    
    // ์›Œ์น˜๋… ๋ฆฌ์…‹ (์ •์ƒ ๋™์ž‘ ์ค‘์ž„์„ ์•Œ๋ฆผ)
    feedWatchdog();
    
    delay(1000);
}
์žฌ์‹œ๋„ ๋กœ์ง ๊ตฌํ˜„
WiFi ์—ฐ๊ฒฐ์ด๋‚˜ ์„ผ์„œ ์ฝ๊ธฐ๊ฐ€ ์‹คํŒจํ–ˆ์„ ๋•Œ ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„ํ•˜๋Š” ๋กœ์ง:
typedef struct {
    int maxRetries;
    int currentRetry;
    unsigned long retryDelay;
    unsigned long lastAttempt;
} RetryHandler;

bool readSensorWithRetry(RetryHandler *retry) {
    unsigned long currentTime = millis();
    
    // ์žฌ์‹œ๋„ ๊ฐ„๊ฒฉ ์ฒดํฌ
    if (currentTime - retry->lastAttempt < retry->retryDelay) {
        return false;
    }
    
    retry->lastAttempt = currentTime;
    
    // ์„ผ์„œ ์ฝ๊ธฐ ์‹œ๋„
    float temp = dht.readTemperature();
    
    if (isnan(temp)) {
        retry->currentRetry++;
        
        Serial.print("โŒ ์„ผ์„œ ์ฝ๊ธฐ ์‹คํŒจ (");
        Serial.print(retry->currentRetry);
        Serial.print("/");
        Serial.print(retry->maxRetries);
        Serial.println(")");
        
        if (retry->currentRetry >= retry->maxRetries) {
            Serial.println("๐Ÿšจ ์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ!");
            // ์„ผ์„œ ์žฌ์ดˆ๊ธฐํ™” ์‹œ๋„
            dht.begin();
            retry->currentRetry = 0;
            retry->retryDelay *= 2;  // ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„
        }
        
        return false;
    }
    
    // ์„ฑ๊ณต!
    retry->currentRetry = 0;
    retry->retryDelay = 1000;  // ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ๋ฆฌ์…‹
    return true;
}

// WiFi ์žฌ์—ฐ๊ฒฐ ๋กœ์ง
bool reconnectWiFi(int maxAttempts) {
    int attempts = 0;
    
    while (WiFi.status() != WL_CONNECTED && attempts < maxAttempts) {
        attempts++;
        
        Serial.print("๐Ÿ”„ WiFi ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ ");
        Serial.print(attempts);
        Serial.print("/");
        Serial.println(maxAttempts);
        
        WiFi.disconnect();
        delay(1000);
        WiFi.begin(ssid, password);
        
        int timeout = 0;
        while (WiFi.status() != WL_CONNECTED && timeout < 20) {
            delay(500);
            Serial.print(".");
            timeout++;
        }
        
        if (WiFi.status() == WL_CONNECTED) {
            Serial.println("\nโœ… WiFi ์žฌ์—ฐ๊ฒฐ ์„ฑ๊ณต!");
            return true;
        }
    }
    
    Serial.println("\nโŒ WiFi ์žฌ์—ฐ๊ฒฐ ์‹คํŒจ");
    return false;
}
์‹œ์Šคํ…œ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง
์‹œ์Šคํ…œ์˜ ๊ฑด๊ฐ• ์ƒํƒœ๋ฅผ ์ง€์†์ ์œผ๋กœ ์ฒดํฌํ•˜๋Š” ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ:
typedef struct {
    unsigned long uptime;
    int wifiReconnects;
    int sensorErrors;
    int dataTransmissions;
    int failedTransmissions;
    float avgLoopTime;
    unsigned long freeHeap;
} SystemStats;

SystemStats stats = {0};

void updateSystemStats() {
    static unsigned long lastUpdate = 0;
    static unsigned long loopStartTime = 0;
    static int loopCount = 0;
    
    unsigned long currentTime = millis();
    
    // ๋ฃจํ”„ ์‹œ๊ฐ„ ์ธก์ •
    if (loopStartTime > 0) {
        unsigned long loopTime = currentTime - loopStartTime;
        stats.avgLoopTime = (stats.avgLoopTime * loopCount + loopTime) / (loopCount + 1);
        loopCount++;
    }
    loopStartTime = currentTime;
    
    // 1๋ถ„๋งˆ๋‹ค ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
    if (currentTime - lastUpdate >= 60000) {
        lastUpdate = currentTime;
        
        stats.uptime = currentTime / 1000;  // ์ดˆ ๋‹จ์œ„
        stats.freeHeap = ESP.getFreeHeap();
        
        printSystemStats();
    }
}

void printSystemStats() {
    Serial.println("\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    Serial.println("๐Ÿ“Š ์‹œ์Šคํ…œ ํ†ต๊ณ„");
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    
    // ๊ฐ€๋™ ์‹œ๊ฐ„
    unsigned long hours = stats.uptime / 3600;
    unsigned long minutes = (stats.uptime % 3600) / 60;
    Serial.print("โฑ๏ธ  ๊ฐ€๋™ ์‹œ๊ฐ„: ");
    Serial.print(hours);
    Serial.print("์‹œ๊ฐ„ ");
    Serial.print(minutes);
    Serial.println("๋ถ„");
    
    // WiFi ํ†ต๊ณ„
    Serial.print("๐Ÿ“ก WiFi ์žฌ์—ฐ๊ฒฐ: ");
    Serial.print(stats.wifiReconnects);
    Serial.println("ํšŒ");
    
    // ์„ผ์„œ ํ†ต๊ณ„
    Serial.print("๐ŸŒก๏ธ  ์„ผ์„œ ์—๋Ÿฌ: ");
    Serial.print(stats.sensorErrors);
    Serial.println("ํšŒ");
    
    // ์ „์†ก ํ†ต๊ณ„
    Serial.print("๐Ÿ“ค ๋ฐ์ดํ„ฐ ์ „์†ก: ");
    Serial.print(stats.dataTransmissions);
    Serial.print("ํšŒ (์‹คํŒจ: ");
    Serial.print(stats.failedTransmissions);
    Serial.println("ํšŒ)");
    
    float successRate = 100.0 * (stats.dataTransmissions - stats.failedTransmissions) / 
                        max(stats.dataTransmissions, 1);
    Serial.print("โœ… ์„ฑ๊ณต๋ฅ : ");
    Serial.print(successRate, 1);
    Serial.println("%");
    
    // ์„ฑ๋Šฅ ํ†ต๊ณ„
    Serial.print("โšก ํ‰๊ท  ๋ฃจํ”„ ์‹œ๊ฐ„: ");
    Serial.print(stats.avgLoopTime);
    Serial.println("ms");
    
    // ๋ฉ”๋ชจ๋ฆฌ ํ†ต๊ณ„
    Serial.print("๐Ÿ’พ ์—ฌ์œ  ๋ฉ”๋ชจ๋ฆฌ: ");
    Serial.print(stats.freeHeap);
    Serial.println(" bytes");
    
    // ๋ฐฐํ„ฐ๋ฆฌ ์ƒํƒœ
    Serial.print("๐Ÿ”‹ ๋ฐฐํ„ฐ๋ฆฌ ์ „์••: ");
    Serial.print(batteryVoltage, 2);
    Serial.println("V");
    
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n");
}

// ์‹œ์Šคํ…œ ๊ฑด๊ฐ• ์ฒดํฌ
bool isSystemHealthy() {
    bool healthy = true;
    
    // ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ์ฒดํฌ
    if (stats.freeHeap < 10000) {
        Serial.println("โš ๏ธ ๊ฒฝ๊ณ : ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ!");
        healthy = false;
    }
    
    // ์„ผ์„œ ์—๋Ÿฌ์œจ ์ฒดํฌ
    if (stats.sensorErrors > 100) {
        Serial.println("โš ๏ธ ๊ฒฝ๊ณ : ์„ผ์„œ ์—๋Ÿฌ ๊ณผ๋‹ค!");
        healthy = false;
    }
    
    // ์ „์†ก ์‹คํŒจ์œจ ์ฒดํฌ
    float failRate = 100.0 * stats.failedTransmissions / max(stats.dataTransmissions, 1);
    if (failRate > 50.0) {
        Serial.println("โš ๏ธ ๊ฒฝ๊ณ : ๋ฐ์ดํ„ฐ ์ „์†ก ์‹คํŒจ์œจ ๋†’์Œ!");
        healthy = false;
    }
    
    // ๋ฐฐํ„ฐ๋ฆฌ ์ฒดํฌ
    if (batteryVoltage < 3.3) {
        Serial.println("๐Ÿ”ด ๊ฒฝ๊ณ : ๋ฐฐํ„ฐ๋ฆฌ ์œ„ํ—˜ ์ˆ˜์ค€!");
        healthy = false;
    }
    
    return healthy;
}
์ด๋Ÿฐ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ์ด ์žˆ์œผ๋ฉด ๋ฌธ์ œ๋ฅผ ์กฐ๊ธฐ์— ๋ฐœ๊ฒฌํ•˜๊ณ  ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽฏ

๐ŸŒ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์Šค๋งˆํŠธ ํ™˜๊ฒฝ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ

์ž, ์ด์ œ ์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋ชจ๋“  ๊ฑธ ์ข…ํ•ฉํ•ด์„œ ์™„์ „ํ•œ IoT ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž! ์ด๊ฑด ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์˜ ํ”„๋กœ์ ํŠธ์•ผ. ๐Ÿ†
์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜
์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค ์‹œ์Šคํ…œ์€ ์ด๋Ÿฐ ๊ตฌ์กฐ์•ผ:

**ํ•˜๋“œ์›จ์–ด ๋ ˆ์ด์–ด**
โ€ข ESP32 ๋ฉ”์ธ ๋ณด๋“œ
โ€ข DHT22 ์˜จ์Šต๋„ ์„ผ์„œ
โ€ข BH1750 ์กฐ๋„ ์„ผ์„œ
โ€ข MQ-135 ๊ณต๊ธฐ์งˆ ์„ผ์„œ
โ€ข OLED ๋””์Šคํ”Œ๋ ˆ์ด
โ€ข SD ์นด๋“œ ๋ชจ๋“ˆ
โ€ข ๋ฐฐํ„ฐ๋ฆฌ + ์ถฉ์ „ ํšŒ๋กœ

**์†Œํ”„ํŠธ์›จ์–ด ๋ ˆ์ด์–ด**
โ€ข ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ชจ๋“ˆ
โ€ข ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง ๋ฐ ์ฒ˜๋ฆฌ
โ€ข ๋กœ์ปฌ ์ €์žฅ (SD ์นด๋“œ)
โ€ข WiFi ํ†ต์‹  (MQTT)
โ€ข ์›น ์„œ๋ฒ„ (์„ค์ • ํŽ˜์ด์ง€)
โ€ข ์ „๋ ฅ ๊ด€๋ฆฌ
โ€ข ์—๋Ÿฌ ์ฒ˜๋ฆฌ
// ์™„์ „ํ•œ ์Šค๋งˆํŠธ ํ™˜๊ฒฝ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#include <BH1750.h>
#include <SD.h>
#include <ArduinoJson.h>

// ========== ํ•€ ์ •์˜ ==========
#define DHT_PIN 4
#define DHT_TYPE DHT22
#define MQ135_PIN 34
#define SD_CS_PIN 5
#define BATTERY_PIN 35

// ========== ๋””์Šคํ”Œ๋ ˆ์ด ์„ค์ • ==========
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// ========== ์„ผ์„œ ๊ฐ์ฒด ==========
DHT dht(DHT_PIN, DHT_TYPE);
BH1750 lightMeter;

// ========== WiFi & MQTT ์„ค์ • ==========
const char* ssid = "YourWiFiSSID";
const char* password = "YourPassword";
const char* mqtt_server = "broker.hivemq.com";
const char* mqtt_topic = "smartenv/data";

WiFiClient espClient;
PubSubClient mqtt(espClient);

// ========== ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ฒด ==========
typedef struct {
    float temperature;
    float humidity;
    float light;
    int airQuality;
    float batteryVoltage;
    unsigned long timestamp;
    bool valid;
} EnvironmentData;

EnvironmentData currentData;
EnvironmentData dataHistory[100];
int historyIndex = 0;

// ========== ์‹œ์Šคํ…œ ์ƒํƒœ ==========
typedef enum {
    STATE_INIT,
    STATE_NORMAL,
    STATE_LOW_POWER,
    STATE_ERROR
} SystemState;

SystemState systemState = STATE_INIT;

// ========== ํ•„ํ„ฐ ==========
MovingAverage tempFilter;
MovingAverage humidFilter;
MovingAverage lightFilter;

// ========== ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜๋“ค ==========
void setupDisplay() {
    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
        Serial.println("โŒ OLED ์ดˆ๊ธฐํ™” ์‹คํŒจ!");
        return;
    }
    
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.println("Smart Environment");
    display.println("Monitor v1.0");
    display.display();
    delay(2000);
    
    Serial.println("โœ… ๋””์Šคํ”Œ๋ ˆ์ด ์ดˆ๊ธฐํ™” ์™„๋ฃŒ");
}

void setupSensors() {
    // DHT22 ์ดˆ๊ธฐํ™”
    dht.begin();
    Serial.println("โœ… DHT22 ์ดˆ๊ธฐํ™”");
    
    // BH1750 ์ดˆ๊ธฐํ™”
    if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
        Serial.println("โœ… BH1750 ์ดˆ๊ธฐํ™”");
    } else {
        Serial.println("โŒ BH1750 ์ดˆ๊ธฐํ™” ์‹คํŒจ");
    }
    
    // MQ-135 ํ•€ ์„ค์ •
    pinMode(MQ135_PIN, INPUT);
    Serial.println("โœ… MQ-135 ์ดˆ๊ธฐํ™”");
    
    // ํ•„ํ„ฐ ์ดˆ๊ธฐํ™”
    initMovingAverage(&tempFilter);
    initMovingAverage(&humidFilter);
    initMovingAverage(&lightFilter);
}

void setupSDCard() {
    if (!SD.begin(SD_CS_PIN)) {
        Serial.println("โŒ SD ์นด๋“œ ์ดˆ๊ธฐํ™” ์‹คํŒจ");
        return;
    }
    
    Serial.println("โœ… SD ์นด๋“œ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ");
    
    // ๋กœ๊ทธ ํŒŒ์ผ ์ƒ์„ฑ
    File logFile = SD.open("/env_log.csv", FILE_WRITE);
    if (logFile) {
        logFile.println("Timestamp,Temp,Humid,Light,AirQuality,Battery");
        logFile.close();
    }
}

void setupWiFiAndMQTT() {
    Serial.print("WiFi ์—ฐ๊ฒฐ ์ค‘");
    WiFi.begin(ssid, password);
    
    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
        delay(500);
        Serial.print(".");
        attempts++;
    }
    
    if (WiFi.status() == WL_CONNECTED) {
        Serial.println("\nโœ… WiFi ์—ฐ๊ฒฐ ์„ฑ๊ณต!");
        Serial.print("IP: ");
        Serial.println(WiFi.localIP());
        
        mqtt.setServer(mqtt_server, 1883);
        connectMQTT();
    } else {
        Serial.println("\nโŒ WiFi ์—ฐ๊ฒฐ ์‹คํŒจ");
    }
}

void connectMQTT() {
    while (!mqtt.connected()) {
        Serial.print("MQTT ์—ฐ๊ฒฐ ์‹œ๋„...");
        
        if (mqtt.connect("SmartEnvMonitor")) {
            Serial.println("โœ… ์—ฐ๊ฒฐ ์„ฑ๊ณต!");
        } else {
            Serial.print("โŒ ์‹คํŒจ, rc=");
            Serial.println(mqtt.state());
            delay(5000);
        }
    }
}

// ========== ์„ผ์„œ ์ฝ๊ธฐ ํ•จ์ˆ˜๋“ค ==========
bool readAllSensors() {
    currentData.timestamp = millis();
    currentData.valid = true;
    
    // ์˜จ์Šต๋„ ์ฝ๊ธฐ
    float temp = dht.readTemperature();
    float humid = dht.readHumidity();
    
    if (isnan(temp) || isnan(humid)) {
        Serial.println("โŒ DHT22 ์ฝ๊ธฐ ์‹คํŒจ");
        currentData.valid = false;
        return false;
    }
    
    // ํ•„ํ„ฐ๋ง ์ ์šฉ
    currentData.temperature = addValue(&tempFilter, temp);
    currentData.humidity = addValue(&humidFilter, humid);
    
    // ์กฐ๋„ ์ฝ๊ธฐ
    float lux = lightMeter.readLightLevel();
    currentData.light = addValue(&lightFilter, lux);
    
    // ๊ณต๊ธฐ์งˆ ์ฝ๊ธฐ (0-4095)
    currentData.airQuality = analogRead(MQ135_PIN);
    
    // ๋ฐฐํ„ฐ๋ฆฌ ์ „์•• ์ฝ๊ธฐ
    int batteryADC = analogRead(BATTERY_PIN);
    currentData.batteryVoltage = (batteryADC / 4095.0) * 3.3 * 2.0;
    
    // ํžˆ์Šคํ† ๋ฆฌ์— ์ €์žฅ
    dataHistory[historyIndex] = currentData;
    historyIndex = (historyIndex + 1) % 100;
    
    return true;
}

// ========== ๋””์Šคํ”Œ๋ ˆ์ด ์—…๋ฐ์ดํŠธ ==========
void updateDisplay() {
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0, 0);
    
    // ์˜จ๋„
    display.print("Temp: ");
    display.print(currentData.temperature, 1);
    display.println(" C");
    
    // ์Šต๋„
    display.print("Humid: ");
    display.print(currentData.humidity, 1);
    display.println(" %");
    
    // ์กฐ๋„
    display.print("Light: ");
    display.print(currentData.light, 0);
    display.println(" lux");
    
    // ๊ณต๊ธฐ์งˆ
    display.print("Air: ");
    if (currentData.airQuality < 1000) {
        display.println("Good");
    } else if (currentData.airQuality < 2000) {
        display.println("Moderate");
    } else {
        display.println("Poor");
    }
    
    // ๋ฐฐํ„ฐ๋ฆฌ
    display.print("Batt: ");
    display.print(currentData.batteryVoltage, 2);
    display.println("V");
    
    // WiFi ์ƒํƒœ
    if (WiFi.status() == WL_CONNECTED) {
        display.println("WiFi: OK");
    } else {
        display.println("WiFi: --");
    }
    
    display.display();
}

// ========== ๋ฐ์ดํ„ฐ ์ €์žฅ ==========
void saveToSD() {
    File logFile = SD.open("/env_log.csv", FILE_APPEND);
    
    if (logFile) {
        logFile.print(currentData.timestamp);
        logFile.print(",");
        logFile.print(currentData.temperature);
        logFile.print(",");
        logFile.print(currentData.humidity);
        logFile.print(",");
        logFile.print(currentData.light);
        logFile.print(",");
        logFile.print(currentData.airQuality);
        logFile.print(",");
        logFile.println(currentData.batteryVoltage);
        
        logFile.close();
        Serial.println("๐Ÿ’พ SD ์ €์žฅ ์™„๋ฃŒ");
    }
}

// ========== MQTT ์ „์†ก ==========
void publishToMQTT() {
    if (!mqtt.connected()) {
        connectMQTT();
    }
    
    // JSON ์ƒ์„ฑ
    StaticJsonDocument<256> doc;
    doc["temperature"] = currentData.temperature;
    doc["humidity"] = currentData.humidity;
    doc["light"] = currentData.light;
    doc["airQuality"] = currentData.airQuality;
    doc["battery"] = currentData.batteryVoltage;
    doc["timestamp"] = currentData.timestamp;
    
    char jsonBuffer[256];
    serializeJson(doc, jsonBuffer);
    
    if (mqtt.publish(mqtt_topic, jsonBuffer)) {
        Serial.println("๐Ÿ“ค MQTT ์ „์†ก ์™„๋ฃŒ");
    } else {
        Serial.println("โŒ MQTT ์ „์†ก ์‹คํŒจ");
    }
}

// ========== ๊ฒฝ๊ณ  ์‹œ์Šคํ…œ ==========
void checkAlerts() {
    bool alert = false;
    
    if (currentData.temperature > 35.0) {
        Serial.println("๐Ÿ”ด ๊ฒฝ๊ณ : ๊ณ ์˜จ!");
        alert = true;
    }
    
    if (currentData.humidity > 80.0) {
        Serial.println("๐Ÿ’ฆ ๊ฒฝ๊ณ : ๊ณ ์Šต๋„!");
        alert = true;
    }
    
    if (currentData.airQuality > 2000) {
        Serial.println("๐Ÿ˜ท ๊ฒฝ๊ณ : ๊ณต๊ธฐ์งˆ ๋‚˜์จ!");
        alert = true;
    }
    
    if (currentData.batteryVoltage < 3.5) {
        Serial.println("๐Ÿ”‹ ๊ฒฝ๊ณ : ๋ฐฐํ„ฐ๋ฆฌ ๋ถ€์กฑ!");
        alert = true;
    }
    
    if (alert) {
        // ๊ธด๊ธ‰ ์•Œ๋ฆผ ์ „์†ก
        mqtt.publish("smartenv/alerts", "ALERT");
    }
}

// ========== ๋ฉ”์ธ ํ•จ์ˆ˜๋“ค ==========
void setup() {
    Serial.begin(115200);
    Serial.println("\n๐Ÿš€ ์Šค๋งˆํŠธ ํ™˜๊ฒฝ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ ์‹œ์ž‘!");
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    
    // ์ดˆ๊ธฐํ™”
    setupDisplay();
    setupSensors();
    setupSDCard();
    setupWiFiAndMQTT();
    
    systemState = STATE_NORMAL;
    Serial.println("โœ… ๋ชจ๋“  ์ดˆ๊ธฐํ™” ์™„๋ฃŒ!");
}

void loop() {
    static unsigned long lastSensorRead = 0;
    static unsigned long lastMQTTPublish = 0;
    static unsigned long lastSDSave = 0;
    static unsigned long lastDisplayUpdate = 0;
    
    unsigned long currentTime = millis();
    
    // ์„ผ์„œ ์ฝ๊ธฐ (10์ดˆ๋งˆ๋‹ค)
    if (currentTime - lastSensorRead >= 10000) {
        lastSensorRead = currentTime;
        
        if (readAllSensors()) {
            checkAlerts();
        }
    }
    
    // ๋””์Šคํ”Œ๋ ˆ์ด ์—…๋ฐ์ดํŠธ (2์ดˆ๋งˆ๋‹ค)
    if (currentTime - lastDisplayUpdate >= 2000) {
        lastDisplayUpdate = currentTime;
        updateDisplay();
    }
    
    // SD ์นด๋“œ ์ €์žฅ (1๋ถ„๋งˆ๋‹ค)
    if (currentTime - lastSDSave >= 60000) {
        lastSDSave = currentTime;
        saveToSD();
    }
    
    // MQTT ์ „์†ก (30์ดˆ๋งˆ๋‹ค)
    if (currentTime - lastMQTTPublish >= 30000) {
        lastMQTTPublish = currentTime;
        
        if (WiFi.status() == WL_CONNECTED) {
            publishToMQTT();
        }
    }
    
    // MQTT ๋ฃจํ”„
    mqtt.loop();
    
    delay(100);
}
์™€! ์ •๋ง ์™„์ „ํ•œ ์‹œ์Šคํ…œ์ด์ง€? ์ด ์ฝ”๋“œ๋Š” ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š” ํ”„๋กœ๋•์…˜ ์ˆ˜์ค€์˜ IoT ์‹œ์Šคํ…œ์ด์•ผ! ๐ŸŽ‰
๐ŸŽ“ ํ•™์Šต ํŒ

์ด๋Ÿฐ ๋ณต์žกํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์™„๋ฒฝํ•˜๊ฒŒ ๋งŒ๋“ค๋ ค๊ณ  ํ•˜์ง€ ๋งˆ! ์ž‘์€ ๋ถ€๋ถ„๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ํ•ด๊ฐ€๋Š” ๊ฒŒ ์ข‹์•„. ์žฌ๋Šฅ๋„ท์—์„œ IoT ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜์ž๋“ค์˜ ์กฐ์–ธ์„ ๋ฐ›์œผ๋ฉด ํ›จ์”ฌ ๋น ๋ฅด๊ฒŒ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด!

๐Ÿ“ˆ ๋ฐ์ดํ„ฐ ๋ถ„์„ ๋ฐ ์‹œ๊ฐํ™”

์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ–ˆ์œผ๋ฉด ์ด์ œ ๋ถ„์„ํ•ด์•ผ๊ฒ ์ง€? ๊ฐ„๋‹จํ•œ ํ†ต๊ณ„ ๋ถ„์„๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž! ๐Ÿ“Š
๊ธฐ๋ณธ ํ†ต๊ณ„ ๋ถ„์„
typedef struct {
    float min;
    float max;
    float mean;
    float stdDev;
    float median;
} Statistics;

Statistics calculateStatistics(float* data, int count) {
    Statistics stats = {0};
    
    if (count == 0) return stats;
    
    // ์ตœ์†Œ/์ตœ๋Œ€/ํ•ฉ๊ณ„
    stats.min = data[0];
    stats.max = data[0];
    float sum = 0.0;
    
    for (int i = 0; i < count; i++) {
        if (data[i] < stats.min) stats.min = data[i];
        if (data[i] > stats.max) stats.max = data[i];
        sum += data[i];
    }
    
    // ํ‰๊ท 
    stats.mean = sum / count;
    
    // ํ‘œ์ค€ํŽธ์ฐจ
    float sumSquares = 0.0;
    for (int i = 0; i < count; i++) {
        float diff = data[i] - stats.mean;
        sumSquares += diff * diff;
    }
    stats.stdDev = sqrt(sumSquares / count);
    
    // ์ค‘๊ฐ„๊ฐ’ (์ •๋ ฌ ํ•„์š”)
    float sorted[count];
    memcpy(sorted, data, count * sizeof(float));
    
    // ๊ฐ„๋‹จํ•œ ๋ฒ„๋ธ” ์ •๋ ฌ
    for (int i = 0; i < count - 1; i++) {
        for (int j = 0; j < count - i - 1; j++) {
            if (sorted[j] > sorted[j + 1]) {
                float temp = sorted[j];
                sorted[j] = sorted[j + 1];
                sorted[j + 1] = temp;
            }
        }
    }
    
    if (count % 2 == 0) {
        stats.median = (sorted[count/2 - 1] + sorted[count/2]) / 2.0;
    } else {
        stats.median = sorted[count/2];
    }
    
    return stats;
}

void analyzeTemperatureData() {
    float tempData[100];
    
    // ํžˆ์Šคํ† ๋ฆฌ์—์„œ ์˜จ๋„ ๋ฐ์ดํ„ฐ ์ถ”์ถœ
    for (int i = 0; i < 100; i++) {
        tempData[i] = dataHistory[i].temperature;
    }
    
    Statistics stats = calculateStatistics(tempData, 100);
    
    Serial.println("\n๐Ÿ“Š ์˜จ๋„ ํ†ต๊ณ„ ๋ถ„์„ (์ตœ๊ทผ 100๊ฐœ ๋ฐ์ดํ„ฐ)");
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    Serial.print("์ตœ์†Œ๊ฐ’: ");
    Serial.print(stats.min, 2);
    Serial.println("ยฐC");
    Serial.print("์ตœ๋Œ€๊ฐ’: ");
    Serial.print(stats.max, 2);
    Serial.println("ยฐC");
    Serial.print("ํ‰๊ท : ");
    Serial.print(stats.mean, 2);
    Serial.println("ยฐC");
    Serial.print("ํ‘œ์ค€ํŽธ์ฐจ: ");
    Serial.print(stats.stdDev, 2);
    Serial.println("ยฐC");
    Serial.print("์ค‘๊ฐ„๊ฐ’: ");
    Serial.print(stats.median, 2);
    Serial.println("ยฐC");
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n");
}
์ƒ๊ด€๊ด€๊ณ„ ๋ถ„์„
์˜จ๋„์™€ ์Šต๋„์˜ ์ƒ๊ด€๊ด€๊ณ„๋ฅผ ๋ถ„์„ํ•ด๋ณด์ž:
float calculateCorrelation(float* x, float* y, int count) {
    if (count < 2) return 0.0;
    
    // ํ‰๊ท  ๊ณ„์‚ฐ
    float meanX = 0.0, meanY = 0.0;
    for (int i = 0; i < count; i++) {
        meanX += x[i];
        meanY += y[i];
    }
    meanX /= count;
    meanY /= count;
    
    // ๊ณต๋ถ„์‚ฐ๊ณผ ํ‘œ์ค€ํŽธ์ฐจ ๊ณ„์‚ฐ
    float covariance = 0.0;
    float stdX = 0.0, stdY = 0.0;
    
    for (int i = 0; i < count; i++) {
        float diffX = x[i] - meanX;
        float diffY = y[i] - meanY;
        covariance += diffX * diffY;
        stdX += diffX * diffX;
        stdY += diffY * diffY;
    }
    
    stdX = sqrt(stdX / count);
    stdY = sqrt(stdY / count);
    
    if (stdX == 0.0 || stdY == 0.0) return 0.0;
    
    return covariance / (count * stdX * stdY);
}

void analyzeCorrelations() {
    float tempData[100], humidData[100], lightData[100];
    
    for (int i = 0; i < 100; i++) {
        tempData[i] = dataHistory[i].temperature;
        humidData[i] = dataHistory[i].humidity;
        lightData[i] = dataHistory[i].light;
    }
    
    float tempHumidCorr = calculateCorrelation(tempData, humidData, 100);
    float tempLightCorr = calculateCorrelation(tempData, lightData, 100);
    float humidLightCorr = calculateCorrelation(humidData, lightData, 100);
    
    Serial.println("\n๐Ÿ”— ์ƒ๊ด€๊ด€๊ณ„ ๋ถ„์„");
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    Serial.print("์˜จ๋„ โ†” ์Šต๋„: ");
    Serial.println(tempHumidCorr, 3);
    Serial.print("์˜จ๋„ โ†” ์กฐ๋„: ");
    Serial.println(tempLightCorr, 3);
    Serial.print("์Šต๋„ โ†” ์กฐ๋„: ");
    Serial.println(humidLightCorr, 3);
    Serial.println("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”");
    Serial.println("(1์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ๊ฐ•ํ•œ ์–‘์˜ ์ƒ๊ด€๊ด€๊ณ„)");
    Serial.println("(-1์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ๊ฐ•ํ•œ ์Œ์˜ ์ƒ๊ด€๊ด€๊ณ„)\n");
}
์ด๋Ÿฐ ๋ถ„์„์„ ํ†ตํ•ด ์„ผ์„œ ๋ฐ์ดํ„ฐ ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์–ด! ์˜ˆ๋ฅผ ๋“ค์–ด ์˜จ๋„๊ฐ€ ์˜ฌ๋ผ๊ฐ€๋ฉด ์Šต๋„๊ฐ€ ๋‚ด๋ ค๊ฐ€๋Š” ๊ฒฝํ–ฅ์ด ์žˆ๋‹ค๋“ ์ง€ ํ•˜๋Š” ํŒจํ„ด์„ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์ง€. ๐Ÿ”

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์ง€? ์šฐ๋ฆฌ๋Š” C์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์™„์ „ํ•œ IoT ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ฐ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ดค์–ด. ๊ธฐ๋ณธ์ ์ธ ์„ผ์„œ ์ฝ๊ธฐ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง, ํ†ต์‹ , ์ €์žฅ, ์ „๋ ฅ ๊ด€๋ฆฌ, ์—๋Ÿฌ ์ฒ˜๋ฆฌ, ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ ๋ถ„์„๊นŒ์ง€ ๋ชจ๋“  ๊ฑธ ๋‹ค๋ค˜์–ด! ๐ŸŽ‰

์ด์ œ ๋„ˆ๋„ ์‹ค์ „ IoT ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ์‹ค๋ ฅ์„ ๊ฐ–์ท„์–ด. ๋ฌผ๋ก  ์ฒ˜์Œ๋ถ€ํ„ฐ ์™„๋ฒฝํ•  ์ˆœ ์—†์–ด. ํ•˜์ง€๋งŒ ์ด ๊ธ€์—์„œ ๋ฐฐ์šด ๊ฐœ๋…๋“ค์„ ํ•˜๋‚˜์”ฉ ์ ์šฉํ•ด๊ฐ€๋ฉด์„œ ์ ์  ๋” ๋‚˜์€ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐Ÿ’ช

๐Ÿš€ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๊ธฐ

1๋‹จ๊ณ„: ๊ฐ„๋‹จํ•œ ์„ผ์„œ ํ•˜๋‚˜๋กœ ์‹œ์ž‘ํ•ด์„œ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ
2๋‹จ๊ณ„: ์—ฌ๋Ÿฌ ์„ผ์„œ๋ฅผ ํ†ตํ•ฉํ•˜๊ณ  ํ•„ํ„ฐ๋ง ์ ์šฉํ•˜๊ธฐ
3๋‹จ๊ณ„: WiFi ํ†ต์‹  ์ถ”๊ฐ€ํ•˜๊ณ  ํด๋ผ์šฐ๋“œ์— ๋ฐ์ดํ„ฐ ์ „์†กํ•˜๊ธฐ
4๋‹จ๊ณ„: ์ „๋ ฅ ๊ด€๋ฆฌ์™€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋กœ ์‹œ์Šคํ…œ ์•ˆ์ •ํ™”ํ•˜๊ธฐ
5๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ ๋ถ„์„๊ณผ ๋จธ์‹ ๋Ÿฌ๋‹์œผ๋กœ ์Šค๋งˆํŠธํ•œ ์‹œ์Šคํ…œ ๋งŒ๋“ค๊ธฐ
ํ˜ผ์ž์„œ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ์žฌ๋Šฅ๋„ท์—์„œ IoT ์ „๋ฌธ๊ฐ€๋“ค์˜ ๋„์›€์„ ๋ฐ›์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ์‹ค์ „ ๊ฒฝํ—˜์ด ํ’๋ถ€ํ•œ ๊ฐœ๋ฐœ์ž๋“ค์˜ ์กฐ์–ธ์€ ์ •๋ง ๊ฐ’์ง„ ์ž์‚ฐ์ด ๋  ๊ฑฐ์•ผ! ๐ŸŒŸ

IoT๋Š” ์ •๋ง ์žฌ๋ฏธ์žˆ๊ณ  ๋ฌดํ•œํ•œ ๊ฐ€๋Šฅ์„ฑ์„ ๊ฐ€์ง„ ๋ถ„์•ผ์•ผ. ์Šค๋งˆํŠธํ™ˆ, ๋†์—…, ์˜๋ฃŒ, ์‚ฐ์—… ์ž๋™ํ™” ๋“ฑ ๋ชจ๋“  ๊ณณ์—์„œ IoT ๊ธฐ์ˆ ์ด ํ•„์š”ํ•ด. ๋„ˆ์˜ ์ฐฝ์˜๋ ฅ๊ณผ C์–ธ์–ด ์‹ค๋ ฅ์„ ๊ฒฐํ•ฉํ•˜๋ฉด ์ •๋ง ๋ฉ‹์ง„ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐ŸŽจ

์ž, ์ด์ œ ์ง์ ‘ ์„ผ์„œ๋ฅผ ์—ฐ๊ฒฐํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž! ์‹คํŒจ๋ฅผ ๋‘๋ ค์›Œํ•˜์ง€ ๋ง๊ณ , ํ•˜๋‚˜์”ฉ ์‹œ๋„ํ•ด๋ณด๋ฉด์„œ ๋ฐฐ์›Œ๊ฐ€๋Š” ๊ฑฐ์•ผ. ๊ทธ๊ฒŒ ๋ฐ”๋กœ ์ง„์งœ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜๋Š” ๊ธธ์ด๋‹ˆ๊นŒ! ๐Ÿ˜Š

ํ–‰์šด์„ ๋นŒ์–ด! ๊ทธ๋ฆฌ๊ณ  ๋ฉ‹์ง„ IoT ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด ๊ผญ ์ž๋ž‘ํ•ด์ค˜! ๐Ÿš€โœจ
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0