Skip to content

API Reference

Complete reference for all public exports from the @ts-ha/core package. Organised by module.

import { createEngine, Automation, type Trigger, type TriggerContext } from "@ts-ha/core";

Engine

createEngine(options)

Factory function that wires all core services together and returns an Engine object.

function createEngine(options: EngineOptions): Engine

EngineOptions

Field Type Default Description
automationsDir string required Path to directory containing automation files
recursive boolean false Scan subdirectories for automations
config Partial<Config> (from env) Override environment-derived configuration
logger Logger (auto) Provide a custom pino logger
state StateManagerOptions (from env) State persistence options
deviceRegistry object (from env) Device registry options (see below)
deviceState DeviceStateStoreOptions (from env) Durable last-known device state store options (see below)
stateToggles StateToggleConfig[] [] Boolean StateManager keys presented as devices through the unified device layer (Zigbee/Shelly/Nanoleaf/state toggles) — controllable from the web UI and HomeKit alike
services object {} Optional service instances or factories

deviceRegistry sub-options:

Field Type Description
names DeviceNiceNames Nice-name mapping (see Device Registry)
persist boolean Persist device list and state to disk
filePath string Path for the persistence file

deviceState sub-options (DeviceStateStoreOptions, mirroring StateManagerOptions):

Field Type Default Description
persist boolean true Persist last-known device state (debounced write-behind) and restore it before any source starts (from DEVICE_STATE_PERSIST)
filePath string "./device-state.json" Path to the persistence file
flushIntervalMs number 1000 Milliseconds between coalesced saves; 0 saves on every write

services sub-options:

Field Type Description
notifications NotificationService \| ServiceFactory<NotificationService> Push notification service
weather WeatherService \| ServiceFactory<WeatherService> Weather data service
shelly ShellyService \| ShellyServiceFactory Shelly device service
nanoleaf NanoleafService \| ServiceFactory<NanoleafService> Nanoleaf panel service
homekit HomekitServiceFactory HomeKit bridge factory
[key: string] unknown Any custom service

ServiceFactory<T>

type ServiceFactory<T> = (http: HttpClient, logger: Logger) => T;

A factory function that receives the engine's shared HTTP client and a scoped logger, and returns a service instance.

HomekitServiceFactory

interface HomekitServiceContext {
  http: HttpClient;
  logger: Logger;
  devices: AggregateDeviceSource;
}

type HomekitServiceFactory = (ctx: HomekitServiceContext) => HomekitService;

Dedicated factory for the HomeKit bridge. It receives a single HomekitServiceContext object carrying the shared HTTP client, a scoped logger, and the engine's aggregate device accessor. All are resolved by the engine before the factory is called. Breaking change (task 6.16b): the context previously also carried mqtt, deviceRegistry, shelly, and state directly; HomekitService now reads every device family through devices instead, since it consumes the shared device-source layer rather than talking to Zigbee2MQTT, MQTT, or Shelly RPC itself.

ShellyServiceFactory

interface ShellyServiceContext {
  http: HttpClient;
  mqtt: MqttService;
  logger: Logger;
}

type ShellyServiceFactory = (ctx: ShellyServiceContext) => ShellyService;

Dedicated factory for ShellyService, mirroring HomekitServiceFactory. It receives a single ShellyServiceContext object carrying the shared HTTP client, the MQTT service (needed for MQTT-transport devices), and a scoped logger. Breaking change: replaces the previous (http, logger) => ShellyService factory signature, and ShellyService's own constructor now takes (http, mqtt, logger) instead of (http, logger). See Shelly Devices for MQTT-transport registration.

Engine

The object returned by createEngine().

Property / Method Type Description
start() Promise<void> Connect MQTT, discover automations, start HTTP server and services
stop() Promise<void> Gracefully stop all automations, services, MQTT, and HTTP
config Config (readonly) Resolved configuration
logger Logger (readonly) Root pino logger
mqtt MqttService (readonly) MQTT client
http HttpClient (readonly) Shared HTTP client
state StateManager (readonly) State store
notifications NotificationService \| null (readonly) Notification service (shortcut for services.get("notifications"))
services ServiceRegistry (readonly) All registered services
manager AutomationManager (readonly) Automation lifecycle manager
deviceRegistry DeviceRegistry \| null (readonly) Zigbee device registry (null when disabled)

