์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ“ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋กœ ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋‚ ๋ ค๋ณด๋‚ด๊ธฐ! MQTT ์™„์ „์ •๋ณต ๊ฐ€์ด๋“œ

๐Ÿ“ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋กœ ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋‚ ๋ ค๋ณด๋‚ด๊ธฐ! MQTT ์™„์ „์ •๋ณต ๊ฐ€์ด๋“œ

์ž‘์€ ์ปดํ“จํ„ฐ์™€ ๊ฐ€๋ฒผ์šด ํ”„๋กœํ† ์ฝœ์ด ๋งŒ๋‚˜๋ฉด ์ƒ๊ธฐ๋Š” ๋งˆ๋ฒ• โœจ

์•ˆ๋…•! ์˜ค๋Š˜์€ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋ผ๋Š” ๊ท€์—ฌ์šด ์‹ฑ๊ธ€๋ณด๋“œ ์ปดํ“จํ„ฐ์™€ MQTT๋ผ๋Š” ๋˜‘๋˜‘ํ•œ ํ†ต์‹  ํ”„๋กœํ† ์ฝœ์„ ๊ฐ€์ง€๊ณ  ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ ๊ฑฐ์•ผ. ๐Ÿ˜Š

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

๋ฐ”๋กœ ์—ฌ๊ธฐ์„œ MQTT ํ”„๋กœํ† ์ฝœ์ด ๋น›์„ ๋ฐœํ•˜๋Š” ๊ฑฐ์•ผ! ๊ฐ€๋ณ๊ณ , ๋น ๋ฅด๊ณ , ์•ˆ์ •์ ์ด๋ฉด์„œ๋„ ๊ตฌํ˜„์ด ์‰ฌ์›Œ์„œ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด ๊ฐ™์€ ์ž‘์€ ๋””๋ฐ”์ด์Šค์— ๋”ฑ ๋งž๋Š” ์†”๋ฃจ์…˜์ด์ง€. ๐ŸŽฏ

๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด ๐Ÿ“ ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ฐ ๋ฐœํ–‰ MQTT ๋ธŒ๋กœ์ปค ๐Ÿ“ฎ ๋ฉ”์‹œ์ง€ ์ค‘๊ณ„ ํ† ํ”ฝ ๊ด€๋ฆฌ ๊ตฌ๋…์ž ์—ฐ๊ฒฐ ๊ตฌ๋…์ž ๐Ÿ’ป๐Ÿ“ฑ ๋ฐ์ดํ„ฐ ์ˆ˜์‹  ๋ฐ ์ฒ˜๋ฆฌ Publish Subscribe

๐Ÿค” MQTT๊ฐ€ ๋ญ๊ธธ๋ž˜ ์ด๋ ‡๊ฒŒ ์ธ๊ธฐ๊ฐ€ ๋งŽ์„๊นŒ?

MQTT๋Š” Message Queuing Telemetry Transport์˜ ์•ฝ์ž์•ผ. ์ด๋ฆ„์ด ์ข€ ๊ธธ๊ณ  ์–ด๋ ค์›Œ ๋ณด์ด์ง€? ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ์ •๋ง ๋‹จ์ˆœํ•˜๊ณ  ํšจ์œจ์ ์ธ ํ”„๋กœํ† ์ฝœ์ด์•ผ.

1999๋…„์— IBM์˜ ์•ค๋”” ์Šคํƒ ํฌ๋“œ-ํด๋ผํฌ์™€ ์•„๋ฅผ๋ Œ ๋‹ˆํผ๊ฐ€ ๊ฐœ๋ฐœํ–ˆ๋Š”๋ฐ, ์›๋ž˜๋Š” ์„์œ  ํŒŒ์ดํ”„๋ผ์ธ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ๋Œ€. ๋‹น์‹œ์—๋Š” ๋„คํŠธ์›Œํฌ ๋Œ€์—ญํญ์ด ์ œํ•œ์ ์ด๊ณ  ๋ถˆ์•ˆ์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ตœ์†Œํ•œ์˜ ๋ฐ์ดํ„ฐ๋กœ ์ตœ๋Œ€ํ•œ์˜ ํšจ์œจ์„ ๋‚ด์•ผ ํ–ˆ๋˜ ๊ฑฐ์ง€. ๐Ÿ›ข๏ธ

๊ทธ๋ž˜์„œ MQTT๋Š” ํƒœ์ƒ๋ถ€ํ„ฐ "๊ฐ€๋ณ๊ณ  ํšจ์œจ์ "์ด๋ผ๋Š” DNA๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด. HTTP ๊ฐ™์€ ํ”„๋กœํ† ์ฝœ์— ๋น„ํ•ด ํ—ค๋” ํฌ๊ธฐ๊ฐ€ ํ›จ์”ฌ ์ž‘๊ณ (์ตœ์†Œ 2๋ฐ”์ดํŠธ!), ๋„คํŠธ์›Œํฌ ๋Œ€์—ญํญ์„ ์ ๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด์„œ๋„ ์•ˆ์ •์ ์ธ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ์„ ๋ณด์žฅํ•˜์ง€.

๐Ÿ’ก MQTT์˜ ํ•ต์‹ฌ ํŠน์ง• 1. ๊ฒฝ๋Ÿ‰์„ฑ: ํ—ค๋”๊ฐ€ ์ž‘์•„์„œ ์ €์ „๋ ฅ ๋””๋ฐ”์ด์Šค์— ์ตœ์ ํ™”
2. Pub/Sub ๋ชจ๋ธ: ๋ฐœํ–‰์ž์™€ ๊ตฌ๋…์ž๊ฐ€ ์ง์ ‘ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•„ ์œ ์—ฐํ•จ
3. QoS ๋ ˆ๋ฒจ: ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ ํ’ˆ์งˆ์„ 3๋‹จ๊ณ„๋กœ ์กฐ์ ˆ ๊ฐ€๋Šฅ
4. ์–‘๋ฐฉํ–ฅ ํ†ต์‹ : ํด๋ผ์ด์–ธํŠธ ๊ฐ„ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ตํ™˜
5. ์—ฐ๊ฒฐ ์œ ์ง€: Keep-alive ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์—ฐ๊ฒฐ ์ƒํƒœ ๊ด€๋ฆฌ

๐Ÿ“ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด, ์™œ IoT ํ”„๋กœ์ ํŠธ์˜ ๋‹จ๊ณจ์†๋‹˜์ผ๊นŒ?

๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋Š” ์˜๊ตญ์˜ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด ์žฌ๋‹จ์—์„œ ๋งŒ๋“  ์‹ ์šฉ์นด๋“œ ํฌ๊ธฐ์˜ ์‹ฑ๊ธ€๋ณด๋“œ ์ปดํ“จํ„ฐ์•ผ. ์ฒ˜์Œ์—๋Š” ๊ต์œก์šฉ์œผ๋กœ ๊ฐœ๋ฐœ๋์ง€๋งŒ, ์ง€๊ธˆ์€ ์ „ ์„ธ๊ณ„ ๋ฉ”์ด์ปค๋“ค์˜ ์‚ฌ๋ž‘์„ ๋ฐ›๋Š” IoT ํ”„๋กœ์ ํŠธ์˜ ํ•ต์‹ฌ ๋„๊ตฌ๊ฐ€ ๋์ง€. ๐ŸŽ“

๊ฐ€๊ฒฉ์€ ์ €๋ ดํ•˜๋ฉด์„œ๋„(์•ฝ 3๋งŒ~7๋งŒ์›๋Œ€) ์„ฑ๋Šฅ์€ ๊ฝค ๊ดœ์ฐฎ์•„. ์ตœ์‹  ๋ชจ๋ธ์ธ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด 4๋Š” ์ฟผ๋“œ์ฝ”์–ด CPU์— ์ตœ๋Œ€ 8GB RAM๊นŒ์ง€ ์ง€์›ํ•˜๊ฑฐ๋“ . ์—ฌ๊ธฐ์— GPIO(General Purpose Input/Output) ํ•€์ด 40๊ฐœ๋‚˜ ์žˆ์–ด์„œ ๊ฐ์ข… ์„ผ์„œ์™€ ์•ก์ถ”์—์ดํ„ฐ๋ฅผ ์—ฐ๊ฒฐํ•˜๊ธฐ์— ๋”ฑ์ด์•ผ! ๐Ÿ“Œ

๐ŸŽฏ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๊ฐ€ IoT์— ์ ํ•ฉํ•œ ์ด์œ  โ€ข ๋‹ค์–‘ํ•œ ์ธํ„ฐํŽ˜์ด์Šค: GPIO, I2C, SPI, UART ๋“ฑ ์„ผ์„œ ์—ฐ๊ฒฐ ์˜ต์…˜ ํ’๋ถ€
โ€ข ๋ฆฌ๋ˆ…์Šค ๊ธฐ๋ฐ˜: ํŒŒ์ด์ฌ, C++, ์ž๋ฐ” ๋“ฑ ๋‹ค์–‘ํ•œ ์–ธ์–ด ์ง€์›
โ€ข ๋„คํŠธ์›Œํฌ ๋‚ด์žฅ: Wi-Fi, ๋ธ”๋ฃจํˆฌ์Šค, ์ด๋”๋„ท ์ง€์›
โ€ข ์ €์ „๋ ฅ: 5V ์ „์›์œผ๋กœ ๋™์ž‘ ๊ฐ€๋Šฅ
โ€ข ์ปค๋ฎค๋‹ˆํ‹ฐ: ๋ฐฉ๋Œ€ํ•œ ์ž๋ฃŒ์™€ ํ™œ๋ฐœํ•œ ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ

ํŠนํžˆ ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘์—๋Š” ์ •๋ง ์ตœ์ ํ™”๋˜์–ด ์žˆ์–ด. DHT22 ์˜จ์Šต๋„ ์„ผ์„œ, BMP280 ๊ธฐ์•• ์„ผ์„œ, PIR ๋ชจ์…˜ ์„ผ์„œ, ์กฐ๋„ ์„ผ์„œ ๋“ฑ๋“ฑ... ์‹œ์ค‘์— ๋‚˜์™€ ์žˆ๋Š” ๊ฑฐ์˜ ๋ชจ๋“  ์„ผ์„œ๋ฅผ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐ŸŒก๏ธ๐Ÿ’ง

๐Ÿ“ฎ MQTT์˜ ์ž‘๋™ ์›๋ฆฌ: Pub/Sub ๋ชจ๋ธ ์ดํ•ดํ•˜๊ธฐ

MQTT์˜ ๊ฐ€์žฅ ํฐ ํŠน์ง•์€ ๋ฐ”๋กœ ๋ฐœํ–‰-๊ตฌ๋…(Publish-Subscribe) ๋ชจ๋ธ์ด์•ผ. ์ด๊ฒŒ ๋ญ๋ƒ๋ฉด, ์ „ํ†ต์ ์ธ ํด๋ผ์ด์–ธํŠธ-์„œ๋ฒ„ ๋ชจ๋ธ๊ณผ๋Š” ์ข€ ๋‹ค๋ฅธ ๋ฐฉ์‹์ด์ง€.

์ผ๋ฐ˜์ ์ธ HTTP ํ†ต์‹ ์„ ์ƒ๊ฐํ•ด๋ด. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ์ง์ ‘ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ์„œ๋ฒ„๊ฐ€ ์‘๋‹ต์„ ์ฃผ๋Š” ๋ฐฉ์‹์ด์ž–์•„? ๊ทผ๋ฐ MQTT๋Š” ์ค‘๊ฐ„์— ๋ธŒ๋กœ์ปค(Broker)๋ผ๋Š” ์ค‘์žฌ์ž๊ฐ€ ์žˆ์–ด. ๐Ÿ“ฌ

๐Ÿ”„ MQTT ํ†ต์‹  ํ๋ฆ„ 1๋‹จ๊ณ„: ๋ฐœํ–‰์ž(Publisher)๊ฐ€ ๋ธŒ๋กœ์ปค์— ์—ฐ๊ฒฐ
2๋‹จ๊ณ„: ๊ตฌ๋…์ž(Subscriber)๊ฐ€ ๋ธŒ๋กœ์ปค์— ์—ฐ๊ฒฐํ•˜๊ณ  ๊ด€์‹ฌ ์žˆ๋Š” ํ† ํ”ฝ ๊ตฌ๋…
3๋‹จ๊ณ„: ๋ฐœํ–‰์ž๊ฐ€ ํŠน์ • ํ† ํ”ฝ์œผ๋กœ ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰
4๋‹จ๊ณ„: ๋ธŒ๋กœ์ปค๊ฐ€ ํ•ด๋‹น ํ† ํ”ฝ์„ ๊ตฌ๋…ํ•œ ๋ชจ๋“  ๊ตฌ๋…์ž์—๊ฒŒ ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ
5๋‹จ๊ณ„: ๊ตฌ๋…์ž๋“ค์ด ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ๋ฐ ์ฒ˜๋ฆฌ

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฐœ๋…์ด ํ† ํ”ฝ(Topic)์ด์•ผ. ํ† ํ”ฝ์€ ์ผ์ข…์˜ ๋ฉ”์‹œ์ง€ ์นดํ…Œ๊ณ ๋ฆฌ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ. ์˜ˆ๋ฅผ ๋“ค์–ด home/livingroom/temperature ๊ฐ™์€ ์‹์œผ๋กœ ๊ณ„์ธต์  ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์ง€.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฐœํ–‰์ž์™€ ๊ตฌ๋…์ž๊ฐ€ ์„œ๋กœ๋ฅผ ๋ชฐ๋ผ๋„ ๋ผ. ๋ฐœํ–‰์ž๋Š” ๊ทธ๋ƒฅ ํ† ํ”ฝ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋˜์ง€๋ฉด ๋˜๊ณ , ๊ตฌ๋…์ž๋Š” ๊ด€์‹ฌ ์žˆ๋Š” ํ† ํ”ฝ๋งŒ ๊ตฌ๋…ํ•˜๋ฉด ๋˜๋Š” ๊ฑฐ์•ผ. ์™„์ „ ์œ ์—ฐํ•˜์ง€? ๐ŸŽช

๐ŸŽš๏ธ QoS(Quality of Service) ๋ ˆ๋ฒจ

MQTT๋Š” ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ ํ’ˆ์งˆ์„ 3๋‹จ๊ณ„๋กœ ์ œ๊ณตํ•ด. ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€.

QoS ๋ ˆ๋ฒจ ์„ค๋ช… ์‚ฌ์šฉ ์‚ฌ๋ก€
QoS 0 ์ตœ๋Œ€ 1ํšŒ ์ „๋‹ฌ (Fire and Forget)
์ „๋‹ฌ ๋ณด์žฅ ์—†์Œ
์‹ค์‹œ๊ฐ„ ์„ผ์„œ ๋ฐ์ดํ„ฐ(์ผ๋ถ€ ์†์‹ค ํ—ˆ์šฉ)
QoS 1 ์ตœ์†Œ 1ํšŒ ์ „๋‹ฌ
์ค‘๋ณต ๊ฐ€๋Šฅ์„ฑ ์žˆ์Œ
์ค‘์š”ํ•˜์ง€๋งŒ ์ค‘๋ณต ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ
QoS 2 ์ •ํ™•ํžˆ 1ํšŒ ์ „๋‹ฌ
4๋‹จ๊ณ„ ํ•ธ๋“œ์…ฐ์ดํฌ
๊ฒฐ์ œ, ์ œ์–ด ๋ช…๋ น ๋“ฑ ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ

์„ผ์„œ ๋ฐ์ดํ„ฐ ์ „์†ก์—๋Š” ๋ณดํ†ต QoS 0์ด๋‚˜ 1์„ ๋งŽ์ด ์จ. ์˜จ๋„ ๋ฐ์ดํ„ฐ๊ฐ€ 1์ดˆ์— ํ•œ ๋ฒˆ์”ฉ ๋“ค์–ด์˜ค๋Š”๋ฐ ํ•˜๋‚˜ ๋น ์ง„๋‹ค๊ณ  ํฐ ๋ฌธ์ œ๋Š” ์—†์ž–์•„? ํ•˜์ง€๋งŒ ๋„์–ด๋ฝ ์ œ์–ด ๊ฐ™์€ ์ค‘์š”ํ•œ ๋ช…๋ น์€ QoS 2๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์•ˆ์ „ํ•˜์ง€. ๐Ÿ”

๐Ÿ› ๏ธ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์— MQTT ํ™˜๊ฒฝ ๊ตฌ์ถ•ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์— MQTT ํ™˜๊ฒฝ์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ์ƒ๊ฐ๋ณด๋‹ค ์ •๋ง ๊ฐ„๋‹จํ•ด! ๐Ÿ˜Š

1๏ธโƒฃ MQTT ๋ธŒ๋กœ์ปค ์„ค์น˜ (Mosquitto)

๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ์˜คํ”ˆ์†Œ์Šค MQTT ๋ธŒ๋กœ์ปค๋Š” Mosquitto์•ผ. Eclipse ์žฌ๋‹จ์—์„œ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๊ณ , ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์—์„œ ์™„๋ฒฝํ•˜๊ฒŒ ๋™์ž‘ํ•ด.

# ํŒจํ‚ค์ง€ ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ
sudo apt update

# Mosquitto ๋ธŒ๋กœ์ปค์™€ ํด๋ผ์ด์–ธํŠธ ์„ค์น˜
sudo apt install mosquitto mosquitto-clients -y

# Mosquitto ์„œ๋น„์Šค ์‹œ์ž‘
sudo systemctl start mosquitto

# ๋ถ€ํŒ… ์‹œ ์ž๋™ ์‹œ์ž‘ ์„ค์ •
sudo systemctl enable mosquitto

# ์ƒํƒœ ํ™•์ธ
sudo systemctl status mosquitto

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ธŒ๋กœ์ปค๊ฐ€ 1883๋ฒˆ ํฌํŠธ(๊ธฐ๋ณธ MQTT ํฌํŠธ)์—์„œ ์‹คํ–‰๋ผ. ์ •๋ง ๊ฐ„๋‹จํ•˜์ง€? ๐ŸŽ‰

2๏ธโƒฃ ๋ธŒ๋กœ์ปค ์„ค์ • ํŒŒ์ผ ์ˆ˜์ •

๊ธฐ๋ณธ ์„ค์ •์œผ๋กœ๋„ ๋™์ž‘ํ•˜์ง€๋งŒ, ๋ณด์•ˆ๊ณผ ์„ฑ๋Šฅ์„ ์œ„ํ•ด ์„ค์ •์„ ์กฐ๊ธˆ ์ˆ˜์ •ํ•ด์ฃผ๋Š” ๊ฒŒ ์ข‹์•„.

# ์„ค์ • ํŒŒ์ผ ํŽธ์ง‘
sudo nano /etc/mosquitto/mosquitto.conf

# ์•„๋ž˜ ๋‚ด์šฉ ์ถ”๊ฐ€
listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd

allow_anonymous false๋Š” ์ต๋ช… ์ ‘์†์„ ๋ง‰๋Š” ๊ฑฐ์•ผ. ๋ณด์•ˆ์„ ์œ„ํ•ด ๊ผญ ์„ค์ •ํ•˜๋Š” ๊ฒŒ ์ข‹์•„! ๐Ÿ”’

