๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
IoT ๐Ÿ /Homebridge

[JS] Homebridge ๋ฐ์Šคํฌํƒ‘ ์›๊ฒฉ ์ „์› ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐœ์„ 

by GeekSean 2026. 2. 25.
๋ฐ˜์‘ํ˜•

[IoT ๐Ÿ /Homebridge] - [JS] Homebridge ๋ฐ์Šคํฌํƒ‘ ์›๊ฒฉ ์ „์› ํ”Œ๋Ÿฌ๊ทธ์ธ ์ œ์ž‘

 

๊ณ„์† Homebridge ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐœ๋ฐœ์„ ํ•˜๋ฉด์„œ, ์ด์ „์—” ๋ชฐ๋ž๋˜ ์ง€์‹๋“ค๋„ ์•Œ๊ฒŒ ๋˜์—ˆ๊ณ 

๊ธฐ์กด ํ”Œ๋Ÿฌ๊ทธ์ธ์—์„œ ๊ฐœ์„ ํ•  ๋ถ€๋ถ„์ด ๋ณด์ด๊ธฐ ์‹œ์ž‘ํ•ด์„œ,

๊ทธ๋™์•ˆ ์ œ์ž‘ ํ–ˆ๋˜ ํ”Œ๋Ÿฌ๊ทธ์ธ๋“ค์„ ํ•˜๋‚˜ํ•˜๋‚˜ ๊ฐœ์„ ํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

 

์ด์ „์— ๋ฐ์Šคํฌํƒ‘ ์›๊ฒฉ ์ „์› ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ ,

๋‹จ์ถ•์–ด๋กœ ์‚ฌ์šฉํ•˜๋˜ ๊ฒƒ์„ Homebridge Plugin์œผ๋กœ ๋งŒ๋“ค์–ด

๋‚ด ์Šค๋งˆํŠธํ™ˆ ์ƒํƒœ๊ณ„์— ์™„์ „ํžˆ ์ ์šฉ ์‹œ์ผฐ์—ˆ๋‹ค.

 

๋จผ์ € ํ•ด๋‹น ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ์•…์„ธ์„œ๋ฆฌ ๋น„์ฃผ์–ผ ์—…๋ฐ์ดํŠธ์™€,

์•…์„ธ์„œ๋ฆฌ์™€ PC ์ƒํƒœ๋ฅผ ๋™๊ธฐํ™” ํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ˆ˜์ •ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

 

ํ”Œ๋Ÿฌ๊ทธ์ธ ์ˆ˜์ •

 

๊ธฐ์กด์—๋Š”, ๋ชจ๋‹ˆํ„ฐ ๋ชจ์–‘์˜ TV ํƒ€์ผ์ด ์™ธ๋ถ€ ์•…์„ธ์„œ๋ฆฌ๋กœ๋งŒ ๋“ฑ๋ก์ด ๊ฐ€๋Šฅํ•œ์ง€ ๋ชฐ๋ž์—ˆ๋‹ค.

์ €๋ฒˆ์— Smartthings ์—ฐ๋™ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ง์ ‘ ๋งŒ๋“ค์–ด ๋ณด๋ฉด์„œ ์„ฑ๊ณต์ ์œผ๋กœ ๊ตฌํ˜„ํ–ˆ์œผ๋‹ˆ

์ด๋ฒˆ์—๋„ ์ ์šฉํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

๋จผ์ €, ๊ธฐ์กด Switch ์•…์„ธ์„œ๋ฆฌ์—์„œ TV ์•…์„ธ์„œ๋ฆฌ๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค.

์ด๋ฒˆ์—๋Š” ์•…์„ธ์„œ๋ฆฌ์— ์ „์› ๊ธฐ๋Šฅ๋งŒ ์žˆ์œผ๋ฉด ๋˜๋ฏ€๋กœ, ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ธฐ๋ณธ ์„ค์ •์„ ํ•˜๊ณ 

publishExternalAccessories๋กœ ์™ธ๋ถ€ ์•…์„ธ์„œ๋ฆฌ๋กœ ์ง€์ •ํ•ด์ฃผ์—ˆ๋‹ค.

