npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@webarray/esphome-native-api

v1.0.5

Published

TypeScript/Node.js client for ESPHome native API with encryption and deep sleep support

Downloads

326

Readme

ESPHome Native API for Node.js

npm version npm downloads CI codecov License: MIT TypeScript Node

A TypeScript/Node.js implementation of the ESPHome native API protocol. This library allows you to interact with ESPHome devices directly from Node.js applications.

Features

  • Full ESPHome Native API Support - Complete implementation of the ESPHome native API protocol
  • Automatic Reconnection - Robust connection handling with automatic reconnection on network issues
  • Event-Driven Architecture - Uses EventEmitter pattern for real-time state updates
  • Device Discovery - Automatic discovery of ESPHome devices on your network using mDNS
  • TypeScript Support - Full TypeScript support with comprehensive type definitions and utility types
  • High Performance - Efficient binary protocol handling with protobuf
  • Well Tested - Comprehensive test suite ensuring reliability
  • Flexible Logging - Built-in debug logging with optional custom logger integration (Winston, Pino, etc.)
  • Custom Timer Support - Optional timer factory for platforms like Homey that require custom timer handling
  • Connection Health Monitoring - Built-in health metrics and connection diagnostics
  • Enhanced Error Handling - Detailed error messages with codes and helpful suggestions
  • Entity Helper Methods - Convenient methods to search, filter, and manage entities
  • Debug Utilities - Built-in debugging tools for troubleshooting

Requirements

  • Node.js >= 18.0.0
  • ESPHome device with Native API component enabled

Installation

npm install @webarray/esphome-native-api

Quick Start

Basic Usage

import { ESPHomeClient } from '@webarray/esphome-native-api';

// Create a client and connect to an ESPHome device
const client = new ESPHomeClient({
  host: '192.168.1.100',
  port: 6053,
  encryptionKey: 'your-base64-encryption-key' // Recommended: secure encryption
  // password: 'your-api-password' // Deprecated: use encryptionKey instead
});

// Connect to the device
await client.connect();

// Get device information
const deviceInfo = client.getDeviceInfo();
console.log('Device:', deviceInfo?.name);
console.log('Version:', deviceInfo?.esphomeVersion);

// Subscribe to state changes
client.subscribeStates((state) => {
  console.log('State update:', state);
});

// Control a switch
await client.switchCommand(switchKey, true);

// Disconnect when done
client.disconnect();

💡 Tip: Enable debug logging to see what's happening:

DEBUG=esphome:* node your-app.js

Device Discovery

import { discover } from '@webarray/esphome-native-api';

// Discover ESPHome devices on the network
const devices = await discover(5000); // Search for 5 seconds

for (const device of devices) {
  console.log(`Found device: ${device.name} at ${device.host}:${device.port}`);
}

Event-Based State Monitoring

import { ESPHomeClient } from '@webarray/esphome-native-api';

const client = new ESPHomeClient({
  host: 'device.local',
  encryptionKey: 'your-base64-encryption-key' // Recommended
});

// Listen for specific entity types
client.on('binarySensorState', (state) => {
  console.log(`Binary sensor ${state.key}: ${state.state}`);
});

client.on('sensorState', (state) => {
  console.log(`Sensor ${state.key}: ${state.state}`);
});

client.on('switchState', (state) => {
  console.log(`Switch ${state.key}: ${state.state}`);
});

// Listen for log messages
client.on('logs', (log) => {
  console.log(`[${log.level}] ${log.message}`);
});

await client.connect();

Logging

Subscribe to device logs using log level constants:

import { ESPHomeClient, LogLevel, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG } from '@webarray/esphome-native-api';

const client = new ESPHomeClient({ host: 'device.local' });

// Using enum
client.subscribeLogs(LogLevel.INFO);

// Using constants (easier to import)
client.subscribeLogs(LOG_LEVEL_DEBUG);

// Using numeric value (also supported)
client.subscribeLogs(3); // INFO level

// Available log levels:
// LOG_LEVEL_NONE (0)         - No logging
// LOG_LEVEL_ERROR (1)        - Errors only
// LOG_LEVEL_WARN (2)         - Warnings and errors
// LOG_LEVEL_INFO (3)         - Info, warnings, and errors (default)
// LOG_LEVEL_CONFIG (4)       - Configuration messages
// LOG_LEVEL_DEBUG (5)        - Debug messages
// LOG_LEVEL_VERBOSE (6)      - Verbose debug
// LOG_LEVEL_VERY_VERBOSE (7) - All messages