# ์‚ฌ์šฉ์ž ๊ณ„์ • ์ƒ์„ฑ
sudo mosquitto_passwd -c /etc/mosquitto/passwd username

# ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๋‚˜ํƒ€๋‚จ
# ์›ํ•˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ

# Mosquitto ์žฌ์‹œ์ž‘
sudo systemctl restart mosquitto

3๏ธโƒฃ Python MQTT ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

ํŒŒ์ด์ฌ์—์„œ MQTT๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด paho-mqtt ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด. ์ด๊ฒŒ ๊ฐ€์žฅ ๋„๋ฆฌ ์“ฐ์ด๋Š” ํŒŒ์ด์ฌ MQTT ํด๋ผ์ด์–ธํŠธ์•ผ.

# pip ์—…๊ทธ๋ ˆ์ด๋“œ
pip3 install --upgrade pip

# paho-mqtt ์„ค์น˜
pip3 install paho-mqtt

์ด์ œ ์ค€๋น„ ์™„๋ฃŒ! ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ค€๋น„๊ฐ€ ๋์–ด. ๐Ÿ’ป

๐Ÿ’ป ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋ฐœํ–‰ํ•˜๊ธฐ: Publisher ์ฝ”๋“œ ์ž‘์„ฑ

์ด์ œ ์‹ค์ œ๋กœ ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์„œ MQTT๋กœ ๋ฐœํ–‰ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž. ์˜ˆ์ œ๋กœ๋Š” DHT22 ์˜จ์Šต๋„ ์„ผ์„œ๋ฅผ ์‚ฌ์šฉํ• ๊ฒŒ. ๐ŸŒก๏ธ

๐Ÿ“ฆ ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

# DHT ์„ผ์„œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜
pip3 install Adafruit_DHT

# ๋˜๋Š” ์ตœ์‹  ๋ฒ„์ „
pip3 install adafruit-circuitpython-dht

๐Ÿ”Œ ํ•˜๋“œ์›จ์–ด ์—ฐ๊ฒฐ

DHT22 ์„ผ์„œ๋Š” 3๊ฐœ์˜ ํ•€์ด ์žˆ์–ด:
โ€ข VCC: ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด 3.3V ๋˜๋Š” 5V ํ•€์— ์—ฐ๊ฒฐ
โ€ข DATA: GPIO ํ•€(์˜ˆ: GPIO4)์— ์—ฐ๊ฒฐ
โ€ข GND: ๊ทธ๋ผ์šด๋“œ ํ•€์— ์—ฐ๊ฒฐ

DATA ํ•€๊ณผ VCC ์‚ฌ์ด์— 10kฮฉ ํ’€์—… ์ €ํ•ญ์„ ์—ฐ๊ฒฐํ•˜๋ฉด ๋” ์•ˆ์ •์ ์ด์•ผ. ๐Ÿ“

๐Ÿ“ Publisher ์ฝ”๋“œ ์˜ˆ์ œ

import paho.mqtt.client as mqtt
import Adafruit_DHT
import time
import json

# MQTT ๋ธŒ๋กœ์ปค ์„ค์ •
BROKER_ADDRESS = "localhost"  # ๋˜๋Š” ๋ธŒ๋กœ์ปค์˜ IP ์ฃผ์†Œ
BROKER_PORT = 1883
USERNAME = "your_username"
PASSWORD = "your_password"

# ํ† ํ”ฝ ์„ค์ •
TOPIC_TEMPERATURE = "home/sensor/temperature"
TOPIC_HUMIDITY = "home/sensor/humidity"

# DHT ์„ผ์„œ ์„ค์ •
DHT_SENSOR = Adafruit_DHT.DHT22
DHT_PIN = 4  # GPIO4

# MQTT ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
client = mqtt.Client("RaspberryPi_Publisher")

# ์‚ฌ์šฉ์ž ์ธ์ฆ ์„ค์ •
client.username_pw_set(USERNAME, PASSWORD)

# ์—ฐ๊ฒฐ ์ฝœ๋ฐฑ ํ•จ์ˆ˜
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("โœ… MQTT ๋ธŒ๋กœ์ปค์— ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")
    else:
        print(f"โŒ ์—ฐ๊ฒฐ ์‹คํŒจ. ์ฝ”๋“œ: {rc}")

# ๋ฐœํ–‰ ์ฝœ๋ฐฑ ํ•จ์ˆ˜
def on_publish(client, userdata, mid):
    print(f"๐Ÿ“ค ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰ ์™„๋ฃŒ (ID: {mid})")

# ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ๋“ฑ๋ก
client.on_connect = on_connect
client.on_publish = on_publish

# ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ
try:
    client.connect(BROKER_ADDRESS, BROKER_PORT, 60)
    client.loop_start()  # ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋„คํŠธ์›Œํฌ ๋ฃจํ”„ ์‹œ์ž‘
    
    print("๐ŸŒก๏ธ ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ์‹œ์ž‘...")
    
    while True:
        # ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ
        humidity, temperature = Adafruit_DHT.read_retry(DHT_SENSOR, DHT_PIN)
        
        if humidity is not None and temperature is not None:
            # ๋ฐ์ดํ„ฐ๋ฅผ JSON ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
            sensor_data = {
                "temperature": round(temperature, 2),
                "humidity": round(humidity, 2),
                "timestamp": time.time()
            }
            
            # ์˜จ๋„ ๋ฐ์ดํ„ฐ ๋ฐœํ–‰
            temp_payload = json.dumps({"value": sensor_data["temperature"], 
                                      "unit": "ยฐC",
                                      "timestamp": sensor_data["timestamp"]})
            client.publish(TOPIC_TEMPERATURE, temp_payload, qos=1)
            
            # ์Šต๋„ ๋ฐ์ดํ„ฐ ๋ฐœํ–‰
            humid_payload = json.dumps({"value": sensor_data["humidity"], 
                                       "unit": "%",
                                       "timestamp": sensor_data["timestamp"]})
            client.publish(TOPIC_HUMIDITY, humid_payload, qos=1)
            
            print(f"๐ŸŒก๏ธ ์˜จ๋„: {temperature:.2f}ยฐC | ๐Ÿ’ง ์Šต๋„: {humidity:.2f}%")
        else:
            print("โš ๏ธ ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ ์‹คํŒจ")
        
        # 10์ดˆ ๋Œ€๊ธฐ
        time.sleep(10)
        
except KeyboardInterrupt:
    print("\nโน๏ธ ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ")
    client.loop_stop()
    client.disconnect()
