更新版本号,重构核心类,移除未使用的自动化和事件功能,优化灯光管理,调整测试用例以适应新结构

This commit is contained in:
2025-12-23 00:23:06 +08:00
parent 47a5c600ea
commit 3e46287a89
11 changed files with 98 additions and 120 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@kevisual/ha-api", "name": "@kevisual/ha-api",
"version": "0.0.1", "version": "0.0.2",
"description": "", "description": "",
"main": "src/index.ts", "main": "src/index.ts",
"scripts": { "scripts": {

View File

@@ -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;
}
}

View File

@@ -8,9 +8,10 @@ export type HACoreOptions = {
export class HACore { export class HACore {
token: string; token: string;
homeassistantURL?: string; homeassistantURL?: string;
static serviceName = '';
constructor(options: HACoreOptions) { constructor(options: HACoreOptions) {
this.token = options.token; this.token = options?.token;
this.homeassistantURL = options.homeassistantURL || 'http://localhost:8123'; this.homeassistantURL = options?.homeassistantURL || 'http://localhost:8123';
} }
async get(opts: { url: string, params?: Record<string, any> }): Promise<any> { async get(opts: { url: string, params?: Record<string, any> }): Promise<any> {
const _u = new URL(opts.url, this.homeassistantURL); const _u = new URL(opts.url, this.homeassistantURL);
@@ -46,14 +47,14 @@ export class HACore {
return response.json(); return response.json();
}); });
} }
async getEntities(filter?: (entity: any) => boolean): Promise<any[]> { async getEntities(filter?: (entity: EntityItem) => boolean): Promise<EntityItem[]> {
const entities = await this.get({ url: '/api/states' }); const entities = await this.get({ url: '/api/states' });
if (filter) { if (filter) {
return entities.filter(filter); return entities.filter(filter);
} }
return entities; return entities;
} }
async getState(entity_id?: string): Promise<any> { async getState(entity_id?: string): Promise<EntityItem> {
return this.get({ url: `/api/states/${entity_id}` }); return this.get({ url: `/api/states/${entity_id}` });
} }
async getEntityTypes(): Promise<any[]> { async getEntityTypes(): Promise<any[]> {
@@ -65,9 +66,54 @@ export class HACore {
}); });
return Array.from(types); return Array.from(types);
} }
async getServiceEntities(): Promise<EntityItem[]> {
const serviceName = (this.constructor as typeof HACore).serviceName;
return this.getEntities((entity: EntityItem) => entity.entity_id.startsWith(`${serviceName}.`));
}
async getInfoList(): Promise<InfoItem[]> {
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<string, any> }) {
// 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 = [ export const entitiesTypes = [
{ "type": "conversation", "desc": "处理自然语言对话指令,如语音助手" }, { "type": "conversation", "desc": "处理自然语言对话指令,如语音助手" },
@@ -99,3 +145,16 @@ export const entitiesTypes = [
{ "type": "notify", "desc": "发送通知,如推送、邮件、语音播报" }, { "type": "notify", "desc": "发送通知,如推送、邮件、语音播报" },
{ "type": "number", "desc": "可调整的数值控件,有上下限,如温度设定、定时器" } { "type": "number", "desc": "可调整的数值控件,有上下限,如温度设定、定时器" }
] ]
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
}

View File

@@ -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;
}
}

View File

@@ -1,8 +1,2 @@
export * from './light.ts' export * from './light.ts'
export * from './core.ts' export * from './core.ts'
export * from './auto.ts'
export * from './event.ts'
export * from './script.ts'

View File

@@ -1,57 +1,12 @@
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import { HACore, HACoreOptions } from "./core"; import { HACore, HACoreOptions, InfoItem } from "./core.ts";
export class LightHA extends HACore { export class LightHA extends HACore {
static serviceName = 'light';
constructor(options: HACoreOptions) { constructor(options: HACoreOptions) {
super(options); super(options);
} }
async getLights(): Promise<any[]> { async searchLight(keyword: string): Promise<{ result: InfoItem[], lights: InfoItem[], id?: string, hasMore?: boolean }> {
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<string, any> }): Promise<any> {
// 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<LightItem[]> {
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 }> {
const devices = await this.getInfoList(); const devices = await this.getInfoList();
const fuse = new Fuse(devices, { const fuse = new Fuse(devices, {
keys: ['name'], // 搜索字段 keys: ['name'], // 搜索字段
@@ -69,17 +24,33 @@ export class LightHA extends HACore {
return { result: resultItems, lights: devices, id, hasMore }; return { result: resultItems, lights: devices, id, hasMore };
} }
async closeAllLights(): Promise<void> { async closeAllLights(): Promise<void> {
const lights = await this.getLights(); const lights = await this.getServiceEntities();
for (const light of lights) { for (const light of lights) {
if (light.state === 'on') { 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 = { export class EventHA extends HACore {
entity_id: string; static serviceName = 'event';
name: string; constructor(options: HACoreOptions) {
state: string; 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);
}
} }

View File

@@ -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<string, any>) {
const serviceName = ScriptHA.serviceName;
return this.post({
url: `/api/services/${serviceName}/turn_on`,
body: {
entity_id,
...data,
},
});
}
}

View File

@@ -1,5 +1,5 @@
import { auto, showMore } from './common.ts' import { auto, showMore } from './common.ts'
const autos = await auto.getAutos(); const autos = await auto.getServiceEntities();
console.log(showMore(autos)); console.log(showMore(autos));

View File

@@ -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 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 }); export const script = new ScriptHA({ token: process.env.HAAS_TOKEN || '', homeassistantURL: process.env.HAAS_URL });
// const enti = await hacore.getLights(); const enti = await hacore.getEntities((item) => item.attributes?.friendly_name?.includes('电视'));
// console.log(showMore(enti), enti.length); console.log(showMore(enti), enti.length);
// const lightEntities = enti.filter((entity: any) => { // const lightEntities = await hacore.getInfoList();
// const hasLight = entity.entity_id.startsWith('light.');
// const name = entity.attributes?.friendly_name || '';
// // return hasLight && name.includes('次卧');
// return hasLight;
// });
// console.log(showMore(lightEntities)); // 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: '次卧灯', // // name: '次卧灯',
// // service: 'turn_off' // // service: 'turn_off'
// name: '阳台灯' // name: '阳台灯'

View File

@@ -2,6 +2,7 @@ import { hacore, showMore } from "./common.ts";
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
const devices = await hacore.getInfoList(); const devices = await hacore.getInfoList();
console.log(showMore(devices));
const fuse = new Fuse(devices, { const fuse = new Fuse(devices, {
keys: ['name'], // 搜索字段 keys: ['name'], // 搜索字段
threshold: 0.4, // 匹配宽松度0~1越小越严格 threshold: 0.4, // 匹配宽松度0~1越小越严格

View File

@@ -1,6 +1,6 @@
import { script, showMore } from './common.ts'; import { script, showMore } from './common.ts';
const scripts = await script.getScripts(); const scripts = await script.getEntities();
const values = scripts.map(e => { const values = scripts.map(e => {
return { return {