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
JavaScript
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