except Exception as e:
    print(f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
    client.loop_stop()
    client.disconnect()

์ด ์ฝ”๋“œ๋Š” 10์ดˆ๋งˆ๋‹ค ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์„œ MQTT ๋ธŒ๋กœ์ปค๋กœ ๋ฐœํ–‰ํ•ด. JSON ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์— ๊ตฌ๋…์ž ์ธก์—์„œ ํŒŒ์‹ฑํ•˜๊ธฐ๋„ ์‰ฝ์ง€! ๐Ÿ“Š

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

๐Ÿ“ฅ ์„ผ์„œ ๋ฐ์ดํ„ฐ ๊ตฌ๋…ํ•˜๊ธฐ: Subscriber ์ฝ”๋“œ ์ž‘์„ฑ

์ด์ œ ๋ฐœํ–‰๋œ ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ๋…์ž ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž. ์ด ์ฝ”๋“œ๋Š” ๋‹ค๋ฅธ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋‚˜ PC, ์‹ฌ์ง€์–ด ํด๋ผ์šฐ๋“œ ์„œ๋ฒ„์—์„œ๋„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด! โ˜๏ธ

import paho.mqtt.client as mqtt
import json
from datetime import datetime

# MQTT ๋ธŒ๋กœ์ปค ์„ค์ •
BROKER_ADDRESS = "192.168.1.100"  # Publisher๊ฐ€ ์žˆ๋Š” ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด IP
BROKER_PORT = 1883
USERNAME = "your_username"
PASSWORD = "your_password"

# ๊ตฌ๋…ํ•  ํ† ํ”ฝ (์™€์ผ๋“œ์นด๋“œ ์‚ฌ์šฉ)
TOPIC_SUBSCRIBE = "home/sensor/#"

# MQTT ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
client = mqtt.Client("Data_Subscriber")
client.username_pw_set(USERNAME, PASSWORD)

# ์—ฐ๊ฒฐ ์ฝœ๋ฐฑ
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("โœ… MQTT ๋ธŒ๋กœ์ปค์— ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")
        # ์—ฐ๊ฒฐ ์„ฑ๊ณต ์‹œ ํ† ํ”ฝ ๊ตฌ๋…
        client.subscribe(TOPIC_SUBSCRIBE, qos=1)
        print(f"๐Ÿ“ฌ ํ† ํ”ฝ ๊ตฌ๋… ์‹œ์ž‘: {TOPIC_SUBSCRIBE}")
    else:
        print(f"โŒ ์—ฐ๊ฒฐ ์‹คํŒจ. ์ฝ”๋“œ: {rc}")

# ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ฝœ๋ฐฑ
def on_message(client, userdata, message):
    try:
        # ๋ฉ”์‹œ์ง€ ๋””์ฝ”๋”ฉ
        topic = message.topic
        payload = json.loads(message.payload.decode())
        
        # ํ˜„์žฌ ์‹œ๊ฐ„
        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        
        # ํ† ํ”ฝ๋ณ„ ์ฒ˜๋ฆฌ
        if "temperature" in topic:
            print(f"๐ŸŒก๏ธ [{current_time}] ์˜จ๋„: {payload['value']}{payload['unit']}")
            # ์—ฌ๊ธฐ์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ €์žฅ, ์•Œ๋ฆผ ๋“ฑ ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
            
        elif "humidity" in topic:
            print(f"๐Ÿ’ง [{current_time}] ์Šต๋„: {payload['value']}{payload['unit']}")
            
        # ๋ฐ์ดํ„ฐ ์ €์žฅ ์˜ˆ์ œ (CSV ํŒŒ์ผ)
        save_to_csv(topic, payload)
        
    except json.JSONDecodeError:
        print(f"โš ๏ธ JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜: {message.payload}")
    except Exception as e:
        print(f"โŒ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {e}")

# CSV ํŒŒ์ผ๋กœ ์ €์žฅํ•˜๋Š” ํ•จ์ˆ˜
def save_to_csv(topic, data):
    import csv
    import os
    
    filename = "sensor_data.csv"
    file_exists = os.path.isfile(filename)
    
    with open(filename, 'a', newline='') as csvfile:
        fieldnames = ['timestamp', 'topic', 'value', 'unit']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        
        if not file_exists:
            writer.writeheader()
        
        writer.writerow({
            'timestamp': datetime.fromtimestamp(data['timestamp']).strftime("%Y-%m-%d %H:%M:%S"),
            'topic': topic,
            'value': data['value'],
            'unit': data['unit']
        })

# ๊ตฌ๋… ์ฝœ๋ฐฑ
def on_subscribe(client, userdata, mid, granted_qos):
    print(f"โœ… ๊ตฌ๋… ์™„๋ฃŒ! QoS: {granted_qos}")

# ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ๋“ฑ๋ก
client.on_connect = on_connect
client.on_message = on_message
client.on_subscribe = on_subscribe

# ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ๋ฐ ๋ฃจํ”„ ์‹œ์ž‘
try:
    client.connect(BROKER_ADDRESS, BROKER_PORT, 60)
    print("๐Ÿ”„ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ๋Œ€๊ธฐ ์ค‘... (Ctrl+C๋กœ ์ข…๋ฃŒ)")
    client.loop_forever()  # ๋ฌดํ•œ ๋ฃจํ”„๋กœ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ 
    
except KeyboardInterrupt:
    print("\nโน๏ธ ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ")
    client.disconnect()
except Exception as e:
    print(f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
    client.disconnect()
๐ŸŽฏ Subscriber ์ฝ”๋“œ์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ 1. ์™€์ผ๋“œ์นด๋“œ ๊ตฌ๋…: home/sensor/#๋กœ ํ•˜์œ„ ๋ชจ๋“  ํ† ํ”ฝ ๊ตฌ๋…
2. ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ: ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ฆ‰์‹œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ์‹คํ–‰
3. ๋ฐ์ดํ„ฐ ์ €์žฅ: CSV ํŒŒ์ผ๋กœ ์„ผ์„œ ๋ฐ์ดํ„ฐ ๊ธฐ๋ก
4. ํ™•์žฅ ๊ฐ€๋Šฅ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™, ์•Œ๋ฆผ, ์‹œ๊ฐํ™” ๋“ฑ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์šฉ์ด
5. loop_forever(): ๊ณ„์† ์‹คํ–‰๋˜๋ฉด์„œ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ๋Œ€๊ธฐ

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์„ผ์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ˆ˜์ง‘๋˜๊ณ  ์ €์žฅ๋ผ. ๋‚˜์ค‘์— ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ฑฐ๋‚˜ ๊ทธ๋ž˜ํ”„๋กœ ์‹œ๊ฐํ™”ํ•  ์ˆ˜๋„ ์žˆ์ง€! ๐Ÿ“ˆ

๐ŸŽจ ์‹ค์ „ ํ™œ์šฉ: ๋‹ค์–‘ํ•œ ์„ผ์„œ ํ†ตํ•ฉํ•˜๊ธฐ

ํ•˜๋‚˜์˜ ์„ผ์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฑด ์žฌ๋ฏธ์—†์ž–์•„? ์—ฌ๋Ÿฌ ์„ผ์„œ๋ฅผ ํ†ตํ•ฉํ•ด์„œ ๋” ํ’๋ถ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•ด๋ณด์ž! ๐ŸŒŸ

๐ŸŒˆ ๋ฉ€ํ‹ฐ ์„ผ์„œ Publisher ์˜ˆ์ œ

import paho.mqtt.client as mqtt
import Adafruit_DHT
import time
import json
import RPi.GPIO as GPIO

# GPIO ์„ค์ •
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

# ์„ผ์„œ ํ•€ ์„ค์ •
DHT_PIN = 4          # ์˜จ์Šต๋„ ์„ผ์„œ
PIR_PIN = 17         # ๋ชจ์…˜ ์„ผ์„œ
LIGHT_PIN = 27       # ์กฐ๋„ ์„ผ์„œ (๋””์ง€ํ„ธ)

# PIR ์„ผ์„œ ์ž…๋ ฅ ์„ค์ •
GPIO.setup(PIR_PIN, GPIO.IN)
GPIO.setup(LIGHT_PIN, GPIO.IN)

# MQTT ์„ค์ •
BROKER = "localhost"
PORT = 1883
BASE_TOPIC = "home/livingroom"

client = mqtt.Client("MultiSensor_Publisher")
client.username_pw_set("username", "password")
client.connect(BROKER, PORT, 60)
client.loop_start()

print("๐Ÿ  ์Šค๋งˆํŠธํ™ˆ ์„ผ์„œ ์‹œ์Šคํ…œ ์‹œ์ž‘!")

try:
    while True:
        sensor_readings = {}
        
        # 1. ์˜จ์Šต๋„ ์„ผ์„œ ์ฝ๊ธฐ
        humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.DHT22, DHT_PIN)
        if humidity and temperature:
            sensor_readings['temperature'] = round(temperature, 2)
            sensor_readings['humidity'] = round(humidity, 2)
            
            client.publish(f"{BASE_TOPIC}/temperature", 
                         json.dumps({"value": sensor_readings['temperature'], "unit": "ยฐC"}))
            client.publish(f"{BASE_TOPIC}/humidity", 
                         json.dumps({"value": sensor_readings['humidity'], "unit": "%"}))
        
        # 2. ๋ชจ์…˜ ์„ผ์„œ ์ฝ๊ธฐ
        motion_detected = GPIO.input(PIR_PIN)
        sensor_readings['motion'] = bool(motion_detected)
        client.publish(f"{BASE_TOPIC}/motion", 
                      json.dumps({"detected": sensor_readings['motion']}))
        
        if motion_detected:
            print("๐Ÿšถ ์›€์ง์ž„ ๊ฐ์ง€!")
        
        # 3. ์กฐ๋„ ์„ผ์„œ ์ฝ๊ธฐ
        light_status = GPIO.input(LIGHT_PIN)
        sensor_readings['light'] = "bright" if light_status else "dark"
        client.publish(f"{BASE_TOPIC}/light", 
                      json.dumps({"status": sensor_readings['light']}))
        
        # ํ†ตํ•ฉ ๋ฐ์ดํ„ฐ ๋ฐœํ–‰
        sensor_readings['timestamp'] = time.time()
        client.publish(f"{BASE_TOPIC}/all", json.dumps(sensor_readings))
        
        print(f"๐Ÿ“Š ์˜จ๋„: {sensor_readings.get('temperature', 'N/A')}ยฐC | "
              f"์Šต๋„: {sensor_readings.get('humidity', 'N/A')}% | "
              f"์›€์ง์ž„: {sensor_readings['motion']} | "
              f"์กฐ๋„: {sensor_readings['light']}")
        
        time.sleep(5)
        
except KeyboardInterrupt:
    print("\nโน๏ธ ์‹œ์Šคํ…œ ์ข…๋ฃŒ")
    GPIO.cleanup()
    client.loop_stop()
    client.disconnect()

์ด ์ฝ”๋“œ๋Š” ์—ฌ๋Ÿฌ ์„ผ์„œ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋™์‹œ์— ์ˆ˜์ง‘ํ•ด์„œ ๊ฐ๊ฐ์˜ ํ† ํ”ฝ์œผ๋กœ ๋ฐœํ–‰ํ•ด. ๋˜ํ•œ ๋ชจ๋“  ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ํ†ตํ•ฉํ•œ ๋ฉ”์‹œ์ง€๋„ ํ•จ๊ป˜ ๋ณด๋‚ด์„œ, ๊ตฌ๋…์ž๊ฐ€ ์„ ํƒ์ ์œผ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์–ด. ๐ŸŽฏ

๐Ÿ” ๋ณด์•ˆ ๊ฐ•ํ™”: TLS/SSL ์ ์šฉํ•˜๊ธฐ

์ธํ„ฐ๋„ท์„ ํ†ตํ•ด ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•œ๋‹ค๋ฉด ๋ณด์•ˆ์ด ์ •๋ง ์ค‘์š”ํ•ด! MQTT๋Š” TLS/SSL ์•”ํ˜ธํ™”๋ฅผ ์ง€์›ํ•˜๋‹ˆ๊นŒ ๊ผญ ์ ์šฉํ•˜์ž. ๐Ÿ›ก๏ธ

๐Ÿ”‘ ์ธ์ฆ์„œ ์ƒ์„ฑ

# OpenSSL๋กœ ์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ ์ƒ์„ฑ
# CA ํ‚ค ์ƒ์„ฑ
openssl genrsa -out ca.key 2048

# CA ์ธ์ฆ์„œ ์ƒ์„ฑ
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

# ์„œ๋ฒ„ ํ‚ค ์ƒ์„ฑ
openssl genrsa -out server.key 2048

# ์„œ๋ฒ„ ์ธ์ฆ์„œ ์š”์ฒญ ์ƒ์„ฑ
openssl req -new -key server.key -out server.csr

# ์„œ๋ฒ„ ์ธ์ฆ์„œ ์„œ๋ช…
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650

โš™๏ธ Mosquitto TLS ์„ค์ •

# /etc/mosquitto/mosquitto.conf ์ˆ˜์ •
listener 8883
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
require_certificate false
allow_anonymous false
password_file /etc/mosquitto/passwd

๐Ÿ”’ Python ํด๋ผ์ด์–ธํŠธ TLS ์ ์šฉ

import paho.mqtt.client as mqtt
import ssl

client = mqtt.Client("Secure_Publisher")
client.username_pw_set("username", "password")

# TLS ์„ค์ •
client.tls_set(ca_certs="/path/to/ca.crt",
               certfile=None,
               keyfile=None,
               cert_reqs=ssl.CERT_REQUIRED,
               tls_version=ssl.PROTOCOL_TLSv1_2)

# 8883 ํฌํŠธ๋กœ ์—ฐ๊ฒฐ (TLS ๊ธฐ๋ณธ ํฌํŠธ)
client.connect("broker_address", 8883, 60)

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์•”ํ˜ธํ™”๋˜์–ด ์ „์†ก๋ผ์„œ ์ค‘๊ฐ„์— ๋ˆ„๊ฐ€ ๊ฐ€๋กœ์ฑ„๋„ ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์—†์–ด. ํŠนํžˆ ์™ธ๋ถ€ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•  ๋•Œ๋Š” ํ•„์ˆ˜์•ผ! ๐Ÿ”

โ˜๏ธ ํด๋ผ์šฐ๋“œ MQTT ๋ธŒ๋กœ์ปค ํ™œ์šฉํ•˜๊ธฐ

๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์— ์ง์ ‘ ๋ธŒ๋กœ์ปค๋ฅผ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์ง€๋งŒ, ํด๋ผ์šฐ๋“œ MQTT ๋ธŒ๋กœ์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ํŽธ๋ฆฌํ•  ๋•Œ๊ฐ€ ๋งŽ์•„. ํŠนํžˆ ์—ฌ๋Ÿฌ ์žฅ์†Œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ฑฐ๋‚˜, ๋†’์€ ๊ฐ€์šฉ์„ฑ์ด ํ•„์š”ํ•  ๋•Œ ์œ ์šฉํ•˜์ง€! โ˜๏ธ

๐ŸŒ ์ธ๊ธฐ ์žˆ๋Š” ํด๋ผ์šฐ๋“œ MQTT ๋ธŒ๋กœ์ปค 1. HiveMQ Cloud: ๋ฌด๋ฃŒ ํ‹ฐ์–ด ์ œ๊ณต, ์ตœ๋Œ€ 100๊ฐœ ์—ฐ๊ฒฐ
2. CloudMQTT: ๊ฐ„๋‹จํ•œ ์„ค์ •, ๋‹ค์–‘ํ•œ ์š”๊ธˆ์ œ
3. AWS IoT Core: AWS ์„œ๋น„์Šค์™€ ํ†ตํ•ฉ, ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰
4. Azure IoT Hub: Microsoft ํด๋ผ์šฐ๋“œ ์ƒํƒœ๊ณ„
5. Mosquitto Test Server: ํ…Œ์ŠคํŠธ์šฉ ๋ฌด๋ฃŒ ์„œ๋ฒ„ (test.mosquitto.org)

๐Ÿš€ HiveMQ Cloud ์—ฐ๊ฒฐ ์˜ˆ์ œ

import paho.mqtt.client as mqtt
import ssl

# HiveMQ Cloud ์„ค์ •
BROKER = "your-cluster.s1.eu.hivemq.cloud"
PORT = 8883
USERNAME = "your_username"
PASSWORD = "your_password"

client = mqtt.Client("RaspberryPi_Cloud")
client.username_pw_set(USERNAME, PASSWORD)

# TLS ์„ค์ • (HiveMQ Cloud๋Š” TLS ํ•„์ˆ˜)
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)