List All Entities

const client = new ESPHomeClient({
  host: 'device.local',
  encryptionKey: 'your-base64-encryption-key' // Recommended
});

await client.connect();

// Get all entities
const entities = await client.listEntities();

for (const entity of entities) {
  console.log(`Entity: ${entity.name} (${entity.objectId})`);
  console.log(`  Key: ${entity.key}`);
  console.log(`  Type: ${entity.constructor.name}`);
}

Connection with Reconnection

const client = new ESPHomeClient({
  host: 'device.local',
  encryptionKey: 'your-base64-encryption-key', // Recommended
  reconnect: true,              // Enable automatic reconnection
  reconnectInterval: 5000,      // Reconnect every 5 seconds
  pingInterval: 20000,          // Send ping every 20 seconds
  pingTimeout: 5000,            // Ping timeout after 5 seconds
  connectTimeout: 10000         // Connection timeout after 10 seconds
});

// Monitor connection state
client.on('connected', () => {
  console.log('Connected to device');
});

client.on('disconnected', (error) => {
  if (error) {
    console.error('Disconnected with error:', error);
  } else {
    console.log('Disconnected');
  }
});

await client.connect();

Advanced Discovery with Event Monitoring

import { Discovery } from '@webarray/esphome-native-api';

const discovery = new Discovery();

// Listen for discovered devices
discovery.on('device', (device) => {
  console.log(`Discovered: ${device.name} at ${device.host}`);
  if (device.macAddress) {
    console.log(`  MAC: ${device.macAddress}`);
  }
  if (device.version) {
    console.log(`  Version: ${device.version}`);
  }
});

// Start discovery
discovery.start();

// Stop after 10 seconds
setTimeout(() => {
  const devices = discovery.getDevices();
  console.log(`Found ${devices.length} devices`);
  discovery.stop();
}, 10000);

API Reference

ESPHomeClient

The main client for connecting to ESPHome devices.

Constructor Options

interface ConnectionOptions {
  host: string;                 // Device hostname or IP address
  port?: number;                // Port number (default: 6053)
  encryptionKey?: string;       // Base64 encryption key (recommended for secure communication)
  password?: string;            // API password (deprecated, use encryptionKey instead)
  clientInfo?: string;          // Client identification string
  reconnect?: boolean;          // Enable auto-reconnection (default: true)
  reconnectInterval?: number;   // Reconnection interval in ms (default: 5000)
  pingInterval?: number;        // Ping interval in ms (default: 20000)
  pingTimeout?: number;         // Ping timeout in ms (default: 90000)
  connectTimeout?: number;      // Connection timeout in ms (default: 10000)
  expectedServerName?: string;  // Expected server name for additional security (optional)
  respondToTimeRequests?: boolean; // Respond to GetTimeRequest from device (default: true)
  logger?: Logger;              // Custom logger function
  timerFactory?: TimerFactory;  // Custom timer implementation
}

Note on respondToTimeRequests:

By default, the client responds to GetTimeRequest messages from the device with the current system time. This is useful for devices that need to know the current time (e.g., for scheduling or logging). However, responding to these requests is not required for the connection to work properly.

You may want to disable this if you don't want to affect the device's timekeeping.

const client = new ESPHomeClient({
  host: 'device.local',
  respondToTimeRequests: false, // Don't respond to time requests
});

Events

  • connected - Emitted when connected to the device
  • disconnected - Emitted when disconnected from the device
  • error - Emitted on error
  • deviceInfo - Emitted when device info is received
  • logs - Emitted when a log message is received
  • state - Emitted on any state change
  • binarySensorState - Emitted on binary sensor state change
  • sensorState - Emitted on sensor state change
  • switchState - Emitted on switch state change
  • textSensorState - Emitted on text sensor state change
  • fanState - Emitted on fan state change
  • coverState - Emitted on cover state change
  • lightState - Emitted on light state change
  • entity - Emitted when an entity is discovered

Discovery

Service discovery for finding ESPHome devices on the network.

Methods

  • start(duration?: number): void - Start discovery
  • stop(): void - Stop discovery
  • getDevices(): DiscoveredDevice[] - Get all discovered devices
  • clear(): void - Clear discovered devices
  • destroy(): void - Clean up resources

Events

  • device - Emitted when a device is discovered
  • error - Emitted on error

