UNPKG

@webarray/esphome-native-api

Version:

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

320 lines (236 loc) 8.31 kB
# Deep Sleep Support This library fully supports ESPHome devices with deep sleep configuration, following the same approach as the official Python `aioesphomeapi` library. ## How It Works When an ESPHome device has deep sleep configured and goes to sleep, it: 1. **Sends a `DisconnectRequest` message** to the client 2. **Waits for `DisconnectResponse` acknowledgment** from the client 3. **Enters deep sleep mode** The client library automatically: 1. **Detects deep sleep capability** from `DeviceInfo.hasDeepSleep` 2. **Handles `DisconnectRequest` messages** gracefully 3. **Sends `DisconnectResponse` acknowledgment** 4. **Stops ping mechanism** (no timeout errors) 5. **Disables auto-reconnect** (waits for device to wake) 6. **Maintains connection state** for clean reconnection ## Protocol Flow ### Device Going to Sleep ``` Device Client | | | ---- DisconnectRequest ---> | (device notifying sleep) | | | <--- DisconnectResponse --- | (client acknowledges) | | | (enters deep sleep) | (stops pinging, waits) | | v v (sleeping) (passive wait) ``` ### Device Waking Up ``` Device Client | | | (wakes from sleep) | | | | ---- TCP Connect ---------> | (device initiates) | | | ---- HelloRequest --------> | | | | <--- HelloResponse ------- | | | | (full handshake resumes) | | | ``` ## Automatic Detection The library automatically detects and configures deep sleep support: ```typescript import { ESPHomeClient } from '@webarray/esphome-native-api'; const client = new ESPHomeClient({ host: 'sleeping-sensor.local', port: 6053, }); await client.connect(); // Device info is automatically retrieved const deviceInfo = client.getDeviceInfo(); if (deviceInfo?.hasDeepSleep) { console.log('Deep sleep device detected!'); // Ping mechanism is automatically disabled // Reconnection behavior is automatically adjusted } ``` ## What Happens Automatically ### For Deep Sleep Devices | Scenario | Behavior | |----------|----------| | **Ping Mechanism** | Disabled (no unnecessary pings while device may sleep) | | **DisconnectRequest** | Handled gracefully, sends DisconnectResponse | | **Connection Loss** | Treated as expected disconnect (no errors) | | **Auto-Reconnect** | Disabled (waits for device to wake and reconnect) | | **State Preservation** | Connection state tracked for clean reconnection | ### For Normal Devices | Scenario | Behavior | |----------|----------| | **Ping Mechanism** | Enabled (monitors connection health) | | **DisconnectRequest** | Still handled gracefully if received | | **Connection Loss** | ⚠️ Treated as unexpected (may trigger reconnection) | | **Auto-Reconnect** | Enabled by default (automatically reconnects) | ## Manual Control You can also manually configure deep sleep mode: ```typescript import { Connection } from '@webarray/esphome-native-api'; const connection = new Connection({ host: 'device.local', port: 6053, }); // Manually enable deep sleep mode connection.setDeepSleepMode(true); await connection.connect(); // Check if disconnect was expected (deep sleep) connection.on('disconnect', () => { if (connection.isExpectedDisconnect()) { console.log('Device went to sleep (expected)'); } else { console.log('Connection lost (unexpected)'); } }); ``` ## Example: Battery-Powered Sensor ```typescript import { ESPHomeClient } from '@webarray/esphome-native-api'; const client = new ESPHomeClient({ host: 'battery-sensor.local', port: 6053, }); // Device wakes every 5 minutes to send readings client.on('connected', () => { console.log('Device woke up and connected'); }); client.on('deviceInfo', (info) => { console.log('Device:', info.name); console.log('Has deep sleep:', info.hasDeepSleep); }); client.on('state', (state) => { console.log('Received state update:', state); }); client.on('disconnected', () => { console.log('Device went back to sleep'); // Client will passively wait for next wake-up // No need to manually reconnect }); await client.connect(); ``` ## Example: ESPHome Configuration On the ESPHome device side: ```yaml # ESPHome configuration esphome: name: battery-sensor esp32: board: esp32dev # Enable deep sleep deep_sleep: id: deep_sleep_1 run_duration: 30s # Stay awake for 30 seconds sleep_duration: 5min # Sleep for 5 minutes # Sensors sensor: - platform: dht pin: GPIO4 temperature: name: "Temperature" humidity: name: "Humidity" update_interval: 10s # API for connection api: encryption: key: !secret api_encryption_key ``` When this device goes to sleep: 1. It sends `DisconnectRequest` to all connected clients 2. Your TypeScript client responds with `DisconnectResponse` 3. Device enters deep sleep for 5 minutes 4. After 5 minutes, device wakes, connects, sends updates 5. Cycle repeats ## Implementation Details ### Connection Class Both `Connection` and `EncryptedConnection` classes support: ```typescript class Connection { // Enable/disable deep sleep mode setDeepSleepMode(enabled: boolean): void; // Check if last disconnect was expected isExpectedDisconnect(): boolean; // Private: handles DisconnectRequest automatically private handleDisconnectRequest(): void; } ``` ### Message Types The library handles these protocol messages: - `MessageType.DisconnectRequest = 5` (device → client) - `MessageType.DisconnectResponse = 6` (client → device) These are part of the ESPHome Native API protocol specification. ## Testing The library includes comprehensive tests for deep sleep functionality: ```bash npm test -- --testPathPattern=deep-sleep ``` Test coverage includes: - ✅ DisconnectRequest/Response handling - ✅ Ping mechanism disabling - ✅ Expected disconnect detection - ✅ Reconnection behavior - ✅ State management - ✅ Integration scenarios See `test/deep-sleep.test.ts` for full test suite. ## Comparison with Python Library This implementation matches the behavior of the official Python `aioesphomeapi` library: | Feature | Python (aioesphomeapi) | This Library (TypeScript) | |---------|----------------------|---------------------------| | DisconnectRequest handling | ✅ | ✅ | | DisconnectResponse sending | ✅ | ✅ | | Ping disabling | ✅ | ✅ | | Auto-reconnect control | ✅ | ✅ | | hasDeepSleep detection | ✅ | ✅ | | Expected disconnect tracking | ✅ | ✅ | ## Benefits ### For Battery-Powered Devices - ✅ No wasted CPU cycles trying to ping sleeping devices - ✅ No false timeout errors - ✅ Proper cooperative shutdown protocol - ✅ Clean reconnection when device wakes ### For Developers - ✅ Automatic detection and configuration - ✅ No manual intervention required - ✅ Works out of the box - ✅ Full protocol compliance ### For Home Automation - ✅ Reliable state tracking - ✅ No spurious unavailable states - ✅ Efficient power management - ✅ Seamless integration with ESPHome ecosystem ## Troubleshooting ### Device not reconnecting after sleep **Cause**: Device might not be sending DisconnectRequest before sleeping. **Solution**: Ensure your ESPHome device has proper deep sleep configuration: ```yaml deep_sleep: id: deep_sleep_1 run_duration: 30s sleep_duration: 5min ``` ### Getting timeout errors **Cause**: Library might not have detected deep sleep mode. **Solution**: Check device info: ```typescript const info = client.getDeviceInfo(); console.log('Has deep sleep:', info?.hasDeepSleep); ``` If `hasDeepSleep` is false but device actually has deep sleep, update your ESPHome firmware. ### Connection not closing cleanly **Cause**: Device might be crashing instead of sleeping properly. **Solution**: Check ESPHome logs: ```bash esphome logs your-device.yaml ``` Look for "DisconnectRequest sent" messages before sleep.