publishTVAccessory() {
    const uuid = UUID.generate(this.config.domain);
    const acc  = new this.api.platformAccessory(this.config.name, uuid);

    acc.category = Categories.TELEVISION;

    this.tvService = acc.addService(Service.Television, this.config.name);
    this.tvService.setCharacteristic(Characteristic.ConfiguredName, this.config.name);
    this.tvService.setCharacteristic(Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE);

    this.tvService.getCharacteristic(Characteristic.Active)
        .onGet(() => this.isOnline ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE)
        .onSet(async (value) => {
            if (value === Characteristic.Active.ACTIVE) {
                await this._powerOn();
                this.isOnline = true;
            } else {
                await this._powerOff();
                this.isOnline = false;
            }
        });

    this.api.publishExternalAccessories('homebridge-wol-ssh', [acc]);
}
 

 

๊ทธ๋ฆฌ๊ณ  PC์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด, Port Check ๋ฐฉ์‹์„ ์ด์šฉํ•  ๊ฒƒ์ด๋‹ค.

์ฒ˜์Œ์—” Ping์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ฃผ๋Š” ๋ฐฉ์‹์„ ๊ณ ๋ คํ–ˆ์—ˆ๋Š”๋ฐ,

์•„๋ฌด๋ž˜๋„ ์ข…๋ฃŒ ๋ช…๋ น์–ด๋ฅผ SSH๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ

SSH ํฌํŠธ๊ฐ€ ์‘๋‹ตํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ์ฒดํฌ ํ•˜๋Š” ๋ฐฉ์‹์ด ๋” ํšจ์œจ์ ์ธ ๊ฒƒ ๊ฐ™์•„์„œ ์ฑ„ํƒํ–ˆ๋‹ค.

 

PC๋ฅผ ์ƒํƒœ๋ฅผ ์ฒดํฌํ•˜๋Š” ํ•จ์ˆ˜ ๋ถ€๋ถ„์ด๋‹ค.

ํฌํŠธ๊ฐ€ ์—ด๋ ค์žˆ์œผ๋ฉด connect ์ด๋ฒคํŠธ ๋ฐœ์ƒ, ์•…์„ธ์„œ๋ฆฌ๋ฅผ ์˜จ๋ผ์ธ์œผ๋กœ ๋‘๊ณ 

ํƒ€์ž„์•„์›ƒ์ด๋‚˜ ์—๋Ÿฌ ๋ฐœ์ƒ์‹œ์—๋Š” ์˜คํ”„๋ผ์ธ์œผ๋กœ ๋‘๊ฒŒ ํ–ˆ๋‹ค.

์†Œ์ผ“ ์—ฐ๊ฒฐ์‹œ, on ๋ฉ”์„œ๋“œ ๋Œ€์‹  once ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ–ˆ๋‹ค.

checkPCStatus() {
    const urlData = new URL(this.config.domain);
    const hostname = urlData.hostname;
    const port = this.config.sshPort || 22;
    const socket = new net.Socket();
    socket.setTimeout(2000);

    socket.once('connect', () => {
        this._updateState(true);
        socket.destroy();
    });

    socket.once('timeout', () => {
        this._updateState(false);
        socket.destroy();
    });

    socket.once('error', () => {
        this._updateState(false);
        socket.destroy();
    });

    socket.connect(port, hostname);
}
 

 

์‹ค์ œ ์•…์„ธ์„œ๋ฆฌ ์ƒํƒœ๋ฅผ ์ œ์–ด ํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์ด๋‹ค.

PC๊ฐ€ ์ด์ „ ์ƒํƒœ์™€ ๋‹ค๋ฅผ ๊ฒฝ์šฐ์—๋งŒ ์‹คํ–‰ํ•˜๊ฒŒ ํ•ด๋‘์—ˆ๋‹ค.