Helper Functions

  • createClient(options: ConnectionOptions): ESPHomeClient - Create a new client
  • discover(duration?: number): Promise<DiscoveredDevice[]> - Discover devices
  • connectToFirstDevice(encryptionKey?: string, duration?: number): Promise<ESPHomeClient | null> - Connect to first discovered device

ESPHome Configuration

To use this library, you need to enable the Native API component in your ESPHome configuration:

# Example ESPHome configuration
esphome:
  name: my-device

# Recommended: Use encryption for secure communication
api:
  encryption:
    key: "YOUR_BASE64_ENCRYPTION_KEY"  # Generate with: openssl rand -base64 32

# Legacy (deprecated): Password-based authentication
# api:
#   password: "your-secure-password"  # Not recommended, use encryption instead
  
# Optional: mDNS for device discovery
mdns:
  disabled: false

Authentication Methods:

  • Encryption Key (Recommended): Provides end-to-end encryption using the Noise protocol with ChaCha20-Poly1305. This is the modern, secure approach.
  • Password (Deprecated): Simple password authentication without encryption. Use only for legacy devices or testing.

Generating an encryption key:

# Generate a new encryption key
openssl rand -base64 32

# Or let ESPHome generate one for you:
esphome run your-device.yaml
# The key will be shown in the output

Error Handling

The library provides specific error types for different scenarios:

import { 
  ESPHomeError,
  ConnectionError,
  AuthenticationError,
  ProtocolError 
} from '@webarray/esphome-native-api';

try {
  await client.connect();
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid password');
  } else if (error instanceof ConnectionError) {
    console.error('Connection failed:', error.message);
  } else if (error instanceof ProtocolError) {
    console.error('Protocol error:', error.message);
  }
}

Logging

The library uses the debug package for logging. By default, logging works automatically without any configuration.

Default Logging (No Setup Required)

Simply set the DEBUG environment variable to enable logs:

# Enable all ESPHome logs
DEBUG=esphome:* node your-app.js

# Enable specific namespaces
DEBUG=esphome:client node your-app.js
DEBUG=esphome:connection node your-app.js
DEBUG=esphome:discovery node your-app.js

# Multiple namespaces
DEBUG=esphome:client,esphome:connection node your-app.js

Available namespaces:

  • esphome:client - Client operations and events
  • esphome:connection - Connection management
  • esphome:encrypted-connection - Encrypted connections
  • esphome:discovery - Device discovery
  • esphome:protocol - Protocol messages
  • esphome:noise - Encryption details

Custom Logging

For integration with custom logging systems (Winston, Pino, Bunyan, Homey, etc.), provide a custom logger function:

import { ESPHomeClient } from '@webarray/esphome-native-api';

const client = new ESPHomeClient({
  host: '192.168.1.100',
  
  // Custom logger function
  logger: (namespace, message, ...args) => {
    // Use your logging system
    winston.info({ namespace, message, args });
    // Or: pino.info({ namespace, args }, message);
    // Or: console.log(`[${namespace}] ${message}`, ...args);
  }
});

Global Logger Setup

To redirect all library logs to a custom logger:

import { setupGlobalLogger } from '@webarray/esphome-native-api';

setupGlobalLogger((message) => {
  myLogger.info(message);
});

Example: Integration with Winston

import winston from 'winston';
import { ESPHomeClient } from '@webarray/esphome-native-api';

const logger = winston.createLogger({
  transports: [new winston.transports.Console()]
});

const client = new ESPHomeClient({
  host: '192.168.1.100',
  logger: (namespace, message, ...args) => {
    logger.info({ namespace, args }, message);
  }
});

See examples/custom-logging-example.js for more integration examples.

Connection Health Monitoring

Monitor connection health and performance metrics:

import { ESPHomeClient } from '@webarray/esphome-native-api';

const client = new ESPHomeClient({
  host: '192.168.1.100',
});

await client.connect();

// Get health metrics
const metrics = client.getHealthMetrics();
console.log('Connection uptime:', metrics.connectionUptime);
console.log('Messages sent:', metrics.messagesSent);
console.log('Average ping latency:', metrics.averagePingLatency);

// Get health status with analysis
const health = client.getConnectionHealth();
console.log('Status:', health.status); // 'healthy', 'degraded', 'unhealthy', or 'disconnected'
console.log('Issues:', health.issues); // Array of identified issues