def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("โœ… HiveMQ Cloud ์—ฐ๊ฒฐ ์„ฑ๊ณต!")
        client.subscribe("sensor/#")
    else:
        print(f"โŒ ์—ฐ๊ฒฐ ์‹คํŒจ: {rc}")

client.on_connect = on_connect
client.connect(BROKER, PORT, 60)
client.loop_forever()

ํด๋ผ์šฐ๋“œ ๋ธŒ๋กœ์ปค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ธํ„ฐ๋„ท๋งŒ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์œผ๋ฉด ์ „ ์„ธ๊ณ„ ์–ด๋””์„œ๋“  ์„ผ์„œ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? ๐ŸŒ

๐Ÿ“Š ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ์ด๊ฑธ ๋ณด๊ธฐ ์ข‹๊ฒŒ ์‹œ๊ฐํ™”ํ•˜๋ฉด ํ›จ์”ฌ ๋” ์œ ์šฉํ•ด! ๋ช‡ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ• ๊ฒŒ. ๐Ÿ“ˆ

๐Ÿ“ฑ Node-RED๋กœ ๋Œ€์‹œ๋ณด๋“œ ๋งŒ๋“ค๊ธฐ

Node-RED๋Š” ์‹œ๊ฐ์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋„๊ตฌ๋กœ, MQTT ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ์‹œ๊ฐํ™”ํ•  ์ˆ˜ ์žˆ์–ด.

# Node-RED ์„ค์น˜
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)

# Node-RED ์‹œ์ž‘
node-red-start

# ๋ธŒ๋ผ์šฐ์ €์—์„œ http://๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ดIP:1880 ์ ‘์†

Node-RED์—์„œ MQTT ๋…ธ๋“œ๋ฅผ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ ์—ฐ๊ฒฐํ•˜๊ณ , ์ฐจํŠธ๋‚˜ ๊ฒŒ์ด์ง€ ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์‹ค์‹œ๊ฐ„ ๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ์™„์„ฑ๋ผ! ์ฝ”๋”ฉ ์—†์ด๋„ ๋ฉ‹์ง„ UI๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ๐ŸŽจ

๐Ÿ“‰ Grafana + InfluxDB ์กฐํ•ฉ

๋” ์ „๋ฌธ์ ์ธ ์‹œ๊ฐํ™”๋ฅผ ์›ํ•œ๋‹ค๋ฉด Grafana์™€ InfluxDB ์กฐํ•ฉ์ด ์ตœ๊ณ ์•ผ!

# InfluxDB ์„ค์น˜
sudo apt install influxdb influxdb-client -y
sudo systemctl start influxdb

# Grafana ์„ค์น˜
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install grafana -y
sudo systemctl start grafana-server

๊ทธ๋ฆฌ๊ณ  MQTT ๋ฐ์ดํ„ฐ๋ฅผ InfluxDB์— ์ €์žฅํ•˜๋Š” Python ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด ๋ผ:

from influxdb import InfluxDBClient
import paho.mqtt.client as mqtt
import json

# InfluxDB ํด๋ผ์ด์–ธํŠธ
influx_client = InfluxDBClient(host='localhost', port=8086)
influx_client.create_database('sensor_data')
influx_client.switch_database('sensor_data')

def on_message(client, userdata, message):
    data = json.loads(message.payload.decode())
    
    json_body = [{
        "measurement": message.topic.split('/')[-1],
        "tags": {"location": "livingroom"},
        "fields": {"value": data['value']}
    }]
    
    influx_client.write_points(json_body)
    print(f"๐Ÿ’พ ๋ฐ์ดํ„ฐ ์ €์žฅ: {message.topic} = {data['value']}")

mqtt_client = mqtt.Client()
mqtt_client.on_message = on_message
mqtt_client.connect("localhost", 1883, 60)
mqtt_client.subscribe("home/sensor/#")
mqtt_client.loop_forever()

์ด์ œ Grafana์—์„œ InfluxDB๋ฅผ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋กœ ์ถ”๊ฐ€ํ•˜๊ณ  ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ๋ฉ‹์ง„ ๊ทธ๋ž˜ํ”„๊ฐ€ ์™„์„ฑ๋ผ! ๐Ÿ“Š

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ๋ฌดํ•œํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ๊ฐ€์ง„ ๊ฑด ์•„๋‹ˆ์•ผ. ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ํŒ์„ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿš€

๐ŸŽฏ ์ตœ์ ํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ 1. ์ ์ ˆํ•œ QoS ์„ ํƒ: ์„ผ์„œ ๋ฐ์ดํ„ฐ๋Š” QoS 0์œผ๋กœ๋„ ์ถฉ๋ถ„ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„
2. ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ ์ตœ์†Œํ™”: JSON ๋Œ€์‹  ๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท ๊ณ ๋ ค
3. Keep-alive ๊ฐ„๊ฒฉ ์กฐ์ •: 60์ดˆ ์ •๋„๊ฐ€ ์ ๋‹น
4. Retained ๋ฉ”์‹œ์ง€ ํ™œ์šฉ: ์ตœ์‹  ์ƒํƒœ๋ฅผ ๋ธŒ๋กœ์ปค์— ์ €์žฅ
5. ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ: ์—ฌ๋Ÿฌ ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฌถ์–ด์„œ ์ „์†ก
6. ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ: ๋งค๋ฒˆ ์ƒˆ๋กœ ์—ฐ๊ฒฐํ•˜์ง€ ๋ง๊ณ  ํ•˜๋‚˜์˜ ์—ฐ๊ฒฐ ์œ ์ง€
7. ํ† ํ”ฝ ๊ตฌ์กฐ ์ตœ์ ํ™”: ๊ณ„์ธต์„ ๋„ˆ๋ฌด ๊นŠ๊ฒŒ ๋งŒ๋“ค์ง€ ์•Š๊ธฐ