_updateState(newState) {
    if (this.isOnline !== newState) {
        this.isOnline = newState;

        if (this.tvService) {
            this.tvService.updateCharacteristic(
                Characteristic.Active,
                this.isOnline ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE
            );
        }
    }
}
 

 

๋งˆ์ง€๋ง‰์œผ๋กœ 10์ดˆ ์ฃผ๊ธฐ๋กœ Polling ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์„œ

์ƒํƒœ ๊ฐ์ง€๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์—ˆ๋‹ค.

api.on('didFinishLaunching', () => {
    this.publishTVAccessory();
    setInterval(() => this.checkPCStatus(), 10000);
});
 

 

์ฝ”๋“œ์ž‘์—…์€ ์™„๋ฃŒํ–ˆ์œผ๋‹ˆ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์—…๋ฐ์ดํŠธ ํ•ด๋ณด์ž

 

 

ํ…Œ์ŠคํŠธ

 

์™„์„ฑ๋œ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ ์šฉํ•˜๊ณ  ๋“ฑ๋กํ•ด๋ณด๋‹ˆ

๋ชจ๋‹ˆํ„ฐ ๋ชจ์–‘์˜ TV ์•…์„ธ์„œ๋ฆฌ๋กœ ์ž˜ ๋“ฑ๋ก๋˜์—ˆ๋‹ค.

 

์ „์—๋Š”, Toggle๋ฐฉ์‹์œผ๋กœ ์ž๋™์œผ๋กœ ์•…์„ธ์„œ๋ฆฌ๊ฐ€ Off ๋˜๊ฒŒ ํ–ˆ์—ˆ๋‹ค๋ฉด

์ด์ œ๋Š” ์ƒํƒœ ๋™๊ธฐํ™”๊ฐ€ ๋˜์„œ On ์ƒํƒœ๋กœ ์ž˜ ์œ ์ง€ ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

(๋“œ๋””์–ด ์ปดํ“จํ„ฐ ์ „์› ์ƒํƒœ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์กฐ๊ฑด์„ ๊ฑธ ์ˆ˜ ์žˆ๊ฒŒ ๋๋‹ค!)

 

์ˆ˜๋™์œผ๋กœ ์ข…๋ฃŒ์‹œ์—๋„, ์ž๋™์œผ๋กœ ์•…์„ธ์„œ๋ฆฌ๊ฐ€ ๋™๊ธฐํ™” ๋˜๋Š” ๋ชจ์Šต

 

 

์ด๋ ‡๊ฒŒ, ๊ธฐ์กด ์ œ์ž‘ํ–ˆ๋˜ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฐœ์„ ํ•˜๊ณ  ์—…๊ทธ๋ ˆ์ด๋“œ ํ•˜๋Š” ์ž‘์—…์„ ํ•ด๋ณด์•˜๋‹ค.

์ด์ œ์•ผ ์™„์„ฑ๋œ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ๋œ ๊ฒƒ ๊ฐ™์•„์„œ ๋ฟŒ๋“ฏํ•˜๋‹ค.

์•ž์œผ๋กœ๋„, ๋ถˆํŽธํ•œ ์ ์ด ๋ณด์ด๋ฉด ๋ฐ”๋กœ๋ฐ”๋กœ ๊ณ ์ณ์„œ ์จ๋ด์•ผ๊ฒ ๋‹ค.

 

 

์›๋ณธ ๊ฒŒ์‹œ๊ธ€

https://blog.naver.com/101artspace/224146920273

 

[JS] Homebridge ๋ฐ์Šคํฌํƒ‘ ์›๊ฒฉ ์ „์› ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐœ์„ 

๊ณ„์† Homebridge ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฐœ๋ฐœ์„ ํ•˜๋ฉด์„œ, ์ด์ „์—” ๋ชฐ๋ž๋˜ ์ง€์‹๋“ค๋„ ์•Œ๊ฒŒ ๋˜์—ˆ๊ณ  ๊ธฐ์กด ํ”Œ๋Ÿฌ๊ทธ์ธ์—์„œ ๊ฐœ์„ ํ• ...

blog.naver.com

 

๋ฐ˜์‘ํ˜•