// Reset metrics
client.resetHealthMetrics();

Entity Helper Methods

Convenient methods for working with entities:

// Find entities by name
const tempSensor = client.findEntity('temperature');

// Find all entities matching a search
const sensors = client.findEntities('sensor');

// Get entities by type
const allSwitches = client.getEntitiesByType('switch');

// Check if entity exists
if (client.hasEntity('light_living_room')) {
  // ...
}

// Wait for an entity to appear
const entity = await client.waitForEntity('new_sensor', 30000);

// Get entity count
console.log('Total entities:', client.getEntityCount());

Debug Utilities

Built-in debugging tools:

import { ESPHomeClient } from '@webarray/esphome-native-api';

const client = new ESPHomeClient({
  host: '192.168.1.100',
});

// Enable detailed logging
client.enableDetailedLogging();

// Get connection metrics for debugging
const metrics = client.getConnectionMetrics();
console.log(metrics);

// Capture protocol messages
client.captureProtocolDump(true);

Enhanced Error Handling

Detailed error messages with codes and suggestions:

import { 
  ESPHomeClient,
  ErrorCode,
  ConnectionError,
  AuthenticationError 
} from '@webarray/esphome-native-api';

try {
  await client.connect();
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Error code:', error.code);
    console.error('Suggestion:', error.suggestion);
    console.error('Context:', error.context);
  } else if (error instanceof ConnectionError) {
    switch (error.code) {
      case ErrorCode.CONNECTION_TIMEOUT:
        console.error('Connection timed out');
        break;
      case ErrorCode.CONNECTION_REFUSED:
        console.error('Connection refused');
        break;
    }
  }
}

Custom Timer Implementations

For environments that require custom timer handling (like Athom Homey), you can provide a timerFactory:

import { ESPHomeClient, TimerFactory } from '@webarray/esphome-native-api';

// Example: Homey timer factory
const homeyTimers: TimerFactory = {
  setTimeout: (callback, ms) => this.homey.setTimeout(callback, ms),
  setInterval: (callback, ms) => this.homey.setInterval(callback, ms),
  clearTimeout: (id) => this.homey.clearTimeout(id),
  clearInterval: (id) => this.homey.clearInterval(id),
};

const client = new ESPHomeClient({
  host: '192.168.1.100',
  timerFactory: homeyTimers,  // Use Homey's timer system
});

This is useful for platforms where:

  • Timers need to be managed by a specific global object
  • You want to track or control all timers centrally
  • The environment has custom timer lifecycle requirements

If not provided, the library uses Node.js's standard setTimeout/setInterval.

Development

Building

npm run build

Testing

npm test                 # Run tests
npm run test:watch      # Run tests in watch mode
npm run test:coverage   # Run tests with coverage

Protocol Buffer Files

The library uses protocol buffers for communication. The proto files are pre-generated and committed to the repository.

To update the proto files from ESPHome:

npm run proto:generate

This automatically:

  • Downloads the latest proto files from ESPHome
  • Generates JavaScript and TypeScript code
  • Fixes the void keyword conflict (ESPHome uses void as a message name)
  • Formats the generated files with Prettier

The generated files (src/proto/api.js and src/proto/api.d.ts) are:

  • Excluded from ESLint checks (in .eslintignore)
  • Excluded from Prettier checks (in .prettierignore)
  • Automatically formatted during generation

This ensures automated proto updates (via GitHub Actions) won't cause linter failures.

See proto/README.md for more details.

Auto-Generated Entity Types

import { ALL_ENTITY_TYPES, isValidEntityType, EntityType } from '@webarray/esphome-native-api';

// All entity types from proto files
console.log(ALL_ENTITY_TYPES);
// ['binary_sensor', 'sensor', 'switch', 'light', ...]

// Type guard for runtime validation
if (isValidEntityType(someString)) {
  // TypeScript knows someString is EntityType
  const type: EntityType = someString;
}

Regenerating entity types:

When ESPHome updates its proto files, run:

npm run proto:generate  # Regenerates proto files AND entity types
# or
npm run generate:entity-types  # Only regenerates entity types

The entity types in src/types/generated-entity-types.ts are automatically generated from proto/api.proto by parsing all ListEntities*Response messages.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • ESPHome - The amazing platform that this library interfaces with
  • aioesphomeapi - The Python implementation that inspired this library

Support

For issues, questions, or contributions, please visit the GitHub repository.