Add TypeScript integrations package

This commit is contained in:
2026-05-05 12:01:30 +00:00
commit e91176fb9b
5889 changed files with 53433 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
node_modules/
dist/
dist_*/
dist_ts/
coverage/
.nyc_output/
.nogit/
.playwright-mcp/
*.log
.DS_Store
.env
.env.*
!.env.example
+1
View File
@@ -0,0 +1 @@
@smarthome.exchange:registry=https://packages.foss.global/
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env node
import './dist_ts/cli/index.js';
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env node
import '@git.zone/tsrun';
import './ts/cli/index.ts';
+59
View File
@@ -0,0 +1,59 @@
{
"name": "@smarthome.exchange/integrations",
"version": "0.1.0",
"private": false,
"description": "TypeScript-native device integrations for smarthome.exchange.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"exports": {
".": "./dist_ts/index.js"
},
"bin": {
"shx-integrations": "./cli.js"
},
"type": "module",
"author": "Task Venture Capital GmbH",
"license": "MIT",
"scripts": {
"cli": "pnpm run build && node cli.js",
"generate:ha": "node scripts/generate-homeassistant-ports.mjs",
"test": "tstest test/ --verbose --logfile --timeout 60",
"build": "tsbuild tsfolders --allowimplicitany",
"buildDocs": "tsdoc"
},
"dependencies": {
"@ecobridge.xyz/devicemanager": "^3.1.0",
"@smarthome.exchange/interfaces": "workspace:*"
},
"devDependencies": {
"@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsdoc": "^2.0.3",
"@git.zone/tsrun": "^2.0.3",
"@git.zone/tstest": "^3.6.3",
"@types/node": "^25.6.0"
},
"files": [
"ts/**/*",
"dist/**/*",
"dist_*/**/*",
"dist_ts/**/*",
"cli.js",
"readme.md",
"changelog.md",
"license"
],
"publishConfig": {
"registry": "https://packages.foss.global/"
},
"browserslist": [
"last 1 chrome versions"
],
"keywords": [
"smarthome.exchange",
"integrations",
"device integrations",
"home automation",
"typescript"
],
"packageManager": "pnpm@10.28.2"
}
+27
View File
@@ -0,0 +1,27 @@
# @smarthome.exchange/integrations
TypeScript-native device integrations for smarthome.exchange.
This package owns discovery, configuration flows, vendor clients, mappers, events, and normalized service calls for device ecosystems such as Hue and Wolf Smartset.
The package also includes generated native TypeScript port skeletons for every upstream Home Assistant component domain under `ts/integrations/<domain>`. These are not Python wrappers and not a compatibility namespace. They are TypeScript classes that start as `descriptor-only` integrations and are replaced by handwritten clients/mappers/runtime code as each port matures.
It does not own the canonical device registry, approvals, audit receipts, automations, or persistent home state. Those stay in `@smarthome.exchange/hub`.
Publishing is restricted to `https://packages.foss.global/` through `publishConfig`.
## CLI
```bash
pnpm cli list
pnpm cli inspect hue
pnpm cli discover
```
## Regenerating Home Assistant Port Skeletons
```bash
pnpm generate:ha
```
The generator reads `HA_CORE_COMPONENTS_DIR` or `/tmp/opencode/homeassistant-core/homeassistant/components`, preserves handwritten integration folders, and regenerates only folders with the `.generated-by-smarthome-exchange` marker.
+119
View File
@@ -0,0 +1,119 @@
import { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
const componentsDir = process.env.HA_CORE_COMPONENTS_DIR || '/tmp/opencode/homeassistant-core/homeassistant/components';
const integrationsRoot = new URL('../ts/integrations/', import.meta.url);
const generatedRoot = new URL('../ts/integrations/generated/', import.meta.url);
const markerName = '.generated-by-smarthome-exchange';
const toClassName = (domain) => {
const parts = domain.split(/[^a-zA-Z0-9]+/).filter(Boolean);
const pascal = parts.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join('') || 'Unknown';
return `HomeAssistant${pascal}Integration`;
};
const readManifest = async (componentDir, fallbackDomain) => {
try {
return JSON.parse(await readFile(join(componentDir, 'manifest.json'), 'utf8'));
} catch {
return { domain: fallbackDomain, name: fallbackDomain };
}
};
const isGeneratedFolder = async (folderUrl) => {
try {
await stat(new URL(`${markerName}`, folderUrl));
return true;
} catch {
return false;
}
};
const json = (value) => JSON.stringify(value, null, 2);
await mkdir(integrationsRoot, { recursive: true });
await mkdir(generatedRoot, { recursive: true });
for (const entry of await readdir(integrationsRoot, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
if (entry.name === 'generated') continue;
const folderUrl = new URL(`./${entry.name}/`, integrationsRoot);
if (await isGeneratedFolder(folderUrl)) {
await rm(folderUrl, { recursive: true, force: true });
}
}
const ports = [];
for (const entry of await readdir(componentsDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
if (entry.name.startsWith('__')) continue;
const componentDir = join(componentsDir, entry.name);
const manifest = await readManifest(componentDir, entry.name);
const domain = String(manifest.domain || entry.name);
const folderName = domain.replace(/[^a-z0-9_]/gi, '_').toLowerCase();
const folderUrl = new URL(`./${folderName}/`, integrationsRoot);
const className = toClassName(domain);
const metadata = {
source: 'home-assistant/core',
upstreamPath: `homeassistant/components/${entry.name}`,
upstreamDomain: domain,
integrationType: manifest.integration_type ? String(manifest.integration_type) : undefined,
iotClass: manifest.iot_class ? String(manifest.iot_class) : undefined,
qualityScale: manifest.quality_scale ? String(manifest.quality_scale) : undefined,
requirements: Array.isArray(manifest.requirements) ? manifest.requirements.map(String) : [],
dependencies: Array.isArray(manifest.dependencies) ? manifest.dependencies.map(String) : [],
afterDependencies: Array.isArray(manifest.after_dependencies) ? manifest.after_dependencies.map(String) : [],
codeowners: Array.isArray(manifest.codeowners) ? manifest.codeowners.map(String) : [],
};
let handwritten = false;
try {
await stat(folderUrl);
handwritten = !(await isGeneratedFolder(folderUrl));
} catch {}
if (!handwritten) {
await mkdir(folderUrl, { recursive: true });
await writeFile(new URL(markerName, folderUrl), 'This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.\n');
await writeFile(
new URL('index.ts', folderUrl),
`export * from './${folderName}.classes.integration.js';\nexport * from './${folderName}.types.js';\n`
);
await writeFile(
new URL(`${folderName}.types.ts`, folderUrl),
`export interface I${className.replace(/Integration$/, 'Config')} {\n // TODO: replace with the TypeScript-native config for ${domain}.\n [key: string]: unknown;\n}\n`
);
await writeFile(
new URL(`${folderName}.classes.integration.ts`, folderUrl),
`import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';\n\nexport class ${className} extends DescriptorOnlyIntegration {\n constructor() {\n super({\n domain: ${JSON.stringify(domain)},\n displayName: ${JSON.stringify(manifest.name ? String(manifest.name) : domain)},\n status: 'descriptor-only',\n metadata: ${json(metadata)},\n });\n }\n}\n`
);
}
ports.push({
domain,
folderName,
className,
handwritten,
});
}
ports.sort((a, b) => a.domain.localeCompare(b.domain));
const imports = ports
.filter((port) => !port.handwritten)
.map((port) => `import { ${port.className} } from '../${port.folderName}/index.js';`)
.join('\n');
const constructorPushes = ports
.filter((port) => !port.handwritten)
.map((port) => `generatedHomeAssistantPortIntegrations.push(new ${port.className}());`)
.join('\n');
await writeFile(
new URL('index.ts', generatedRoot),
`// Generated by scripts/generate-homeassistant-ports.mjs. Do not edit manually.\n\nimport type { BaseIntegration } from '../../core/classes.baseintegration.js';\n${imports}\n\nexport const generatedHomeAssistantPortIntegrations: BaseIntegration[] = [];\n${constructorPushes}\n\nexport const generatedHomeAssistantPortCount = ${ports.filter((port) => !port.handwritten).length};\nexport const handwrittenHomeAssistantPortDomains = ${json(ports.filter((port) => port.handwritten).map((port) => port.domain))};\n`
);
console.log(`Generated ${ports.filter((port) => !port.handwritten).length} native TypeScript port skeletons. Preserved ${ports.filter((port) => port.handwritten).length} handwritten folders.`);
@@ -0,0 +1,11 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DiscoveryDescriptor } from '../../ts/core/index.js';
tap.test('keeps probes, matchers, and validators inspectable', async () => {
const descriptor = new DiscoveryDescriptor({ integrationDomain: 'test', displayName: 'Test' });
expect(descriptor.getProbes()).toEqual([]);
expect(descriptor.getMatchers()).toEqual([]);
expect(descriptor.getValidators()).toEqual([]);
});
export default tap.start();
+10
View File
@@ -0,0 +1,10 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createDefaultIntegrationRegistry, DiscoveryEngine } from '../../ts/index.js';
tap.test('runs active discovery across default integrations', async () => {
const engine = new DiscoveryEngine(createDefaultIntegrationRegistry());
const candidates = await engine.runActiveDiscovery();
expect(candidates.some((candidateArg) => candidateArg.integrationDomain === 'hue')).toBeTrue();
});
export default tap.start();
+13
View File
@@ -0,0 +1,13 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { generatedHomeAssistantPortCount, handwrittenHomeAssistantPortDomains } from '../../ts/integrations/generated/index.js';
import { createDefaultIntegrationRegistry } from '../../ts/index.js';
tap.test('registers generated native Home Assistant port skeletons', async () => {
expect(generatedHomeAssistantPortCount).toBeGreaterThan(1000);
expect(handwrittenHomeAssistantPortDomains).toContain('hue');
const registry = createDefaultIntegrationRegistry();
expect(registry.get('3_day_blinds')).toBeTruthy();
expect(registry.get('hue')?.status).toEqual('control-runtime');
});
export default tap.start();
+18
View File
@@ -0,0 +1,18 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createHueDiscoveryDescriptor } from '../../ts/integrations/hue/index.js';
tap.test('matches Hue mDNS records', async () => {
const descriptor = createHueDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[0];
const result = await matcher.matches({
host: 'hue.local',
port: 443,
txt: {
bridgeid: '001788fffe123456',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.normalizedDeviceId).toEqual('001788fffe123456');
});
export default tap.start();
+20
View File
@@ -0,0 +1,20 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { HueMapper } from '../../ts/integrations/hue/index.js';
tap.test('maps Hue lights to canonical devices and entities', async () => {
const resources = {
devices: [],
lights: [
{
id: 'light-1',
metadata: { name: 'Kitchen Ceiling' },
on: { on: true },
dimming: { brightness: 80 },
},
],
};
expect(HueMapper.toDevices(resources).length).toEqual(1);
expect(HueMapper.toEntities(resources)[0].id).toEqual('light.kitchen_ceiling');
});
export default tap.start();
+21
View File
@@ -0,0 +1,21 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createShellyDiscoveryDescriptor } from '../../ts/integrations/shelly/index.js';
tap.test('matches Shelly zeroconf records', async () => {
const descriptor = createShellyDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[0];
const result = await matcher.matches({
name: 'shellyplus1pm-a8032abe54dc',
type: '_http._tcp.local.',
host: 'shellyplus1pm-a8032abe54dc.local',
port: 80,
txt: {
id: 'shellyplus1pm-a8032abe54dc',
model: 'SNSW-001P16EU',
},
}, {});
expect(result.matched).toBeTrue();
expect(result.normalizedDeviceId).toEqual('shellyplus1pm-a8032abe54dc');
});
export default tap.start();
+52
View File
@@ -0,0 +1,52 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { ShellyMapper } from '../../ts/integrations/shelly/index.js';
const snapshot = {
deviceInfo: {
id: 'shellyplus1pm-a8032abe54dc',
mac: 'A8032ABE54DC',
model: 'SNSW-001P16EU',
gen: 2,
ver: '1.0.0',
},
status: {
sys: {
mac: 'A8032ABE54DC',
uptime: 120,
},
'switch:0': {
id: 0,
source: 'init',
output: true,
apower: 8.9,
voltage: 237.5,
aenergy: {
total: 6.532,
},
temperature: {
tC: 23.5,
},
},
},
deviceConfig: {
sys: {
device: {
name: 'Kitchen Plug',
},
},
'switch:0': {
name: 'Counter Outlet',
},
},
};
tap.test('maps Shelly switch status to canonical device features and entities', async () => {
const devices = ShellyMapper.toDevices(snapshot);
const entities = ShellyMapper.toEntities(snapshot);
expect(devices[0].id).toEqual('shelly.device.shellyplus1pm_a8032abe54dc');
expect(devices[0].features.some((featureArg) => featureArg.id === 'switch_0_power')).toBeTrue();
expect(entities[0].id).toEqual('switch.kitchen_plug_0');
expect(entities.some((entityArg) => entityArg.id === 'sensor.kitchen_plug_0_energy')).toBeTrue();
});
export default tap.start();
@@ -0,0 +1,12 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createWolfSmartsetDiscoveryDescriptor } from '../../ts/integrations/wolf_smartset/index.js';
tap.test('matches manual Wolf Smartset setup hints', async () => {
const descriptor = createWolfSmartsetDiscoveryDescriptor();
const matcher = descriptor.getMatchers()[0];
const result = await matcher.matches({ host: 'wolf.local' }, {});
expect(result.matched).toBeTrue();
expect(result.candidate?.integrationDomain).toEqual('wolf_smartset');
});
export default tap.start();
+30
View File
@@ -0,0 +1,30 @@
import { createDefaultIntegrationRegistry } from '../index.js';
import { ConsoleLogger, DiscoveryEngine } from '../core/index.js';
import { commandDiscover } from './commands.discover.js';
import { commandInspect } from './commands.inspect.js';
import { commandList } from './commands.list.js';
import { commandSetup } from './commands.setup.js';
export class CliRuntime {
public integrationRegistry = createDefaultIntegrationRegistry();
public discoveryEngine = new DiscoveryEngine(this.integrationRegistry);
public logger = new ConsoleLogger();
public async list(): Promise<string> {
return commandList(this.integrationRegistry);
}
public async inspect(domainArg: string): Promise<string> {
return commandInspect(this.integrationRegistry, domainArg);
}
public async discover() {
return commandDiscover(this.discoveryEngine, {
logger: this.logger,
});
}
public async setup(domainArg: string) {
return commandSetup(this.integrationRegistry, domainArg);
}
}
+8
View File
@@ -0,0 +1,8 @@
import type { DiscoveryEngine, IDiscoveryCandidate, IDiscoveryContext } from '../core/index.js';
export const commandDiscover = async (
discoveryEngineArg: DiscoveryEngine,
contextArg: IDiscoveryContext = {}
): Promise<IDiscoveryCandidate[]> => {
return discoveryEngineArg.runActiveDiscovery(contextArg);
};
+24
View File
@@ -0,0 +1,24 @@
import type { IntegrationRegistry } from '../core/index.js';
export const commandInspect = (registryArg: IntegrationRegistry, domainArg: string): string => {
const integration = registryArg.get(domainArg);
if (!integration) {
throw new Error(`Integration not found: ${domainArg}`);
}
const descriptor = integration.discoveryDescriptor;
const probes = descriptor.getProbes();
const matchers = descriptor.getMatchers();
const validators = descriptor.getValidators();
return [
`Domain: ${integration.domain}`,
`Name: ${integration.displayName}`,
`Status: ${integration.status}`,
'',
'Discovery:',
` probes: ${probes.length ? probes.map((probeArg) => probeArg.id).join(', ') : 'none'}`,
` matchers: ${matchers.length ? matchers.map((matcherArg) => matcherArg.id).join(', ') : 'none'}`,
` validators: ${validators.length ? validators.map((validatorArg) => validatorArg.id).join(', ') : 'none'}`,
].join('\n');
};
+8
View File
@@ -0,0 +1,8 @@
import type { IntegrationRegistry } from '../core/index.js';
export const commandList = (registryArg: IntegrationRegistry): string => {
return registryArg
.list()
.map((integrationArg) => `${integrationArg.domain}\t${integrationArg.displayName}\t${integrationArg.status}`)
.join('\n');
};
+32
View File
@@ -0,0 +1,32 @@
import type { IntegrationRegistry } from '../core/index.js';
export const commandSetup = async (registryArg: IntegrationRegistry, domainArg: string) => {
const integration = registryArg.get(domainArg) as any;
if (!integration) {
throw new Error(`Integration not found: ${domainArg}`);
}
if (!integration.configFlow) {
return {
domain: domainArg,
status: 'no-config-flow',
};
}
const step = await integration.configFlow.start(
{
source: 'manual',
integrationDomain: domainArg,
id: `${domainArg}:manual`,
},
{}
);
return {
domain: domainArg,
step: {
kind: step.kind,
title: step.title,
description: step.description,
fields: step.fields,
error: step.error,
},
};
};
+41
View File
@@ -0,0 +1,41 @@
import { CliRuntime } from './classes.cliruntime.js';
export * from './classes.cliruntime.js';
export * from './commands.discover.js';
export * from './commands.inspect.js';
export * from './commands.list.js';
export * from './commands.setup.js';
export const runCli = async (argvArg = process.argv.slice(2)) => {
const runtime = new CliRuntime();
const [commandArg = 'list', integrationArg] = argvArg;
if (commandArg === 'list') {
console.log(await runtime.list());
return;
}
if (commandArg === 'inspect') {
console.log(await runtime.inspect(integrationArg || 'hue'));
return;
}
if (commandArg === 'discover') {
console.log(JSON.stringify(await runtime.discover(), null, 2));
return;
}
if (commandArg === 'setup') {
console.log(JSON.stringify(await runtime.setup(integrationArg || 'hue'), null, 2));
return;
}
throw new Error(`Unknown integrations CLI command: ${commandArg}`);
};
if (process.argv[1]?.endsWith('/cli.js') || process.argv[1]?.endsWith('/cli.ts.js')) {
runCli().catch((errorArg) => {
console.error(errorArg.message);
process.exit(1);
});
}
+16
View File
@@ -0,0 +1,16 @@
import type { DiscoveryDescriptor } from './classes.discoverydescriptor.js';
import type { IIntegrationRuntime, IIntegrationSetupContext, TIntegrationStatus } from './types.js';
export abstract class BaseIntegration<TConfig = unknown> {
public abstract readonly domain: string;
public abstract readonly displayName: string;
public abstract readonly status: TIntegrationStatus;
public abstract readonly discoveryDescriptor: DiscoveryDescriptor;
public abstract setup(
configArg: TConfig,
contextArg: IIntegrationSetupContext
): Promise<IIntegrationRuntime>;
public abstract destroy(): Promise<void>;
}
+42
View File
@@ -0,0 +1,42 @@
import * as plugins from '../plugins.js';
import type { IConfigStore } from './types.js';
export class JsonFileConfigStore implements IConfigStore {
constructor(private readonly filePath: string) {}
public async get<TValue>(keyArg: string): Promise<TValue | undefined> {
const data = await this.readFile();
return data[keyArg] as TValue | undefined;
}
public async set<TValue>(keyArg: string, valueArg: TValue): Promise<void> {
const data = await this.readFile();
data[keyArg] = valueArg;
await this.writeFile(data);
}
public async delete(keyArg: string): Promise<void> {
const data = await this.readFile();
delete data[keyArg];
await this.writeFile(data);
}
public async list(prefixArg = ''): Promise<string[]> {
const data = await this.readFile();
return Object.keys(data).filter((keyArg) => keyArg.startsWith(prefixArg));
}
private async readFile(): Promise<Record<string, unknown>> {
try {
const fileString = await plugins.fs.readFile(this.filePath, 'utf8');
return JSON.parse(fileString) as Record<string, unknown>;
} catch (error) {
return {};
}
}
private async writeFile(dataArg: Record<string, unknown>): Promise<void> {
await plugins.fs.mkdir(plugins.path.dirname(this.filePath), { recursive: true });
await plugins.fs.writeFile(this.filePath, `${JSON.stringify(dataArg, null, 2)}\n`);
}
}
@@ -0,0 +1,42 @@
import { BaseIntegration } from './classes.baseintegration.js';
import { DiscoveryDescriptor } from './classes.discoverydescriptor.js';
import { IntegrationError } from './errors.js';
import type { IIntegrationRuntime, IIntegrationSetupContext, TIntegrationStatus } from './types.js';
export interface IDescriptorOnlyIntegrationOptions {
domain: string;
displayName: string;
status?: TIntegrationStatus;
metadata?: Record<string, unknown>;
}
export class DescriptorOnlyIntegration extends BaseIntegration<unknown> {
public readonly domain: string;
public readonly displayName: string;
public readonly status: TIntegrationStatus;
public readonly discoveryDescriptor: DiscoveryDescriptor;
public readonly metadata: Record<string, unknown>;
constructor(optionsArg: IDescriptorOnlyIntegrationOptions) {
super();
this.domain = optionsArg.domain;
this.displayName = optionsArg.displayName;
this.status = optionsArg.status || 'descriptor-only';
this.metadata = optionsArg.metadata || {};
this.discoveryDescriptor = new DiscoveryDescriptor({
integrationDomain: this.domain,
displayName: this.displayName,
});
}
public async setup(configArg: unknown, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
void configArg;
void contextArg;
throw new IntegrationError(
`Integration ${this.domain} is descriptor-only and has no TypeScript runtime yet.`,
'DESCRIPTOR_ONLY_INTEGRATION'
);
}
public async destroy(): Promise<void> {}
}
+47
View File
@@ -0,0 +1,47 @@
import type {
IDiscoveryDescriptorOptions,
IDiscoveryMatcher,
IDiscoveryProbe,
IDiscoveryValidator,
} from './types.js';
export class DiscoveryDescriptor {
public readonly integrationDomain: string;
public readonly displayName: string;
private readonly probes: IDiscoveryProbe[] = [];
private readonly matchers: IDiscoveryMatcher[] = [];
private readonly validators: IDiscoveryValidator[] = [];
constructor(optionsArg: IDiscoveryDescriptorOptions) {
this.integrationDomain = optionsArg.integrationDomain;
this.displayName = optionsArg.displayName;
}
public addProbe(probeArg: IDiscoveryProbe): this {
this.probes.push(probeArg);
return this;
}
public addMatcher<TInput>(matcherArg: IDiscoveryMatcher<TInput>): this {
this.matchers.push(matcherArg as IDiscoveryMatcher);
return this;
}
public addValidator(validatorArg: IDiscoveryValidator): this {
this.validators.push(validatorArg);
return this;
}
public getProbes(): IDiscoveryProbe[] {
return [...this.probes];
}
public getMatchers(): IDiscoveryMatcher[] {
return [...this.matchers];
}
public getValidators(): IDiscoveryValidator[] {
return [...this.validators];
}
}
+74
View File
@@ -0,0 +1,74 @@
import type { IntegrationRegistry } from './classes.integrationregistry.js';
import type {
IDiscoveryCandidate,
IDiscoveryContext,
IDiscoveryMatch,
TDiscoverySource,
} from './types.js';
export class DiscoveryEngine {
constructor(private readonly integrationRegistry: IntegrationRegistry) {}
public async runActiveDiscovery(contextArg: IDiscoveryContext = {}): Promise<IDiscoveryCandidate[]> {
const candidates: IDiscoveryCandidate[] = [];
for (const integration of this.integrationRegistry.list()) {
const descriptor = integration.discoveryDescriptor;
for (const probe of descriptor.getProbes()) {
const result = await probe.probe(contextArg);
for (const candidate of result.candidates) {
candidates.push({
...candidate,
integrationDomain: candidate.integrationDomain ?? integration.domain,
});
}
}
}
return candidates;
}
public async matchExistingData<TInput>(
sourceArg: TDiscoverySource,
inputArg: TInput,
contextArg: IDiscoveryContext = {}
): Promise<IDiscoveryMatch[]> {
const matches: IDiscoveryMatch[] = [];
for (const integration of this.integrationRegistry.list()) {
const descriptor = integration.discoveryDescriptor;
for (const matcher of descriptor.getMatchers()) {
if (matcher.source !== sourceArg) {
continue;
}
const result = await matcher.matches(inputArg, contextArg);
if (result.matched) {
matches.push(result);
}
}
}
return matches;
}
public async validateCandidate(
candidateArg: IDiscoveryCandidate,
contextArg: IDiscoveryContext = {}
): Promise<IDiscoveryMatch[]> {
const matches: IDiscoveryMatch[] = [];
const integrations = candidateArg.integrationDomain
? this.integrationRegistry.list().filter((integrationArg) => integrationArg.domain === candidateArg.integrationDomain)
: this.integrationRegistry.list();
for (const integration of integrations) {
for (const validator of integration.discoveryDescriptor.getValidators()) {
const result = await validator.validate(candidateArg, contextArg);
if (result.matched) {
matches.push(result);
}
}
}
return matches;
}
}
+25
View File
@@ -0,0 +1,25 @@
import type { BaseIntegration } from './classes.baseintegration.js';
import type { DiscoveryDescriptor } from './classes.discoverydescriptor.js';
export class IntegrationRegistry {
private readonly integrations = new Map<string, BaseIntegration>();
public register(integrationArg: BaseIntegration): void {
if (this.integrations.has(integrationArg.domain)) {
throw new Error(`Integration already registered: ${integrationArg.domain}`);
}
this.integrations.set(integrationArg.domain, integrationArg);
}
public get(domainArg: string): BaseIntegration | undefined {
return this.integrations.get(domainArg);
}
public list(): BaseIntegration[] {
return [...this.integrations.values()];
}
public getDiscoveryDescriptors(): DiscoveryDescriptor[] {
return this.list().map((integrationArg) => integrationArg.discoveryDescriptor);
}
}
+8
View File
@@ -0,0 +1,8 @@
import type { ILogger } from './types.js';
export class ConsoleLogger implements ILogger {
public log(levelArg: 'debug' | 'info' | 'warn' | 'error', messageArg: string, metadataArg?: Record<string, unknown>): void {
const payload = metadataArg ? ` ${JSON.stringify(metadataArg)}` : '';
console[levelArg === 'debug' ? 'log' : levelArg](`[${levelArg}] ${messageArg}${payload}`);
}
}
+31
View File
@@ -0,0 +1,31 @@
import type { BaseIntegration } from './classes.baseintegration.js';
import type { IIntegrationRuntime, IIntegrationSetupContext } from './types.js';
export class IntegrationRuntimeManager {
private readonly runtimes = new Map<string, IIntegrationRuntime>();
public async setupIntegration<TConfig>(
integrationArg: BaseIntegration<TConfig>,
configArg: TConfig,
contextArg: IIntegrationSetupContext = {}
): Promise<IIntegrationRuntime> {
const runtime = await integrationArg.setup(configArg, contextArg);
this.runtimes.set(integrationArg.domain, runtime);
return runtime;
}
public getRuntime(domainArg: string): IIntegrationRuntime | undefined {
return this.runtimes.get(domainArg);
}
public listRuntimes(): IIntegrationRuntime[] {
return [...this.runtimes.values()];
}
public async destroyAll(): Promise<void> {
for (const runtime of this.runtimes.values()) {
await runtime.destroy();
}
this.runtimes.clear();
}
}
+27
View File
@@ -0,0 +1,27 @@
export class IntegrationError extends Error {
constructor(
messageArg: string,
public readonly code: string,
public readonly cause?: unknown
) {
super(messageArg);
}
}
export class DiscoveryError extends IntegrationError {
constructor(messageArg: string, causeArg?: unknown) {
super(messageArg, 'DISCOVERY_ERROR', causeArg);
}
}
export class AuthenticationError extends IntegrationError {
constructor(messageArg: string, causeArg?: unknown) {
super(messageArg, 'AUTHENTICATION_ERROR', causeArg);
}
}
export class DeviceCommunicationError extends IntegrationError {
constructor(messageArg: string, causeArg?: unknown) {
super(messageArg, 'DEVICE_COMMUNICATION_ERROR', causeArg);
}
}
+10
View File
@@ -0,0 +1,10 @@
export * from './classes.baseintegration.js';
export * from './classes.configstore.js';
export * from './classes.descriptoronlyintegration.js';
export * from './classes.discoverydescriptor.js';
export * from './classes.discoveryengine.js';
export * from './classes.integrationregistry.js';
export * from './classes.logger.js';
export * from './classes.runtimemanager.js';
export * from './errors.js';
export * from './types.js';
+208
View File
@@ -0,0 +1,208 @@
import * as plugins from '../plugins.js';
export type TDiscoverySource =
| 'mdns'
| 'ssdp'
| 'dhcp'
| 'bluetooth'
| 'usb'
| 'mqtt'
| 'http'
| 'manual'
| 'broker'
| 'cloud'
| 'custom';
export type TDiscoveryConfidence = 'low' | 'medium' | 'high' | 'certain';
export type TIntegrationStatus =
| 'descriptor-only'
| 'discovery-supported'
| 'config-flow-supported'
| 'read-only-runtime'
| 'control-runtime'
| 'production-ready';
export interface ILogger {
log(levelArg: 'debug' | 'info' | 'warn' | 'error', messageArg: string, metadataArg?: Record<string, unknown>): void;
}
export interface INetworkInterface {
name: string;
address: string;
family: 'IPv4' | 'IPv6';
internal: boolean;
}
export interface IConfigStore {
get<TValue>(keyArg: string): Promise<TValue | undefined>;
set<TValue>(keyArg: string, valueArg: TValue): Promise<void>;
delete(keyArg: string): Promise<void>;
list(prefixArg?: string): Promise<string[]>;
}
export interface IDiscoveryContext {
abortSignal?: AbortSignal;
logger?: ILogger;
networkInterfaces?: INetworkInterface[];
configStore?: IConfigStore;
}
export interface IDiscoveryCandidate {
source: TDiscoverySource;
integrationDomain?: string;
id?: string;
host?: string;
port?: number;
name?: string;
manufacturer?: string;
model?: string;
serialNumber?: string;
macAddress?: string;
metadata?: Record<string, unknown>;
}
export interface IDiscoveryProbeResult {
candidates: IDiscoveryCandidate[];
}
export interface IDiscoveryMatch {
matched: boolean;
confidence: TDiscoveryConfidence;
reason: string;
candidate?: IDiscoveryCandidate;
normalizedDeviceId?: string;
metadata?: Record<string, unknown>;
}
export interface IDiscoveryProbe {
id: string;
source: TDiscoverySource;
description?: string;
probe(contextArg: IDiscoveryContext): Promise<IDiscoveryProbeResult>;
}
export interface IDiscoveryMatcher<TInput = unknown> {
id: string;
source: TDiscoverySource;
description?: string;
matches(inputArg: TInput, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch>;
}
export interface IDiscoveryValidator {
id: string;
description?: string;
validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch>;
}
export interface IDiscoveryDescriptorOptions {
integrationDomain: string;
displayName: string;
}
export interface IIntegrationSetupContext {
logger?: ILogger;
configStore?: IConfigStore;
abortSignal?: AbortSignal;
}
export type TEntityPlatform =
| 'light'
| 'switch'
| 'sensor'
| 'binary_sensor'
| 'button'
| 'climate'
| 'cover'
| 'fan'
| 'number'
| 'select'
| 'text'
| 'update';
export interface IIntegrationEntity<TState = unknown> {
id: string;
uniqueId: string;
integrationDomain: string;
deviceId: string;
platform: TEntityPlatform;
name: string;
state: TState;
attributes?: Record<string, unknown>;
available: boolean;
}
export interface IServiceCallRequest {
domain: string;
service: string;
target: {
entityId?: string;
deviceId?: string;
};
data?: Record<string, unknown>;
}
export interface IServiceCallResult {
success: boolean;
error?: string;
data?: unknown;
}
export type TIntegrationEventType =
| 'device_added'
| 'device_removed'
| 'entity_added'
| 'entity_removed'
| 'state_changed'
| 'availability_changed'
| 'error';
export interface IIntegrationEvent {
type: TIntegrationEventType;
integrationDomain: string;
deviceId?: string;
entityId?: string;
data?: unknown;
timestamp: number;
}
export interface IIntegrationRuntime {
domain: string;
devices(): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]>;
entities(): Promise<IIntegrationEntity[]>;
subscribe?(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>>;
callService?(requestArg: IServiceCallRequest): Promise<IServiceCallResult>;
destroy(): Promise<void>;
}
export type TConfigFlowStepKind = 'form' | 'wait' | 'done' | 'error';
export interface IConfigFlowField {
name: string;
label: string;
type: 'text' | 'password' | 'number' | 'boolean' | 'select';
required?: boolean;
options?: Array<{
label: string;
value: string;
}>;
}
export interface IConfigFlowContext {
logger?: ILogger;
configStore?: IConfigStore;
}
export interface IConfigFlowStep<TConfig = unknown> {
kind: TConfigFlowStepKind;
title?: string;
description?: string;
fields?: IConfigFlowField[];
submit?(valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<TConfig>>;
config?: TConfig;
error?: string;
}
export interface IConfigFlow<TConfig = unknown> {
start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<TConfig>>;
}
+28
View File
@@ -0,0 +1,28 @@
export * from './core/index.js';
export * from './protocols/index.js';
export * from './integrations/index.js';
import { HueIntegration } from './integrations/hue/index.js';
import { ShellyIntegration } from './integrations/shelly/index.js';
import { WolfSmartsetIntegration } from './integrations/wolf_smartset/index.js';
import { generatedHomeAssistantPortIntegrations } from './integrations/generated/index.js';
import { IntegrationRegistry } from './core/index.js';
export const integrations = [
new HueIntegration(),
new ShellyIntegration(),
new WolfSmartsetIntegration(),
];
export const createDefaultIntegrationRegistry = (): IntegrationRegistry => {
const registry = new IntegrationRegistry();
for (const integration of integrations) {
registry.register(integration);
}
for (const integration of generatedHomeAssistantPortIntegrations) {
if (!registry.get(integration.domain)) {
registry.register(integration);
}
}
return registry;
};
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,21 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistant3DayBlindsIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "3_day_blinds",
displayName: "3 Day Blinds",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/3_day_blinds",
"upstreamDomain": "3_day_blinds",
"integrationType": "virtual",
"requirements": [],
"dependencies": [],
"afterDependencies": [],
"codeowners": []
},
});
}
}
@@ -0,0 +1,4 @@
export interface IHomeAssistant3DayBlindsConfig {
// TODO: replace with the TypeScript-native config for 3_day_blinds.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './3_day_blinds.classes.integration.js';
export * from './3_day_blinds.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,25 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAbodeIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "abode",
displayName: "Abode",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/abode",
"upstreamDomain": "abode",
"iotClass": "cloud_push",
"requirements": [
"jaraco.abode==6.4.0"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": [
"@shred86"
]
},
});
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface IHomeAssistantAbodeConfig {
// TODO: replace with the TypeScript-native config for abode.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './abode.classes.integration.js';
export * from './abode.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,29 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAcaiaIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "acaia",
displayName: "Acaia",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/acaia",
"upstreamDomain": "acaia",
"integrationType": "device",
"iotClass": "local_push",
"qualityScale": "platinum",
"requirements": [
"aioacaia==0.1.17"
],
"dependencies": [
"bluetooth_adapters"
],
"afterDependencies": [],
"codeowners": [
"@zweckj"
]
},
});
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface IHomeAssistantAcaiaConfig {
// TODO: replace with the TypeScript-native config for acaia.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './acaia.classes.integration.js';
export * from './acaia.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,26 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAccuweatherIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "accuweather",
displayName: "AccuWeather",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/accuweather",
"upstreamDomain": "accuweather",
"integrationType": "service",
"iotClass": "cloud_polling",
"requirements": [
"accuweather==5.1.0"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": [
"@bieniu"
]
},
});
}
}
@@ -0,0 +1,4 @@
export interface IHomeAssistantAccuweatherConfig {
// TODO: replace with the TypeScript-native config for accuweather.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './accuweather.classes.integration.js';
export * from './accuweather.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,24 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAcerProjectorIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "acer_projector",
displayName: "Acer Projector",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/acer_projector",
"upstreamDomain": "acer_projector",
"iotClass": "local_polling",
"qualityScale": "legacy",
"requirements": [
"serialx==1.4.1"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": []
},
});
}
}
@@ -0,0 +1,4 @@
export interface IHomeAssistantAcerProjectorConfig {
// TODO: replace with the TypeScript-native config for acer_projector.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './acer_projector.classes.integration.js';
export * from './acer_projector.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,25 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAcmedaIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "acmeda",
displayName: "Rollease Acmeda Automate",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/acmeda",
"upstreamDomain": "acmeda",
"iotClass": "local_push",
"requirements": [
"aiopulse==0.4.6"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": [
"@atmurray"
]
},
});
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface IHomeAssistantAcmedaConfig {
// TODO: replace with the TypeScript-native config for acmeda.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './acmeda.classes.integration.js';
export * from './acmeda.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,21 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAcomaxIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "acomax",
displayName: "Acomax",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/acomax",
"upstreamDomain": "acomax",
"integrationType": "virtual",
"requirements": [],
"dependencies": [],
"afterDependencies": [],
"codeowners": []
},
});
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface IHomeAssistantAcomaxConfig {
// TODO: replace with the TypeScript-native config for acomax.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './acomax.classes.integration.js';
export * from './acomax.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,22 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantActiontecIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "actiontec",
displayName: "Actiontec",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/actiontec",
"upstreamDomain": "actiontec",
"iotClass": "local_polling",
"qualityScale": "legacy",
"requirements": [],
"dependencies": [],
"afterDependencies": [],
"codeowners": []
},
});
}
}
@@ -0,0 +1,4 @@
export interface IHomeAssistantActiontecConfig {
// TODO: replace with the TypeScript-native config for actiontec.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './actiontec.classes.integration.js';
export * from './actiontec.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,28 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantActronAirIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "actron_air",
displayName: "Actron Air",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/actron_air",
"upstreamDomain": "actron_air",
"integrationType": "hub",
"iotClass": "cloud_polling",
"qualityScale": "silver",
"requirements": [
"actron-neo-api==0.5.6"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": [
"@kclif9",
"@JagadishDhanamjayam"
]
},
});
}
}
@@ -0,0 +1,4 @@
export interface IHomeAssistantActronAirConfig {
// TODO: replace with the TypeScript-native config for actron_air.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './actron_air.classes.integration.js';
export * from './actron_air.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,27 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAdaxIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "adax",
displayName: "Adax",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/adax",
"upstreamDomain": "adax",
"iotClass": "local_polling",
"requirements": [
"adax==0.4.0",
"Adax-local==0.3.0"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": [
"@danielhiversen",
"@lazytarget"
]
},
});
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface IHomeAssistantAdaxConfig {
// TODO: replace with the TypeScript-native config for adax.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './adax.classes.integration.js';
export * from './adax.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,26 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAdguardIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "adguard",
displayName: "AdGuard Home",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/adguard",
"upstreamDomain": "adguard",
"integrationType": "service",
"iotClass": "local_polling",
"requirements": [
"adguardhome==0.8.1"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": [
"@frenck"
]
},
});
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface IHomeAssistantAdguardConfig {
// TODO: replace with the TypeScript-native config for adguard.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './adguard.classes.integration.js';
export * from './adguard.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,26 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAdsIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "ads",
displayName: "ADS",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/ads",
"upstreamDomain": "ads",
"iotClass": "local_push",
"qualityScale": "legacy",
"requirements": [
"pyads==3.4.0"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": [
"@mrpasztoradam"
]
},
});
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface IHomeAssistantAdsConfig {
// TODO: replace with the TypeScript-native config for ads.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './ads.classes.integration.js';
export * from './ads.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,26 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAdvantageAirIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "advantage_air",
displayName: "Advantage Air",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/advantage_air",
"upstreamDomain": "advantage_air",
"integrationType": "hub",
"iotClass": "local_polling",
"requirements": [
"advantage-air==0.4.4"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": [
"@Bre77"
]
},
});
}
}
@@ -0,0 +1,4 @@
export interface IHomeAssistantAdvantageAirConfig {
// TODO: replace with the TypeScript-native config for advantage_air.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './advantage_air.classes.integration.js';
export * from './advantage_air.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,26 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAemetIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "aemet",
displayName: "AEMET OpenData",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/aemet",
"upstreamDomain": "aemet",
"integrationType": "service",
"iotClass": "cloud_polling",
"requirements": [
"AEMET-OpenData==0.6.4"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": [
"@Noltari"
]
},
});
}
}
+4
View File
@@ -0,0 +1,4 @@
export interface IHomeAssistantAemetConfig {
// TODO: replace with the TypeScript-native config for aemet.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './aemet.classes.integration.js';
export * from './aemet.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,21 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAepOhioIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "aep_ohio",
displayName: "AEP Ohio",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/aep_ohio",
"upstreamDomain": "aep_ohio",
"integrationType": "virtual",
"requirements": [],
"dependencies": [],
"afterDependencies": [],
"codeowners": []
},
});
}
}
@@ -0,0 +1,4 @@
export interface IHomeAssistantAepOhioConfig {
// TODO: replace with the TypeScript-native config for aep_ohio.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './aep_ohio.classes.integration.js';
export * from './aep_ohio.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,21 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAepTexasIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "aep_texas",
displayName: "AEP Texas",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/aep_texas",
"upstreamDomain": "aep_texas",
"integrationType": "virtual",
"requirements": [],
"dependencies": [],
"afterDependencies": [],
"codeowners": []
},
});
}
}
@@ -0,0 +1,4 @@
export interface IHomeAssistantAepTexasConfig {
// TODO: replace with the TypeScript-native config for aep_texas.
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './aep_texas.classes.integration.js';
export * from './aep_texas.types.js';
@@ -0,0 +1 @@
This folder is generated from Home Assistant component metadata. Replace it with a handwritten TypeScript port when implementing runtime support.
@@ -0,0 +1,24 @@
import { DescriptorOnlyIntegration } from '../../core/classes.descriptoronlyintegration.js';
export class HomeAssistantAftershipIntegration extends DescriptorOnlyIntegration {
constructor() {
super({
domain: "aftership",
displayName: "AfterShip",
status: 'descriptor-only',
metadata: {
"source": "home-assistant/core",
"upstreamPath": "homeassistant/components/aftership",
"upstreamDomain": "aftership",
"integrationType": "service",
"iotClass": "cloud_polling",
"requirements": [
"pyaftership==21.11.0"
],
"dependencies": [],
"afterDependencies": [],
"codeowners": []
},
});
}
}
@@ -0,0 +1,4 @@
export interface IHomeAssistantAftershipConfig {
// TODO: replace with the TypeScript-native config for aftership.
[key: string]: unknown;
}

Some files were not shown because too many files have changed in this diff Show More