๐Ÿ’พ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ธ ์ฝ”๋“œ ์˜ˆ์ œ

import paho.mqtt.client as mqtt
import struct
import time

# ๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท์œผ๋กœ ๋ฐ์ดํ„ฐ ์ „์†ก (๋ฉ”์‹œ์ง€ ํฌ๊ธฐ ์ตœ์†Œํ™”)
def pack_sensor_data(temperature, humidity):
    # float 2๊ฐœ๋ฅผ 8๋ฐ”์ดํŠธ๋กœ ์••์ถ•
    return struct.pack('ff', temperature, humidity)

def unpack_sensor_data(payload):
    # ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ float๋กœ ๋ณ€ํ™˜
    return struct.unpack('ff', payload)

# ์‚ฌ์šฉ ์˜ˆ
client = mqtt.Client()
client.connect("localhost", 1883, 60)

# JSON: {"temperature": 25.5, "humidity": 60.2} โ†’ ์•ฝ 40๋ฐ”์ดํŠธ
# Binary: struct.pack('ff', 25.5, 60.2) โ†’ 8๋ฐ”์ดํŠธ!

binary_data = pack_sensor_data(25.5, 60.2)
client.publish("sensor/data", binary_data, qos=0)

print(f"๐Ÿ“ฆ ์ „์†ก ํฌ๊ธฐ: {len(binary_data)} ๋ฐ”์ดํŠธ")

๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ๋ฅผ 80% ์ด์ƒ ์ค„์ผ ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ๋ฐฐํ„ฐ๋ฆฌ๋กœ ๋™์ž‘ํ•˜๋Š” ๋””๋ฐ”์ด์Šค์—์„œ๋Š” ๋ฐ์ดํ„ฐ ์ „์†ก๋Ÿ‰์ด ๊ณง ๋ฐฐํ„ฐ๋ฆฌ ์ˆ˜๋ช…์ด๋‹ˆ๊นŒ ์ค‘์š”ํ•˜์ง€! ๐Ÿ”‹

๐Ÿ› ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ

์‹ค์ œ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค ๋ณด๋ฉด ์—ฌ๋Ÿฌ ๋ฌธ์ œ๋ฅผ ๋งŒ๋‚  ์ˆ˜ ์žˆ์–ด. ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•ด๋ดค์–ด! ๐Ÿ”ง

โš ๏ธ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค ๋ฌธ์ œ 1: "Connection Refused" ์˜ค๋ฅ˜
โ†’ ๋ธŒ๋กœ์ปค๊ฐ€ ์‹คํ–‰ ์ค‘์ธ์ง€ ํ™•์ธ: sudo systemctl status mosquitto
โ†’ ๋ฐฉํ™”๋ฒฝ ์„ค์ • ํ™•์ธ: sudo ufw allow 1883
โ†’ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ํ™•์ธ

๋ฌธ์ œ 2: ๋ฉ”์‹œ์ง€๊ฐ€ ์ˆ˜์‹ ๋˜์ง€ ์•Š์Œ
โ†’ ํ† ํ”ฝ ์ด๋ฆ„ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ (๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„!)
โ†’ QoS ๋ ˆ๋ฒจ ํ™•์ธ
โ†’ ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธ

๋ฌธ์ œ 3: ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ ์‹คํŒจ
โ†’ GPIO ํ•€ ๋ฒˆํ˜ธ ํ™•์ธ (BCM vs BOARD ๋ชจ๋“œ)
โ†’ ์„ผ์„œ ์ „์› ์—ฐ๊ฒฐ ํ™•์ธ
โ†’ ํ’€์—…/ํ’€๋‹ค์šด ์ €ํ•ญ ํ™•์ธ

๋ฌธ์ œ 4: ์—ฐ๊ฒฐ์ด ์ž์ฃผ ๋Š๊น€
โ†’ Keep-alive ์‹œ๊ฐ„ ๋Š˜๋ฆฌ๊ธฐ
โ†’ Wi-Fi ์‹ ํ˜ธ ๊ฐ•๋„ ํ™•์ธ
โ†’ ์ „์› ๊ณต๊ธ‰ ์•ˆ์ •์„ฑ ํ™•์ธ (์ตœ์†Œ 2.5A ์–ด๋Œ‘ํ„ฐ ์‚ฌ์šฉ)

๐Ÿ” ๋””๋ฒ„๊น… ๋„๊ตฌ

# Mosquitto ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฉ”์‹œ์ง€ ๋ชจ๋‹ˆํ„ฐ๋ง
mosquitto_sub -h localhost -t "#" -v -u username -P password

# ํŠน์ • ํ† ํ”ฝ ๊ตฌ๋…
mosquitto_sub -h localhost -t "home/sensor/temperature" -u username -P password

# ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰ ํ…Œ์ŠคํŠธ
mosquitto_pub -h localhost -t "test/topic" -m "Hello MQTT" -u username -P password

# ๋ธŒ๋กœ์ปค ๋กœ๊ทธ ํ™•์ธ
sudo tail -f /var/log/mosquitto/mosquitto.log

์ด๋Ÿฐ ๋ช…๋ น์–ด๋“ค๋กœ ๋ฌธ์ œ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ mosquitto_sub -t "#"๋Š” ๋ชจ๋“  ๋ฉ”์‹œ์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์–ด์„œ ๋””๋ฒ„๊น…ํ•  ๋•Œ ์ •๋ง ์œ ์šฉํ•ด! ๐Ÿ”Ž

๐ŸŒŸ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด

์ด์ œ ๊ธฐ๋ณธ์€ ๋‹ค ๋ฐฐ์› ์œผ๋‹ˆ, ์‹ค์ œ๋กœ ๋งŒ๋“ค์–ด๋ณผ ์ˆ˜ ์žˆ๋Š” ์žฌ๋ฏธ์žˆ๋Š” ํ”„๋กœ์ ํŠธ๋“ค์„ ์†Œ๊ฐœํ• ๊ฒŒ! ๐Ÿ’ก

์Šค๋งˆํŠธ ํ™ˆ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ ๐Ÿ  ์˜จ๋„/์Šต๋„/์กฐ๋„ ๐Ÿšช ์ถœ์ž… ๊ฐ์ง€ ๐Ÿ’ก ์ž๋™ ์กฐ๋ช… ์ œ์–ด ๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ์•Œ๋ฆผ ์‹๋ฌผ ์ž๋™ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ๐ŸŒฑ ํ† ์–‘ ์ˆ˜๋ถ„ ์ธก์ • ๐Ÿ’ง ์ž๋™ ๋ฌผ์ฃผ๊ธฐ โ˜€๏ธ ์กฐ๋„ ๋ชจ๋‹ˆํ„ฐ๋ง ๐Ÿ“Š ์„ฑ์žฅ ๋ฐ์ดํ„ฐ ๋ถ„์„ ํ™˜๊ฒฝ ๋ชจ๋‹ˆํ„ฐ๋ง ์Šคํ…Œ์ด์…˜ ๐ŸŒก๏ธ ์˜จ๋„/์Šต๋„/๊ธฐ์•• ๐Ÿ’จ ๊ณต๊ธฐ์งˆ ์ธก์ • ๐ŸŒง๏ธ ๊ฐ•์šฐ๋Ÿ‰ ๊ฐ์ง€ ๐Ÿ“ˆ ๋‚ ์”จ ์˜ˆ์ธก ์—๋„ˆ์ง€ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ โšก ์ „๋ ฅ ์‚ฌ์šฉ๋Ÿ‰ ์ธก์ • ๐Ÿ’ฐ ๋น„์šฉ ๊ณ„์‚ฐ ๐Ÿ“‰ ์ ˆ์•ฝ ํŒ ์ œ๊ณต ๐Ÿ”” ์ด์ƒ ์‚ฌ์šฉ ์•Œ๋ฆผ ์ฃผ์ฐจ์žฅ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ๐Ÿš— ์ฃผ์ฐจ ๊ณต๊ฐ„ ๊ฐ์ง€ ๐Ÿ“ ์‹ค์‹œ๊ฐ„ ํ˜„ํ™ฉ ํ‘œ์‹œ ๐Ÿ• ์ฃผ์ฐจ ์‹œ๊ฐ„ ๊ธฐ๋ก ๐Ÿ’ณ ์ž๋™ ์š”๊ธˆ ๊ณ„์‚ฐ ๋ฐ˜๋ ค๋™๋ฌผ ์ผ€์–ด ์‹œ์Šคํ…œ ๐Ÿ• ํ™œ๋™๋Ÿ‰ ๋ชจ๋‹ˆํ„ฐ๋ง ๐Ÿ– ์ž๋™ ๊ธ‰์‹๊ธฐ ๐Ÿ“น ์‹ค์‹œ๊ฐ„ ์˜์ƒ ๐Ÿ”” ์ด์ƒ ํ–‰๋™ ์•Œ๋ฆผ

์ด๋Ÿฐ ํ”„๋กœ์ ํŠธ๋“ค์€ ๋ชจ๋‘ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด + MQTT ์กฐํ•ฉ์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ์„ผ์„œ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜๋‹ˆ๊นŒ ํ™•์žฅ์„ฑ๋„ ์ข‹๊ณ ! ๐ŸŽจ

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

๐ŸŽ“ ๋” ๋‚˜์•„๊ฐ€๊ธฐ: ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค

๊ธฐ๋ณธ์„ ๋„˜์–ด์„œ ๋” ์ „๋ฌธ์ ์ธ ๊ธฐ๋Šฅ๋“ค๋„ ์•Œ์•„๋ณด์ž! ๐Ÿš€

