diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d9b49c..7aa81f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,51 +1,26 @@ -## [3.0.0] +## [5.1.0] -### Features - -* feat: Replaced the old sdk with a new sdk - -## [2.6.1] - -### Features - -* **Custom device types support** -* **SR_DEBUG to support debug logs** - -## [2.7.1] - -### Features -* **Push notification feature added** - -## [2.8.1] -* feat: Camera support. +feat: Implemented example applications for various device types including Camera, PowerSensor, Device Settings, Module Settings, and Temperature Sensor. -## [2.9.1] +fix: missing mac in Websocket header. -### Features -* fix: setThermostatMode +fix: missing settings, push-notification controller in Switch. -## [2.10.1] +fix: missing instance id. -### Features -* fix: adjustTargetTemperature +fix: missing scope when responding back to server. -## [2.11.1] +## [5.0.0] ### Features -* fix: targetTemperature -## [2.11.2] +* BREAKING CHANGE: feat: remove restoreDeviceStates in order to change this at device level from server side instead of fixed value in client +* Camera, PowerSensor, Switch, TemperatureSensor examples added. +* Missing capabilities added. +* instanceId support for Mode and Range -### Features -* fix: adjustTargetTemperature response format. - -## [2.11.4] +## [4.0.0] ### Features -* fix: power on, off event -## [2.12.1] - -### Features -* fix: error message when sending an event while connection not established yet -* feat: add onConnected callback \ No newline at end of file +* feat: Replaced the old sdk with a new sdk \ No newline at end of file diff --git a/examples/camera/index.ts b/examples/camera/index.ts new file mode 100644 index 0000000..43428e2 --- /dev/null +++ b/examples/camera/index.ts @@ -0,0 +1,105 @@ +/** + * SinricPro Camera Example + * + * This example demonstrates: + * - Connecting to SinricPro + * - Creating a camera device + * - Handling power state changes + * - Sending events when state changes locally + */ + +import SinricPro from 'sinricpro'; +import { SinricProCamera } from 'sinricpro'; +import { SinricProSdkLogger, LogLevel } from 'sinricpro'; + +// Configuration - Replace with your credentials +const SWITCH_ID = 'YOUR-DEVICE-ID'; // 24-character hex +const APP_KEY = 'YOUR-APP-KEY'; // UUID format +const APP_SECRET = 'YOUR-APP-SECRET'; // Long secret key + +// Simulated device state +let devicePowerState = false; + +async function main() { + // Enable debug logging + SinricProSdkLogger.setLevel(LogLevel.ERROR); + + console.log('='.repeat(60)); + console.log('SinricPro Camera Example'); + console.log('='.repeat(60)); + + // Create camera device + const myCamera = SinricProCamera(SWITCH_ID); + + // Set up power state callback + myCamera.onPowerState(async (deviceId: string, state: boolean) => { + console.log(`\n[Callback] Device ${deviceId} turned ${state ? 'ON' : 'OFF'}`); + + // Update local state + devicePowerState = state; + + // Here you would control actual hardware + // For example: GPIO.write(LED_PIN, state); + + return true; // Return true if successful + }); + + // Add device to SinricPro + SinricPro.add(myCamera); + + // Connection event handlers + SinricPro.onConnected(() => { + console.log('\n✓ Connected to SinricPro!'); + console.log(' You can now control the device via Alexa or Google Home'); + }); + + SinricPro.onDisconnected(() => { + console.log('\n✗ Disconnected from SinricPro'); + }); + + SinricPro.onPong((latency) => { + console.log(`\n♥ Heartbeat (latency: ${latency}ms)`); + }); + + // Initialize SinricPro + await SinricPro.begin({ + appKey: APP_KEY, + appSecret: APP_SECRET, + }); + + // Simulate local button press every 30 seconds + setInterval(async () => { + devicePowerState = !devicePowerState; + console.log(`\n[Local Event] Button pressed - turning ${devicePowerState ? 'ON' : 'OFF'}`); + const success = await myCamera.sendPowerStateEvent(devicePowerState); + + if (success) { + console.log(' ✓ Event sent to SinricPro server'); + } else { + console.log(' ✗ Failed to send event (rate limited or not connected)'); + } + }, 60000); + + console.log('\nWaiting for commands ...'); + console.log('Press Ctrl+C to exit\n'); +} + +// Handle graceful shutdown +process.on('SIGINT', async () => { + console.log('\n\nShutting down...'); + await SinricPro.stop(); + console.log('Goodbye!\n'); + process.exit(0); +}); + +// Handle unhandled errors +process.on('unhandledRejection', (error) => { + console.error('Unhandled error:', error); + process.exit(1); +}); + +// Run the example +main().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/examples/customdevice/index.ts b/examples/customdevice/index.ts new file mode 100644 index 0000000..6a5dd16 --- /dev/null +++ b/examples/customdevice/index.ts @@ -0,0 +1,182 @@ +/** + * SinricPro Custom Device Example + * + * Demonstrates how to use SinricProCustomDevice to create flexible devices + * with any combination of capabilities you need. + */ + +import SinricPro from 'sinricpro'; +import { SinricProCustomDevice } from 'sinricpro'; +import { SinricProSdkLogger, LogLevel } from 'sinricpro'; + +// Configuration - Replace with your credentials +const DEVICE_ID = 'YOUR-DEVICE-ID'; // 24-character hex +const APP_KEY = 'YOUR-APP-KEY'; // UUID format +const APP_SECRET = 'YOUR-APP-SECRET'; // Long secret key + +// Device state +const deviceState = { + power: false, + brightness: 0, + temperature: 22.0, + color: { r: 255, g: 255, b: 255 }, +}; + +async function main() { + SinricProSdkLogger.setLevel(LogLevel.ERROR); + + console.log('='.repeat(70)); + console.log('SinricPro Custom Device Example'); + console.log('='.repeat(70)); + console.log('\nThis example demonstrates a custom device that combines:'); + console.log(' - Power control (on/off)'); + console.log(' - Brightness control (0-100%)'); + console.log(' - Color control (RGB)'); + console.log(' - Color temperature control'); + console.log(' - Range control (generic values)'); + console.log(' - Mode control'); + console.log(' - Temperature sensor (reporting)'); + console.log(' - Thermostat control'); + console.log(' - Lock control'); + console.log(' - Settings control'); + console.log('\nYou can mix and match any capabilities you need!'); + console.log('='.repeat(70)); + + // Create custom device + // You can use any product_type from your SinricPro portal + const customDevice = SinricProCustomDevice(DEVICE_ID, 'CUSTOM_DEVICE'); + + // Register callbacks for capabilities you want to use + // You only need to register the ones you'll actually use! + + // Power control + customDevice.onPowerState(async (deviceId, state) => { + deviceState.power = state; + console.log(`\n[Power] Device ${deviceId} turned ${state ? 'ON' : 'OFF'}`); + return true; + }); + + // Brightness control + customDevice.onBrightness(async (deviceId, brightness) => { + deviceState.brightness = brightness; + console.log(`\n[Brightness] Set to ${brightness}%`); + return true; + }); + + customDevice.onAdjustBrightness(async (deviceId, brightnessDelta) => { + const newBrightness = Math.max(0, Math.min(100, deviceState.brightness + brightnessDelta)); + deviceState.brightness = newBrightness; + console.log(`\n[Brightness] Adjusted by ${brightnessDelta} to ${newBrightness}%`); + return true; + }); + + // Color control + customDevice.onColor(async (deviceId, r, g, b) => { + deviceState.color = { r, g, b }; + console.log(`\n[Color] Set to RGB(${r}, ${g}, ${b})`); + return true; + }); + + // Color temperature control + customDevice.onColorTemperature(async (deviceId, colorTemperature) => { + console.log(`\n[Color Temperature] Set to ${colorTemperature}K`); + return true; + }); + + // Range control (generic) + customDevice.onRangeValue(async (deviceId, rangeValue, instanceId) => { + if (instanceId) { + console.log(`\n[Range] Instance '${instanceId}' set to ${rangeValue}`); + } else { + console.log(`\n[Range] Set to ${rangeValue}`); + } + return true; + }); + + customDevice.onAdjustRangeValue(async (deviceId, rangeValueDelta, instanceId) => { + if (instanceId) { + console.log(`\n[Range] Instance '${instanceId}' adjusted by ${rangeValueDelta}`); + } else { + console.log(`\n[Range] Adjusted by ${rangeValueDelta}`); + } + return true; + }); + + // Mode control + customDevice.onSetMode(async (deviceId, mode, instanceId) => { + if (instanceId) { + console.log(`\n[Mode] Instance '${instanceId}' set to '${mode}'`); + } else { + console.log(`\n[Mode] Set to '${mode}'`); + } + return true; + }); + + // Thermostat control + customDevice.onTargetTemperature(async (deviceId, temperature) => { + deviceState.temperature = temperature; + console.log(`\n[Thermostat] Target temperature set to ${temperature}°C`); + return true; + }); + + customDevice.onThermostatMode(async (deviceId, mode) => { + console.log(`\n[Thermostat] Mode set to ${mode}`); + return true; + }); + + // Lock control + customDevice.onLockState(async (deviceId, state) => { + console.log(`\n[Lock] State set to ${state}`); + return true; + }); + + // Settings control + customDevice.onSetting(async (deviceId, settingId, value) => { + console.log(`\n[Setting] ${settingId} = ${JSON.stringify(value)}`); + return true; + }); + + // Add device to SinricPro + SinricPro.add(customDevice); + + // Connection event handlers + SinricPro.onConnected(() => { + console.log('\n✓ Connected to SinricPro!'); + console.log(' Custom device is ready.'); + }); + + SinricPro.onDisconnected(() => { + console.log('\n✗ Disconnected from SinricPro'); + }); + + // Initialize SinricPro + await SinricPro.begin({ + appKey: APP_KEY, + appSecret: APP_SECRET, + }); + + console.log('\n' + '='.repeat(70)); + console.log('Waiting for commands...'); + console.log('Press Ctrl+C to exit'); + console.log('='.repeat(70) + '\n'); +} + +// Handle graceful shutdown +process.on('SIGINT', async () => { + console.log('\n\nShutting down...'); + await SinricPro.stop(); + console.log('Goodbye!\n'); + process.exit(0); +}); + +// Handle unhandled errors +process.on('unhandledRejection', (error) => { + console.error('Unhandled error:', error); + process.exit(1); +}); + +// Run the example +main().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/examples/fan/index.ts b/examples/fan/index.ts index c7c09be..73deb22 100644 --- a/examples/fan/index.ts +++ b/examples/fan/index.ts @@ -31,7 +31,7 @@ async function main() { }); // Speed control (0-100) - myFan.onRangeValue(async (deviceId, speed) => { + myFan.onRangeValue(async (deviceId, speed, instanceId) => { console.log(`\n[Fan Speed] Device ${deviceId} Set to ${speed}%`); // Map speed to fan levels: 0=off, 1-33=low, 34-66=medium, 67-100=high let level; @@ -44,7 +44,7 @@ async function main() { return true; }); - myFan.onAdjustRangeValue(async (deviceId, delta) => { + myFan.onAdjustRangeValue(async (deviceId, delta, instanceId) => { console.log(`\n[Fan Speed] Device ${deviceId} Adjust by ${delta > 0 ? '+' : ''}${delta}`); return true; }); diff --git a/examples/powersensor/index.ts b/examples/powersensor/index.ts new file mode 100644 index 0000000..9b866c1 --- /dev/null +++ b/examples/powersensor/index.ts @@ -0,0 +1,96 @@ +/** + * SinricPro PowerSensor Sensor Example + * + * This example demonstrates: + * - Connecting to SinricPro + * - Creating a PowerSensor device + * - Handling power state changes + * - Sending events when state changes locally + */ + +import SinricPro from 'sinricpro'; +import { SinricProPowerSensor } from 'sinricpro'; +import { SinricProSdkLogger, LogLevel } from 'sinricpro'; + +// Configuration - Replace with your credentials +const DEVICE_ID = 'YOUR-DEVICE-ID'; // 24-character hex +const APP_KEY = 'YOUR-APP-KEY'; // UUID format +const APP_SECRET = 'YOUR-APP-SECRET'; // Long secret key + +async function main() { + // Enable debug logging + SinricProSdkLogger.setLevel(LogLevel.ERROR); + + console.log('='.repeat(60)); + console.log('SinricPro PowerSensor Example'); + console.log('='.repeat(60)); + + // Create PowerSensor sensor device + const sensor = SinricProPowerSensor(DEVICE_ID); + + // Add device to SinricPro + SinricPro.add(sensor); + + // Connection event handlers + SinricPro.onConnected(() => { + console.log('\n✓ Connected to SinricPro!'); + console.log(' You can now control the device via Alexa or Google Home'); + }); + + SinricPro.onDisconnected(() => { + console.log('\n✗ Disconnected from SinricPro'); + }); + + SinricPro.onPong((latency) => { + console.log(`\n♥ Heartbeat (latency: ${latency}ms)`); + }); + + // Initialize SinricPro + await SinricPro.begin({ + appKey: APP_KEY, + appSecret: APP_SECRET, + }); + + // Simulate local button press every 1 minute + setInterval(async () => { + // Generate random reading + const reading = { + voltage: 230, + current: 1, + power: 230, + apparentPower: 1, + reactivePower: 1, + }; + + const success = await sensor.sendPowerSensorEvent(reading); + + if (success) { + console.log(' ✓ PowerSensor Event sent to SinricPro server'); + } else { + console.log(' ✗ Failed to send event (rate limited or not connected)'); + } + }, 60000); + + console.log('\nWaiting for commands ...'); + console.log('Press Ctrl+C to exit\n'); +} + +// Handle graceful shutdown +process.on('SIGINT', async () => { + console.log('\n\nShutting down...'); + await SinricPro.stop(); + console.log('Goodbye!\n'); + process.exit(0); +}); + +// Handle unhandled errors +process.on('unhandledRejection', (error) => { + console.error('Unhandled error:', error); + process.exit(1); +}); + +// Run the example +main().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/examples/settings/device/index.ts b/examples/settings/device/index.ts new file mode 100644 index 0000000..b2f3302 --- /dev/null +++ b/examples/settings/device/index.ts @@ -0,0 +1,98 @@ +/** + * SinricPro Device Settings Example + * + * Demonstrates how to handle device-level configuration settings. + * Device settings are specific to individual devices, such as tilt angle, + * movement speed, or device-specific preferences. + */ + +import SinricPro from 'sinricpro'; +import { SinricProBlinds } from 'sinricpro'; +import { SinricProSdkLogger, LogLevel } from 'sinricpro'; + +// Configuration - Replace with your credentials +const DEVICE_ID = 'YOUR-DEVICE-ID'; // 24-character hex +const APP_KEY = 'YOUR-APP-KEY'; // UUID format +const APP_SECRET = 'YOUR-APP-SECRET'; // Long secret key + +async function main() { + SinricProSdkLogger.setLevel(LogLevel.ERROR); + + console.log('='.repeat(60)); + console.log('SinricPro Device Settings Example'); + console.log('='.repeat(60)); + + // Create a blinds device + const myBlinds = SinricProBlinds(DEVICE_ID); + + // Power state callback + myBlinds.onPowerState(async (deviceId, state) => { + console.log(`\n[Callback] Device ${deviceId} Power: ${state ? 'ON' : 'OFF'}`); + return true; + }); + + // Device-level setting callback + // This handles settings specific to this device + myBlinds.onSetting(async (deviceId, settingId, value) => { + console.log(`\n[Device Setting] ${settingId} = ${value} (type: ${typeof value})`); + + // Handle tilt setting + if (settingId === 'id_tilt') { + if (typeof value === 'number' && value >= 0 && value <= 100) { + return true; + } else { + console.log(` Invalid tilt value: ${value} (must be 0-100)`); + return false; + } + } + + console.log(` Unknown setting: ${settingId}`); + return false; + }); + + // Add device to SinricPro + SinricPro.add(myBlinds); + + SinricPro.onConnected(() => { + console.log('\nConnected to SinricPro!'); + }); + + // Initialize SinricPro + await SinricPro.begin({ + appKey: APP_KEY, + appSecret: APP_SECRET, + }); + + console.log('\n' + '='.repeat(60)); + 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(' - 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(' - Examples: WiFi retry count, log level'); + + console.log('\n' + '='.repeat(60)); + + console.log('\n' + '='.repeat(60)); + console.log('Voice Commands:'); + console.log('='.repeat(60)); + console.log(" 'Alexa, turn on [device name]'"); + console.log(' (Device settings are configured via SinricPro portal)'); + + console.log('\n' + '='.repeat(60)); + console.log('Waiting for commands...'); + console.log('Press Ctrl+C to exit'); + console.log('='.repeat(60) + '\n'); +} + +process.on('SIGINT', async () => { + console.log('\n\nShutting down...'); + await SinricPro.stop(); + process.exit(0); +}); + +main().catch(console.error); diff --git a/examples/settings/module/index.ts b/examples/settings/module/index.ts new file mode 100644 index 0000000..79c2a14 --- /dev/null +++ b/examples/settings/module/index.ts @@ -0,0 +1,99 @@ +/** + * SinricPro Module Settings Example + * + * Demonstrates how to handle module-level configuration settings. + * Module settings are for the module (dev board) itself, not for individual devices. + */ + +import SinricPro from 'sinricpro'; +import { SinricProSwitch } from 'sinricpro'; +import { SinricProSdkLogger, LogLevel } from 'sinricpro'; + +// Configuration - Replace with your credentials +const DEVICE_ID = 'YOUR-DEVICE-ID'; // 24-character hex +const APP_KEY = 'YOUR-APP-KEY'; // UUID format +const APP_SECRET = 'YOUR-APP-SECRET'; // Long secret key + +// Module configuration values +const moduleConfig: Record = { + wifiRetryCount: 3, +}; + +async function main() { + SinricProSdkLogger.setLevel(LogLevel.ERROR); + + console.log('='.repeat(60)); + console.log('SinricPro Module Settings Example'); + console.log('='.repeat(60)); + + // Create a switch device (module settings work alongside device settings) + const mySwitch = SinricProSwitch(DEVICE_ID); + + // Device-level power state callback + mySwitch.onPowerState(async (deviceId, state) => { + console.log(`\n[Device] ${deviceId} Power: ${state ? 'ON' : 'OFF'}`); + return true; + }); + + // Add device to SinricPro + SinricPro.add(mySwitch); + + // Register module-level setting callback + // This is separate from device-level settings (device.onSetting()) + SinricPro.onSetSetting(async (settingId, value) => { + console.log(`\n[Module Setting] ${settingId} = ${value}`); + + // Handle different setting types + if (settingId === 'if_wifiretrycount') { + if (typeof value === 'number' && value >= 1 && value <= 10) { + moduleConfig.wifiRetryCount = value; + console.log(` WiFi retry count set to ${value}`); + return true; + } else { + console.log(` Invalid wifi_retry_count value: ${value}`); + return false; + } + } + + console.log(` Unknown setting: ${settingId}`); + return false; + }); + + SinricPro.onConnected(() => { + console.log('\nConnected to SinricPro!'); + }); + + // Initialize SinricPro + await SinricPro.begin({ + appKey: APP_KEY, + appSecret: APP_SECRET, + }); + + console.log('\n' + '='.repeat(60)); + 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(' - Examples: WiFi retry count'); + console.log(''); + + console.log('\n' + '='.repeat(60)); + console.log('Current Module Configuration:'); + console.log('='.repeat(60)); + for (const [key, value] of Object.entries(moduleConfig)) { + console.log(` ${key}: ${value}`); + } + + console.log('\n' + '='.repeat(60)); + console.log('Waiting for commands...'); + console.log('Press Ctrl+C to exit'); + console.log('='.repeat(60) + '\n'); +} + +process.on('SIGINT', async () => { + console.log('\n\nShutting down...'); + await SinricPro.stop(); + process.exit(0); +}); + +main().catch(console.error); diff --git a/examples/switch-js/package.json b/examples/switch-js/package.json index e74aab9..7a9f76b 100644 --- a/examples/switch-js/package.json +++ b/examples/switch-js/package.json @@ -17,7 +17,7 @@ "author": "SinricPro", "license": "CC-BY-SA-4.0", "dependencies": { - "sinricpro": "^4.0.0" + "sinricpro": "^5.1.0" }, "engines": { "node": ">=16.0.0" diff --git a/examples/switch/index.ts b/examples/switch/index.ts index e631afc..df93cb1 100644 --- a/examples/switch/index.ts +++ b/examples/switch/index.ts @@ -13,7 +13,7 @@ import { SinricProSwitch } from 'sinricpro'; import { SinricProSdkLogger, LogLevel } from 'sinricpro'; // Configuration - Replace with your credentials -const DEVICE_ID = 'YOUR-DEVICE-ID'; // 24-character hex +const SWITCH_ID = 'YOUR-DEVICE-ID'; // 24-character hex const APP_KEY = 'YOUR-APP-KEY'; // UUID format const APP_SECRET = 'YOUR-APP-SECRET'; // Long secret key @@ -29,7 +29,7 @@ async function main() { console.log('='.repeat(60)); // Create switch device - const mySwitch = SinricProSwitch(DEVICE_ID); + const mySwitch = SinricProSwitch(SWITCH_ID); // Set up power state callback mySwitch.onPowerState(async (deviceId: string, state: boolean) => { diff --git a/examples/temperaturesensor/index.ts b/examples/temperaturesensor/index.ts new file mode 100644 index 0000000..3032cda --- /dev/null +++ b/examples/temperaturesensor/index.ts @@ -0,0 +1,96 @@ +/** + * SinricPro Temperature Sensor Example + * + * This example demonstrates: + * - Connecting to SinricPro + * - Creating a temperature device + * - Handling power state changes + * - Sending events when state changes locally + */ + +import SinricPro from 'sinricpro'; +import { SinricProTemperatureSensor } from 'sinricpro'; +import { SinricProSdkLogger, LogLevel } from 'sinricpro'; + +// Configuration - Replace with your credentials +const DEVICE_ID = 'YOUR-DEVICE-ID'; // 24-character hex +const APP_KEY = 'YOUR-APP-KEY'; // UUID format +const APP_SECRET = 'YOUR-APP-SECRET'; // Long secret key + +async function main() { + // Enable debug logging + SinricProSdkLogger.setLevel(LogLevel.ERROR); + + console.log('='.repeat(60)); + console.log('SinricPro Temperature Example'); + console.log('='.repeat(60)); + + // Create temperature sensor device + const sensor = SinricProTemperatureSensor(DEVICE_ID); + + // Add device to SinricPro + SinricPro.add(sensor); + + // Connection event handlers + SinricPro.onConnected(() => { + console.log('\n✓ Connected to SinricPro!'); + console.log(' You can now control the device via Alexa or Google Home'); + }); + + SinricPro.onDisconnected(() => { + console.log('\n✗ Disconnected from SinricPro'); + }); + + SinricPro.onPong((latency) => { + console.log(`\n♥ Heartbeat (latency: ${latency}ms)`); + }); + + // Initialize SinricPro + await SinricPro.begin({ + appKey: APP_KEY, + appSecret: APP_SECRET, + }); + + // Simulate local button press every 1 minute + setInterval(async () => { + // Generate random temperature (20.0 to 40.0°C) and humidity (10.0% to 90.0%) + const temperature = parseFloat((20 + Math.random() * 20).toFixed(1)); // e.g., 32.7 + const humidity = parseFloat((10 + Math.random() * 80).toFixed(1)); // e.g., 64.3 + + // Optional: Only send if values changed significantly (uncomment if needed) + // const tempChanged = Math.abs(temperature - lastTemperature) >= 0.5; + // const humidityChanged = Math.abs(humidity - lastHumidity) >= 1.0; + // if (!tempChanged && !humidityChanged) return; + + const success = await sensor.sendTemperatureEvent(temperature, humidity); + + if (success) { + console.log(' ✓ Temperature Event sent to SinricPro server'); + } else { + console.log(' ✗ Failed to send event (rate limited or not connected)'); + } + }, 60000); + + console.log('\nWaiting for commands ...'); + console.log('Press Ctrl+C to exit\n'); +} + +// Handle graceful shutdown +process.on('SIGINT', async () => { + console.log('\n\nShutting down...'); + await SinricPro.stop(); + console.log('Goodbye!\n'); + process.exit(0); +}); + +// Handle unhandled errors +process.on('unhandledRejection', (error) => { + console.error('Unhandled error:', error); + process.exit(1); +}); + +// Run the example +main().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/examples/windowac/index.ts b/examples/windowac/index.ts index 53a3eea..e2ad509 100644 --- a/examples/windowac/index.ts +++ b/examples/windowac/index.ts @@ -59,13 +59,13 @@ async function main() { }); // Fan speed control (range 0-100) - myAC.onRangeValue(async (deviceId, speed) => { + myAC.onRangeValue(async (deviceId, speed, instanceId) => { console.log(`\n[Fan Speed] Device ${deviceId} set to ${speed}%`); acState.fanSpeed = speed; return true; }); - myAC.onAdjustRangeValue(async (deviceId, delta) => { + myAC.onAdjustRangeValue(async (deviceId, delta, instanceId) => { console.log(`\n[Fan Speed] Device ${deviceId} adjust by ${delta > 0 ? '+' : ''}${delta}%`); acState.fanSpeed = Math.max(0, Math.min(100, acState.fanSpeed + delta)); console.log(` New fan speed: ${acState.fanSpeed}%`); diff --git a/package.json b/package.json index 8ff439f..d80a7ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sinricpro", - "version": "4.0.0", + "version": "5.1.0", "description": "Official SinricPro SDK for Node.js and TypeScript - Control IoT devices with Alexa and Google Home", "main": "dist/index.js", "exports": { @@ -18,14 +18,15 @@ "lint:fix": "eslint src/**/*.ts --fix", "format": "prettier --write \"src/**/*.ts\" \"examples/**/*.ts\" \"test/**/*.ts\"", "prepublishOnly": "npm run lint && npm run build && npm test", - "example:switch": "ts-node examples/switch/index.ts", - "example:light": "ts-node examples/light/index.ts", - "example:thermostat": "ts-node examples/thermostat/index.ts", - "example:tv": "ts-node examples/tv/index.ts", - "example:fan": "ts-node examples/fan/index.ts", - "example:lock": "ts-node examples/lock/index.ts", - "example:windowac": "ts-node examples/windowac/index.ts", - "example:speaker": "ts-node examples/speaker/index.ts" + "example:switch": "ts-node --project examples/tsconfig.json examples/switch/index.ts", + "example:light": "ts-node --project examples/tsconfig.json examples/light/index.ts", + "example:thermostat": "ts-node --project examples/tsconfig.json examples/thermostat/index.ts", + "example:tv": "ts-node --project examples/tsconfig.json examples/tv/index.ts", + "example:fan": "ts-node --project examples/tsconfig.json examples/fan/index.ts", + "example:lock": "ts-node --project examples/tsconfig.json examples/lock/index.ts", + "example:windowac": "ts-node --project examples/tsconfig.json examples/windowac/index.ts", + "example:speaker": "ts-node --project examples/tsconfig.json examples/speaker/index.ts", + "example:customdevice": "ts-node --project examples/tsconfig.json examples/customdevice/index.ts" }, "keywords": [ "sinricpro", diff --git a/src/capabilities/EqualizerController.ts b/src/capabilities/EqualizerController.ts index 5352654..463ea2b 100644 --- a/src/capabilities/EqualizerController.ts +++ b/src/capabilities/EqualizerController.ts @@ -30,7 +30,7 @@ export interface IEqualizerController { onAdjustBands(callback: AdjustBandsCallback): void; onSetMode(callback: SetModeCallback): void; sendBandsEvent(bands: EqualizerBands, cause?: string): Promise; - sendModeEvent(mode: string, cause?: string): Promise; + sendModeEvent(mode: string, instanceId?: string, cause?: string): Promise; } export function EqualizerController>(Base: T) { @@ -69,12 +69,16 @@ export function EqualizerController>(Base return this.sendEvent('setBands', { bands }, cause); } - async sendModeEvent(mode: string, cause: string = PHYSICAL_INTERACTION): Promise { + async sendModeEvent( + mode: string, + instanceId: string = '', + cause: string = PHYSICAL_INTERACTION + ): Promise { if (this.equalizerEventLimiter.isLimited()) { return false; } - return this.sendEvent('setMode', { mode }, cause); + return this.sendEvent('setMode', { mode }, cause, instanceId); } private async handleEqualizerRequest(request: SinricProRequest): Promise { @@ -124,7 +128,8 @@ export function EqualizerController>(Base if (request.action === 'setMode' && this.setModeCallback) { const mode = request.requestValue.mode; - const result = await this.setModeCallback(this.getDeviceId(), mode); + const instanceId = request.instance || ''; + const result = await this.setModeCallback(this.getDeviceId(), mode, instanceId); // Handle both boolean and object return types let success: boolean; diff --git a/src/capabilities/ModeController.ts b/src/capabilities/ModeController.ts index 0cb89d4..f63dc9e 100644 --- a/src/capabilities/ModeController.ts +++ b/src/capabilities/ModeController.ts @@ -1,5 +1,6 @@ /** * ModeController - Adds generic mode control capability + * Supports optional instanceId for multi-instance mode control */ import { SinricProDevice } from '../core/SinricProDevice'; @@ -12,12 +13,13 @@ type Constructor = new (...args: any[]) => T; export type SetModeCallback = ( deviceId: string, - mode: string + mode: string, + instanceId: string ) => Promise | CallbackResult; export interface IModeController { onSetMode(callback: SetModeCallback): void; - sendModeEvent(mode: string, cause?: string): Promise; + sendModeEvent(mode: string, instanceId?: string, cause?: string): Promise; } export function ModeController>(Base: T) { @@ -35,12 +37,16 @@ export function ModeController>(Base: T) this.setModeCallback = callback; } - async sendModeEvent(mode: string, cause: string = PHYSICAL_INTERACTION): Promise { + async sendModeEvent( + mode: string, + instanceId: string = '', + cause: string = PHYSICAL_INTERACTION + ): Promise { if (this.modeEventLimiter.isLimited()) { return false; } - return this.sendEvent('setMode', { mode }, cause); + return this.sendEvent('setMode', { mode }, cause, instanceId); } private async handleModeRequest(request: SinricProRequest): Promise { @@ -49,7 +55,8 @@ export function ModeController>(Base: T) } const mode = request.requestValue.mode; - const result = await this.setModeCallback(this.getDeviceId(), mode); + const instanceId = request.instance || ''; + const result = await this.setModeCallback(this.getDeviceId(), mode, instanceId); // Handle both boolean and object return types let success: boolean; diff --git a/src/capabilities/RangeController.ts b/src/capabilities/RangeController.ts index 7dd034f..16f24ad 100644 --- a/src/capabilities/RangeController.ts +++ b/src/capabilities/RangeController.ts @@ -1,5 +1,6 @@ /** * RangeController - Adds generic range control capability + * Supports optional instanceId for multi-instance range control */ import { SinricProDevice } from '../core/SinricProDevice'; @@ -12,18 +13,20 @@ type Constructor = new (...args: any[]) => T; export type RangeValueCallback = ( deviceId: string, - rangeValue: number + rangeValue: number, + instanceId: string ) => Promise | boolean; export type AdjustRangeValueCallback = ( deviceId: string, - rangeValueDelta: number + rangeValueDelta: number, + instanceId: string ) => Promise | boolean; export interface IRangeController { onRangeValue(callback: RangeValueCallback): void; onAdjustRangeValue(callback: AdjustRangeValueCallback): void; - sendRangeValueEvent(rangeValue: number, cause?: string): Promise; + sendRangeValueEvent(rangeValue: number, instanceId?: string, cause?: string): Promise; } export function RangeController>(Base: T) { @@ -48,19 +51,21 @@ export function RangeController>(Base: T) async sendRangeValueEvent( rangeValue: number, + instanceId: string = '', cause: string = PHYSICAL_INTERACTION ): Promise { if (this.rangeEventLimiter.isLimited()) { return false; } - return this.sendEvent('setRangeValue', { rangeValue }, cause); + return this.sendEvent('setRangeValue', { rangeValue }, cause, instanceId); } private async handleRangeRequest(request: SinricProRequest): Promise { if (request.action === 'setRangeValue' && this.rangeValueCallback) { const rangeValue = request.requestValue.rangeValue; - const success = await this.rangeValueCallback(this.getDeviceId(), rangeValue); + const instanceId = request.instance || ''; + const success = await this.rangeValueCallback(this.getDeviceId(), rangeValue, instanceId); if (success) { request.responseValue.rangeValue = rangeValue; @@ -71,7 +76,12 @@ export function RangeController>(Base: T) if (request.action === 'adjustRangeValue' && this.adjustRangeValueCallback) { const rangeValueDelta = request.requestValue.rangeValueDelta; - const success = await this.adjustRangeValueCallback(this.getDeviceId(), rangeValueDelta); + const instanceId = request.instance || ''; + const success = await this.adjustRangeValueCallback( + this.getDeviceId(), + rangeValueDelta, + instanceId + ); if (success) { request.responseValue.rangeValue = rangeValueDelta; diff --git a/src/capabilities/SettingController.ts b/src/capabilities/SettingController.ts index 144e126..01bd9bd 100644 --- a/src/capabilities/SettingController.ts +++ b/src/capabilities/SettingController.ts @@ -37,12 +37,12 @@ export function SettingController>(Base: return false; } - const setting = request.requestValue.setting; + const settingId = request.requestValue.id; const value = request.requestValue.value; - const success = await this.settingCallback(this.getDeviceId(), setting, value); + const success = await this.settingCallback(this.getDeviceId(), settingId, value); if (success) { - request.responseValue.setting = setting; + request.responseValue.id = settingId; request.responseValue.value = value; } diff --git a/src/core/SinricPro.ts b/src/core/SinricPro.ts index bf4ed2f..f2ad18b 100644 --- a/src/core/SinricPro.ts +++ b/src/core/SinricPro.ts @@ -16,6 +16,7 @@ import type { ConnectedCallback, DisconnectedCallback, PongCallback, + ModuleSettingCallback, } from './types'; import { SINRICPRO_SERVER_URL } from './types'; @@ -35,6 +36,7 @@ export class SinricPro extends EventEmitter implements ISinricPro { private signature!: Signature; private isInitialized: boolean = false; private processingInterval: NodeJS.Timeout | null = null; + private moduleSettingCallback: ModuleSettingCallback | null = null; private constructor() { super(); @@ -192,6 +194,28 @@ export class SinricPro extends EventEmitter implements ISinricPro { this.on('pong', callback); } + /** + * Register a callback for module-level setting changes + * + * Module settings are configuration values for the module (dev board) itself, + * not for individual devices. Use this to handle settings like WiFi retry count, + * logging level, or other module-wide configurations. + * + * @param callback - Function that receives settingId and value, returns boolean or Promise + * @example + * ```typescript + * SinricPro.onSetSetting(async (settingId, value) => { + * if (settingId === 'wifi_retry_count') { + * setWifiRetryCount(value); + * } + * return true; + * }); + * ``` + */ + onSetSetting(callback: ModuleSettingCallback): void { + this.moduleSettingCallback = callback; + } + /** * Stop the SinricPro SDK and disconnect from the server * @example @@ -293,7 +317,13 @@ export class SinricPro extends EventEmitter implements ISinricPro { // Route message if (message.payload.type === ('request' as MessageType)) { - await this.handleRequest(message); + // Check scope to determine if this is a module or device request + const scope = message.payload.scope || 'device'; + if (scope === 'module') { + await this.handleModuleRequest(message); + } else { + await this.handleRequest(message); + } } else if (message.payload.type === ('response' as MessageType)) { this.emit('response', message); } @@ -344,6 +374,63 @@ export class SinricPro extends EventEmitter implements ISinricPro { this.sendResponse(message, success, request.responseValue, request.errorMessage); } + private async handleModuleRequest(message: SinricProMessage): Promise { + const action = message.payload.action; + const requestValue = message.payload.value || {}; + + if (action === 'setSetting') { + if (!this.moduleSettingCallback) { + SinricProSdkLogger.error('No module setting callback registered'); + this.sendModuleResponse(message, false, {}, 'No module setting callback registered'); + return; + } + + const settingId = requestValue.id || ''; + const value = requestValue.value; + + try { + const success = await this.moduleSettingCallback(settingId, value); + const responseValue = success ? { id: settingId, value } : {}; + this.sendModuleResponse(message, success, responseValue); + } catch (error) { + SinricProSdkLogger.error('Error in module setting callback:', error); + this.sendModuleResponse(message, false, {}, String(error)); + } + } else { + SinricProSdkLogger.error(`Unknown module action: ${action}`); + this.sendModuleResponse(message, false, {}, `Unknown module action: ${action}`); + } + } + + private sendModuleResponse( + requestMessage: SinricProMessage, + success: boolean, + value: Record, + errorMessage?: string + ): void { + // Module response does NOT include deviceId + const responseMessage: SinricProMessage = { + header: { + payloadVersion: 2, + signatureVersion: 1, + }, + payload: { + action: requestMessage.payload.action, + clientId: requestMessage.payload.clientId, + createdAt: Math.floor(new Date().getTime() / 1000), + message: errorMessage || (success ? 'OK' : 'Request failed'), + replyToken: requestMessage.payload.replyToken, + scope: 'module', + success, + type: 'response' as MessageType, + value, + }, + }; + + this.signature.sign(responseMessage); + this.sendQueue.push(JSON.stringify(responseMessage)); + } + private sendResponse( requestMessage: SinricProMessage, success: boolean, @@ -362,6 +449,7 @@ export class SinricPro extends EventEmitter implements ISinricPro { deviceId: requestMessage.payload.deviceId, message: errorMessage || (success ? 'OK' : 'Request failed'), replyToken: requestMessage.payload.replyToken, + scope: 'device', success, type: 'response' as MessageType, value, diff --git a/src/core/SinricProDevice.ts b/src/core/SinricProDevice.ts index 488b9d9..e6eb251 100644 --- a/src/core/SinricProDevice.ts +++ b/src/core/SinricProDevice.ts @@ -63,27 +63,36 @@ export abstract class SinricProDevice { action: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any value: Record, - cause: string = 'PHYSICAL_INTERACTION' + cause: string = 'PHYSICAL_INTERACTION', + instanceId: string = '' ): Promise { if (!this.sinricPro) { SinricProSdkLogger.error('Cannot send event: Device not registered with SinricPro'); return false; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: Record = { + action, + deviceId: this.deviceId, + replyToken: this.generateMessageId(), + type: 'event' as MessageType, + createdAt: Math.floor(new Date().getTime() / 1000), + cause: { type: cause }, + value, + }; + + // Include instanceId if provided + if (instanceId) { + payload.instanceId = instanceId; + } + const eventMessage = { header: { payloadVersion: 2, signatureVersion: 1, }, - payload: { - action, - deviceId: this.deviceId, - replyToken: this.generateMessageId(), - type: 'event' as MessageType, - createdAt: Math.floor(new Date().getTime() / 1000), - cause: { type: cause }, - value, - }, + payload, }; try { diff --git a/src/core/WebSocketClient.ts b/src/core/WebSocketClient.ts index 7e886aa..c179465 100644 --- a/src/core/WebSocketClient.ts +++ b/src/core/WebSocketClient.ts @@ -4,6 +4,7 @@ import WebSocket from 'ws'; import { EventEmitter } from 'events'; +import os from 'os'; import { SinricProSdkLogger } from '../utils/SinricProSdkLogger'; import { SINRICPRO_SERVER_SSL_PORT, @@ -12,6 +13,29 @@ import { } from './types'; import { version } from '../../package.json'; +/** + * Get the MAC address of this machine. + * Returns the MAC address of the first non-internal network interface. + * @returns MAC address string in format XX:XX:XX:XX:XX:XX + */ +function getMacAddress(): string { + const interfaces = os.networkInterfaces(); + + for (const name of Object.keys(interfaces)) { + const netInterfaces = interfaces[name]; + if (!netInterfaces) continue; + + for (const net of netInterfaces) { + // Skip internal and non-MAC interfaces + if (!net.internal && net.mac && net.mac !== '00:00:00:00:00:00') { + return net.mac.toUpperCase(); + } + } + } + + return '00:00:00:00:00:00'; +} + export interface WebSocketConfig { serverUrl: string; appKey: string; @@ -54,6 +78,7 @@ export class WebSocketClient extends EventEmitter { deviceids: this.config.deviceIds.join(';'), platform: this.config.platform || 'NodeJS', SDKVersion: this.config.sdkVersion || version, + mac: getMacAddress(), }; SinricProSdkLogger.info(`Connecting to ${url}`); diff --git a/src/core/types.ts b/src/core/types.ts index 10067b0..9f75344 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -72,6 +72,13 @@ export type ConnectedCallback = () => void; export type DisconnectedCallback = () => void; export type PongCallback = (latency: number) => void; +// Module-level setting callback: (settingId, value) -> boolean +export type ModuleSettingCallback = ( + settingId: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any +) => Promise | boolean; + // Callback result type - supports simple boolean or detailed error response export type CallbackResult = boolean | { success: boolean; message?: string }; diff --git a/src/devices/SinricProCustomDevice.ts b/src/devices/SinricProCustomDevice.ts new file mode 100644 index 0000000..e51e4c1 --- /dev/null +++ b/src/devices/SinricProCustomDevice.ts @@ -0,0 +1,118 @@ +/** + * SinricProCustomDevice - Flexible device with multiple capabilities + * + * A custom device type that supports many SinricPro capabilities, allowing you to + * build devices with any combination of features you need. + */ + +import { SinricProDevice } from '../core/SinricProDevice'; +import { PowerStateController, IPowerStateController } from '../capabilities/PowerStateController'; +import { BrightnessController, IBrightnessController } from '../capabilities/BrightnessController'; +import { ColorController, IColorController } from '../capabilities/ColorController'; +import { + ColorTemperatureController, + IColorTemperatureController, +} from '../capabilities/ColorTemperatureController'; +import { RangeController, IRangeController } from '../capabilities/RangeController'; +import { ModeController, IModeController } from '../capabilities/ModeController'; +import { LockController, ILockController } from '../capabilities/LockController'; +import { ThermostatController, IThermostatController } from '../capabilities/ThermostatController'; +import { TemperatureSensor, ITemperatureSensor } from '../capabilities/TemperatureSensor'; +import { SettingController, ISettingController } from '../capabilities/SettingController'; +import { PushNotification, IPushNotification } from '../capabilities/PushNotification'; +import { PercentageController, IPercentageController } from '../capabilities/PercentageController'; +import { PowerLevelController, IPowerLevelController } from '../capabilities/PowerLevelController'; + +// Apply mixins in order - combining many capabilities +// eslint-disable-next-line @typescript-eslint/no-explicit-any +class SinricProCustomDeviceClass extends SettingController( + PushNotification( + TemperatureSensor( + ThermostatController( + LockController( + ModeController( + RangeController( + PercentageController( + PowerLevelController( + ColorTemperatureController( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ColorController( + BrightnessController(PowerStateController(SinricProDevice as any)) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) +) { + constructor(deviceId: string, productType: string = 'CUSTOM_DEVICE') { + super(deviceId, productType); + } +} + +/** + * Create a SinricProCustomDevice + * + * A flexible device that supports many capabilities. Register callbacks only for + * the capabilities you need. + * + * @param deviceId - Device ID from SinricPro portal + * @param productType - Optional product type (default: 'CUSTOM_DEVICE') + * @returns SinricProCustomDevice instance + * + * @example + * ```typescript + * const device = SinricProCustomDevice('device-id'); + * + * // Register only the callbacks you need + * device.onPowerState(async (deviceId, state) => { + * console.log(`Power: ${state}`); + * return true; + * }); + * + * device.onBrightness(async (deviceId, brightness) => { + * console.log(`Brightness: ${brightness}`); + * return true; + * }); + * ``` + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function SinricProCustomDevice( + deviceId: string, + productType: string = 'CUSTOM_DEVICE' +): SinricProDevice & + IPowerStateController & + IBrightnessController & + IColorController & + IColorTemperatureController & + IPowerLevelController & + IPercentageController & + IRangeController & + IModeController & + ILockController & + IThermostatController & + ITemperatureSensor & + ISettingController & + IPushNotification { + return new SinricProCustomDeviceClass(deviceId, productType) as any; +} + +// Export type for TypeScript +export type SinricProCustomDevice = SinricProDevice & + IPowerStateController & + IBrightnessController & + IColorController & + IColorTemperatureController & + IPowerLevelController & + IPercentageController & + IRangeController & + IModeController & + ILockController & + IThermostatController & + ITemperatureSensor & + ISettingController & + IPushNotification; diff --git a/src/devices/SinricProSwitch.ts b/src/devices/SinricProSwitch.ts index 70b8551..78c56fe 100644 --- a/src/devices/SinricProSwitch.ts +++ b/src/devices/SinricProSwitch.ts @@ -4,9 +4,13 @@ import { SinricProDevice } from '../core/SinricProDevice'; import { PowerStateController, IPowerStateController } from '../capabilities/PowerStateController'; +import { SettingController, ISettingController } from '../capabilities/SettingController'; +import { PushNotification, IPushNotification } from '../capabilities/PushNotification'; // eslint-disable-next-line @typescript-eslint/no-explicit-any -class SinricProSwitchClass extends PowerStateController(SinricProDevice as any) { +class SinricProSwitchClass extends SettingController( + PushNotification(PowerStateController(SinricProDevice as any)) +) { constructor(deviceId: string) { super(deviceId, 'SWITCH'); } @@ -18,9 +22,14 @@ class SinricProSwitchClass extends PowerStateController(SinricProDevice as any) * @returns SinricProSwitch instance */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function SinricProSwitch(deviceId: string): SinricProDevice & IPowerStateController { +export function SinricProSwitch( + deviceId: string +): SinricProDevice & IPowerStateController & IPushNotification & ISettingController { return new SinricProSwitchClass(deviceId) as any; } // Export type for TypeScript -export type SinricProSwitch = SinricProDevice & IPowerStateController; +export type SinricProSwitch = SinricProDevice & + IPowerStateController & + IPushNotification & + ISettingController; diff --git a/src/devices/SinricProTemperatureSensor.ts b/src/devices/SinricProTemperatureSensor.ts new file mode 100644 index 0000000..e48f43d --- /dev/null +++ b/src/devices/SinricProTemperatureSensor.ts @@ -0,0 +1,36 @@ +/** + * SinricProTemperatureSensor - Temperature and humidity sensor device + */ + +import { SinricProDevice } from '../core/SinricProDevice'; +import { TemperatureSensor, ITemperatureSensor } from '../capabilities/TemperatureSensor'; +import { SettingController, ISettingController } from '../capabilities/SettingController'; +import { PushNotification, IPushNotification } from '../capabilities/PushNotification'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +class SinricProTemperatureSensorClass extends SettingController( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + PushNotification(TemperatureSensor(SinricProDevice as any)) +) { + constructor(deviceId: string) { + super(deviceId, 'TEMPERATURE_SENSOR'); + } +} + +/** + * Create a SinricProTemperatureSensor device + * @param deviceId - Device ID from SinricPro portal + * @returns SinricProTemperatureSensor instance + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function SinricProTemperatureSensor( + deviceId: string +): SinricProDevice & ITemperatureSensor & IPushNotification & ISettingController { + return new SinricProTemperatureSensorClass(deviceId) as any; +} + +// Export type for TypeScript +export type SinricProTemperatureSensor = SinricProDevice & + ITemperatureSensor & + IPushNotification & + ISettingController; diff --git a/src/devices/index.ts b/src/devices/index.ts index 2f38eff..40e3a0b 100644 --- a/src/devices/index.ts +++ b/src/devices/index.ts @@ -26,8 +26,12 @@ export * from './SinricProDoorbell'; // Sensors export * from './SinricProMotionSensor'; export * from './SinricProContactSensor'; +export * from './SinricProTemperatureSensor'; export * from './SinricProAirQualitySensor'; export * from './SinricProPowerSensor'; // Window treatments export * from './SinricProBlinds'; + +// Custom/Flexible +export * from './SinricProCustomDevice';