โ† Back to list

๐Ÿงฉ ์ „๊ธฐ์žฅํŒ์„ ์• ํ”Œ ํ™ˆ์— ์—ฐ๊ฒฐํ•ด๋ณด์žโ€Šโ€”โ€Š2ํŽธ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐœ๋ฐœ

Homebridge ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฐœ๋ฐœํ•ด๋ณด์ž!

Seongwoo Jun ยท 2024-11-24 08:10 ยท 10 claps ยท 31.6 min read
#homebridge #smart-home #smart-home-automation #pub-sub #rxjs
Open on Medium โ†—

๐Ÿงฉ ์ „๊ธฐ์žฅํŒ์„ ์• ํ”Œ ํ™ˆ์— ์—ฐ๊ฒฐํ•ด๋ณด์ž โ€” 2ํŽธ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐœ๋ฐœ

Homebridge ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฐœ๋ฐœํ•ด๋ณด์ž!

์ด์ „ ํŽธโ€” ๋ฆฌ๋ฒ„์‹ฑํŽธ

์•ˆ๋…•ํ•˜์„ธ์š”, ์Šค๋งˆํŠธ ํ™ˆ ์ž๋™ํ™”์— ์ง„์‹ฌ์ธ ๊ฐœ๋ฐœ์ž ์ „์„ฑ์šฐ์ž…๋‹ˆ๋‹ค.

[embed]๐Ÿงฉ ์ „๊ธฐ์žฅํŒ์„ ์• ํ”Œ ํ™ˆ์— ์—ฐ๊ฒฐํ•ด๋ณด์ž โ€” 1ํŽธ ๋ฆฌ๋ฒ„์‹ฑ ์ง์ ‘ Homebridge ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฐœ๋ฐœํ•ด๋ณด์ž!medium.com

์ด์ „ ํŽธ์—์„  ์Šค๋งˆํŠธํฐ ์•ฑ์„ ๋ถ„์„ํ•ด์„œ ์ „๊ธฐ์žฅํŒ์„ ์ฝ”๋“œ๋กœ ์ œ์–ดํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ƒˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ํŽธ์—์„  ์•Œ์•„๋‚ธ ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์• ํ”Œ ํ™ˆ์ด ์ „๊ธฐ์žฅํŒ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ํ™ˆ๋ธŒ๋ฆฟ์ง€ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฐœ๋ฐœํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ™ˆ๋ธŒ๋ฆฟ์ง€๋ž€?

์ด์ „ ํŽธ์—์„œ๋„ ์„ค๋ช…ํ–ˆ์ง€๋งŒ ๋‹ค์‹œ ๊ฐ„๋‹จํžˆ ์„ค๋ช…๋“œ๋ฆฌ์ž๋ฉด, ํ™ˆ๋ธŒ๋ฆฟ์ง€๋ž€ node.js ๊ธฐ๋ฐ˜์œผ๋กœ Apple HomeKit๊ณผ ์‰ฝ๊ฒŒ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ”„๋กœ๊ทธ๋žจ์ž…๋‹ˆ๋‹ค.

ํ™ˆ๋ธŒ๋ฆฟ์ง€๋ฅผ ์„ค์น˜ํ•˜๊ณ  ๋‚˜๋ฉด ์œ„์™€ ๊ฐ™์ด UI์—์„œ ์›ํ•˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ”Œ๋Ÿฌ๊ทธ์ธ๋“ค์€ ๊ธฐ์—…/๋‹จ์ฒด์—์„œ ๊ณต์‹์œผ๋กœ ๋ฐฐํฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ณ  ๊ฐœ์ธ ๊ฐœ๋ฐœ์ž๋“ค์ด ์ง์ ‘ ๊ฐœ๋ฐœํ•˜๊ณ  ๋ฐฐํฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ €๋„ ๊ฐœ๋ฐœ์ž๋กœ์„œ, ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฐœ๋ฐœํ•ด๋ณด์ž ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

[embed]Homebridge Plugin Developer Documentation Homebridge plugin developer documentation and API reference.developers.homebridge.io

ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐœ๋ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ฒ€์ƒ‰ํ•ด๋ณด๋‹ˆ ์œ„ ๊ณต์‹ ๊ฐœ๋ฐœ ๋ฌธ์„œ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ ํ…œํ”Œ๋ฆฟ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋ฅผ ํฌํฌํ•ด์„œ ํ™ˆ๋ธŒ๋ฆฟ์ง€ API์— ๋งž๊ฒŒ ์ฝ”๋“œ๋ฅผ ์งœ์ฃผ๋ฉด ๋œ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ ๊ฐœ๋ฐœํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•ด๋ด…์‹œ๋‹ค.

์ค€๋น„ 1๋‹จ๊ณ„ โ€” ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์ถ•ํ•˜๊ธฐ

[embed]GitHub - homebridge/homebridge-plugin-template: A template you can use to create your ownโ€ฆ A template you can use to create your own Homebridge plugins. - homebridge/homebridge-plugin-templategithub.com

๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„  ์œ„ ํ…œํ”Œ๋ฆฟ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์˜ README์— ์ž์„ธํžˆ ์ ํ˜€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ธ€์„ ์ฝ์–ด๋ณด๋‹ˆ ๊ฐœ๋ฐœํ•˜๊ธฐ ๋ฒˆ๊ฑฐ๋กœ์šธ ๊ฒƒ ๊ฐ™์€ ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Link To Homebridge ์„ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜๋ผ๊ณ  ์ ํ˜€ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. npm link๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ์ค‘์ธ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ธ€๋กœ๋ฒŒ๋กœ ์„ค์น˜ํ•œ๋‹ค.
  2. homebridge ์ธ์Šคํ„ด์Šค๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๋Š” ํ™ˆ๋ธŒ๋ฆฟ์ง€๊ฐ€ ์„ค์น˜๋œ ๊ธฐ๊ธฐ์™€ ํ”Œ๋Ÿฌ๊ทธ์ธ๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๊ธฐ๊ธฐ๊ฐ€ ๊ฐ™์•„์•ผ ํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์ €๋Š” ํ™ˆ๋ธŒ๋ฆฟ์ง€๋Š” ์‹œ๋†€๋กœ์ง€ ๋‚˜์Šค์— ์„ค์น˜ํ–ˆ๊ณ , ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐœ๋ฐœ์€ ์ œ ๋งฅ๋ถ์—์„œ ํ•˜๋ ค๊ณ  ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ๋งž์ถฐ์ค„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ฅ

๊ฐ€์žฅ ์‹ฌํ”Œํ•œ ํ•ด๊ฒฐ์ฑ…์€ ์‹คํ–‰ํ•  ๋•Œ ๋งˆ๋‹ค scp๋ฅผ ํ•˜๊ฑฐ๋‚˜ git push & pull์„ ํ•ด์„œ ๋ฐฐํฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๊ธด ํ•ฉ๋‹ˆ๋‹ค๋งŒ, ๋งค์šฐ ๋ฒˆ๊ฑฐ๋กœ์šธ ๊ฒƒ์ด ๋ป”ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ƒ๊ฐํ•œ ๋ฐฉ๋ฒ•์€ ์‹ค์ œ ์‹œ๋†€๋กœ์ง€ ๋‚˜์Šค๋Š” ๋‚ฉ๋‘๊ณ , ์ œ ๋งฅ๋ถ์—๋‹ค๊ฐ€ ๋ณ„๋„์˜ ํ™ˆ๋ธŒ๋ฆฟ์ง€๋ฅผ ์„ค์น˜ํ•ด ๊ฐœ๋ฐœํ•˜๋Š” ๋™์•ˆ ์ œ ๋งฅ๋ถ์„ ํ™ˆ๋ธŒ๋ฆฟ์ง€ ์„œ๋ฒ„๋กœ์จ ๋™์ž‘์‹œํ‚ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

