/** * MQTT Client for AWS IoT Core * * This client uses MQTT over WebSocket to communicate with AWS IoT Core, * mirroring the communication pattern used by the iOS MyDolphin Plus app. * * The iOS app uses these MQTT topics: * - Subscribe: $aws/things/{serial}/shadow/update/accepted * - Subscribe: $aws/things/{serial}/shadow/update/rejected * - Subscribe: $aws/things/{serial}/shadow/get/accepted * - Subscribe: $aws/things/{serial}/shadow/get/rejected * - Subscribe: Maytronics/{serial}/main (dynamic channel) * - Publish: $aws/things/{serial}/shadow/get (to request shadow) * - Publish: $aws/things/{serial}/shadow/update (to send commands) * - Publish: Maytronics/{serial}/main (dynamic commands) */ import { EventEmitter } from 'events'; import type { Logger } from 'homebridge'; import type { AWSIoTCredentials } from './auth/types.js'; import type { RawShadowState } from '../parsers/types.js'; export interface MQTTClientConfig { serialNumber: string; region: string; iotEndpoint: string; credentials: AWSIoTCredentials; } /** * MQTT Client for AWS IoT Core communication */ export declare class MQTTClient extends EventEmitter { private readonly log; private client; private readonly serialNumber; private readonly truncatedSerial; private readonly region; private readonly iotEndpoint; private credentials; private connected; private reconnectAttempts; private readonly maxReconnectAttempts; private currentShadow; private lastShadowReceivedAt; private readonly pendingRequests; private requestCounter; private requestGate; private lastRequestAt; private pendingGet; private throttledRequests; private lastThrottleLogAt; constructor(config: MQTTClientConfig, log: Logger); /** * Connect to AWS IoT Core via MQTT over WebSocket */ connect(): Promise; /** * Generate AWS Signature V4 signed WebSocket URL */ private getSignedWebSocketUrl; /** * Format date for AWS SigV4 */ private formatAmzDate; /** * URI encode per AWS SigV4 spec (RFC 3986) */ private uriEncode; /** * Subscribe to relevant MQTT topics */ private subscribeToTopics; /** * Handle incoming MQTT messages */ private handleMessage; /** * Log a shadow rejection. Throttling (429) is expected on the shared MyDolphin * AWS account and is retried transparently, so it is only counted here and * surfaced by reportThrottling() once retries are exhausted. */ private logShadowRejection; /** * Surface persistent throttling at most once per SHADOW_THROTTLE_LOG_INTERVAL_MS */ private reportThrottling; /** * Ensure client is connected, throw if not */ private ensureConnected; /** * Wait for the response to a single shadow request, correlated by clientToken. * Resolves with the accepted shadow or the rejection payload; rejects on timeout. */ private waitForShadowResponse; /** * Hand a shadow response to the request that asked for it. * * AWS IoT echoes our clientToken, so a matching token settles that request and * a foreign one (the phone app talking to the same robot) settles nothing. * `update/accepted` is also broadcast without a token when the robot reports * its own state: that document answers a pending `get`, but must never be read * as acceptance of our update, and an untagged rejection is attributed to nobody. */ private settlePendingRequest; /** * Token of the request an untagged response may settle, if any */ private findUntaggedRecipient; /** * Unique token used to match a shadow response to its request */ private nextClientToken; /** * Serialize shadow publishes and keep a minimum gap between them so bursts * (e.g. set mode + start + refresh) do not trip the AWS IoT throttle */ private awaitRequestSlot; /** * Publish a shadow request, retrying with exponential backoff while AWS IoT * answers with 429 TOO_MANY_REQUESTS */ private requestShadow; /** * Request current shadow state. * Concurrent callers share a single in-flight request. */ getShadow(): Promise; /** * Update shadow with desired state. * Retries are kept short because HomeKit is waiting on the result. */ updateShadow(desired: Record): Promise; /** * Send command via dynamic channel (Maytronics/{serial}/main) */ sendDynamicCommand(command: Record): Promise; /** * Send a named command via the dynamic channel using the BLE protocol format */ sendCommand(commandName: string, data?: string): Promise; /** * Update credentials (for refresh) */ updateCredentials(credentials: AWSIoTCredentials): void; /** * Disconnect from MQTT broker */ disconnect(): void; /** * Check if connected */ isConnected(): boolean; /** * Get current shadow */ getCurrentShadow(): RawShadowState | null; /** * Timestamp of the last shadow document received (0 if none yet) */ getLastShadowReceivedAt(): number; } //# sourceMappingURL=mqttClient.d.ts.map