diff --git a/package.json b/package.json index bfc0725..3ae4eb2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/ha-api", - "version": "0.0.1", + "version": "0.0.2", "description": "", "main": "src/index.ts", "scripts": { diff --git a/src/auto.ts b/src/auto.ts deleted file mode 100644 index b821a94..0000000 --- a/src/auto.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Fuse from "fuse.js"; -import { HACore, HACoreOptions } from "./core"; - -export class AutoHA extends HACore { - constructor(options: HACoreOptions) { - super(options); - } - getAutos() { - const auto = this.getEntities((entity: any) => entity.entity_id.startsWith('automation.')); - return auto; - } - -} \ No newline at end of file diff --git a/src/core.ts b/src/core.ts index 3856a8a..46a0e34 100644 --- a/src/core.ts +++ b/src/core.ts @@ -8,9 +8,10 @@ export type HACoreOptions = { export class HACore { token: string; homeassistantURL?: string; + static serviceName = ''; constructor(options: HACoreOptions) { - this.token = options.token; - this.homeassistantURL = options.homeassistantURL || 'http://localhost:8123'; + this.token = options?.token; + this.homeassistantURL = options?.homeassistantURL || 'http://localhost:8123'; } async get(opts: { url: string, params?: Record }): Promise { const _u = new URL(opts.url, this.homeassistantURL); @@ -46,14 +47,14 @@ export class HACore { return response.json(); }); } - async getEntities(filter?: (entity: any) => boolean): Promise { + async getEntities(filter?: (entity: EntityItem) => boolean): Promise { const entities = await this.get({ url: '/api/states' }); if (filter) { return entities.filter(filter); } return entities; } - async getState(entity_id?: string): Promise { + async getState(entity_id?: string): Promise { return this.get({ url: `/api/states/${entity_id}` }); } async getEntityTypes(): Promise { @@ -65,9 +66,54 @@ export class HACore { }); return Array.from(types); } + async getServiceEntities(): Promise { + const serviceName = (this.constructor as typeof HACore).serviceName; + return this.getEntities((entity: EntityItem) => entity.entity_id.startsWith(`${serviceName}.`)); + } + async getInfoList(): Promise { + const lights = await this.getServiceEntities(); + const infoList = lights.map((light: any) => { + return { + entity_id: light.entity_id, + name: light.attributes?.friendly_name || '', + state: light.state, + }; + }); + return infoList; + } + async runService(opts: { entity_id?: string, name?: string, service?: string, data?: Record }) { + // e.g., service: 'turn_on', 'turn_off', 'toggle' + const serviceName = (this.constructor as typeof HACore).serviceName; + let { entity_id, service = '', data } = opts; + if (!entity_id && opts.name) { + const entities = await this.getServiceEntities(); + const target = entities.find((entity: any) => entity.attributes?.friendly_name.includes(opts.name || '')); + if (target) { + entity_id = target.entity_id; + } else { + throw new Error(`${serviceName} 服务中名为为 "${opts.name}" 的不存在.`); + } + } + if (!service) { + const state = await this.getState(entity_id!); + service = state.state === 'on' ? 'turn_off' : 'turn_on'; + } + return this.post({ + url: `/api/services/${serviceName}/${service}`, + body: { + entity_id, + ...data, + }, + }); + } } +export type InfoItem = { + entity_id: string; + name: string; + state: string; +} export const entitiesTypes = [ { "type": "conversation", "desc": "处理自然语言对话指令,如语音助手" }, @@ -98,4 +144,17 @@ export const entitiesTypes = [ { "type": "light", "desc": "控制照明设备,支持开关、亮度、颜色、色温" }, { "type": "notify", "desc": "发送通知,如推送、邮件、语音播报" }, { "type": "number", "desc": "可调整的数值控件,有上下限,如温度设定、定时器" } -] \ No newline at end of file +] + +type EntityItem = { + entity_id: string; + attributes: { + friendly_name?: string; + [key: string]: any; + }; + state: 'on' | 'off' | 'unavailable' | 'unknown' | string; + last_changed: string; + last_updated: string; + last_reported?: string; + context?: any +} \ No newline at end of file diff --git a/src/event.ts b/src/event.ts deleted file mode 100644 index fe3b1f9..0000000 --- a/src/event.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Fuse from "fuse.js"; -import { HACore, HACoreOptions } from "./core"; - -export class EventHA extends HACore { - getEvents() { - const events = this.getEntities((entity: any) => entity.entity_id.startsWith('event.')); - return events; - } -} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index cf94d9c..131e4a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,2 @@ export * from './light.ts' -export * from './core.ts' - -export * from './auto.ts' - -export * from './event.ts' - -export * from './script.ts' \ No newline at end of file +export * from './core.ts' \ No newline at end of file diff --git a/src/light.ts b/src/light.ts index 5dbebdb..f31d840 100644 --- a/src/light.ts +++ b/src/light.ts @@ -1,57 +1,12 @@ import Fuse from "fuse.js"; -import { HACore, HACoreOptions } from "./core"; +import { HACore, HACoreOptions, InfoItem } from "./core.ts"; export class LightHA extends HACore { + static serviceName = 'light'; constructor(options: HACoreOptions) { super(options); } - async getLights(): Promise { - return this.getEntities((entity: any) => entity.entity_id.startsWith('light.')); - } - - /** - * 切换灯的状态,若未指定 service,则根据当前状态切换 - * @param opts - * @returns - */ - async toggleLight(opts: { entity_id?: string, name?: string, service?: string, data?: Record }): Promise { - // e.g., service: 'turn_on', 'turn_off', 'toggle' - let { entity_id, service = '', data } = opts; - if (!entity_id && opts.name) { - const entities = await this.getLights(); - const target = entities.find((entity: any) => entity.attributes?.friendly_name.includes(opts.name || '')); - if (target) { - entity_id = target.entity_id; - } else { - throw new Error(`Light with name including "${opts.name}" not found.`); - } - } else if (!entity_id) { - throw new Error('实体 ID 或 名称 必须提供其一。'); - } - if (!service) { - const state = await this.getState(entity_id!); - service = state.state === 'on' ? 'turn_off' : 'turn_on'; - } - return this.post({ - url: `/api/services/light/${service}`, - body: { - entity_id, - ...data, - }, - }); - } - async getInfoList(): Promise { - const lights = await this.getLights(); - const infoList = lights.map((light: any) => { - return { - entity_id: light.entity_id, - name: light.attributes?.friendly_name || '', - state: light.state, - }; - }); - return infoList; - } - async searchLight(keyword: string): Promise<{ result: LightItem[], lights: LightItem[], id?: string, hasMore?: boolean }> { + async searchLight(keyword: string): Promise<{ result: InfoItem[], lights: InfoItem[], id?: string, hasMore?: boolean }> { const devices = await this.getInfoList(); const fuse = new Fuse(devices, { keys: ['name'], // 搜索字段 @@ -69,17 +24,33 @@ export class LightHA extends HACore { return { result: resultItems, lights: devices, id, hasMore }; } async closeAllLights(): Promise { - const lights = await this.getLights(); + const lights = await this.getServiceEntities(); for (const light of lights) { if (light.state === 'on') { - this.toggleLight({ entity_id: light.entity_id, service: 'turn_off' }); + this.runService({ entity_id: light.entity_id, service: 'turn_off' }); } } } } -type LightItem = { - entity_id: string; - name: string; - state: string; +export class EventHA extends HACore { + static serviceName = 'event'; + constructor(options: HACoreOptions) { + super(options); + } +} + +export class AutoHA extends HACore { + static serviceName = 'automation'; + constructor(options: HACoreOptions) { + super(options); + } + +} + +export class ScriptHA extends HACore { + static serviceName = 'script'; + constructor(options: HACoreOptions) { + super(options); + } } \ No newline at end of file diff --git a/src/script.ts b/src/script.ts deleted file mode 100644 index f66debb..0000000 --- a/src/script.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { HACore, HACoreOptions } from "./core"; - -export class ScriptHA extends HACore { - static serviceName = 'script'; - getScripts() { - const scripts = this.getEntities((entity: any) => entity.entity_id.startsWith('script.')); - return scripts; - } - runScript(entity_id: string, data?: Record) { - const serviceName = ScriptHA.serviceName; - return this.post({ - url: `/api/services/${serviceName}/turn_on`, - body: { - entity_id, - ...data, - }, - }); - } -} \ No newline at end of file diff --git a/test/auto.ts b/test/auto.ts index ffdd9b2..e14adda 100644 --- a/test/auto.ts +++ b/test/auto.ts @@ -1,5 +1,5 @@ import { auto, showMore } from './common.ts' -const autos = await auto.getAutos(); +const autos = await auto.getServiceEntities(); console.log(showMore(autos)); \ No newline at end of file diff --git a/test/common.ts b/test/common.ts index 04db59f..72bc60e 100644 --- a/test/common.ts +++ b/test/common.ts @@ -13,16 +13,10 @@ export const auto = new AutoHA({ token: process.env.HAAS_TOKEN || '', homeassist export const event = new EventHA({ token: process.env.HAAS_TOKEN || '', homeassistantURL: process.env.HAAS_URL }); export const script = new ScriptHA({ token: process.env.HAAS_TOKEN || '', homeassistantURL: process.env.HAAS_URL }); -// const enti = await hacore.getLights(); -// console.log(showMore(enti), enti.length); +const enti = await hacore.getEntities((item) => item.attributes?.friendly_name?.includes('电视')); +console.log(showMore(enti), enti.length); -// const lightEntities = enti.filter((entity: any) => { -// const hasLight = entity.entity_id.startsWith('light.'); -// const name = entity.attributes?.friendly_name || ''; - -// // return hasLight && name.includes('次卧'); -// return hasLight; -// }); +// const lightEntities = await hacore.getInfoList(); // console.log(showMore(lightEntities)); @@ -116,7 +110,7 @@ export const script = new ScriptHA({ token: process.env.HAAS_TOKEN || '', homeas // } // ] -// const res = await hacore.toggleLight({ +// const res = await hacore.runService({ // // name: '次卧灯', // // service: 'turn_off' // name: '阳台灯' diff --git a/test/light.ts b/test/light.ts index 2f4c063..13e0162 100644 --- a/test/light.ts +++ b/test/light.ts @@ -2,6 +2,7 @@ import { hacore, showMore } from "./common.ts"; import Fuse from 'fuse.js'; const devices = await hacore.getInfoList(); +console.log(showMore(devices)); const fuse = new Fuse(devices, { keys: ['name'], // 搜索字段 threshold: 0.4, // 匹配宽松度:0~1,越小越严格 diff --git a/test/script.ts b/test/script.ts index 017764e..551de27 100644 --- a/test/script.ts +++ b/test/script.ts @@ -1,6 +1,6 @@ import { script, showMore } from './common.ts'; -const scripts = await script.getScripts(); +const scripts = await script.getEntities(); const values = scripts.map(e => { return {