Example:

import { createEngine } from "@ts-ha/core";

const engine = createEngine({
  automationsDir: new URL("./automations", import.meta.url).pathname,
  config: { mqtt: { host: "192.168.1.10" } },
  state: { persist: true },
});

process.on("SIGINT", () => engine.stop().then(() => process.exit(0)));
await engine.start();

Automation

abstract class Automation

Base class for all automations. Subclasses must implement name, triggers, and execute().

import { Automation, type Trigger, type TriggerContext } from "@ts-ha/core";

export default class MyAutomation extends Automation {
  readonly name = "my-automation";
  readonly triggers: Trigger[] = [/* ... */];
  async execute(context: TriggerContext): Promise<void> { /* ... */ }
}

Abstract members

Member Type Description
name readonly string Unique automation identifier (kebab-case)
triggers readonly Trigger[] Array of trigger definitions
execute(context) (context: TriggerContext) => Promise<void> Main logic — called when any trigger fires

Optional members

Member Type Description
requiredServices readonly string[] Service keys that must be present at startup

Protected fields (injected by the framework)

Field Type Description
mqtt MqttService MQTT client
http HttpClient HTTP client
state StateManager State store
logger Logger Child logger scoped to this automation
config Config Application configuration

Protected methods

Method Signature Description
notify (options: NotificationOptions) => Promise<void> Send a push notification; no-ops with a warning if no service is configured
require<T> (key: string) => T Non-null service retrieval from the registry; throws if absent
services ServiceRegistry (getter) Access the full service registry
deviceRegistry DeviceRegistry \| null (getter) Access the device registry

Lifecycle hooks

Override these for setup and teardown. Both have empty default implementations.

Hook When called
onStart(): Promise<void> After the automation is registered and all triggers are wired
onStop(): Promise<void> Before the automation is unregistered on shutdown

Trigger

A discriminated union defining when an automation fires. Seven types are supported:

MQTT trigger