๐Ÿ”„ Last Will and Testament (LWT)

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์˜ˆ๊ธฐ์น˜ ์•Š๊ฒŒ ์—ฐ๊ฒฐ์ด ๋Š์–ด์กŒ์„ ๋•Œ ๋ธŒ๋กœ์ปค๊ฐ€ ์ž๋™์œผ๋กœ ๋ณด๋‚ด๋Š” ๋ฉ”์‹œ์ง€์•ผ. ๋””๋ฐ”์ด์Šค ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง์— ์œ ์šฉํ•ด!

import paho.mqtt.client as mqtt

client = mqtt.Client("Device_Monitor")

# LWT ์„ค์ • (์—ฐ๊ฒฐ ์ „์— ์„ค์ •ํ•ด์•ผ ํ•จ)
client.will_set("device/status", 
                payload="offline", 
                qos=1, 
                retain=True)

def on_connect(client, userdata, flags, rc):
    if rc == 0:
        # ์—ฐ๊ฒฐ ์„ฑ๊ณต ์‹œ ์˜จ๋ผ์ธ ์ƒํƒœ ๋ฐœํ–‰
        client.publish("device/status", "online", qos=1, retain=True)
        print("โœ… ๋””๋ฐ”์ด์Šค ์˜จ๋ผ์ธ")

client.on_connect = on_connect
client.connect("localhost", 1883, 60)
client.loop_forever()

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋””๋ฐ”์ด์Šค๊ฐ€ ๊ฐ‘์ž๊ธฐ ๊บผ์ง€๊ฑฐ๋‚˜ ๋„คํŠธ์›Œํฌ๊ฐ€ ๋Š์–ด์ ธ๋„ ๋ธŒ๋กœ์ปค๊ฐ€ ์ž๋™์œผ๋กœ "offline" ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด์ค˜. ์ •๋ง ๋˜‘๋˜‘ํ•˜์ง€? ๐Ÿง 

๐Ÿ’พ Retained Messages

Retained ๋ฉ”์‹œ์ง€๋Š” ๋ธŒ๋กœ์ปค๊ฐ€ ๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€๋ฅผ ์ €์žฅํ•ด๋’€๋‹ค๊ฐ€, ์ƒˆ๋กœ์šด ๊ตฌ๋…์ž๊ฐ€ ์—ฐ๊ฒฐ๋˜๋ฉด ์ฆ‰์‹œ ์ „๋‹ฌํ•˜๋Š” ๊ธฐ๋Šฅ์ด์•ผ.

# ์˜จ๋„ ์„ผ์„œ์˜ ์ตœ์‹  ๊ฐ’์„ retained๋กœ ๋ฐœํ–‰
client.publish("sensor/temperature", 
               payload="25.5", 
               qos=1, 
               retain=True)

# ๋‚˜์ค‘์— ๊ตฌ๋…์ž๊ฐ€ ์—ฐ๊ฒฐ๋˜๋ฉด ์ฆ‰์‹œ 25.5๋ฅผ ๋ฐ›๊ฒŒ ๋จ!

์ด ๊ธฐ๋Šฅ์€ ์ƒํƒœ ์ •๋ณด๋ฅผ ๊ณต์œ ํ•  ๋•Œ ํŠนํžˆ ์œ ์šฉํ•ด. ์ƒˆ๋กœ์šด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ๋  ๋•Œ๋งˆ๋‹ค ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๋ฐ”๋กœ ์•Œ ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐Ÿ“Œ

๐Ÿ”€ Shared Subscriptions

MQTT 5.0๋ถ€ํ„ฐ ์ง€์›๋˜๋Š” ๊ธฐ๋Šฅ์œผ๋กœ, ์—ฌ๋Ÿฌ ๊ตฌ๋…์ž๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ๋‚˜๋ˆ ์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. ๋ถ€ํ•˜ ๋ถ„์‚ฐ์— ์ข‹์ง€!

# ๊ณต์œ  ๊ตฌ๋… (MQTT 5.0)
# $share/๊ทธ๋ฃน๋ช…/ํ† ํ”ฝ ํ˜•์‹
client.subscribe("$share/workers/sensor/data")

# ๊ฐ™์€ ๊ทธ๋ฃน์˜ ์—ฌ๋Ÿฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ๋ถ„์‚ฐ ์ฒ˜๋ฆฌ
# ํด๋ผ์ด์–ธํŠธ1, 2, 3์ด ์žˆ์œผ๋ฉด ๋ฉ”์‹œ์ง€๊ฐ€ ์ˆœ์„œ๋Œ€๋กœ ๋ถ„๋ฐฐ๋จ

๐Ÿ”‹ ๋ฐฐํ„ฐ๋ฆฌ ์ตœ์ ํ™” ์ „๋žต

๋ฐฐํ„ฐ๋ฆฌ๋กœ ๋™์ž‘ํ•˜๋Š” ์„ผ์„œ ๋…ธ๋“œ๋ฅผ ๋งŒ๋“ ๋‹ค๋ฉด ์ „๋ ฅ ๊ด€๋ฆฌ๊ฐ€ ์ •๋ง ์ค‘์š”ํ•ด! ๋ช‡ ๊ฐ€์ง€ ํŒ์„ ์•Œ๋ ค์ค„๊ฒŒ. ๐Ÿ”‹

โšก ์ „๋ ฅ ์ ˆ์•ฝ ๊ธฐ๋ฒ• 1. Deep Sleep ๋ชจ๋“œ: ์„ผ์„œ ์ฝ๊ธฐ ์‚ฌ์ด์— ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋ฅผ ์Šฌ๋ฆฝ ๋ชจ๋“œ๋กœ
2. ์ „์†ก ๊ฐ„๊ฒฉ ์ตœ์ ํ™”: ๊ผญ ํ•„์š”ํ•œ ๋งŒํผ๋งŒ ๋ฐ์ดํ„ฐ ์ „์†ก
3. QoS 0 ์‚ฌ์šฉ: ํ•ธ๋“œ์…ฐ์ดํฌ ์˜ค๋ฒ„ํ—ค๋“œ ์ œ๊ฑฐ
4. Keep-alive ๊ฐ„๊ฒฉ ๋Š˜๋ฆฌ๊ธฐ: ๋ถˆํ•„์š”ํ•œ ํ•‘ ๋ฉ”์‹œ์ง€ ์ค„์ด๊ธฐ
5. ๋ฉ”์‹œ์ง€ ์••์ถ•: ๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท ์‚ฌ์šฉ
6. Wi-Fi ์ ˆ์ „ ๋ชจ๋“œ: ์ „์†ก ํ›„ Wi-Fi ๋„๊ธฐ
import paho.mqtt.client as mqtt
import time
import os

def send_sensor_data():
    client = mqtt.Client()
    client.connect("broker", 1883, 60)
    
    # ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ
    data = read_sensor()
    
    # ๋ฐ์ดํ„ฐ ๋ฐœํ–‰
    client.publish("sensor/data", data, qos=0)
    
    # ์—ฐ๊ฒฐ ์ข…๋ฃŒ
    client.disconnect()
    
    # Wi-Fi ๋„๊ธฐ (๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด)
    os.system("sudo ifconfig wlan0 down")
    
    # 10๋ถ„ ์Šฌ๋ฆฝ
    time.sleep(600)
    
    # Wi-Fi ์ผœ๊ธฐ
    os.system("sudo ifconfig wlan0 up")
    time.sleep(10)  # Wi-Fi ์—ฐ๊ฒฐ ๋Œ€๊ธฐ

while True:
    send_sensor_data()

์ด๋Ÿฐ ์‹์œผ๋กœ ํ•˜๋ฉด ๋ฐฐํ„ฐ๋ฆฌ ์ˆ˜๋ช…์„ ๋ช‡ ๋ฐฐ๋‚˜ ๋Š˜๋ฆด ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ์•ผ์™ธ ์„ผ์„œ ๋…ธ๋“œ์—์„œ๋Š” ํ•„์ˆ˜์ ์ธ ๊ธฐ๋ฒ•์ด์ง€! ๐ŸŒณ

๐ŸŒ ์‹ค์ œ ๋ฐฐํฌ ์‹œ ๊ณ ๋ ค์‚ฌํ•ญ

ํ”„๋กœํ† ํƒ€์ž…์„ ๋„˜์–ด์„œ ์‹ค์ œ๋กœ ์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•œ๋‹ค๋ฉด ์ด๋Ÿฐ ๊ฒƒ๋“ค์„ ๊ผญ ๊ณ ๋ คํ•ด์•ผ ํ•ด! ๐Ÿญ

โœ… ํ”„๋กœ๋•์…˜ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ๋ณด์•ˆ:
โ€ข TLS/SSL ์•”ํ˜ธํ™” ํ•„์ˆ˜
โ€ข ๊ฐ•๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ •์ฑ…
โ€ข ์ •๊ธฐ์ ์ธ ์ธ์ฆ์„œ ๊ฐฑ์‹ 
โ€ข ๋ฐฉํ™”๋ฒฝ ์„ค์ •

์•ˆ์ •์„ฑ:
โ€ข ์ž๋™ ์žฌ์—ฐ๊ฒฐ ๋กœ์ง
โ€ข ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๋ฐ ๋กœ๊น…
โ€ข ํ•˜ํŠธ๋น„ํŠธ ๋ชจ๋‹ˆํ„ฐ๋ง
โ€ข ๋ฐฑ์—… ๋ธŒ๋กœ์ปค ์„ค์ •

