Files
integrations/ts/integrations/androidtv/androidtv.classes.integration.ts
T

154 lines
6.4 KiB
TypeScript

import * as plugins from '../../plugins.js';
import { BaseIntegration } from '../../core/classes.baseintegration.js';
import type { IIntegrationEntity, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
import { AndroidtvClient } from './androidtv.classes.client.js';
import { AndroidtvConfigFlow } from './androidtv.classes.configflow.js';
import { createAndroidtvDiscoveryDescriptor } from './androidtv.discovery.js';
import { AndroidtvMapper } from './androidtv.mapper.js';
import type { IAndroidtvConfig } from './androidtv.types.js';
export class AndroidtvIntegration extends BaseIntegration<IAndroidtvConfig> {
public readonly domain = 'androidtv';
public readonly displayName = 'Android Debug Bridge';
public readonly status = 'control-runtime' as const;
public readonly discoveryDescriptor = createAndroidtvDiscoveryDescriptor();
public readonly configFlow = new AndroidtvConfigFlow();
public readonly metadata = {
source: 'home-assistant/core',
upstreamPath: 'homeassistant/components/androidtv',
upstreamDomain: 'androidtv',
integrationType: 'device',
iotClass: 'local_polling',
requirements: ['adb-shell[async]==0.4.4', 'androidtv[async]==0.0.75'],
dependencies: [],
afterDependencies: [],
codeowners: ['@JeffLIrion', '@ollo69'],
};
public async setup(configArg: IAndroidtvConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
void contextArg;
return new AndroidtvRuntime(new AndroidtvClient(configArg));
}
public async destroy(): Promise<void> {}
}
export class HomeAssistantAndroidtvIntegration extends AndroidtvIntegration {}
class AndroidtvRuntime implements IIntegrationRuntime {
public domain = 'androidtv';
constructor(private readonly client: AndroidtvClient) {}
public async devices(): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
return AndroidtvMapper.toDevices(await this.client.getSnapshot());
}
public async entities(): Promise<IIntegrationEntity[]> {
return AndroidtvMapper.toEntities(await this.client.getSnapshot());
}
public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
try {
if (requestArg.domain === 'remote') {
return await this.callRemoteService(requestArg);
}
if (requestArg.domain === 'androidtv') {
return await this.callAndroidtvService(requestArg);
}
if (requestArg.domain !== 'media_player') {
return { success: false, error: `Unsupported Android TV service domain: ${requestArg.domain}` };
}
return await this.callMediaPlayerService(requestArg);
} catch (errorArg) {
return { success: false, error: errorArg instanceof Error ? errorArg.message : String(errorArg) };
}
}
public async destroy(): Promise<void> {
await this.client.destroy();
}
private async callRemoteService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
if (requestArg.service !== 'send_command') {
return { success: false, error: `Unsupported Android TV remote service: ${requestArg.service}` };
}
const command = requestArg.data?.command;
const commands = typeof command === 'string' ? [command] : Array.isArray(command) ? command.filter((itemArg): itemArg is string => typeof itemArg === 'string') : [];
if (!commands.length) {
return { success: false, error: 'Android TV remote.send_command requires data.command.' };
}
const repeats = typeof requestArg.data?.num_repeats === 'number' ? requestArg.data.num_repeats : 1;
await this.client.sendCommand(commands, repeats);
return { success: true };
}
private async callAndroidtvService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
if (requestArg.service === 'adb_command') {
const command = requestArg.data?.command;
if (typeof command !== 'string' || !command) {
return { success: false, error: 'Android TV adb_command requires data.command.' };
}
await this.client.adbCommand(command);
return { success: true };
}
return { success: false, error: `Unsupported Android TV service: ${requestArg.service}` };
}
private async callMediaPlayerService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
if (requestArg.service === 'turn_on') {
await this.client.turnOn();
return { success: true };
}
if (requestArg.service === 'turn_off') {
await this.client.turnOff();
return { success: true };
}
if (requestArg.service === 'play' || requestArg.service === 'media_play') {
await this.client.mediaPlay();
return { success: true };
}
if (requestArg.service === 'pause' || requestArg.service === 'media_pause') {
await this.client.mediaPause();
return { success: true };
}
if (requestArg.service === 'play_pause' || requestArg.service === 'media_play_pause') {
await this.client.mediaPlayPause();
return { success: true };
}
if (requestArg.service === 'stop' || requestArg.service === 'media_stop') {
await this.client.mediaStop();
return { success: true };
}
if (requestArg.service === 'volume_set') {
const level = requestArg.data?.volume_level;
if (typeof level !== 'number') {
return { success: false, error: 'Android TV volume_set requires data.volume_level.' };
}
await this.client.setVolumeLevel(level);
return { success: true };
}
if (requestArg.service === 'volume_up' || requestArg.service === 'volume_down') {
await this.client.stepVolume(requestArg.service === 'volume_up' ? 1 : -1);
return { success: true };
}
if (requestArg.service === 'volume_mute') {
const muted = requestArg.data?.is_volume_muted ?? requestArg.data?.muted;
if (typeof muted !== 'boolean') {
return { success: false, error: 'Android TV volume_mute requires data.is_volume_muted.' };
}
await this.client.muteVolume(muted);
return { success: true };
}
if (requestArg.service === 'select_source') {
const source = requestArg.data?.source;
if (typeof source !== 'string' || !source) {
return { success: false, error: 'Android TV select_source requires data.source.' };
}
await this.client.selectSource(source);
return { success: true };
}
return { success: false, error: `Unsupported Android TV media_player service: ${requestArg.service}` };
}
}