Control and query LifeSmart Smart Station IoT devices registered in the RootONLocal app. Queries local device/zone info via http://127.0.0.1:18080, then calls the LifeSmart LocalWeb API directly to get or set device state (lights, switches, blinds, AC, custom IR remotes) and read measurements (air q
---
name: rootonlocal-iot
description: Control and query LifeSmart Smart Station IoT devices registered in the RootONLocal app. Queries local device/zone info via http://127.0.0.1:18080, then calls the LifeSmart LocalWeb API directly to get or set device state (lights, switches, blinds, AC, custom IR remotes) and read measurements (air quality, temperature, power/energy meters, solar).
version: 2.0.4
metadata:
openclaw:
emoji: "๐ "
homepage: https://github.com/Kcobstkin/RootONLocal_Monorepo
requires:
bins:
- curl
---
# RootONLocal IoT Skill (OpenClaw)
์ด ํด๋๋ **OpenClaw**(๋๋ ์์์ LLM ์คํฌ ํธ์คํธ)์์ ๋ณธ RootONLocal ์ฑ์ด ๋ง๋
๊ตฌ์ญ(group)๋ณ/๋์๋ณด๋ IoT ์ฅ์น๋ฅผ ์์ฐ์ด๋ก ์ ์ดํ๊ธฐ ์ํ **์คํฌ ํจํค์ง**์
๋๋ค.
๋ณธ ์คํฌ์ ๋ ๋จ๊ณ์ HTTP ํธ์ถ๋ง์ผ๋ก ๋์ํฉ๋๋ค.
1. ๋ณธ ์ฑ(ํ๋ธ๋ฆฟ/PC)์ด ๋์ด **๋ก์ปฌ ์ ๋ณด API** (`http://127.0.0.1:18080`) ํธ์ถ
โ ์ฌ์ฉ ๊ฐ๋ฅํ ์คํ
์ด์
/ ๊ตฌ์ญ / ์ฅ์น / LocalWeb ์๊ฒฉ์ฆ๋ช
์ ๋ฐ์์จ๋ค.
2. 1๋ฒ์์ ๋ฐ์ ์ ๋ณด๋ก **LifeSmart Local Web API**๋ฅผ **์ง์ ํธ์ถ**
โ ์ฅ์น ์ํ ์กฐํ(`EpGet`) / ์ ์ด(`EpSet`).
---
## ํด๋ ๊ตฌ์ฑ
| ํ์ผ | ์ค๋ช
|
|---|---|
| `SKILL.md` | (์ด ํ์ผ) ์คํฌ ์ฌ์ฉ ๊ฐ์ โ OpenClaw ์ ๊ทธ๋๋ก ๋ฑ๋กํ๋ค. |
| `api-spec.md` | 1๋จ๊ณ IoT ์ ๋ณด API ์ ์ฒด ๋ช
์ธ (`/iot/info`, `/iot/station`, `/iot/group`, `/iot/device_dash`, `/iot/device_group`) |
| `localweb-spec.md` | 2๋จ๊ณ LifeSmart Local Web API ๋ช
์ธ (URL, ์ธ์ฆ, `EpGet`, `EpSet`, `type/val` ์ฝ๋) |
| `device-types.md` | devtype โ ์นดํ
๊ณ ๋ฆฌ(`switch`/`light`/`blind`/`ac`/`sensor`/...) โ ์ ์ด ์ฑ๋ ๋งคํ, ๊ณ์ธก ์ฅ์น(๊ณต๊ธฐ์ง/์ ๋ ฅ/ํ์๊ด/๋ณํฉ) ์กฐํ ์ฑ๋, IR ๋ฆฌ๋ชจ์ปจ ๋งค์นญ ๊ท์น |
| `examples.md` | "๊ฑฐ์ค ์์ด์ปจ 25๋", "์๋ฐฉ ๋ถ ๊บผ์ค" ๋ฑ ์์ฐ์ด โ API ํธ์ถ ๋ณํ ์์ |
---
## ์ฌ์ฉ ํ๋ฆ (์์ฝ)
```
[์ฌ์ฉ์] "๊ฑฐ์ค์ด ๋๋ฌด ๋ฅ๋ค"
โ
[OpenClaw]
1) ์ ๋ณด API 4๊ฐ ์ ๋ถ ์กฐํ (์ฅ์น ๋ชฉ๋ก๋ง์ผ๋ก๋ ๊ตฌ์ญ๋ช
๋งค์นญ ๋ถ๊ฐ):
GET http://127.0.0.1:18080/iot/info
โ { useHttps, scheme, port, user, password, url_template }
GET http://127.0.0.1:18080/iot/group
โ [{ group_id, groupname:"๊ฑฐ์ค", ... }, ...] โ group_id โ ๊ตฌ์ญ๋ช
๋งคํ
GET http://127.0.0.1:18080/iot/device_dash โ ๋์๋ณด๋ ์ฅ์น
GET http://127.0.0.1:18080/iot/device_group โ ๊ตฌ์ญ๋ณ ์ฅ์น (group_id ํฌํจ)
2) "๊ฑฐ์ค" โ group ๋ชฉ๋ก์์ group_id ์ฐพ๊ธฐ โ
device_group ์์ group_id ์ผ์น + dev_typeโ์์ด์ปจ ์ธ ์ฅ์น ์ถ์ถ
โ { station_ip:"192.168.0.10", agt, me, dev_type:"V_AIR_P", ... }
(๊ตฌ์ญ ์ฅ์น์ ์์ผ๋ฉด device_dash ์์ ์ด๋ฆ์ผ๋ก ๊ฒ์)
3) (ํ์ ์) ํ์ฌ ์ํ โ LocalWeb ์ง์ ํธ์ถ:
POST {scheme}://{station_ip}:{port}/mgaweb/api.EpGet
body: { id:1, user, password, method:"EpGet", params:{ me } }
4) ์ ์ด โ LocalWeb ์ง์ ํธ์ถ:
POST {scheme}://{station_ip}:{port}/mgaweb/api.EpSet
body: { id:1, user, password, method:"EpSet",
params:{ me, idx:"O", type:129, val:1 } } โ ์ ์ ON
```
> โ **LocalWeb ์์ฒญ ๋ณธ๋ฌธ์ ํญ์ `id`/`user`/`password`/`method`/`params` 5๊ฐ ํ๋ ์ ๋ถ ํฌํจ.**
> ํ๋๋ผ๋ ๋น ์ง๋ฉด Station ์ด `{ "message":"ENPF...", "status":"ENL" }` ์ค๋ฅ๋ฅผ ๋ฐํํ๋ค.
>
> โ **์ฑ๊ณต ํ์ ์ ์๋ต์ `code` ํ๋๊ฐ ์๊ณ `code === 0` ์ธ ๊ฒฝ์ฐ๋ง.**
> `code` ๊ฐ ์๋ ์๋ต(์ ENL ์ค๋ฅ ๋ฑ)์ด๋ `code !== 0` ์ ์ ๋ถ ์คํจ๋ค.
> ์คํจ๋ฅผ ์ฑ๊ณต์ฒ๋ผ ์ฌ์ฉ์์๊ฒ ๋ณด๊ณ ํ์ง ์๋๋ค. (`localweb-spec.md` ยง3, ยง5)
---
## ์ฌ์ ์กฐ๊ฑด
- ๋ณธ ์ฑ์ด ์คํ ์ค์ด์ด์ผ ํ๋ค. (Android ํฌ๊ทธ๋ผ์ด๋ ์๋น์ค ๋๋ Electron main ํ๋ก์ธ์ค๊ฐ
127.0.0.1:18080 ์ ์ ๋ณด ์๋ฒ๋ฅผ ๋์ด๋ค.)
- ๋ณธ ์ฑ์์ **Local Web ์ฌ์ฉ ON** + **๊ณตํต ๊ณ์ /๋น๋ฐ๋ฒํธ** ์
๋ ฅ์ด ๋์ด ์์ด์ผ ํ๋ค.
(`/iot/info.enabled === true` ๋ก ํ์ธ ๊ฐ๋ฅ)
- OpenClaw ๊ฐ ๋ณธ ์ฑ๊ณผ **๊ฐ์ ๋๋ฐ์ด์ค**์์ ์คํ๋์ด์ผ ํ๋ค.
์๋ฒ๋ **127.0.0.1 (loopback) ์ ์ฉ ๋ฐ์ธ๋ฉ**์ด๋ผ ์ธ๋ถ์์ ์ ๊ทผ ๋ถ๊ฐ.
---
## ๋ฉํฐ ๋งค์นญ ์ฒ๋ฆฌ ๊ท์น
์์ฐ์ด โ ์ฅ์น ๋ณํ ์ ๋ค์ ์ฐ์ ์์๋ก ํ๋ณด๋ฅผ ์ขํ๋ค.
1. **๊ตฌ์ญ(groupname)** ํค์๋ ๋งค์นญ โ `/iot/device_group` ์ `group_id` ๋ก 1์ฐจ ํํฐ.
2. **์ฅ์น ์นดํ
๊ณ ๋ฆฌ** (์: ์์ด์ปจ/๋ถ/๋ธ๋ผ์ธ๋) โ `dev_type` ์ `category` ๋ก 2์ฐจ ํํฐ.
3. 1+2 ๊ฒฐ๊ณผ๊ฐ:
- 0๊ฐ โ "์ฐพ์ ์ ์์" ์๋ต.
- 1๊ฐ โ ๋ฐ๋ก ์คํ.
- 2๊ฐ ์ด์ โ ํ๋ณด ๋ชฉ๋ก์ ์ฌ์ฉ์์๊ฒ ๋ค์ ๋ฌป๋๋ค.
---
## ๋ช
๋ น ์ ํจ์ฑ ๊ท์น
> โ **type-val ๊ฒฐํฉ ๊ท์น (switch ์ฑ๋ ยท AC ์ ์ ํ์ )**
> - ON = `type:129, val:1` ์ด ์กฐํฉ๋ง ์ ํจํ๋ค.
> - OFF = `type:128, val:0` ์ด ์กฐํฉ๋ง ์ ํจํ๋ค.
> - `type:128, val:1` ๋๋ `type:129, val:0` ์ ์ ๋ ์ฌ์ฉ ๊ธ์ง. type ๊ณผ val ์ ๋
๋ฆฝ์ ์ผ๋ก ์ ํํ์ง ์๋๋ค.
> - ์กฐ๋ช
ยท๋ธ๋ผ์ธ๋๋ `type=128/129` ๋ฅผ **์ฌ์ฉํ์ง ์๋๋ค**. (์๋ ๊ท์น ๋ฐ `device-types.md` ์ฐธ์กฐ)
- **์์ด์ปจ** (`V_AIR_P`/`V_FRESH_P` ๋ง ์ ์ด): ์ ์(`O`)/๋ชจ๋(`MODE`)/์จ๋(`tT`, val=์จ๋ร10)/ํ๋(`F`) ์ธ ๋์์ ๊ฑฐ๋ถ (`์์ด์ปจ ๋ซ์์ค` ๋ฑ).
category ๊ฐ `ac` ๋ผ๋ `dev_type=UL_IR_AC` ๋ ๋ฏธ์ง์ ์ฅ์น โ ์ ์ด ๊ฑฐ๋ถ.
- **๋ธ๋ผ์ธ๋** (`SL_DOOYA`): `idx=P2, type=207, val=์ด๋ฆผ๋ฅ (0~100)` ์ผ๋ก ์ด๊ธฐ(100)/๋ซ๊ธฐ(0)/๋ถ๋ถ ์ด๊ธฐ(X).
์ ์ง๋ `idx=P2, type=206, val=128`. `P1` ์ ์ํ ์กฐํ ์ ์ฉ(read-only).
- **์กฐ๋ช
**: ๋ชจ๋ ์กฐ๋ช
(`SL_LI_WW`/`SL_SW_WW`/`V_HG_WW` ๋ฑ)์ ON/OFFยท๋ฐ๊ธฐ ๋ชจ๋ `type=207(ON)/206(OFF)`,
`val=๋ฐ๊ธฐ(1~255)`. ์์จ๋๋ `idx=P2, type=207, val=1~255` (2700K~6000K ๋งคํ).
- **์ค์์น(๋ค์ฑ๋)**: `idx` ๊ฐ ์ฅ์น๋ณ๋ก `P1~P8`, `L1~L3`, `P2~P4` ๋ฑ ๋ค๋ฅด๋ฏ๋ก
๋ฐ๋์ `device-types.md` ์ ์ฑ๋ ํ์์ ๊ฐ๋ฅ idx ์์์๋ง ์ ํ.
- **CUBE Button (`SL_SC_BB_V2`)**: ์ด๋ฒคํธ ์ ์ฉ. ์ ์ด ๋ช
๋ น ๊ฑฐ๋ถ.
- **์ผ์/๊ณต๊ธฐ์ง/์ ๋ ฅ๋์ธก์ ๊ธฐ/AWAIR/ํ์๊ด**: ์กฐํ(`EpGet`) ๋ง ํ์ฉ, ์ ์ด ๊ฑฐ๋ถ.
"์จ๋ ๋ช ๋์ผ?", "์ ๋ ฅ ์ผ๋ง๋ ์จ?", "๋ฐ์ ๋ ์๋ ค์ค" ๊ฐ์ ์ํ ์ง๋ฌธ์ ์ฌ์ฉ โ ์ฑ๋/๋จ์ ๋ณํ์ `device-types.md` ยง8.
"๋ฌธ ์ด๋ ค ์์ด?", "๋์ ๊ฐ์ง๋์ด?" ๊ฐ์ ๊ฐ์งํ ์ผ์(๋์/์ฌ์ค/๋ฌธ์ด๋ฆผ/๋์/ํ์ฌ) ์ง์๋ `device-types.md` ยง6 ์
ํ์ ํ๋ฅผ ๋ฐ๋ฅธ๋ค (devtype ๋ณ๋ก ํ์ ํ๋๊ฐ `type`/`val` ๋ก ๋ค๋ฆ).
์ค๋งํธํ๋ฌ๊ทธ(`SL_OE_DE`)๋ ์์ธ์ ์ผ๋ก **์ ์ด(P1) + ์ ๋ ฅ ์กฐํ(P2/P3)** ๋ ๋ค ์ง์.
- **๋ณํฉ ๊ฐ์ ์ฅ์น (`MERGED_AQ`/`MERGED_PMM`/`MERGED_SOLAR`)**: `me` ๊ฐ ๊ฐ์ ID๋ผ `EpGet`/`EpSet` ๊ธ์ง.
`attribute.mergedSourceMes` ์ ์์ค ์ฅ์น๋ค์ ๊ฐ๋ณ `EpGet` ํ AQ=ํ๊ท / PMMยทSOLAR=ํฉ์ฐ (`device-types.md` ยง8.4).
- **Custom IR ๋ฆฌ๋ชจ์ปจ (`UL_IR_CU`)**: `EpGet`/`EpSet` ๊ธ์ง. `/iot/device_*` ์ `me` ๋ฅผ `ai` ๋ก ์ฌ์ฉํด `GetRemote`๋ก ๋ฒํผ์ ์กฐํํ๊ณ , ์ ํํ code 1๊ฐ๋ฅผ `SendKeys` ์ `keys` ๋ฌธ์์ด ๋ฐฐ์ด๋ก ๋ณด๋ธ๋ค.
`last_stat` ์ ๋ฒํผ ์บ์(`remoteMe`/`keys`/`codes`)๊ฐ ์์ผ๋ฉด `GetRemote` ์๋ต ๊ฐ๋ฅ.
๋ฌผ๋ฆฌ ์ฅ์น(`V_AIR_P` ๋ฑ)๊ฐ ์์ ๋ ๋ฆฌ๋ชจ์ปจ ์ด๋ฆ("๊ฑฐ์ค์์ด์ปจ")ยท๋ฒํผ ์ด๋ฆ("์ ์", "๋๋ฐฉ")์ผ๋ก ์๋๋ฅผ ์ ์ถํด ์คํํ๋ค โ ์ ์ถ ๊ท์น์ `device-types.md` ยง7.1.
์์ธํ ๋งคํ์ [`device-types.md`](./device-types.md) ์ฐธ์กฐ.
---
## ์๋ต ์ ์ฑ
(์คํฌ์ด ํธ์ถ์์๊ฒ ๋ฐํ)
์คํฌ์ ๋ค์ ํํ์ JSON ์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ๋๋ ค์ฃผ๋ ๊ฒ์ ๊ถ์ฅ.
```json
{
"ok": true,
"action": "epSet",
"station_id": 3,
"device": { "me": "0001ABC...", "name": "๊ฑฐ์ค ์์ด์ปจ", "dev_type": "V_AIR_P" },
"request": { "idx": "O", "type": 129, "val": 1 },
"result": { "code": 0, "message": "ok" }
}
```
์ค๋ฅ:
```json
{
"ok": false,
"error": "ambiguous_device",
"candidates": [ { "me":"...", "name":"...", "groupname":"..." }, ... ]
}
```
---
## ํฅํ ํ์ฅ (resolve API โ ๋ณธ ์ฑ ์ธก ์ถ๊ฐ ๊ธฐ๋ฅ)
๋ณธ ์ฑ์ ์ถ๊ฐ๋ ์์ ์ธ ๋ณด์กฐ ์๋ํฌ์ธํธ:
- `POST /iot/resolve` โ OpenClaw ๊ฐ ๋ง๋ค์ด๋ธ ๋ช
๋ น(๊ตฌ์ญ๋ช
/์ฅ์น์ข
๋ฅ/๋์)์ ๋ณด๋ด๋ฉด
๋ณธ ์ฑ์ด ์ ๊ท์น์ ์ ์ฉํด ๋งค์นญ/์ ํจ์ฑ ๊ฒ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.
- ๋ค์ค ๋งค์นญ ์ ํ๋ณด ๋ฐํ.
- ์ ํจํ์ง ์์ ๋ช
๋ น(์: ์์ด์ปจ์ close)์ reject.
๋ณธ 1.0 ๋ฒ์ ์คํฌ์ ์ resolve API ์์ด๋ client-side ๋งค์นญ์ผ๋ก ๋์ ๊ฐ๋ฅ.
don't have the plugin yet? install it then click "run inline in claude" again.