ํ™•์žฅ์„ฑ:
โ€ข ํ† ํ”ฝ ๋„ค์ด๋ฐ ๊ทœ์น™ ์ •๋ฆฝ
โ€ข ๋ฉ”์‹œ์ง€ ํฌ๋งท ํ‘œ์ค€ํ™”
โ€ข ๋ธŒ๋กœ์ปค ํด๋Ÿฌ์Šคํ„ฐ๋ง
โ€ข ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒค๋”ฉ

๋ชจ๋‹ˆํ„ฐ๋ง:
โ€ข ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ (Prometheus ๋“ฑ)
โ€ข ์•Œ๋ฆผ ์‹œ์Šคํ…œ ๊ตฌ์ถ•
โ€ข ๋กœ๊ทธ ์ค‘์•™ํ™”
โ€ข ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์„ฑ

๐Ÿ”„ ์ž๋™ ์žฌ์—ฐ๊ฒฐ ๊ตฌํ˜„

import paho.mqtt.client as mqtt
import time
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class RobustMQTTClient:
    def __init__(self, broker, port, username, password):
        self.broker = broker
        self.port = port
        self.username = username
        self.password = password
        self.client = None
        self.connected = False
        
    def on_connect(self, client, userdata, flags, rc):
        if rc == 0:
            self.connected = True
            logger.info("โœ… ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์„ฑ๊ณต")
            # ์žฌ์—ฐ๊ฒฐ ์‹œ ๊ตฌ๋… ๋ณต์›
            client.subscribe("sensor/#", qos=1)
        else:
            self.connected = False
            logger.error(f"โŒ ์—ฐ๊ฒฐ ์‹คํŒจ: {rc}")
    
    def on_disconnect(self, client, userdata, rc):
        self.connected = False
        logger.warning(f"โš ๏ธ ์—ฐ๊ฒฐ ๋Š๊น€: {rc}")
        if rc != 0:
            logger.info("๐Ÿ”„ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ ์ค‘...")
            self.reconnect()
    
    def reconnect(self):
        retry_count = 0
        max_retries = 10
        
        while retry_count < max_retries and not self.connected:
            try:
                logger.info(f"์žฌ์—ฐ๊ฒฐ ์‹œ๋„ {retry_count + 1}/{max_retries}")
                self.client.reconnect()
                time.sleep(5)
                retry_count += 1
            except Exception as e:
                logger.error(f"์žฌ์—ฐ๊ฒฐ ์‹คํŒจ: {e}")
                time.sleep(10)
        
        if not self.connected:
            logger.error("โŒ ์ตœ๋Œ€ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ")
    
    def connect(self):
        self.client = mqtt.Client()
        self.client.username_pw_set(self.username, self.password)
        self.client.on_connect = self.on_connect
        self.client.on_disconnect = self.on_disconnect
        
        try:
            self.client.connect(self.broker, self.port, 60)
            self.client.loop_start()
        except Exception as e:
            logger.error(f"์ดˆ๊ธฐ ์—ฐ๊ฒฐ ์‹คํŒจ: {e}")
            self.reconnect()
    
    def publish(self, topic, payload, qos=1):
        if self.connected:
            self.client.publish(topic, payload, qos=qos)
        else:
            logger.warning("โš ๏ธ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์Œ. ๋ฉ”์‹œ์ง€ ๋Œ€๊ธฐ ์ค‘...")
            # ์—ฌ๊ธฐ์„œ ๋ฉ”์‹œ์ง€๋ฅผ ํ์— ์ €์žฅํ–ˆ๋‹ค๊ฐ€ ์žฌ์—ฐ๊ฒฐ ์‹œ ์ „์†ก ๊ฐ€๋Šฅ

# ์‚ฌ์šฉ ์˜ˆ
robust_client = RobustMQTTClient("broker", 1883, "user", "pass")
robust_client.connect()

while True:
    robust_client.publish("sensor/data", "test")
    time.sleep(10)

์ด๋Ÿฐ ์‹์œผ๋กœ ๊ฒฌ๊ณ ํ•œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด ๋„คํŠธ์›Œํฌ๊ฐ€ ๋ถˆ์•ˆ์ •ํ•ด๋„ ์•ˆ์ •์ ์œผ๋กœ ๋™์ž‘ํ•ด! ๐Ÿ›ก๏ธ

๐Ÿ“š ํ•™์Šต ๋ฆฌ์†Œ์Šค ๋ฐ ์ปค๋ฎค๋‹ˆํ‹ฐ

MQTT์™€ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋ฅผ ๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฆฌ์†Œ์Šค๋“ค์„ ํ™œ์šฉํ•ด๋ด! ๐Ÿ“–

๐ŸŒŸ ์ถ”์ฒœ ํ•™์Šต ์ž๋ฃŒ ๊ณต์‹ ๋ฌธ์„œ:
โ€ข MQTT.org - ๊ณต์‹ ์ŠคํŽ™ ๋ฌธ์„œ
โ€ข Eclipse Paho - ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฌธ์„œ
โ€ข Raspberry Pi Documentation - ๊ณต์‹ ๊ฐ€์ด๋“œ

์˜จ๋ผ์ธ ๊ฐ•์˜:
โ€ข Udemy - IoT ๊ด€๋ จ ๊ฐ•์ขŒ
โ€ข Coursera - Raspberry Pi ํ”„๋กœ์ ํŠธ
โ€ข YouTube - ๋‹ค์–‘ํ•œ ํŠœํ† ๋ฆฌ์–ผ

์ปค๋ฎค๋‹ˆํ‹ฐ:
โ€ข Reddit r/raspberry_pi
โ€ข Stack Overflow - MQTT ํƒœ๊ทธ
โ€ข ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด ๊ณต์‹ ํฌ๋Ÿผ
โ€ข ๊ตญ๋‚ด ๋ฉ”์ด์ปค ์ปค๋ฎค๋‹ˆํ‹ฐ

ํ”„๋กœ์ ํŠธ ๊ณต์œ :
โ€ข GitHub - ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ
โ€ข Hackster.io - IoT ํ”„๋กœ์ ํŠธ ๊ฐค๋Ÿฌ๋ฆฌ
โ€ข Instructables - DIY ๊ฐ€์ด๋“œ
โ€ข ์žฌ๋Šฅ๋„ท - ์žฌ๋Šฅ ๊ณต์œ  ๋ฐ ๊ฑฐ๋ž˜

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

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

์ž, ์—ฌ๊ธฐ๊นŒ์ง€ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์™€ MQTT๋ฅผ ํ™œ์šฉํ•œ ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ „์†ก์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์–ด! ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์ง€? ๐Ÿ˜Š

์ฒ˜์Œ์—๋Š” ๋ณต์žกํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ๋”ฐ๋ผ ํ•˜๋‹ค ๋ณด๋ฉด ์ƒ๊ฐ๋ณด๋‹ค ์‰ฝ๋‹ค๋Š” ๊ฑธ ์•Œ๊ฒŒ ๋  ๊ฑฐ์•ผ. MQTT์˜ Pub/Sub ๋ชจ๋ธ์€ ์ •๋ง ์ง๊ด€์ ์ด๊ณ , ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋Š” ์„ผ์„œ ์—ฐ๊ฒฐ์ด ๋„ˆ๋ฌด ์‰ฌ์›Œ์„œ ๊ธˆ๋ฐฉ ์ต์ˆ™ํ•ด์งˆ ๊ฑฐ์•ผ! ๐Ÿ“

๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฑด ์ง์ ‘ ํ•ด๋ณด๋Š” ๊ฒƒ์ด์•ผ. ๊ฐ„๋‹จํ•œ ์˜จ๋„ ์„ผ์„œ ํ•˜๋‚˜๋กœ ์‹œ์ž‘ํ•ด์„œ, ์ ์  ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๊ฐ€๋ฉด์„œ ์ž์‹ ๋งŒ์˜ IoT ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ด. ์‹คํŒจํ•ด๋„ ๊ดœ์ฐฎ์•„! ๊ทธ๊ฒŒ ๋ฐ”๋กœ ๋ฐฐ์šฐ๋Š” ๊ณผ์ •์ด๋‹ˆ๊นŒ. ๐Ÿ’ช

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

ํ˜น์‹œ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๊ฑฐ๋‚˜ ๋” ๋ฐฐ์šฐ๊ณ  ์‹ถ์€ ๊ฒŒ ์žˆ๋‹ค๋ฉด, ์˜จ๋ผ์ธ ์ปค๋ฎค๋‹ˆํ‹ฐ๋‚˜ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์„ ์ ๊ทน ํ™œ์šฉํ•ด๋ด. ํ•จ๊ป˜ ๋ฐฐ์šฐ๊ณ  ์„ฑ์žฅํ•˜๋Š” ๊ฒŒ ํ›จ์”ฌ ์žฌ๋ฏธ์žˆ๊ฑฐ๋“ ! ๐ŸŒŸ

๊ทธ๋Ÿผ ๋ชจ๋‘ ์ฆ๊ฑฐ์šด ๋ฉ”์ดํ‚น ๋˜๊ธธ ๋ฐ”๋ผ! ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฐฝ์˜์ ์ธ IoT ํ”„๋กœ์ ํŠธ๋ฅผ ๊ธฐ๋Œ€ํ• ๊ฒŒ! ํ™”์ดํŒ…! ๐ŸŽ‰โœจ

๐Ÿ“ Happy Making with Raspberry Pi & MQTT! ๐ŸŒ
์„ผ์„œ ๋ฐ์ดํ„ฐ๋กœ ์„ธ์ƒ์„ ๋” ์Šค๋งˆํŠธํ•˜๊ฒŒ! ๐Ÿ’ก

๋Œ“๊ธ€ ์ž‘์„ฑ

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

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.