$ npm install -g homebridge homebridge-config-ui-x

ํ™ˆ๋ธŒ๋ฆฟ์ง€ ์„ค์น˜๋Š” ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. npm ํŒจํ‚ค์ง€๋ฅผ ๊ธ€๋กœ๋ฒŒ๋กœ ์„ค์น˜ํ•ด์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. (์ฐธ๊ณ ๋กœ homebridge-config-ui-x ํŒจํ‚ค์ง€๋Š” ํ™ˆ๋ธŒ๋ฆฟ์ง€ UI์— ๋Œ€ํ•œ ํŒจํ‚ค์ง€๋กœ ํ™ˆ๋ธŒ๋ฆฟ์ง€๊ฐ€ ์‹คํ–‰ ์ค‘์— localhost:8581๋กœ ์ ‘๊ทผํ•˜๋ฉด ์›น UI๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.)

์ด์ œ ์ œ ๋งฅ๋ถ์—์„œ ํ”Œ๋Ÿฌ๊ทธ์ธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.๐Ÿฅณ

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ์„œ ๋์ด ์•„๋‹ˆ๊ณ  ์ด์ œ ๋””๋ฒ„๊น… ํ™˜๊ฒฝ์„ ๊ฐ–์ถฐ์•ผํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ๊ฐœ๋ฐœ์„ ํ•  ๋•Œ, IDE(์ฃผ๋กœ VS Code)์—์„œ ๋””๋ฒ„๊น… ๋ชจ๋“œ๋กœ ์‹คํ–‰(ํ˜น์€ Attach)ํ•˜์—ฌ ๋ธŒ๋ ˆ์ดํ‚น ํฌ์ธํŠธ๋ฅผ ์žก๊ฑฐ๋‚˜, ๋ณ€์ˆ˜์˜ ๊ฐ’์„ ํ™•์ธํ•˜๋Š” ๋“ฑ์˜ ๋””๋ฒ„๊น… ํ™˜๊ฒฝ์„ ๊ฐ–์ถ”๋Š” ๊ฒƒ์ด ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์ธ ๊ฐœ๋ฐœ(ํ”„๋ก ๋“œ์—”๋“œ or ๋ฐฑ์—”๋“œ)์—์„œ์˜ ๋””๋ฒ„๊น… ํ™˜๊ฒฝ์„ ๊ฐ–์ถ”๋Š” ๊ฒƒ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํŠน์ˆ˜ํ•œ ์ƒํ™ฉ์—์„œ๋Š” ๊ฐ–์ถ”๊ธฐ๊ฐ€ ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค. ๋‹คํ–‰ํžˆ๋„ ๊ด€๋ จํ•ด์„œ ์ฐพ์•„๋ณด๋‹ˆ ๋น„๊ต์  ๊ฐ„๋‹จํžˆ ๋””๋ฒ„๊น… ํ™˜๊ฒฝ์„ ๊ฐ–์ถœ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜ ๋‘ ๊ธ€์„ ์ฐธ๊ณ ํ•ด์„œ VS Code์—์„œ ๋””๋ฒ„๊น… ํ™˜๊ฒฝ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค

**lanch.json**

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "name": "Debug Homebridge",
      "program": "${workspaceFolder}/node_modules/.bin/homebridge",
      "preLaunchTask": "tsc: build - tsconfig.json",
      "internalConsoleOptions": "neverOpen",
      "args": ["-I", "-D"],
      "request": "launch",
      "skipFiles": [
        "<node_internals>/**"
      ],
    }
  ]
}

program์— ์„ค์น˜ํ•œ ํ™ˆ๋ธŒ๋ฆฟ์ง€ ํ”„๋กœ๊ทธ๋žจ์˜ ์ฃผ์†Œ๋ฅผ ์ ์–ด์ค๋‹ˆ๋‹ค. ($ which homebridge)

์ฐธ๊ณ ๋กœ ์ €๋Š” ๊ธ€๋กœ๋ฒŒ๋กœ ์„ค์น˜ํ•˜์ง€ ์•Š๊ณ  devDependencies์— homebridge์™€ homebridge-config-ui-x๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  node_modules์— ์žˆ๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฃผ์†Œ๋ฅผ ๋„ฃ์–ด์คฌ์Šต๋‹ˆ๋‹ค.

**tasks.json**

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "tsc: build - tsconfig.json",
            "type": "typescript",
            "tsconfig": "tsconfig.json",
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": [
                "$tsc"
            ]
        }
    ]
}

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ”„๋กœ์ ํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์‹คํ–‰ํ•˜๊ธฐ ์ „ ๋ฌด์กฐ๊ฑด ๋นŒ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋นŒ๋“œ ํƒœ์Šคํฌ๋„ ์ถ”๊ฐ€ํ•ด์คฌ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•ด์„œ VS Code์—์„œ Ctrl + F5 ๋กœ ํ™ˆ๋ธŒ๋ฆฟ์ง€ ์„œ๋ฒ„์™€ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‹คํ–‰์‹œํ‚ค๊ณ , ๋ธŒ๋ ˆ์ดํฌ ํฌ์ธํŠธ ์žก์„ ์ˆ˜ ์žˆ๋Š” ๋“ฑ ๊ฐœ๋ฐœ + ๋””๋ฒ„๊น… ํ™˜๊ฒฝ์„ ๋ชจ๋‘ ๊ฐ–์ถ”์—ˆ์Šต๋‹ˆ๋‹ค.

(์ฐธ๊ณ ๋กœ ๋ณ„๋„์˜ ํ™ˆ๋ธŒ๋ฆฟ์ง€ ์„œ๋ฒ„๊ธฐ ๋•Œ๋ฌธ์—, ์ตœ์ดˆ ํ•œ ๋ฒˆ localhost:8581์„ ์ผœ๊ณ  ์• ํ”Œ ํ™ˆ์—์„œ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.)

์ค€๋น„ 2๋‹จ๊ณ„ โ€” ํ™ˆ๋ธŒ๋ฆฟ์ง€ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์กฐ ํŒŒ์•…ํ•˜๊ธฐ

[embed]Homebridge Plugin Developer Documentation Homebridge plugin developer documentation and API reference.developers.homebridge.io

๋ญ๋“ ์ง€ ๊ฐœ๋ฐœ์„ ํ•˜๊ธฐ ์•ž์„œ ๋ณธ์ธ์ด ๊ฐœ๋ฐœํ•˜๋ ค๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์— ๋Œ€ํ•ด ์ž˜ ์ดํ•ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ™ˆ๋ธŒ๋ฆฟ์ง€ ํ”Œ๋Ÿฌ๊ทธ์ธ์— ๋Œ€ํ•œ ์„ค๋ช…์€ ์œ„ ์‚ฌ์ดํŠธ์—์„œ ์˜ˆ์‹œ ์ฝ”๋“œ ๋“ฑ์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ ์‚ฌ์‹ค ์ด๊ฒƒ๋งŒ ๋ณด๊ณ  ์ดํ•ดํ•˜๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ œ๊ฐ€ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๋А๋‚€ ๊ตฌ์กฐ๋ฅผ ๋ณด๊ธฐ ์‰ฝ๊ฒŒ ๋‹ค์ด์–ด๊ทธ๋žจ์œผ๋กœ ์ •๋ฆฌํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

