From 2eca5bf7e99ea78e853090ceed94cc26a8bdad80 Mon Sep 17 00:00:00 2001 From: Aruna Tennakoon Date: Sun, 4 Jan 2026 09:11:14 +0700 Subject: [PATCH] feat: Send a device setting event to SinricPro --- .gitignore | 3 +- CHANGELOG.md | 4 ++ examples/settings/device/index.ts | 14 +++++- examples/settings/module/index.ts | 15 ++++++- package-lock.json | 4 +- package.json | 2 +- src/capabilities/SettingController.ts | 16 +++++++ src/core/SinricPro.ts | 65 ++++++++++++++++++++++++++- 8 files changed, 115 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index e6fec2f..9bfde05 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,5 @@ yarn-error.log* .claude.md -claude.md \ No newline at end of file +claude.md +notes-to-aruna.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aa81f2..fe0540f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [5.2.0] + +feat: Send a device setting event to SinricPro + ## [5.1.0] feat: Implemented example applications for various device types including Camera, PowerSensor, Device Settings, Module Settings, and Temperature Sensor. diff --git a/examples/settings/device/index.ts b/examples/settings/device/index.ts index b2f3302..7a49978 100644 --- a/examples/settings/device/index.ts +++ b/examples/settings/device/index.ts @@ -50,6 +50,14 @@ async function main() { return false; }); + // Example: Send a device setting event to SinricPro + // This can be used to report setting changes made locally (e.g., via physical button) + // setTimeout(async () => { + // console.log('\n[Example] Sending device setting event...'); + // const sent = await myBlinds.sendSettingEvent('id_tilt', 75); + // console.log(` Setting event sent: ${sent}`); + // }, 5000); + // Add device to SinricPro SinricPro.add(myBlinds); @@ -67,12 +75,14 @@ async function main() { console.log('Device Settings vs Module Settings:'); console.log('='.repeat(60)); console.log(' Device Settings: Configuration for THIS specific device'); - console.log(' - Registered via: device.onSetting(callback)'); + console.log(' - Receive via: device.onSetting(callback)'); + console.log(' - Send via: device.sendSettingEvent(settingId, value)'); console.log(' - Examples: Tilt angle'); console.log(' - Callback receives: (deviceId, settingId, value)'); console.log(''); console.log(' Module Settings: Configuration for the module/board'); - console.log(' - Registered via: SinricPro.onSetSetting(callback)'); + console.log(' - Receive via: SinricPro.onSetSetting(callback)'); + console.log(' - Send via: SinricPro.sendSettingEvent(settingId, value)'); console.log(' - Examples: WiFi retry count, log level'); console.log('\n' + '='.repeat(60)); diff --git a/examples/settings/module/index.ts b/examples/settings/module/index.ts index 79c2a14..5f84b35 100644 --- a/examples/settings/module/index.ts +++ b/examples/settings/module/index.ts @@ -61,6 +61,14 @@ async function main() { SinricPro.onConnected(() => { console.log('\nConnected to SinricPro!'); + + // Example: Send a module setting event to SinricPro + // This can be used to report module-level settings changes + // setTimeout(async () => { + // console.log('\n[Example] Sending module setting event...'); + // const sent = await SinricPro.sendSettingEvent('if_wifiretrycount', 5); + // console.log(` Module setting event sent: ${sent}`); + // }, 5000); }); // Initialize SinricPro @@ -73,9 +81,14 @@ async function main() { console.log('Module Settings vs Device Settings:'); console.log('='.repeat(60)); console.log(' Module Settings: Configuration for the module/board itself'); - console.log(' - Registered via: SinricPro.onSetSetting(callback)'); + console.log(' - Receive via: SinricPro.onSetSetting(callback)'); + console.log(' - Send via: SinricPro.sendSettingEvent(settingId, value)'); console.log(' - Examples: WiFi retry count'); console.log(''); + console.log(' Device Settings: Configuration for individual devices'); + console.log(' - Receive via: device.onSetting(callback)'); + console.log(' - Send via: device.sendSettingEvent(settingId, value)'); + console.log(''); console.log('\n' + '='.repeat(60)); console.log('Current Module Configuration:'); diff --git a/package-lock.json b/package-lock.json index 6af4d73..0093c81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sinricpro", - "version": "3.0.0", + "version": "5.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sinricpro", - "version": "3.0.0", + "version": "5.2.0", "license": "CC-BY-SA-4.0", "dependencies": { "ws": "^8.14.2" diff --git a/package.json b/package.json index d80a7ee..60d483f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sinricpro", - "version": "5.1.0", + "version": "5.2.0", "description": "Official SinricPro SDK for Node.js and TypeScript - Control IoT devices with Alexa and Google Home", "main": "dist/index.js", "exports": { diff --git a/src/capabilities/SettingController.ts b/src/capabilities/SettingController.ts index 01bd9bd..d66dc67 100644 --- a/src/capabilities/SettingController.ts +++ b/src/capabilities/SettingController.ts @@ -3,7 +3,9 @@ */ import { SinricProDevice } from '../core/SinricProDevice'; +import { EventLimiter } from '../core/EventLimiter'; import type { SinricProRequest } from '../core/types'; +import { EVENT_LIMIT_STATE, PHYSICAL_INTERACTION } from '../core/types'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type Constructor = new (...args: any[]) => T; @@ -16,11 +18,13 @@ export type SettingCallback = ( export interface ISettingController { onSetting(callback: SettingCallback): void; + sendSettingEvent(settingId: string, value: unknown, cause?: string): Promise; } export function SettingController>(Base: T) { return class extends Base implements ISettingController { private settingCallback: SettingCallback | null = null; + private settingEventLimiter: EventLimiter = new EventLimiter(EVENT_LIMIT_STATE); // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(...args: any[]) { @@ -32,6 +36,18 @@ export function SettingController>(Base: this.settingCallback = callback; } + async sendSettingEvent( + settingId: string, + value: unknown, + cause: string = PHYSICAL_INTERACTION + ): Promise { + if (this.settingEventLimiter.isLimited()) { + return false; + } + + return this.sendEvent('setSetting', { id: settingId, value }, cause); + } + private async handleSettingRequest(request: SinricProRequest): Promise { if (request.action !== 'setSetting' || !this.settingCallback) { return false; diff --git a/src/core/SinricPro.ts b/src/core/SinricPro.ts index f2ad18b..8c16e5e 100644 --- a/src/core/SinricPro.ts +++ b/src/core/SinricPro.ts @@ -18,7 +18,8 @@ import type { PongCallback, ModuleSettingCallback, } from './types'; -import { SINRICPRO_SERVER_URL } from './types'; +import { SINRICPRO_SERVER_URL, EVENT_LIMIT_STATE, PHYSICAL_INTERACTION } from './types'; +import { EventLimiter } from './EventLimiter'; // Internal config type with serverUrl interface InternalConfig extends Required { @@ -37,6 +38,7 @@ export class SinricPro extends EventEmitter implements ISinricPro { private isInitialized: boolean = false; private processingInterval: NodeJS.Timeout | null = null; private moduleSettingCallback: ModuleSettingCallback | null = null; + private settingEventLimiter: EventLimiter = new EventLimiter(EVENT_LIMIT_STATE); private constructor() { super(); @@ -216,6 +218,67 @@ export class SinricPro extends EventEmitter implements ISinricPro { this.moduleSettingCallback = callback; } + /** + * Send a module-level setting event to SinricPro server + * + * Module settings are configuration values for the module (dev board) itself. + * Use this to report setting changes like WiFi configuration, logging level, + * or other module-wide settings. + * + * @param settingId - The setting identifier + * @param value - The setting value (can be any JSON-serializable type) + * @param cause - (optional) Reason for the event (default: 'PHYSICAL_INTERACTION') + * @returns Promise - true if event was sent, false if rate limited + * @example + * ```typescript + * await SinricPro.sendSettingEvent('wifi_retry_count', 5); + * await SinricPro.sendSettingEvent('debug_mode', true); + * ``` + */ + async sendSettingEvent( + settingId: string, + value: unknown, + cause: string = PHYSICAL_INTERACTION + ): Promise { + if (this.settingEventLimiter.isLimited()) { + return false; + } + + if (!this.isConnected()) { + SinricProSdkLogger.error('Cannot send setting event: Not connected to SinricPro'); + return false; + } + + const eventMessage: SinricProMessage = { + header: { + payloadVersion: 2, + signatureVersion: 1, + }, + payload: { + action: 'setSetting', + replyToken: this.generateMessageId(), + type: 'event' as MessageType, + createdAt: this.getTimestamp(), + cause: { type: cause }, + scope: 'module', + value: { id: settingId, value }, + }, + }; + + try { + await this.sendMessage(eventMessage); + SinricProSdkLogger.debug(`Module setting event sent: ${settingId}`, value); + return true; + } catch (error) { + SinricProSdkLogger.error(`Failed to send module setting event ${settingId}:`, error); + return false; + } + } + + private generateMessageId(): string { + return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; + } + /** * Stop the SinricPro SDK and disconnect from the server * @example