UNPKG

homebridge-http-sensors-switches

Version:

This plugin communicates with your devices over HTTP or MQTT. Currently it supports Light Bulb, Switches, Outlets, Fan, Garage Door, Shades / Blinds, Temperature/Humidity, Motion, Contact and Occupancy sensor, Door, Sprinkler, Valve, Air Quality, Smoke, C

167 lines 7.72 kB
import axios from 'axios'; import { EventEmitter } from 'events'; /** * The `SharedPolling` class provides a centralized polling mechanism for devices that share a common data source. * It minimizes redundant HTTP requests by grouping devices under a shared polling instance, identified by a unique ID. * Each instance fetches data from a specified URL at customizable intervals and distributes the data to all grouped devices. * * ### Key Features: * - **Shared Polling:** Groups devices under a common polling instance for efficient data retrieval. * - **Dynamic Grouping:** Dynamically adds or removes devices from a polling group. * - **Centralized Data Management:** Maintains the latest fetched data for easy access by devices. * - **Customizable Polling Intervals:** Allows the polling interval to be set dynamically when registering a shared polling instance. * - **Event-Based Updates:** Notifies devices in the group about data updates using an event-driven architecture. * - **Error Handling:** Logs errors encountered during polling and ensures graceful failure recovery. * - **Optional HTTPS Agent Support:** Integrates with `HttpsAgentManager` to support trusted certificates and error skipping for HTTPS endpoints. * * ### Use Cases: * - Environmental sensors (e.g., temperature, humidity, pressure) sharing a single data source. * - Multiple devices (e.g., switches, outlets) relying on a single status URL. * - Dynamic addition or removal of devices within a shared polling group. * - Varying polling intervals based on the needs of different devices or groups. * - Secure polling from HTTPS endpoints with custom certificate handling. * * ### Public Methods: * - `registerPolling(uniqueId: string, url: string, platform: HttpSensorsAndSwitchesHomebridgePlatform, interval: number = 5000, * httpsAgentManager?: HttpsAgentManager): SharedPolling`: * Registers a new shared polling instance or adds a device to an existing instance. Accepts a polling interval in milliseconds, * defaulting to 5000ms. Optionally accepts an `HttpsAgentManager` for HTTPS agent reuse. * * - `unregisterPolling(uniqueId: string): void`: * Removes a device from a polling group. If no devices remain in the group, stops polling. * * - `getData(): SharedData`: * Retrieves the latest data fetched by the polling instance. * * ### Private Methods: * - `startPolling(): void`: * Starts polling the specified URL at the defined interval. The interval is customizable and passed during instance registration. * * - `stopPolling(): void`: * Stops the polling process and clears the interval. * * - `fetchData(): Promise<void>`: * Performs an HTTP GET request to fetch the data and updates the internal state. Uses `HttpsAgentManager` if provided. * Emits a `dataUpdated` event to notify all subscribers. * * ### Internal Structure: * - Maintains a `pollingInstances` Map to associate unique IDs with polling instances and device counts. * - Stores fetched data in a `data` property, accessible via `getData()`. * - Uses an event-driven approach to notify devices in the group about data updates. * - Optionally reuses HTTPS agents via `HttpsAgentManager` for secure and efficient polling. * * ### Example Usage: * ```typescript * // Register a shared polling instance with a 10-second interval and HTTPS agent * const httpsAgentManager = new HttpsAgentManager(trustedCert, ignoreCertErrors, "https://example.com/status"); * const sharedPolling = SharedPolling.registerPolling( * "environmentGroup", * "https://example.com/status", * platformInstance, * 10000, // Set polling interval to 10 seconds * httpsAgentManager * ); * * // Subscribe to data updates * sharedPolling.on('dataUpdated', (data: SharedData) => { * console.log('Data updated:', data); * }); * * // Access the fetched data * const data = sharedPolling.getData(); * console.log(data.temperature); // Example: 23.5 * * // Unregister a device from polling * SharedPolling.unregisterPolling("environmentGroup"); * ``` */ export class SharedPolling extends EventEmitter { url; platform; static pollingInstances = new Map(); intervalId; data = {}; deviceCount = 0; interval; // Polling interval in milliseconds httpsAgentManager; // Constructor with platform for logging and interval constructor(url, platform, interval, // Add interval parameter httpsAgentManager) { super(); this.url = url; this.platform = platform; this.interval = interval; this.httpsAgentManager = httpsAgentManager; } // Register a shared polling instance static registerPolling(uniqueId, url, platform, interval = 5000, httpsAgentManager) { let instance = SharedPolling.pollingInstances.get(uniqueId); if (instance) { instance.deviceCount += 1; instance.platform.log.info(`${uniqueId}: Device added to existing SharedPolling group. Total devices: ${instance.deviceCount}`); } else { platform.log.info(`${uniqueId}: Registering new SharedPolling instance for group: ${uniqueId}`); instance = new SharedPolling(url, platform, interval, httpsAgentManager); instance.deviceCount = 1; SharedPolling.pollingInstances.set(uniqueId, instance); instance.startPolling(); } return instance; } // Unregister a device from shared polling static unregisterPolling(uniqueId) { const instance = SharedPolling.pollingInstances.get(uniqueId); if (instance) { instance.deviceCount -= 1; instance.platform.log.debug(`${uniqueId}: Device removed from SharedPolling group. Remaining devices: ${instance.deviceCount}`); if (instance.deviceCount === 0) { instance.stopPolling(); SharedPolling.pollingInstances.delete(uniqueId); instance.platform.log.debug(`${uniqueId}: Stopped polling as no devices remain.`); } } else { console.warn(`${uniqueId}: No polling group found to unregister.`); } } // Start polling startPolling() { this.platform.log.debug(`Started polling for URL: ${this.url} with interval: ${this.interval}ms`); this.fetchData(); // Initial data fetch this.intervalId = setInterval(() => { this.fetchData(); }, this.interval); // Use dynamic interval } // Stop polling stopPolling() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = undefined; this.platform.log.debug(`Stopped polling for URL: ${this.url}`); } } // Fetch data async fetchData() { try { const httpsAgent = this.httpsAgentManager?.getAgent(); const response = await axios.get(this.url, { timeout: 8000, httpsAgent }); this.data = response.data; // Emit an event to notify .on('dataUpdated') listeners this.emit('dataUpdated', this.data); this.platform.log.debug(`Updated data for URL: ${this.url}`); } catch (error) { // If necessary, emit an error event here so the device can be marked as "No Response" const errorMessage = error.message; this.platform.log.warn(`Error fetching data for URL: ${this.url} - ${errorMessage}`); // Emit error event with optional error info this.emit('dataError', error); } } // Get the latest data getData() { return this.data; } } //# sourceMappingURL=SharedPolling.js.map