[embed]

  • ํ”Œ๋žซํผ ํ”Œ๋Ÿฌ๊ทธ์ธ (Platform Plugin) ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์‹คํ–‰๋˜๋Š” ์ตœ์ƒ๋‹จ ์—”ํŠธ๋ฆฌ ํฌ์ธํŠธ๋กœ์จ, ์บ์‹œ๋œ ์—‘์„ธ์„œ๋ฆฌ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ฑฐ๋‚˜ ๋™์ (API ํ˜ธ์ถœ ๋“ฑ)์œผ๋กœ ๊ธฐ๊ธฐ๋ฅผ ๊ฒ€์ƒ‰/๋ธŒ๋ผ์šฐ์ง• ํ•œ ํ›„ ์ด๋ฅผ ํ”Œ๋žซํผ ์—‘์„ธ์„œ๋ฆฌ ๊ฐ์ฒด๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • ์•ก์„ธ์„œ๋ฆฌ (Accessory) ๊ธฐ๊ธฐ ํ•˜๋‚˜์— ๋Œ€์‘๋Œ€๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์Šค๋งˆํŠธ ์ „๊ตฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด๋ผ๋ฉด ์ง‘ ์•ˆ์˜ ์—ฌ๋Ÿฌ ์ „๊ตฌ ํ•˜๋‚˜ํ•˜๋‚˜ ๋งˆ๋‹ค ์•ก์„ธ์„œ๋ฆฌ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. (๊ธฐ๊ธฐ๋งˆ๋‹ค ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ณ ์œ  ์•„์ด๋”” ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.)
  • ์„œ๋น„์Šค (Service) ๊ด€๋ จ๋œ ์—ฌ๋Ÿฌ ํŠน์„ฑ/์ •๋ณด(์บ๋ฆญํ„ฐ๋ฆฌ์Šคํ‹ฑ)์— ๋Œ€ํ•œ ๊ทธ๋ฃน์œผ๋กœ์จ, ์—‘์„ธ์„œ๋ฆฌ ํ•˜๋‚˜์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์„œ๋น„์Šค๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ถˆ๋น›์ด ๋‚˜๋Š” ์„ ํ’๊ธฐ๋ž€ ์—‘์„ธ์„œ๋ฆฌ๋Š” LightBulb ์„œ๋น„์Šค๋กœ ๋ถˆ๋น› ์ œ์–ด, Fan ์„œ๋น„์Šค๋กœ ํ’ํ–ฅ, ํ’์† ์ œ์–ด ๋“ฑ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • ์บ๋ฆญํ„ฐ๋ฆฌ์Šคํ‹ฑ (Characteristic) ์„œ๋น„์Šค๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํŠน์„ฑ/์ •๋ณด ๋“ค์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Fan ์„œ๋น„์Šค์—๋Š” [On](https://developers.homebridge.io/#/characteristic/On) (boolean), [Name](https://developers.homebridge.io/#/characteristic/Name) (string), [Rotation Direction](https://developers.homebridge.io/#/characteristic/RotationDirection) (enum), [Rotation Speed](https://developers.homebridge.io/#/characteristic/RotationSpeed) (float) ์˜ ์บ๋ฆญํ„ฐ๋ฆฌ์Šคํ‹ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์บ๋ฆญํ„ฐ๋ฆฌ์Šคํ‹ฑ์€ ๊ฐ๊ฐ์˜ ์บ๋ฆญํ„ฐ๋ฆฌ์Šคํ‹ฑ๋งˆ๋‹ค onGet, onSet ๋ผ๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์žˆ๋Š”๋ฐ, onGet์€ ์• ํ”Œ ํ™ˆ ์•ฑ์ด ์ผœ์งˆ ๋•Œ์ฒ˜๋Ÿผ ์ •๋ณด๋ฅผ ์š”์ฒญํ•  ๋•Œ ๋ถˆ๋ฆฌ๊ณ , onSet์€ ์• ํ”Œ ํ™ˆ์—์„œ ์กฐ์ž‘์„ ํ•  ๋•Œ ๊ฐ™์ด ๊ธฐ๊ธฐ๋ฅผ ์ œ์–ดํ•  ๋•Œ ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” onGet ์—์„  ๊ธฐ๊ธฐ์˜ ์ƒํƒœ๋ฅผ ์ฝ์–ด์„œ ๋ฆฌํ„ดํ•˜๊ณ , onSet์—์„  API๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ๋งŒ ํ•˜๋„๋ก ๊ตฌํ˜„ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ 1๋‹จ๊ณ„ โ€” ๊ฐ€์งœ๋กœ ๋™์ž‘ํ•˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ ๋งŒ๋“ค๊ธฐ

์‹ค์ œ API์™€ ์†Œํ†ตํ•˜์ง€ ์•Š๊ณ  ๊ฐ€์งœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜ํ•˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์กฐ

์‹ค์ œ API์™€ ์†Œํ†ตํ•˜์ง€ ์•Š๊ณ  ๊ฐ€์งœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜ํ•˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์กฐ

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

์ด๋ ‡๊ฒŒ ๋ ˆ์ด์–ด๋ฅผ ๋‚˜๋ˆ ์•ผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ๋„ ์‰ฝ๊ณ , ๊ฐœ๋ฐœ ์ค‘ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ถ€๋ถ„์ด ๋ฌธ์ œ์ธ์ง€, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ถ€๋ถ„์ด ๋ฌธ์ œ์ธ์ง€ ์•Œ๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ์— ๊ฐœ๋ฐœํ•œ๋‹ค๋ฉด ์–ด๋””์„œ ๋ฌธ์ œ์ธ์ง€ ์•Œ๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š์ฃ . ๋งˆ์ง€๋ง‰์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๊ณ  ๋‚˜๋ฉด ๊ฐ€์งœ ๋ถ€๋ถ„์„ ์‹ค์ œ ์„œ๋น„์Šค๋กœ ๊ฐˆ์•„์น˜์šฐ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ด์ œ PlatformPlugin๋ฅผ ๊ตฌํ˜„ํ•ด๋ด…์‹œ๋‹ค.

export class NavienHomebridgePlatform implements DynamicPlatformPlugin {
  constructor(
    public readonly log: Logging,
    config: PlatformConfig,
    public readonly api: API,
  ) {
    // ...
    this.api.on('didFinishLaunching', this.onLaunched.bind(this));
  }

  async onLaunched() {
    this.log.info('onLaunched called');

    // run the method to discover / register your devices as accessories
    const devices = this.service.getDevices();
    for (const device of devices) {
      this._registerDeviceAsAccessory(device);
    }
  }

  private _registerDeviceAsAccessory(device: NavienDevice) {
    this.log.info('Adding new accessory:', device.name);

    const uuid = this.api.hap.uuid.generate(device.id);
    const accessory = new this.api.platformAccessory<NavienDeviceContext>(device.name, uuid);
    accessory.context.device = device;

    new ElectricMat(this, accessory);

    this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
  }
}

(๋Œ€๋ถ€๋ถ„ ํ…œํ”Œ๋ฆฟ์—์„œ ํฌํฌํ•œ ์ฝ”๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ค‘์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๊ฐ€์ ธ์™€๋ดค์Šต๋‹ˆ๋‹ค.)

ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์‹คํ–‰๋˜์—ˆ์„ ๋•Œ, ๊ธฐ๊ธฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์ด๋ฅผ ์•ก์„ธ์„œ๋ฆฌ๋กœ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์šฐ์„  API ํ˜ธ์ถœ์„ ํ•˜์ง€ ์•Š๊ณ  service.getDevices()์—์„œ ํ•˜๋“œ ์ฝ”๋”ฉ๋œ ๊ธฐ๊ธฐ ๋ชฉ๋ก์„ ๋ฆฌํ„ดํ•˜๋„๋ก ํ•ฉ์‹œ๋‹ค.

์ด์ œ ์ƒ์„ฑ๋œ Accessory๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ElectricMat ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค.

export default class ElectricMat {
  constructor(
    private readonly platform: NavienHomebridgePlatform,
    private readonly accessory: NavienPlatformAccessory,
  ) {
    const { device } = accessory.context;

    // set accessory information
    this.accessory.getService(Service.AccessoryInformation)!
      .setCharacteristic(Characteristic.Manufacturer, 'Navien')
      .setCharacteristic(Characteristic.Model, device.modelName)
      .setCharacteristic(Characteristic.SerialNumber, device.id);

    // TODO: implement
  }
}

๊ธฐ๋ณธ์ ์œผ๋กœ ํ•„์š”ํ•œ [AccessoryInformation](https://developers.homebridge.io/#/service/AccessoryInformation) ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด ๊ธฐ๊ธฐ์˜ ์ •๋ณด๋“ค์„ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค. ๋„ฃ์–ด์ค€ ์ •๋ณด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์• ํ”Œ ํ™ˆ์—์„œ ๊ธฐ๊ธฐ์˜ ์ƒ์„ธ ์ •๋ณด ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ 1โ€“2๋‹จ๊ณ„ โ€” ๊ธฐ๊ธฐ์˜ ์นดํ…Œ๊ณ ๋ฆฌ ์„ค์ •ํ•˜๊ธฐ

[embed]Homebridge Plugin Developer Documentation Homebridge plugin developer documentation and API reference.developers.homebridge.io

์• ํ”Œ ํ™ˆ์—์„  ๋‹ค์–‘ํ•œ ๊ธฐ๊ธฐ์˜ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ „๊ตฌ๐Ÿ’ก๋‚˜ ์Šค์œ„์น˜๐Ÿ”Œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ๋„์–ด๋ฒจ๐Ÿ””, ๋„์–ด๋ฝ๐Ÿ”’์ด๋ผ๋“ ์ง€, ์‹ฌ์ง€์–ด ์ƒค์›Œ ํ—ค๋“œ๐Ÿšฟ (โ‰๏ธ)ํƒ€์ž…๋„ ์žˆ์Šต๋‹ˆ๋‹ค! ๐Ÿซข

์ „๊ธฐ ์žฅํŒ์€ ์–ด๋–ค ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์ ํ•ฉํ•  ์ง€ ์ฐพ์•„๋ณด๋‹ˆ, ์˜จ๋„ ์กฐ์ ˆ ๊ด€๋ จํ•œ ๊ธฐ๊ธฐ ์นดํ…Œ๊ณ ๋ฆฌ๋Š” [HeaterCooler](https://developers.homebridge.io/#/service/HeaterCooler)์™€ [Thermostat](https://developers.homebridge.io/#/service/Thermostat)์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋ฌธ์„œ๋ฅผ ์ฝ์–ด๋ณด๋‹ˆ HeaterCooler๋Š” ์—์–ด์ปจ์ธ๋ฐ, ํŠน์ • ์˜จ๋„๋กœ ์„ค์ •ํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋ƒ‰/๋‚œ๋ฐฉ๋งŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๊ธฐ, Thermostat์€ ๋ณด์ผ๋Ÿฌ๋‚˜ ์ง‘์•ˆ ์˜จ๋„์กฐ์ ˆ๊ธฐ์ฒ˜๋Ÿผ ํŠน์ • ์˜จ๋„๋ฅผ ์„ค์ •ํ•˜๋ฉด ๊ทธ์— ๋”ฐ๋ผ ๋ƒ‰/๋‚œ๋ฐฉ์ด ๋™์ž‘ํ•˜๋Š” ๊ธฐ๊ธฐ๋กœ ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ Thermostat์ด ๋” ์ ํ•ฉํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

// create thermostat service
const thermostat = this.accessory.getService(Service.Thermostat)
  || this.accessory.addService(Service.Thermostat);

์œ„์™€ ๊ฐ™์ด Thermostat ์„œ๋น„์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์—‘์„ธ์„œ๋ฆฌ๊ฐ€ ์ฒ˜์Œ ์ƒ์„ฑ๋  ๋• getService()๊ฐ€ undefined๋ฅผ ๋ฆฌํ„ดํ•˜๊ณ , addService()๋ฅผ ํ†ตํ•ด์„œ ์„œ๋น„์Šค๊ฐ€ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์บ์‹ฑ์ด ๋œ ์ดํ›„๋กœ๋ถ€ํ„ด getService()๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค.

Thermostat์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ 5๊ฐœ์˜ ์บ๋ฆญํ„ฐ๋ฆฌ์Šคํ‹ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ๋ชจ๋‘ ๊ตฌํ˜„ํ•ด์ค˜์•ผ ํ•˜๋Š”๋ฐ, ๊ทธ ์ „์— ์ž ๊น ๊ฐ๊ฐ ์บ๋ฆญํ„ฐ๋ฆฌ์Šคํ‹ฑ์˜ onGet, onSet ์—์„œ ๋ถˆ๋ฆด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ชจ๋‘ ์ •์˜ํ•ด์ค์‹œ๋‹ค.

export default class ElectricMat {
  private async getHeatingState(): Promise<CharacteristicValue> {
    const { isActive } = this.device;
    const state = isActive ? Characteristic.CurrentHeatingCoolingState.HEAT : Characteristic.CurrentHeatingCoolingState.OFF;
    return state;
  }

  private async setHeatingState(value: CharacteristicValue) {
    const state = value as number;
    const isActive = !!state;
    this.device.isActive = isActive;
  }

  private async getTemperature(): Promise<CharacteristicValue> {
    const { temperature } = this.device;
    return temperature;
  }

  private async setTemperature(value: CharacteristicValue) {
    const temperature = value as number;
    this.device.temperature = temperature;
  }
}

ํ˜„์žฌ๋Š” ๋‹จ์ˆœํžˆ device์˜ ํ•„๋“œ ๊ฐ’์„ ์ฝ๊ณ  ์“ฐ๊ธฐ๋งŒ ํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ž‘์„ฑํ•ฉ์‹œ๋‹ค. ์ฐธ๊ณ ๋กœ ์ถ”ํ›„์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ API ์š”์ฒญ์„ ๋ณด๋‚ผ ๊ฒƒ์ด๋ฏ€๋กœ ๋ชจ๋‘ async ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์–ด ๋†“์Šต๋‹ˆ๋‹ค.

์ด์ œ ๊ฐ๊ฐ์˜ ์บ๋ฆญํ„ฐ๋ฆฌ์Šคํ‹ฑ์—๋‹ค๊ฐ€ ์ž‘์„ฑํ•œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋„ฃ์–ด์ค์‹œ๋‹ค.

// current state
thermostat.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
  .setProps({
    validValues: [
      Characteristic.CurrentHeatingCoolingState.OFF,
      Characteristic.CurrentHeatingCoolingState.HEAT,
    ],
  })
  .onGet(this.getHeatingState.bind(this));
// target state
thermostat.getCharacteristic(Characteristic.TargetHeatingCoolingState)
  .setProps({
    validValues: [
      Characteristic.TargetHeatingCoolingState.OFF,
      Characteristic.TargetHeatingCoolingState.HEAT,
    ],
  })
  .onGet(this.getHeatingState.bind(this))
  .onSet(this.setHeatingState.bind(this));

์ฒซ๋ฒˆ์งธ HeatingCoolingState๋Š” ํ˜„์žฌ ์„ค์ •๋œ ๋ƒ‰/๋‚œ๋ฐฉ ์ƒํƒœ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์ „๊ธฐ์žฅํŒ์€ (๋‹น์—ฐํ•˜๊ฒŒ๋„) ๋ƒ‰๋ฐฉ์„ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋‹ˆ๊นŒ validValues์— OFF์™€ HEAT ๋งŒ์„ ์ฃผ์–ด์„œ ๋„๊ธฐ/๋‚œ๋ฐฉ๋งŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋กœ Current์™€ Target ์˜ ์ฐจ์ด๋Š” ์ž๋™ ๋ƒ‰/๋‚œ๋ฐฉ์„ ๋ชจ๋‘ ์ง€์›ํ•˜๋Š” ๊ธฐ๊ธฐ๋ฅผ ์œ„ํ•œ ์˜ต์…˜์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Target์„ ์ž๋™(TargetHeatingCoolingState.AUTO)์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ๊ธฐ๊ธฐ๋Š” ์„ค์ •๋œ ์˜จ๋„์— ๋”ฐ๋ผ ์•Œ์•„์„œ ๋ƒ‰/๋‚œ๋ฐฉ์„ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ๋•Œ ํ˜„์žฌ ๋™์ž‘ํ•˜๊ณ  ์žˆ๋Š” ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ํŠน์„ฑ์ด Current์ž…๋‹ˆ๋‹ค. (๋‹น์—ฐํ•˜๊ฒŒ๋„CurrentHeatingCoolingState๋Š” AUTO๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.)

์šฐ๋ฆฌ๋Š” ํ•ด๋‹น ์‚ฌํ•ญ์ด ์•„๋‹ˆ๋ฏ€๋กœ ๋‘ ํŠน์„ฑ์„ ๋˜‘๊ฐ™์ด ์ฒ˜๋ฆฌํ•ฉ์‹œ๋‹ค.

// current temperature
thermostat.getCharacteristic(Characteristic.CurrentTemperature)
  .onGet(this.getTemperature.bind(this));

๋‘๋ฒˆ์งธ CurrentTemperature๋Š” ํ˜„์žฌ ์ธก์ •๋˜๋Š” ์˜จ๋„(์‹ค๋‚ด ์˜จ๋„)๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์‹ค๋‚ด์˜จ๋„ ์ •๋ณด๋„ ์žˆ๋Š” ์—์–ด์ปจ๊ณผ ๋‹ฌ๋ฆฌ ์ „๊ธฐ์žฅํŒ์—๋Š” ๊ทธ๋Ÿฐ ๊ธฐ๋Šฅ์ด ์—†์œผ๋ฏ€๋กœ ๊ทธ๋ƒฅ ํ˜„์žฌ ์„ค์ •๋œ ์˜จ๋„๋ฅผ ๋ฆฌํ„ดํ•˜๋„๋ก ํ•ฉ์‹œ๋‹ค. (์ด๋กœ ์ธํ•ด ์• ํ”Œ ํ™ˆ ์š”์•ฝ์—์„œ ์ง‘์˜ ์˜จ๋„๊ฐ€ ์ „๊ธฐ์žฅํŒ ์„ค์ •์˜จ๋„๋กœ ๋‚˜์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์š”์•ฝ์—์„œ ์ œ์™ธ๋ฅผ ๋ˆŒ๋Ÿฌ์„œ ์ œ์™ธ์‹œ์ผœ ์ค์‹œ๋‹ค.)

// target temperature
const { heatRange } = device.functions;
thermostat.getCharacteristic(Characteristic.TargetTemperature)
  .setProps({
    minValue: heatRange.min,
    maxValue: heatRange.max,
    minStep: heatRange.step,
  })
  .onGet(this.getTemperature.bind(this))
  .onSet(this.setTemperature.bind(this));

์„ธ๋ฒˆ์งธ TargetTemperature๋Š” ํ˜„์žฌ ์„ค์ •๋œ ์˜จ๋„๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. minValue, maxValue, minStep ๋“ฑ์„ ์ฃผ์–ด์„œ ๊ธฐ๊ธฐ๊ฐ€ ์„ค์ •๊ฐ€๋Šฅํ•œ ์˜จ๋„ ๋ฒ”์œ„๋ฅผ ์ •ํ•ด์ค๋‹ˆ๋‹ค.

// temp unit
thermostat.getCharacteristic(Characteristic.TemperatureDisplayUnits)
  .setProps({
    validValues: [Characteristic.TemperatureDisplayUnits.CELSIUS],
  })
  .setValue(Characteristic.TemperatureDisplayUnits.CELSIUS);

๋งˆ์ง€๋ง‰ TemperatureDisplayUnits๋Š” ์˜จ๋„ ๋‹จ์œ„์ž…๋‹ˆ๋‹ค. ์ „๊ธฐ์žฅํŒ์ด ์„ญ์”จ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ™”์”จ๋กœ ์„ค์ •์„ ๋ฐ”๊ฟ”๋„ ๋ฌด์‹œํ•ฉ์‹œ๋‹ค. ๊ธฐ์กด๊ณผ ๋‹ค๋ฅด๊ฒŒ onGet, onSet์„ ์ฃผ์ง€ ์•Š๊ณ  setValue๋กœ ๊ณ ์ •๋œ ๊ฐ’์„ ์ค๋‹ˆ๋‹ค.

์งœ์ž”~๐Ÿคญ ์•„์ง API ์—ฐ๋™์€ ํ•˜์ง€ ์•Š์•„ ์‹ค์ œ ๊ธฐ๊ธฐ ์กฐ์ž‘์€ ์•ˆ ๋˜์ง€๋งŒ, ์ œ ์•„์ดํฐ์˜ ์ปจํŠธ๋กค ์„ผํ„ฐ์—์„œ ์œ„ ํ™”๋ฉด๊ณผ ๊ฐ™์ด ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” UI๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

์˜จ๋„ ๋ฒ”์œ„์™€ OFF/HEAT ์˜ต์…˜ ๋“ฑ์ด ์ฝ”๋“œ์—์„œ ์„ค์ •ํ•œ๋Œ€๋กœ ์ž˜ ๋ฐ˜์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์œ„์—์„œ ๋งŒ๋“  ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ๋กœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๋ฉด UI๋ฅผ ์กฐ์ž‘ํ•  ๋•Œ, ์š”์ฒญ์ด ์ž˜ ์˜ค๋Š” ๊ฒƒ๋„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ ์‹ค์ œ API์™€ ์—ฐ๋™์„ ํ•ด๋ณผ ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ 1โ€“3๋‹จ๊ณ„ โ€” ์„ค์ • UI ๋งŒ๋“ค๊ธฐ

[embed]Homebridge Plugin Developer Documentation Homebridge plugin developer documentation and API reference.developers.homebridge.io

๋‹น์—ฐํ•˜๊ฒ ์ง€๋งŒ ์‹ค์ œ API์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด์„  ๋กœ๊ทธ์ธ์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ ์•ฑ์˜ ๊ณ„์ • ์ •๋ณด๐Ÿ”‘๊ฐ€ ํ•„์š”ํ•œ๋ฐ ์ด ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์œ ์ €๊ฐ€ ์„ค์ • UI์—์„œ ์ž…๋ ฅํ•ด์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค์‹œ๋‹ค.

ํ™ˆ๋ธŒ๋ฆฟ์ง€ ํ”Œ๋Ÿฌ๊ทธ์ธ์€ config.schema.json๋ฅผ ์ŠคํŽ™์— ๋งก๊ฒŒ ๊ตฌ์„ฑํ•˜๋ฉด ์ด์— ๋งž๊ฒŒ ์•Œ์•„์„œ ์„ค์ • UI๋ฅผ ๊ตฌ์„ฑํ•ด์ค๋‹ˆ๋‹ค. (์ฐธ๊ณ ๋กœ ์ด JSON ๊ทœ๊ฒฉ์„ *Angular JSON Schema Form*๋ผ๊ณ  ํ•œ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. demonstration playground๋ฅผ ํ†ตํ•ด์„œ ๋ผ์ด๋ธŒ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

**config.schema.json**

{
  "pluginAlias": "NavienSmart",
  "pluginType": "platform",
  "singular": true,
  "schema": {
    "type": "object",
    "properties": {
      "username": {
        "title": "Username",
        "type": "string"
      },
      "password": {
        "title": "Password",
        "type": "string"
      }
    }
  }
}

์šฐ๋ฆฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์—๋Š” ๋‹จ์ˆœํžˆ ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ๋งŒ ํ•„์š”ํ•˜๋ฏ€๋กœ ์œ„์™€ ๊ฐ™์ด ๊ตฌ์„ฑํ•ด์ค์‹œ๋‹ค.

config.schema.json ์— ๋”ฐ๋ผ ์ƒ์„ฑ๋œ ์„ค์ • UI

config.schema.json ์— ๋”ฐ๋ผ ์ƒ์„ฑ๋œ ์„ค์ • UI

ํ™ˆ๋ธŒ๋ฆฟ์ง€ ์„ค์ • ํŽ˜์ด์ง€์—์„œ ํ™•์ธํ•ด๋ณด๋ฉด ์œ„์™€ ๊ฐ™์€ UI๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ ์ด ์„ค์ •์€ ๋Ÿฐํƒ€์ž„์—์„œ config๋ž€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ์จ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ƒ์„ฑ์ž์—์„œ ์ฃผ์ž…๋˜์–ด {"username": "<your-username>", "password": "<your-password>"}์™€ ๊ฐ™์ด JSON์œผ๋กœ ๋„˜์–ด์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด์ œ ์ด config์—์„œ ๊ฐ’์„ ์ฝ์–ด ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค์‹œ๋‹ค.

๊ฐœ๋ฐœ 2๋‹จ๊ณ„ โ€” ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌํ•˜๊ธฐ

๊ธฐ์กด ๊ฐ€์งœ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์กฐ์—์„œ ๋กœ๊ทธ์ธ๋งŒ ์—ฐ๋™ํ•ด๋ด…์‹œ๋‹ค.

๊ธฐ์กด ๊ฐ€์งœ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์กฐ์—์„œ ๋กœ๊ทธ์ธ๋งŒ ์—ฐ๋™ํ•ด๋ด…์‹œ๋‹ค.

๋กœ๊ทธ์ธ์€ ์ด์ „ ํฌ์ŠคํŠธ์—์„œ ์–˜๊ธฐํ–ˆ๋“ฏ์ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

๋‘ ๊ฐ€์ง€์˜ ์ธ์ฆ ๊ณผ์ •์ด ์žˆ๋Š”๋ฐ 1. ์›น ๋กœ๊ทธ์ธ๊ณผ 2. ํ† ํฐ ๋กœ๊ทธ์ธ ์œผ๋กœ ๋‚˜๋‰˜์–ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์›น์—์„œ UI๋ฅผ ํ†ตํ•ด ์œ ์ €์—์„œ ์•„์ด๋””์™€ ํŒจ์Šค์›Œ๋“œ๋ฅผ ๋ฐ›์œผ๋ฉด Form ํƒœ๊ทธ๋กœ ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ์ธ์ฆ ์ •๋ณด(ํ† ํฐ ๋“ฑ)์„ ์•ˆ๋“œ๋กœ์ด๋“œ/iOS ์›น๋ทฐ์—๊ฒŒ ์ฝœ๋ฐฑ์œผ๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์•ฑ์—์„œ๋Š” ์ด ์ •๋ณด๋ฅผ ๋ฐ›์•„์„œ ํ† ํฐ ๋กœ๊ทธ์ธ API ์š”์ฒญ์„ ๋ณด๋‚ด์„œ ์ตœ์ข…์ ์œผ๋กœ ๋กœ๊ทธ์ธ์„ ์™„๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

์šฐ์„  ์›น๋กœ๊ทธ์ธ์„ ๋”ฐ๋ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์›น ๋กœ๊ทธ์ธ์„ HTML์˜ Form ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์ด๋ฅผ Postman์—์„œ ๋”ฐ๋ผ์„œ ์š”์ฒญํ•ด๋ณด๋‹ˆ ๋‹คํ–‰ํžˆ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๊ฑธ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฝ”๋“œ๋กœ ์˜ฎ๊ฒจ์„œ ํ…Œ์ŠคํŠธํ•ด๋ณด๋‹ˆ node.js ๋‹จ์—์„  500 ์‘๋‹ต์ด ๋„˜์–ด์˜ต๋‹ˆ๋‹ค. ๐Ÿ˜ฑ

node-fetch๋ฅผ axios ๋กœ ๋ฐ”๊ฟ”๋„ ๋ณด๊ณ , HTTP 1.1/2.0 ๋ฌธ์ œ์ธ๊ฐ€ ์‹ถ์–ด์„œ ๋ฐ”๊ฟ”๋ณด๊ณ , ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ณ€๊ฒฝํ•ด๋ด๋„ ๋™์ผํ–ˆ์Šต๋‹ˆ๋‹ค. ๋„์ €ํžˆ ์›์ธ์„ ๋ชจ๋ฅด๊ฒ ๋Š”โ€ฆ ๋ฏธ๊ถ์— ๋น ์กŒ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‹ค๊ฐ€ curl๋กœ ์š”์ฒญ์„ ํ•ด๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋™์ž‘์„ ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ํ™•์ธ์„ ์œ„ํ•ด curl -v๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ณด๋‹ˆ POST /member/login ์€ ๋กœ๊ทธ์ธ ์„ฑ๊ณต์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์„œ /member/loginOk๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ํ•˜๋Š”๋ฐ, ์ด ์š”์ฒญ์˜ ์‘๋‹ต์ด 500์ด ๋‚˜์˜ค๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌ, ์ด๊ฒŒ node.js ์—์„œ ๋ฐ›๋Š” ์‘๋‹ต๊ณผ ๋™์ผํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค.

๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•  ๋•Œ ์–ด๋–ค ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์ด๋‚˜ ํ—ค๋” ๊ฐ™์€ ๊ฒƒ์ด ์—†์—ˆ์œผ๋ฏ€๋กœ, ์„ธ์…˜/์ฟ ํ‚ค ๋“ฑ์„ ํ†ตํ•ด ์ธ์ฆ ์ •๋ณด๋ฅผ ์ฃผ๋Š” ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ถ”์ธก๐Ÿง, Postman์€ ์ž์ฒด ์ฟ ํ‚ค ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์žˆ์œผ๋‹ˆ ๋˜๊ณ , curl๊ณผ node-fetch๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ์ฟ ํ‚ค ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์—†์ด ๋™์ž‘ํ•˜๋‹ˆ 500 ์ด์Šˆ๊ฐ€ ์ƒ๊ธด ๊ฒƒ์œผ๋กœ ๋ณด์˜€์Šต๋‹ˆ๋‹ค.

[embed]fetch-cookie Decorator for a fetch function to support automatic cookies.. Latest version: 3.0.1, last published: 9 months agoโ€ฆwww.npmjs.com

fetch๊ฐ€ ์ฟ ํ‚ค ์ปจํ…Œ์ด๋„ˆ๋ž‘ ํ•จ๊ป˜ ๋™์ž‘ํ•˜๋Š” ํŒจํ‚ค์ง€๋ฅผ ์ฐพ์•„์„œ ์ด๋ฅผ ์ ์šฉํ•ด๋ดค๋Š”๋ฐ, ๋น™๊ณ ~! ์ด์ œ๋Š” 500์—†์ด Postman๊ณผ ๋™์ผํ•œ ์‘๋‹ต์ด ๋„˜์–ด์˜ต๋‹ˆ๋‹ค. ๐Ÿคฉ

์ด ํ›„์—” ์‘๋‹ต HTML ๋‚ด javascript ํƒœ๊ทธ์—์„œ ์›น๋ทฐ๋กœ ๋ณด๋‚ผ JSON์„ ๊ทธ๋Œ€๋กœ ํŒŒ์‹ฑํ•ด์„œ ๊ฐ€์ ธ์˜ค๊ณ , ์ด๋ฅผ ํ†ตํ•ด ํ† ํฐ ๋กœ๊ทธ์ธ API๋ฅผ ํ˜ธ์ถœํ•ด๋ณด๋‹ˆ ์ž˜ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ ์™„๋ฃŒ!

๊ฐœ๋ฐœ 3๋‹จ๊ณ„ โ€” API ํ˜ธ์ถœํ•˜๊ธฐ

์—ฐ๋™ํ•œ ๋กœ๊ทธ์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ API๋„ ์—ฐ๋™ํ•ด๋ด…์‹œ๋‹ค.

์—ฐ๋™ํ•œ ๋กœ๊ทธ์ธ์„ ๊ธฐ๋ฐ˜์œผ๋กœ API๋„ ์—ฐ๋™ํ•ด๋ด…์‹œ๋‹ค.

์ด์ œ ๋กœ๊ทธ์ธ๋„ ์—ฐ๋™ํ–ˆ๊ณ , ์–ป์€ credentials์„ ํ†ตํ•ด API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

ํ•„์š”ํ•œ ๊ณณ์— ์ด์ „์— ๋ฆฌ๋ฒ„์‹ฑํ•ด์„œ ์–ป์€ ๊ธฐ๊ธฐ ๋ชฉ๋ก API, ๊ธฐ๊ธฐ ์ œ์–ด API๋ฅผ ์‚ฌ์šฉํ•ด๋ด…์‹œ๋‹ค.

  • ๊ธฐ๊ธฐ ๋ชฉ๋ก API PlatformPlugin์ด ๊ธฐ๊ธฐ๋ฅผ ์ฐพ๋Š” ๋ถ€๋ถ„์—์„œ API๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
async onLaunched() {
  this.log.info('onLaunched called');

  // run the method to discover / register your devices as accessories
  const devices = await this.service.getDevices()
  for (const device of devices) {
    this._registerDeviceAsAccessory(device);
  }
}
  • ๊ธฐ๊ธฐ ์ œ์–ด API ๊ฐ๊ฐ์˜ ์บ๋ฆญํ„ฐ๋ฆฌ์Šคํ‹ฑ onSet์—์„œ API ํ˜ธ์ถœ์„ ํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
private async setHeatingState(value: CharacteristicValue) {
  const state = value as number;
  const isActive = !!state;

  // api calll
  await this.service.activate(this.device, isActive);
}

private async setTemperature(value: CharacteristicValue) {
  const temperature = value as number;

  // api call
  await this.service.setTemperature(this.device, temperature);
}

(์œ„์—์„œ ์–˜๊ธฐํ–ˆ๋“ฏ์ด, ์—ฌ๊ธฐ์„œ service๋ฅผ ๊ธฐ์กด Fake๋กœ ๋™์ž‘ํ•˜๋Š” ์„œ๋น„์Šค์—์„œ ์‹ค์ œ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์„œ๋น„์Šค๋กœ ๊ฐˆ์•„๋ผ์›Œ ์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.)

์ด์ œ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์‹คํ–‰๋  ๋•Œ, ์•Œ์•„์„œ ๋‚ด ๊ณ„์ •์— ๋“ฑ๋ก๋œ ์ „๊ธฐ์žฅํŒ์„ ์ฐพ์•„์„œ ์• ํ”Œ ํ™ˆ์— ๋ณด์—ฌ์ฃผ๊ณ , ์• ํ”Œ ํ™ˆ UI์—์„œ ์กฐ์ž‘ ์‹œ ์‹ค์ œ๋กœ ๊ธฐ๊ธฐ๊ฐ€ ์กฐ์ž‘์ด ๋ฉ๋‹ˆ๋‹ค. ์ด์ œ ๊ฑฐ์˜ ๋‹ค ์™”๋‹ค!!!

๊ฐœ๋ฐœ 4๋‹จ๊ณ„ โ€” AWS PubSub ์—ฐ๋™ํ•˜๊ธฐ

[embed]๋งˆ์ง€๋ง‰์œผ๋กœ ์‹ค์ œ ๊ธฐ๊ธฐ ์ƒํƒœ๋ฅผ ์•Œ๋ ค์ฃผ๋Š” AWS PubSub ๋ฅผ ์—ฐ๋™ํ•ด๋ด…์‹œ๋‹ค.

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

PubSub์„ ๊ด€๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค. ์ด์ „ ํฌ์ŠคํŠธ์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ๊ณผ ๊ฐ™์ด ์ธ์ฆ๋„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ณ , ์šฐ๋ฆฌ๊ฐ€ ๊ถ๊ธˆํ•œ ์ด๋ฒคํŠธ๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ๋ฉ”์†Œ๋“œ๋„ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค.

import { PubSub } from '@aws-amplify/pubsub';
import { filter, map, Observable } from 'rxjs';

export class AwsPubSub {
  private readonly _pubsub: PubSub;

  constructor(...) {
    // create pubsub instance
    this._pubsub = new PubSub({
      region: AWS_IOT_REGION,
      endpoint: `wss://${AWS_IOT_ENDPOINT}/mqtt`,
      clientId: "<client-id>",
    });
  }

  public deviceStatusChanges(deviceId: string): Observable<DeviceEvent> {
    return this._pubsub.subscribe({
      topics: [
        `$aws/things/${deviceId}/shadow/name/.../get/accepted`,
        `$aws/things/${deviceId}/shadow/name/.../update/accepted`,
      ],
    }).pipe(
      // event is Record<string, unknown> type, so we double cast it
      map((event) => event as unknown as DeviceEvent),
      // ignore if it doesn't have state.reported
      filter((event) => !!event.state.reported),
    );
  }
}

๊ธฐ๊ธฐ์˜ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•˜๊ณ  ๊ทธ ์ค‘ ํ•„์š”ํ•œ ์ด๋ฒคํŠธ๋งŒ ํ•„ํ„ฐํ•˜๋Š” Observable๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค. (์ฐธ๊ณ ๋กœ PubSub ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ rxjs ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๊ธธ๋ž˜ ๋™์ผํ•˜๊ฒŒ ๋งž์ถฐ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.)

import { BehaviorSubject, Subscription } from 'rxjs';

export interface NavienDevice extends Device { }

export class NavienDevice {
  private _isActive: boolean | null = null;
  private _temperature: number | null = null;

  private readonly isActiveSubject = new BehaviorSubject<boolean | null>(null);
  private readonly temperatureSubject = new BehaviorSubject<number | null>(null);

  constructor(
    private readonly pubsub: AwsPubSub,
    json: Device,
  ) {
    Object.assign(this, json);

    this.pubsub.deviceStatusChanges(this.id).subscribe((event) => {
      // parse status
      const state = event.payload.state.reported!;
      const isActive = ...;
      const temperature = ...;

      this.isActive = isActive;
      this.temperature = temperature;
    });
  }

  get isActive() {
    return this._isActive;
  }

  set isActive(value: boolean | null) {
    if (this._isActive === value) {
      return;
    }
    this._isActive = value;
    this.isActiveSubject.next(value);
  }

  get temperature() {
    return this._temperature;
  }

  set temperature(value: number | null) {
    if (this._temperature === value) {
      return;
    }
    this._temperature = value;
    this.temperatureSubject.next(value);
  }

  get activeChanges() {
    return this.isActiveSubject.asObservable();
  }

  get temperatureChanges() {
    return this.temperatureSubject.asObservable();
  }
}

Device ํด๋ž˜์Šค์—์„  BehaviorSubject๋ฅผ ํ†ตํ•ด ์ƒํƒœ, ์„ค์ • ์˜จ๋„๊ฐ€ ๋‹ฌ๋ผ์กŒ์„ ๋•Œ ์•Œ๋ ค์ฃผ๋Š” activeChanges, temperatureChanges๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๊ณ , ์œ„ PubSub ๊ฐ์ฒด๋กœ๋ถ€ํ„ฐ ๊ธฐ๊ธฐ ์ƒํƒœ๋ฅผ ๋ฐ›์•„ ํŒŒ์‹ฑํ•ด์„œ, ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๊ณจ๋ผ ์ €์žฅํ•ด์ฃผ์–ด ์ƒํƒœ๊ฐ€ ๋‹ฌ๋ผ์กŒ์„ ๋•Œ ์ด๋ฒคํŠธ๊ฐ€ ๋ถˆ๋ฆฌ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

// subscribe to device events
this.device.activeChanges.subscribe((isActive) => {
  thermostat.updateCharacteristic(
    CurrentHeatingCoolingState,
    isActive ? CurrentHeatingCoolingState.HEAT : CurrentHeatingCoolingState.OFF,
  );
  thermostat.updateCharacteristic(
    TargetHeatingCoolingState,
    isActive ? TargetHeatingCoolingState.HEAT : TargetHeatingCoolingState.OFF,
  );
});
this.device.temperatureChanges.subscribe((temperature) => {
  thermostat.updateCharacteristic(TargetTemperature, temperature);
  thermostat.updateCharacteristic(CurrentTemperature, temperature);
});

์—‘์„ธ์„œ๋ฆฌ์—์„  ์œ„ Device๋กœ๋ถ€ํ„ฐ ์ƒํƒœ, ์„ค์ •์˜จ๋„๊ฐ€ ๋‹ฌ๋ผ์งˆ ๋•Œ ๋งˆ๋‹ค ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„updateCharacteristic()๋ฅผ ํ˜ธ์ถœํ•ด, ์• ํ”Œ ํ™ˆ์— ์—…๋ฐ์ดํŠธ ์š”์ฒญ์„ ๋ณด๋‚ด๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•ด์„œ PubSub โ†’ Device โ†’ Accessory โ†’ Apple Home ์ˆœ์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•ด ๊ธฐ๊ธฐ์˜ ์ƒํƒœ๊ฐ€ ์ „๋‹ฌ๋˜์–ด ์—…๋ฐ์ดํŠธ๊ฐ€ ์ด๋ค„์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ด๋กœ์จ ์ƒํƒœ ์‹ฑํฌ๋„ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค! ๐Ÿฅณ ์ด์ œ ์‹ค์ œ ๊ธฐ๊ธฐ๋ฅผ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ์กฐ์ž‘ํ•ด๋„ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์„œ ์• ํ”Œ ํ™ˆ์—์„œ ๋ฐ˜์˜์ด ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์™„๋ฃŒ โ€” ๊ฒฐ๊ณผ๋ฌผ

[embed]Siri๋กœ ์ œ์–ดํ•˜๋Š” ๋ชจ์Šต

[embed]์ปจํŠธ๋กค ์„ผํ„ฐ์—์„œ ์ œ์–ดํ•˜๋Š” ๋ชจ์Šต

๊ฒฐ๊ณผ ์˜์ƒ์ž…๋‹ˆ๋‹ค. ์• ํ”Œ ํ™ˆ์— ์—ฐ๋™ํ•œ ๋งŒํผ, ๋ณ„๋„์˜ ์ž‘์—… ์—†์ด๋„ Siri์™€๋„ ์—ฐ๋™๋˜์–ด ๋™์ž‘ํ•˜๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

[embed]GitHub - kyle-seongwoo-jun/homebridge-navien-smart: Homebridge plugin to control Navien devices viaโ€ฆ Homebridge plugin to control Navien devices via Navien Smart - kyle-seongwoo-jun/homebridge-navien-smartgithub.com

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

๋งˆ๋ฌด๋ฆฌ

์ด๋ ‡๊ฒŒ ์—ด์‹ฌํžˆ ๋งŒ๋“  ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ € ํ˜ผ์ž๋งŒ ์“ฐ๊ธฐ์—๋Š” ๋งค์šฐ ์•„๊น์ฃ . ์ € ๋ง๊ณ ๋„ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๋„ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ ค๋ฉด ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋ฐฐํฌ๋ฅผ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ฐฐํฌ๋ฅผ ํ•˜๋Š” ๊ณผ์ •๊ณผ ๊ทธ ์™ธ ์‚ฌ์†Œํ•œ ๋‚ด์šฉ๋“ค์€ ๋‹ค์Œ ๋งˆ์ง€๋ง‰ ํŽธ์—์„œ ์ฝ์–ด์ฃผ์„ธ์š”!

[embed]๐Ÿ“ฆ ์ „๊ธฐ์žฅํŒ์„ ์• ํ”Œ ํ™ˆ์— ์—ฐ๊ฒฐํ•ด๋ณด์ž โ€” 3ํŽธ ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฐฐํฌ ์ž๋™์œผ๋กœ npm ํŒจํ‚ค์ง€๋ฅผ ๋ฐฐํฌํ•ด๋ณด์ž!medium.com

์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~~ ๐Ÿ™‡


๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
post_id
d5f7fa0b0aef
slug
์ „๊ธฐ์žฅํŒ์„-์• ํ”Œ-ํ™ˆ์—-์—ฐ๊ฒฐํ•ด๋ณด์ž-2ํŽธ-ํ”Œ๋Ÿฌ๊ทธ์ธ-๊ฐœ๋ฐœ-d5f7fa0b0aef
url
https://medium.com/@kyle_seongwoo_jun/%EC%A0%84%EA%B8%B0%EC%9E%A5%ED%8C%90%EC%9D%84-%EC%95%A0%ED%94%8C-%ED%99%88%EC%97%90-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90-2%ED%8E%B8-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EA%B0%9C%EB%B0%9C-d5f7fa0b0aef
canonical_url
https://medium.com/@kyle_seongwoo_jun/%EC%A0%84%EA%B8%B0%EC%9E%A5%ED%8C%90%EC%9D%84-%EC%95%A0%ED%94%8C-%ED%99%88%EC%97%90-%EC%97%B0%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%9E%90-2%ED%8E%B8-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EA%B0%9C%EB%B0%9C-d5f7fa0b0aef
author_url
https://medium.com/@kyle_seongwoo_jun
status
ok
fetched_at
2026-09-13 04:12:31