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

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",
"version": "0.0.1",
"version": "0.0.2",
"description": "",
"main": "src/index.ts",
"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 {
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<string, any> }): Promise<any> {
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<any[]> {
async getEntities(filter?: (entity: EntityItem) => boolean): Promise<EntityItem[]> {
const entities = await this.get({ url: '/api/states' });
if (filter) {
return entities.filter(filter);
}
return entities;
}
async getState(entity_id?: string): Promise<any> {
async getState(entity_id?: string): Promise<EntityItem> {
return this.get({ url: `/api/states/${entity_id}` });
}
async getEntityTypes(): Promise<any[]> {
@@ -65,9 +66,54 @@ export class HACore {
});
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 = [
{ "type": "conversation", "desc": "处理自然语言对话指令,如语音助手" },
@@ -98,4 +144,17 @@ export const entitiesTypes = [
{ "type": "light", "desc": "控制照明设备,支持开关、亮度、颜色、色温" },
{ "type": "notify", "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 './core.ts'
export * from './auto.ts'
export * from './event.ts'
export * from './script.ts'
export * from './core.ts'

View File

@@ -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<any[]> {
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 }> {
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<void> {
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);
}
}

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'
const autos = await auto.getAutos();
const autos = await auto.getServiceEntities();
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 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: '阳台灯'

View File

@@ -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越小越严格

View File

@@ -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 {