{ type: "mqtt"; topic: string; filter?: (payload: Record<string, unknown>) => boolean }
Field Type Description
topic string MQTT topic (supports + and # wildcards)
filter function Optional — only fire when this returns true

Cron trigger

{ type: "cron"; expression: string }
Field Type Description
expression string Standard cron expression (5 or 6 fields). Timezone from TZ env var.

State trigger

{ type: "state"; key: string; filter?: (newValue: unknown, oldValue: unknown) => boolean }
Field Type Description
key string State key to watch
filter function Optional — both new and old values are available

Webhook trigger

{ type: "webhook"; path: string; methods?: ("GET" | "POST" | "PUT" | "DELETE")[] }
Field Type Description
path string Path segment — accessible at /webhook/<path>
methods string[] Allowed HTTP methods (default: ["POST"])

Device state trigger

{ type: "device_state"; friendlyName: string; filter?: (state: Record<string, unknown>, device: ZigbeeDevice) => boolean }

Requires DEVICE_REGISTRY_ENABLED=true.

Device joined trigger

{ type: "device_joined"; friendlyName?: string }

Requires DEVICE_REGISTRY_ENABLED=true. Omit friendlyName to fire for any device.

Device left trigger

{ type: "device_left"; friendlyName?: string }

Requires DEVICE_REGISTRY_ENABLED=true. Omit friendlyName to fire for any device.


TriggerContext

A discriminated union matching each trigger type. Use context.type to narrow:

context.type Fields
"mqtt" topic: string, payload: Record<string, unknown>
"cron" expression: string, firedAt: Date
"state" key: string, newValue: unknown, oldValue: unknown
"webhook" path: string, method: string, headers: Record<string, string>, query: Record<string, string>, body: unknown
"device_state" friendlyName: string, state: Record<string, unknown>, device: ZigbeeDevice
"device_joined" device: ZigbeeDevice
"device_left" device: ZigbeeDevice

AutomationManager

Manages discovery, registration, and lifecycle of automations. Available as engine.manager.

Method Signature Description
discoverAndRegister (automationsDir: string, recursive?: boolean) => Promise<void> Scan directory for automation files, instantiate, and register each
register (automation: Automation) => Promise<void> Register a single automation instance
stopAll () => Promise<void> Unregister all automations and clean up triggers
listAutomations () => { name: string; triggers: object[] }[] Serialised list of all automations
getAutomation (name: string) => object \| null Look up a single automation by name
triggerAutomation (name: string, context: TriggerContext) => Promise<boolean> Manually trigger an automation; returns false if not found

MqttService

Manages the MQTT connection and multiplexes subscriptions across automations.

import type { MqttService, MqttMessageHandler } from "@ts-ha/core";

MqttMessageHandler

type MqttMessageHandler = (topic: string, payload: Record<string, unknown>) => void | Promise<void>;

Properties

Property Type Description
isConnected boolean (getter) Whether the MQTT client is currently connected

Methods

Method Signature Description
connect () => Promise<void> Connect to the broker; resolves on first successful connection
subscribe (topic: string, handler: MqttMessageHandler) => void Subscribe to a topic. Supports + and # wildcards.
unsubscribe (topic: string, handler: MqttMessageHandler) => void Remove a handler. Unsubscribes from broker when last handler for a topic is removed.
publish (topic: string, payload: Record<string, unknown>) => void Publish JSON payload to any topic
publishToDevice (friendlyName: string, payload: Record<string, unknown>) => void Publish to {prefix}/{friendlyName}/set
deviceTopic (friendlyName: string) => string Returns {prefix}/{friendlyName}
disconnect () => Promise<void> Disconnect from the broker

Subscription dispatch internals:

  • Exact-match topics use Map lookup (O(1))
  • Wildcard topics use pre-split pattern matching (linear scan)
  • JSON payload is parsed lazily — only when at least one handler matches
  • Each handler is called independently; errors in one handler do not affect others

HttpClient

General-purpose HTTP client with structured logging, timeouts, and retry support.

import type { HttpClient, HttpRequestOptions, HttpResponse } from "@ts-ha/core";

HttpRequestOptions

Field Type Default Description
method "GET" \| "POST" \| "PUT" \| "PATCH" \| "DELETE" "GET" HTTP method
headers Record<string, string> {} Request headers
body unknown (none) Request body (auto-serialised to JSON)
timeout number 30000 Timeout in milliseconds
retries number 0 Retry count on network errors or 5xx responses

HttpResponse<T>

Field Type Description
status number HTTP status code
ok boolean true when status is 2xx
headers Headers Response headers
data T Parsed response body (JSON or text)

Methods

Method Signature Description
request<T> (url: string, options?: HttpRequestOptions) => Promise<HttpResponse<T>> Core request method
get<T> (url: string, headers?) => Promise<HttpResponse<T>> Convenience GET
post<T> (url: string, body?, headers?) => Promise<HttpResponse<T>> Convenience POST
put<T> (url: string, body?, headers?) => Promise<HttpResponse<T>> Convenience PUT
patch<T> (url: string, body?, headers?) => Promise<HttpResponse<T>> Convenience PATCH
del<T> (url: string, headers?) => Promise<HttpResponse<T>> Convenience DELETE

Retry behaviour: On network errors or 5xx responses, retries use exponential backoff starting at 500ms, doubling each attempt, capped at 10s. 4xx responses are never retried.

URL sanitisation: Sensitive query parameters (appid, apikey, api_key, token, key, secret) are masked with *** in log output.


HttpServer

HTTP server for health probes, webhooks, debug API, and web UI.

WebhookHandler

type WebhookHandler = (context: {
  method: string;
  headers: Record<string, string>;
  query: Record<string, string>;
  body: unknown;
}) => Promise<void>;

Route structure

Method Path Auth Description
GET /healthz No Liveness probe — always returns { status: "ok" }
GET /readyz No Readiness probe — checks MQTT and engine status
ALL /webhook/* No Dispatches to registered webhook handlers
GET /api/status Yes Engine + MQTT readiness
GET /api/automations Yes List all automations
GET /api/automations/:name Yes Single automation details
PUT /api/automations/:name/enabled Yes Enable or disable an automation
GET /api/automations/:name/source Yes Read the automation's source file
GET /api/automations/:name/history Yes Recent execution history (newest first)
GET /api/automations/:name/relationships Yes Declared and observed relationships
POST /api/automations/:name/trigger Yes Manually trigger an automation. Returns 404 if the automation does not exist, or 409 if it exists but is currently disabled — a disabled automation is not run, since manual triggering would otherwise be a second, undocumented way to bypass the "off" switch (design.md D27).
GET /api/state Yes All state keys and values
GET /api/state/:key Yes Single state value
PUT /api/state/:key Yes Set a state value
DELETE /api/state/:key Yes Delete a state key
GET /api/logs Yes Query log buffer (?automation=&level=&limit=)
GET /api/device-catalog Yes List devices from every available source
GET /api/device-catalog/:qualifiedId Yes Single device by qualified identifier
POST /api/device-catalog/:qualifiedId/command Yes Issue a validated command to a device
PUT /api/device-catalog/:qualifiedId/room Yes Assign a device to a room ({ roomId })
DELETE /api/device-catalog/:qualifiedId/room Yes Clear a device's room assignment
GET /api/rooms Yes List rooms with membership (including unavailable members)
GET /api/rooms/unassigned Yes Present devices belonging to no room
POST /api/rooms Yes Create a room ({ name })
PUT /api/rooms/:id Yes Rename a room ({ name })
DELETE /api/rooms/:id Yes Delete a room — its devices become unassigned
POST /api/rooms/:id/command Yes Issue one property command to every capable member of a room (best-effort, per-device outcomes)
GET /api/walls Yes List wall profiles
GET /api/walls/:name Yes Read one wall profile (404 if unknown)
POST /api/walls Yes Create a wall profile ({ name, config })
PUT /api/walls/:name Yes Update or rename a wall profile ({ name?, config? })
DELETE /api/walls/:name Yes Delete a wall profile
GET /api/energy Yes Aggregated home energy view — instantaneous power, cumulative total, rolling-window usage with its window and coverage, per-device breakdown, rolling history
GET /api/weather Yes Current conditions and forecast for a configured default or client-supplied location
GET /api/events Yes Realtime server-sent event stream

GET /api/devices and GET /api/devices/:friendlyName (Zigbee-only) are removed — they return 410. Use /api/device-catalog, which spans every device source (Zigbee, Shelly, Nanoleaf, and state toggles), addressed by qualified identifier (design.md R13).

GET /api/events is delta-only after connect, with one exception: when a client opens the stream the server first sends a per-connection initial device snapshot — one device_appeared event per device, each carrying its full descriptor, from the total device enumeration including hidden devices and matching the GET /api/device-catalog snapshot — before any subsequent device event. Later device events remain incremental deltas (design.md D6). The dashboard reconciles this snapshot into its device map by qualified identifier rather than replacing the map, and refetches a device on a delta for an identifier it does not hold (design.md D7).

PUT /api/state/:key and DELETE /api/state/:key return 400 when key falls inside the reserved internal namespace (the $internal: prefix — rooms, automation enabled flags). GET /api/state never lists a reserved key, in either the returned map or its count. See State Management (design.md D20).

Room creation and rename return 409 for a name already in use. Unknown room or unknown device-room assignment target returns 404. Rooms are user-defined groupings that span every device source; a device belongs to at most one room, and an absent member (unpaired, or its source disabled) is retained and reported unavailable rather than dropped (design.md D14).

POST /api/device-catalog/:qualifiedId/command accepts a flat JSON object of properties (e.g. { "on": true } or { "state": "ON" }). A boolean property accepts either a real JSON boolean (true/false) or the capability's declared on/off value — "ON"/"OFF" for a Zigbee2MQTT lamp, true/false for a Shelly. Either accepted form is dispatched to the device in that capability's own declared encoding: the engine translates an accepted real boolean into the capability's declared valueOn/valueOff before dispatch, so a caller with no source-specific knowledge (HomeKit, an automation, the API, the CLI) actuates any device.

POST /api/rooms/:id/command accepts the same flat JSON object of properties as the single-device command endpoint (e.g. { "on": false }). It resolves the room's members, dispatches to each member whose declared capabilities permit the requested property through the same validated DeviceSource.command() path as the single-device endpoint, and returns one result per member — applied, skipped (the member does not declare a writable capability for the property, or its descriptor is unavailable), or failed (dispatch rejected). The response is best-effort and never promises atomicity across members; a malformed body returns 400, an unknown room 404, and an engine with no room manager 503.

Wall profiles are owner-authored compositions for a wall-mounted display, addressed by their unique name and stored in the reserved internal namespace ($internal:wall:<name>) through their own /api/walls surface — so they persist across a restart, are rejected on write through the state API, and are not enumerated by GET /api/state. Creating or renaming a profile to a name already in use returns 409 and leaves the existing profile unchanged; an unknown profile returns 404; a configuration that fails the shared wall-profile schema (grid bounds, known widget types, placement validity) returns 400 and nothing is persisted; deleting a profile affects no other. Every create, update, or delete is broadcast as the typed wall realtime event, never as a raw state key change. See Web UI → Wall displays.

GET /api/energy returns powerWatts (sum across reachable devices declaring an instantaneous-power reading), energyWh (sum across reachable devices declaring a cumulative-energy reading, normalized to watt-hours), a breakdown of per-device contributions with available, and a history array of timestamped powerWatts samples (empty when history is disabled or no samples have been taken). It also carries the home's usage over a rolling window and that window's length and coverage, as three optional flat fields: usageWh — the usage over the retained span in watt-hours, summed across the devices that report a cumulative reading at both ends of the span, so a device that is unreachable or declares no reading at either end is excluded rather than counted as zero (including one that becomes unreachable mid-window), and an absent value means there is no baseline or no contributing device — never a false zero; windowMinutes — the retention window's length in minutes (ENERGY_HISTORY_MINUTES), absent when history is disabled (0); and coveredMinutes — the extent the retained samples actually cover, never above windowMinutes and present only alongside usageWh, so a client can distinguish a full window from a partial one. It responds successfully with zero totals and an empty breakdown when no device meters energy. See Configuration for ENERGY_SAMPLE_MS and ENERGY_HISTORY_MINUTES.

GET /api/weather returns current conditions and a daily forecast for a location resolved as: a client-supplied lat/lon pair when both are present and in range, otherwise the configured default location (WEATHER_LATITUDE/WEATHER_LONGITUDE). A days query parameter requests the forecast horizon and is clamped to a maximum of 7; it defaults to WEATHER_FORECAST_DAYS. When no weather service is registered the endpoint responds 404 with { available: false, reason: "service_unregistered" }; when no location is resolvable it responds 400 with { available: false, reason: "no_location" }. See Weather services.

Authentication uses Authorization: Bearer <token> header or a session cookie (ts-ha-session, HttpOnly, SameSite=Strict) named identically to the web UI's session cookie — the two share one HTTP_TOKEN secret. Set HTTP_TOKEN to enable; leave empty for no authentication. When empty, every route above — including GET /api/automations/:name/source — is reachable with no authentication; see Configuration (design.md D10, R4).

Qualified device identifier

/api/device-catalog/:qualifiedId and /api/device-catalog/:qualifiedId/* address a single device across any source with one percent-encoded path segment, formed by joining the source identifier and the device's own identifier with a single : delimiter (design.md D29):

zigbee  + 0x00124b0022a1b2c3     →  zigbee:0x00124b0022a1b2c3
shelly  + office_plug            →  shelly:office_plug
nanoleaf + panels                →  nanoleaf:panels
state   + motion-light:lights_on →  state:motion-light:lights_on

Parsing splits on the first occurrence of : only — everything after it belongs to the device identifier. This matters because a state toggle's identity is itself a state key, and state keys are already colon-scoped (<automation-name>:<key>), so the device identifier can legitimately contain further delimiters. A source identifier itself never contains :, which is what makes the first-occurrence split total rather than heuristic. Callers must percent-encode the whole identifier as one path segment (e.g. encodeURIComponent("state:motion-light:lights_on")), not split it across multiple segments.


StateManager

In-memory key-value store with typed access, change listeners, and optional file persistence.

import type { StateManager, StateManagerOptions, StateChangeHandler } from "@ts-ha/core";

StateManagerOptions

Field Type Default Description
persist boolean true Save state (debounced write-behind) and restore on startup. Defaults on because the store holds room definitions and automation enabled flags, which must survive a restart (breaking default change — design.md D6, R14).
filePath string "./state.json" Path to the persistence file
flushIntervalMs number 1000 Milliseconds between coalesced saves (write-behind debounce). 0 saves on every mutation instead of scheduling.

StateChangeHandler<T>

type StateChangeHandler<T> = (key: string, newValue: T | undefined, oldValue: T | undefined) => void;

Methods

Method Signature Description
get<T> (key: string, defaultValue?: T) => T \| undefined Read a value with optional default
set<T> (key: string, value: T) => void Write a value; fires listeners only if the value changed. Throws if key falls inside the reserved $internal: namespace (design.md D20).
delete (key: string) => boolean Remove a key; fires listeners with newValue = undefined. Throws if key falls inside the reserved $internal: namespace.
has (key: string) => boolean Check if a key exists
keys () => string[] Get all keys, excluding reserved internal keys
onChange<T> (key: string, handler: StateChangeHandler<T>) => void Subscribe to a specific key
offChange<T> (key: string, handler: StateChangeHandler<T>) => void Unsubscribe from a key
onAnyChange (handler: StateChangeHandler) => void Subscribe to all key changes
offAnyChange (handler: StateChangeHandler) => void Unsubscribe from global listener
load () => Promise<void> Load state from disk (called by engine on startup)
save () => Promise<void> Save state to disk (called by engine on shutdown)

Equality check: set() compares the new value against the current value before firing listeners. Primitives use strict equality (===); objects use JSON.stringify comparison. If values are equal, no listeners fire.

Listener warning: A warning is logged when more than 10 listeners are registered for a single key, to help detect leaks.


CronScheduler

Wraps the cron package for scheduled job management. Internal to the engine — automations interact with it via cron triggers.

Method Signature Description
schedule (id: string, expression: string, callback: () => void) => void Create and start a cron job
remove (id: string) => void Stop and remove a single job
removeByPrefix (prefix: string) => void Stop and remove all jobs with matching prefix
stopAll () => void Stop all jobs

Timezone is read from the TZ environment variable.


ServiceRegistry

Type-safe key-value registry for optional services. Services implementing ServicePlugin receive lifecycle hooks.

import type { ServiceRegistry } from "@ts-ha/core";
Method Signature Description
register<T> (key: string, service: T) => void Register a service under a key
get<T> (key: string) => T \| null Nullable lookup
getOrThrow<T> (key: string) => T Asserted lookup — throws if missing
use<T, R> (key: string, fn: (service: T) => R) => R \| undefined Callback wrapper — no-ops when absent
has (key: string) => boolean Check if a service is registered
keys () => string[] Get all registered keys
startAll (context: CoreContext) => Promise<void> Call onStart() on all ServicePlugin instances
stopAll () => Promise<void> Call onStop() on all ServicePlugin instances

ServicePlugin

Interface for services that need lifecycle hooks and HTTP route registration. See Custom Service Plugins for a full guide.

import type { ServicePlugin, CoreContext } from "@ts-ha/core";

CoreContext

Field Type Description
http HttpClient Shared HTTP client
logger Logger Plugin-scoped child logger

ServicePlugin interface

Member Type Description
serviceKey (readonly) string Unique registration key
onStart?(context) (context: CoreContext) => Promise<void> Called during engine startup
onStop?() () => Promise<void> Called during engine shutdown
registerRoutes?(app) (app: Hono) => void Mount custom HTTP routes on the shared server

LogBuffer

Ring buffer for in-memory log storage. Used by the debug API and web UI.

import type { LogBuffer, LogEntry, LogQuery } from "@ts-ha/core";

LogEntry

interface LogEntry {
  level: number;    // pino numeric level (10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal)
  time: number;     // Unix timestamp in ms
  msg: string;      // Log message
  [key: string]: unknown;  // Additional fields (automation, service, err, etc.)
}

LogQuery

Field Type Default Description
automation string (none) Filter by automation name
level number (none) Minimum log level
limit number 50 Maximum entries returned

Methods

Method Signature Description
query (options?: LogQuery) => LogEntry[] Entries matching the filter, oldest first
subscribe (listener: (entry: LogEntry) => void) => () => void Register a listener notified of each newly-stored entry; returns an unsubscribe function. Notification is deferred off the synchronous write path (design.md D32) — see Architecture: LogBuffer.

DeviceRegistry

Zigbee2MQTT device discovery and state tracking. Available as engine.deviceRegistry or this.deviceRegistry in automations. Returns null when DEVICE_REGISTRY_ENABLED is not true.

See Device Registry for the full feature guide.

Query methods

Method Signature Description
getDevices () => ZigbeeDevice[] All tracked non-coordinator devices
getDevice (friendlyName: string) => ZigbeeDevice \| undefined Single device lookup
hasDevice (friendlyName: string) => boolean Existence check
getNiceName (friendlyName: string) => string Human-readable name via configured mapping
getDeviceState (friendlyName: string) => Record<string, unknown> \| undefined Last-known merged device state

Event methods

Method Signature Description
onDeviceStateChange (friendlyName: string, handler) => void Register a per-device state change handler
offDeviceStateChange (friendlyName: string, handler) => void Remove a state change handler
onDeviceAdded (handler: DeviceAddedHandler) => void Register a handler for device joins
offDeviceAdded (handler: DeviceAddedHandler) => void Remove a join handler
onDeviceRemoved (handler: DeviceRemovedHandler) => void Register a handler for device departures
offDeviceRemoved (handler: DeviceRemovedHandler) => void Remove a departure handler

Persistence options

interface DeviceRegistryPersistenceOptions {
  persist?: boolean;    // default: false
  filePath?: string;    // default: "./device-registry.json"
}

Device base classes

Abstract automation classes for common Zigbee remotes. See Device Base Classes for handler details.

Class Device Handlers
AqaraH1Automation Aqara Wireless Remote Switch H1 (WXKG15LM) 12 handlers (single/double/triple/hold for left/right/both)
IkeaStyrbarAutomation IKEA STYRBAR (E2001/E2002/E2313) 11 handlers (on/off, brightness, arrows)
IkeaRodretAutomation IKEA RODRET (E2201) 5 handlers (on/off, brightness up/down/stop)

Configuration

Config

The resolved configuration object. See Configuration for all environment variables.

import type { Config } from "@ts-ha/core";

loadConfig(overrides?)

function loadConfig(overrides?: Partial<Config>): Config

Reads environment variables, applies Zod validation, merges with optional overrides. Exits the process with a formatted error on validation failure.


Type exports

All Zigbee2MQTT, Shelly, Nanoleaf, Weather, and Notification types are re-exported from the package. See Device Types for a complete catalogue.

// Zigbee types
import type { OccupancyPayload, ColorLightSetCommand, ZigbeeDevice } from "@ts-ha/core";

// Shelly types
import type { ShellySwitchStatus, ShellyCoverStatus } from "@ts-ha/core";
// Or from the subpath:
import type { ShellySwitchStatus } from "@ts-ha/shared/types/shelly";

// Weather types
import type { WeatherService, CurrentWeather, DailyForecast } from "@ts-ha/core";

// Notification types
import type { NotificationService, NotificationOptions } from "@ts-ha/core";

// Nanoleaf types
import type { NanoleafState, NanoleafDeviceInfo } from "@ts-ha/core";

Subpath imports

@ts-ha/shared provides subpath exports for each type category; @ts-ha/core re-exports the cross-cutting contracts from its main barrel:

Import path Contents
@ts-ha/core Everything (main barrel)
@ts-ha/shared/types All Zigbee2MQTT types
@ts-ha/shared/types/shelly Shelly Gen 2 RPC types
@ts-ha/shared/types/nanoleaf Nanoleaf OpenAPI types
@ts-ha/shared/types/weather WeatherService interface and data types
@ts-ha/shared/types/notification NotificationService interface and option types