2021-02-01 21:22:53 +01:00
const axios = require ( 'axios' ) . default ;
2021-02-08 22:23:54 +01:00
const fs = require ( 'fs' ) ;
2021-02-01 21:22:53 +01:00
const Configuration = require ( './Configuration' ) ;
2021-01-21 21:31:37 +01:00
const Logger = require ( './Logger' ) ;
2019-12-04 11:17:06 +01:00
2021-01-22 15:49:02 +01:00
const API _APP _ID = 'zHtVqXt8k4yFyk2QGmgp48D9xZr2G94xWYnF4dak' ;
const API _BASE _URL = 'https://cloud.plejd.com/parse/' ;
const API _LOGIN _URL = 'login' ;
const API _SITE _LIST _URL = 'functions/getSiteList' ;
const API _SITE _DETAILS _URL = 'functions/getSiteById' ;
2019-12-04 11:17:06 +01:00
2021-03-29 12:51:48 +02:00
const TRAITS = {
NO _LOAD : 0 ,
NON _DIMMABLE : 9 ,
DIMMABLE : 11 ,
} ;
2021-01-22 15:49:02 +01:00
const logger = Logger . getLogger ( 'plejd-api' ) ;
2019-12-04 11:17:06 +01:00
2021-02-01 21:22:53 +01:00
class PlejdApi {
2021-03-29 12:51:48 +02:00
/** @private @type {import('types/Configuration').Options} */
2021-02-01 21:22:53 +01:00
config ;
2021-03-29 12:51:48 +02:00
/** @private @type {import('DeviceRegistry')} */
2021-02-01 21:22:53 +01:00
deviceRegistry ;
2021-03-29 12:51:48 +02:00
/** @private @type {string} */
2021-02-01 21:22:53 +01:00
sessionToken ;
2021-03-29 12:51:48 +02:00
/** @private @type {string} */
2021-02-01 21:22:53 +01:00
siteId ;
2021-03-29 12:51:48 +02:00
/** @private @type {import('types/ApiSite').ApiSite} */
2021-02-01 21:22:53 +01:00
siteDetails ;
2019-12-04 11:17:06 +01:00
2021-03-29 12:51:48 +02:00
/ * *
* @ param { import ( "./DeviceRegistry" ) } deviceRegistry
* /
2021-02-01 21:22:53 +01:00
constructor ( deviceRegistry ) {
this . config = Configuration . getOptions ( ) ;
this . deviceRegistry = deviceRegistry ;
}
2019-12-04 11:17:06 +01:00
2021-02-01 21:22:53 +01:00
async init ( ) {
logger . info ( 'init()' ) ;
2021-02-08 22:23:54 +01:00
const cache = await this . getCachedCopy ( ) ;
const cacheExists = cache && cache . siteId && cache . siteDetails && cache . sessionToken ;
logger . debug ( ` Prefer cache? ${ this . config . preferCachedApiResponse } ` ) ;
logger . debug ( ` Cache exists? ${ cacheExists ? ` Yes, created ${ cache . dtCache } ` : 'No' } ` ) ;
if ( this . config . preferCachedApiResponse && cacheExists ) {
logger . info (
` Cache preferred. Skipping api requests and setting api data to response from ${ cache . dtCache } ` ,
) ;
logger . silly ( ` Cached response: ${ JSON . stringify ( cache , null , 2 ) } ` ) ;
this . siteId = cache . siteId ;
this . siteDetails = cache . siteDetails ;
this . sessionToken = cache . sessionToken ;
} else {
try {
await this . login ( ) ;
await this . getSites ( ) ;
await this . getSiteDetails ( ) ;
this . saveCachedCopy ( ) ;
} catch ( err ) {
if ( cacheExists ) {
logger . warn ( 'Failed to get api response, using cached copy instead' ) ;
this . siteId = cache . siteId ;
this . siteDetails = cache . siteDetails ;
this . sessionToken = cache . sessionToken ;
} else {
logger . error ( 'Api request failed, no cached fallback available' , err ) ;
throw err ;
}
}
}
2021-03-31 20:04:45 +02:00
this . deviceRegistry . setApiSite ( this . siteDetails ) ;
2021-02-01 21:22:53 +01:00
this . getDevices ( ) ;
2019-12-04 11:17:06 +01:00
}
2021-03-29 12:51:48 +02:00
/** @returns {Promise<import('types/ApiSite').CachedSite>} */
2021-02-08 22:23:54 +01:00
// eslint-disable-next-line class-methods-use-this
async getCachedCopy ( ) {
logger . info ( 'Getting cached api response from disk' ) ;
try {
const rawData = await fs . promises . readFile ( '/data/cachedApiResponse.json' ) ;
2021-03-29 12:51:48 +02:00
const cachedCopy = JSON . parse ( rawData . toString ( ) ) ;
2021-02-08 22:23:54 +01:00
return cachedCopy ;
} catch ( err ) {
logger . warn ( 'No cached api response could be read. This is normal on the first run' , err ) ;
return null ;
}
}
async saveCachedCopy ( ) {
logger . info ( 'Saving cached copy' ) ;
try {
2021-03-29 12:51:48 +02:00
/** @type {import('types/ApiSite').CachedSite} */
const cachedSite = {
2021-02-08 22:23:54 +01:00
siteId : this . siteId ,
siteDetails : this . siteDetails ,
sessionToken : this . sessionToken ,
dtCache : new Date ( ) . toISOString ( ) ,
2021-03-29 12:51:48 +02:00
} ;
const rawData = JSON . stringify ( cachedSite ) ;
2021-02-08 22:23:54 +01:00
await fs . promises . writeFile ( '/data/cachedApiResponse.json' , rawData ) ;
} catch ( err ) {
logger . error ( 'Failed to save cache of api response' , err ) ;
}
}
2021-02-01 21:22:53 +01:00
async login ( ) {
2021-01-21 21:31:37 +01:00
logger . info ( 'login()' ) ;
2021-02-01 21:22:53 +01:00
logger . info ( ` logging into ${ this . config . site } ` ) ;
2019-12-04 11:17:06 +01:00
2021-02-01 21:22:53 +01:00
logger . debug ( ` sending POST to ${ API _BASE _URL } ${ API _LOGIN _URL } ` ) ;
2019-12-04 11:17:06 +01:00
2021-02-01 21:22:53 +01:00
try {
const response = await this . _getAxiosInstance ( ) . post ( API _LOGIN _URL , {
username : this . config . username ,
password : this . config . password ,
} ) ;
logger . info ( 'got session token response' ) ;
this . sessionToken = response . data . sessionToken ;
if ( ! this . sessionToken ) {
logger . error ( 'No session token received' ) ;
throw new Error ( 'API: No session token received.' ) ;
}
} catch ( error ) {
if ( error . response . status === 400 ) {
logger . error ( 'Server returned status 400. probably invalid credentials, please verify.' ) ;
} else if ( error . response . status === 403 ) {
logger . error (
2021-02-10 10:23:27 +01:00
'Server returned status 403, forbidden. Plejd service does this sometimes, despite correct credentials. Possibly throttling logins. Waiting a long time often fixes this.' ,
2021-02-01 21:22:53 +01:00
) ;
} else {
logger . error ( 'Unable to retrieve session token response: ' , error ) ;
}
logger . verbose ( ` Error details: ${ JSON . stringify ( error . response , null , 2 ) } ` ) ;
throw new Error ( ` API: Unable to retrieve session token response: ${ error } ` ) ;
}
2019-12-04 11:17:06 +01:00
}
2021-02-01 21:22:53 +01:00
async getSites ( ) {
2021-01-21 21:31:37 +01:00
logger . info ( 'Get all Plejd sites for account...' ) ;
2019-12-04 11:17:06 +01:00
2021-02-01 21:22:53 +01:00
logger . debug ( ` sending POST to ${ API _BASE _URL } ${ API _SITE _LIST _URL } ` ) ;
2019-12-04 11:17:06 +01:00
2021-02-01 21:22:53 +01:00
try {
const response = await this . _getAxiosInstance ( ) . post ( API _SITE _LIST _URL ) ;
const sites = response . data . result ;
logger . info (
` Got site list response with ${ sites . length } : ${ sites . map ( ( s ) => s . site . title ) . join ( ', ' ) } ` ,
) ;
logger . silly ( 'All sites found:' ) ;
logger . silly ( JSON . stringify ( sites , null , 2 ) ) ;
const site = sites . find ( ( x ) => x . site . title === this . config . site ) ;
if ( ! site ) {
logger . error ( ` Failed to find a site named ${ this . config . site } ` ) ;
throw new Error ( ` API: Failed to find a site named ${ this . config . site } ` ) ;
}
logger . info ( ` Site found matching configuration name ${ this . config . site } ` ) ;
logger . silly ( JSON . stringify ( site , null , 2 ) ) ;
this . siteId = site . site . siteId ;
} catch ( error ) {
logger . error ( 'error: unable to retrieve list of sites. error: ' , error ) ;
throw new Error ( ` API: unable to retrieve list of sites. error: ${ error } ` ) ;
}
2020-03-13 11:58:15 +01:00
}
2019-12-14 14:10:10 +01:00
2021-02-01 21:22:53 +01:00
async getSiteDetails ( ) {
logger . info ( ` Get site details for ${ this . siteId } ... ` ) ;
2020-03-13 11:58:15 +01:00
2021-02-01 21:22:53 +01:00
logger . debug ( ` sending POST to ${ API _BASE _URL } ${ API _SITE _DETAILS _URL } ` ) ;
2019-12-04 11:17:06 +01:00
2021-02-01 21:22:53 +01:00
try {
const response = await this . _getAxiosInstance ( ) . post ( API _SITE _DETAILS _URL , {
siteId : this . siteId ,
} ) ;
logger . info ( 'got site details response' ) ;
if ( response . data . result . length === 0 ) {
logger . error ( ` No site with ID ${ this . siteId } was found. ` ) ;
throw new Error ( ` API: No site with ID ${ this . siteId } was found. ` ) ;
}
this . siteDetails = response . data . result [ 0 ] ;
logger . info ( ` Site details for site id ${ this . siteId } found ` ) ;
logger . silly ( JSON . stringify ( this . siteDetails , null , 2 ) ) ;
2021-02-08 22:23:54 +01:00
if ( ! this . siteDetails . plejdMesh . cryptoKey ) {
2021-02-01 21:22:53 +01:00
throw new Error ( 'API: No crypto key set for site' ) ;
}
} catch ( error ) {
logger . error ( ` Unable to retrieve site details for ${ this . siteId } . error: ` , error ) ;
throw new Error ( ` API: Unable to retrieve site details. error: ${ error } ` ) ;
}
2019-12-04 11:17:06 +01:00
}
getDevices ( ) {
2021-02-01 21:22:53 +01:00
logger . info ( 'Getting devices from site details response...' ) ;
2019-12-04 11:17:06 +01:00
2021-03-29 12:51:48 +02:00
if ( this . siteDetails . gateways && this . siteDetails . gateways . length ) {
this . siteDetails . gateways . forEach ( ( gwy ) => {
logger . info ( ` Plejd gateway ' ${ gwy . title } ' found on site ` ) ;
} ) ;
} else {
logger . info ( 'No Plejd gateway found on site' ) ;
}
2021-02-01 21:22:53 +01:00
this . _getPlejdDevices ( ) ;
this . _getRoomDevices ( ) ;
this . _getSceneDevices ( ) ;
}
_getAxiosInstance ( ) {
const headers = {
'X-Parse-Application-Id' : API _APP _ID ,
'Content-Type' : 'application/json' ,
} ;
if ( this . sessionToken ) {
headers [ 'X-Parse-Session-Token' ] = this . sessionToken ;
}
2019-12-10 19:15:38 +01:00
2021-02-01 21:22:53 +01:00
return axios . create ( {
baseURL : API _BASE _URL ,
headers ,
} ) ;
}
2019-12-13 17:59:48 +01:00
2021-02-01 21:22:53 +01:00
// eslint-disable-next-line class-methods-use-this
2021-03-29 12:51:48 +02:00
_getDeviceType ( plejdDevice ) {
// Type name is also sometimes available in device.hardware.name
// (maybe only when GWY-01 is present?)
switch ( parseInt ( plejdDevice . hardwareId , 10 ) ) {
2021-02-01 21:22:53 +01:00
case 1 :
2021-05-06 13:28:13 +02:00
return {
name : 'DIM-01' ,
2022-10-16 14:35:43 +02:00
description : '1-channel dimmer LED, 300 VA' ,
2021-05-06 13:28:13 +02:00
type : 'light' ,
dimmable : true ,
broadcastClicks : false ,
} ;
2021-02-01 21:22:53 +01:00
case 2 :
2021-05-06 13:28:13 +02:00
return {
name : 'DIM-02' ,
2022-10-16 14:35:43 +02:00
description : '2-channel dimmer LED, 2*100 VA' ,
2021-05-06 13:28:13 +02:00
type : 'light' ,
dimmable : true ,
broadcastClicks : false ,
} ;
2021-02-01 21:22:53 +01:00
case 3 :
2021-05-06 13:28:13 +02:00
return {
name : 'CTR-01' ,
2022-10-16 14:35:43 +02:00
description : '1-channel relay with 0-10V output, 3500 VA' ,
2021-05-06 13:28:13 +02:00
type : 'light' ,
dimmable : false ,
broadcastClicks : false ,
} ;
2022-10-16 14:35:43 +02:00
// Gateway doesn't show up in devices list in API response
// case 4:
// return {
// name: 'GWY-01',
// description: 'Gateway to enable control via internet and integrations',
// type: 'sensor',
// dimmable: false,
// broadcastClicks: false,
// };
2021-02-01 21:22:53 +01:00
case 5 :
2021-05-06 13:28:13 +02:00
return {
name : 'LED-10' ,
2022-10-16 14:35:43 +02:00
description : '1-channel LED dimmer/driver, 10 W' ,
2021-05-06 13:28:13 +02:00
type : 'light' ,
dimmable : true ,
broadcastClicks : false ,
} ;
2021-02-01 21:22:53 +01:00
case 6 :
2021-05-06 13:28:13 +02:00
return {
name : 'WPH-01' ,
2023-08-16 15:32:53 +02:00
description :
'Wireless push button, 4 buttons. 2 channels, on and off buttons for each channel' ,
2021-05-06 13:28:13 +02:00
type : 'device_automation' ,
dimmable : false ,
broadcastClicks : true ,
} ;
2021-02-01 21:22:53 +01:00
case 7 :
2022-10-16 14:35:43 +02:00
// Unknown, pre-release (?) version, kept for backwards compatibility. See https://github.com/icanos/hassio-plejd/issues/250
2021-05-06 13:28:13 +02:00
return {
name : 'REL-01' ,
2022-10-16 14:35:43 +02:00
description : '1 channel relay, 3500 VA' ,
2021-05-06 13:28:13 +02:00
type : 'switch' ,
dimmable : false ,
broadcastClicks : false ,
} ;
2021-02-01 21:22:53 +01:00
case 8 :
2021-05-06 13:28:13 +02:00
return {
2022-10-16 14:35:43 +02:00
name : 'SPR-01' ,
description : 'Smart plug on/off with relay, 3500 VA' ,
type : 'switch' ,
2021-05-06 13:28:13 +02:00
dimmable : false ,
broadcastClicks : false ,
} ;
2021-02-01 21:22:53 +01:00
case 10 :
2021-05-06 13:28:13 +02:00
return {
name : 'WRT-01' ,
2022-10-16 14:35:43 +02:00
description : 'Wireless rotary button' ,
2021-05-06 13:28:13 +02:00
type : 'device_automation' ,
dimmable : false ,
broadcastClicks : true ,
} ;
2022-10-16 14:35:43 +02:00
case 11 :
2021-05-06 13:28:13 +02:00
return {
2022-10-16 14:35:43 +02:00
name : 'DIM-01-2P' ,
description : '1-channel dimmer LED with 2-pole breaking, 300 VA' ,
2021-05-06 13:28:13 +02:00
type : 'light' ,
2022-10-16 14:35:43 +02:00
dimmable : true ,
2021-05-06 13:28:13 +02:00
broadcastClicks : false ,
} ;
2023-08-25 15:57:00 +02:00
case 12 :
return {
name : 'DAL-01' ,
description : 'Dali broadcast with dimmer and tuneable white support' ,
type : 'light' ,
dimmable : true ,
broadcastClicks : false ,
} ;
2022-10-16 14:35:43 +02:00
case 14 :
2021-05-06 13:28:13 +02:00
return {
2022-10-16 14:35:43 +02:00
name : 'DIM-01' ,
description : '1-channel dimmer LED, 300 VA ("LC" hardware/chip version)' ,
2021-05-06 13:28:13 +02:00
type : 'light' ,
2022-10-16 14:35:43 +02:00
dimmable : true ,
2021-05-06 13:28:13 +02:00
broadcastClicks : false ,
} ;
2021-02-01 21:22:53 +01:00
case 15 :
2021-05-06 13:28:13 +02:00
return {
2022-10-16 14:35:43 +02:00
name : 'DIM-02' ,
description : '2-channel dimmer LED, 2*100 VA ("LC" hardware/chip version)' ,
2021-05-06 13:28:13 +02:00
type : 'light' ,
2022-10-16 14:35:43 +02:00
dimmable : true ,
2021-05-06 13:28:13 +02:00
broadcastClicks : false ,
} ;
2021-02-01 21:22:53 +01:00
case 17 :
2021-05-06 13:28:13 +02:00
return {
2022-10-16 14:35:43 +02:00
name : 'REL-01-2P' ,
description : '1-channel relay with 2-pole 3500 VA' ,
2021-05-06 13:28:13 +02:00
type : 'switch' ,
dimmable : false ,
broadcastClicks : false ,
} ;
2021-02-01 21:22:53 +01:00
case 18 :
2021-05-06 13:28:13 +02:00
return {
name : 'REL-02' ,
2022-10-16 14:35:43 +02:00
description : '2-channel relay with combined 3500 VA' ,
2021-05-06 13:28:13 +02:00
type : 'switch' ,
dimmable : false ,
broadcastClicks : false ,
} ;
2022-10-16 14:35:43 +02:00
case 20 :
2021-05-06 13:28:13 +02:00
return {
2022-10-16 14:35:43 +02:00
// Unknown, pre-release (?) version, kept for backwards compatibility. See https://github.com/icanos/hassio-plejd/issues/250
name : 'SPR-01' ,
description : 'Smart plug on/off with relay, 3500 VA' ,
type : 'device_automation' ,
2021-05-06 13:28:13 +02:00
dimmable : false ,
broadcastClicks : false ,
} ;
2025-08-04 16:38:36 +02:00
case 22 :
return {
name : 'DIM-01' ,
2025-08-05 09:43:37 +02:00
description : '1-channel dimmer LED, 300 VA ("LC2" hardware/chip version 2024 Q2)' ,
2025-08-04 16:38:36 +02:00
type : 'light' ,
dimmable : true ,
broadcastClicks : false ,
} ;
2022-10-16 14:35:43 +02:00
case 36 :
2021-05-06 13:28:13 +02:00
return {
2022-10-16 14:35:43 +02:00
name : 'LED-75' ,
description : '1-channel LED dimmer/driver with tuneable white, 10 W' ,
type : 'light' ,
dimmable : true ,
2021-05-06 13:28:13 +02:00
broadcastClicks : false ,
} ;
2023-10-02 08:58:11 +02:00
case 167 :
return {
name : 'DWN-01' ,
description : 'Smart tunable downlight with a built-in dimmer function, 8W' ,
type : 'light' ,
dimmable : true ,
broadcastClicks : false ,
} ;
2025-08-05 10:35:42 +02:00
case 199 :
return {
name : 'DWN-02' ,
description : 'Smart tunable downlight with a built-in dimmer function, 8W' ,
type : 'light' ,
dimmable : true ,
broadcastClicks : false ,
} ;
2023-10-02 08:58:11 +02:00
// PLEASE CREATE AN ISSUE WITH THE HARDWARE ID if you own one of these devices!
2023-10-06 14:11:23 +02:00
// case
2023-10-02 08:58:11 +02:00
// return {
// name: 'OUT-01',
// description: 'Outdoor wall light with built-in LED, 2x5W',
// type: 'light',
// dimmable: true,
// broadcastClicks: false,
// };
2023-08-16 15:32:53 +02:00
default :
2023-10-06 14:11:23 +02:00
throw new Error (
` Unknown device type with hardware id ${ plejdDevice . hardwareId } . --- PLEASE POST THIS AND THE NEXT LOG ROWS to https://github.com/icanos/hassio-plejd/issues/ --- ` ,
) ;
2021-02-01 21:22:53 +01:00
}
}
2021-03-29 12:51:48 +02:00
/ * *
* Plejd API properties parsed
*
* * ` devices ` - physical Plejd devices , duplicated for devices with multiple outputs
* devices : [ { deviceId , title , objectId , ... } , { ... } ]
* * ` deviceAddress ` - BLE address of each physical device
2021-03-31 20:04:45 +02:00
* deviceAddress : { [ deviceId ] : bleDeviceAddress }
2021-03-29 12:51:48 +02:00
* * ` outputSettings ` - lots of info about load settings , also links devices to output index
* outputSettings : [ { deviceId , output , deviceParseId , ... } ] //deviceParseId === objectId above
* * ` outputAddress ` : BLE address of [ 0 ] main output and [ n ] other output ( loads )
2021-03-31 20:04:45 +02:00
* outputAddress : { [ deviceId ] : { [ output ] : bleDeviceAddress } }
2021-03-29 12:51:48 +02:00
* * ` inputSettings ` - detailed settings for inputs ( buttons , RTR - 01 , ... ) , scenes triggered , ...
* inputSettings : [ { deviceId , input , ... } ] //deviceParseId === objectId above
* * ` inputAddress ` - Links inputs to what BLE device they control , or 255 for unassigned / scene
2021-03-31 20:04:45 +02:00
* inputAddress : { [ deviceId ] : { [ input ] : bleDeviceAddress } }
2021-03-29 12:51:48 +02:00
* /
2021-02-01 21:22:53 +01:00
_getPlejdDevices ( ) {
this . deviceRegistry . clearPlejdDevices ( ) ;
this . siteDetails . devices . forEach ( ( device ) => {
2021-03-31 20:04:45 +02:00
this . deviceRegistry . addPhysicalDevice ( device ) ;
2021-03-29 12:51:48 +02:00
const outputSettings = this . siteDetails . outputSettings . find (
2021-02-01 21:22:53 +01:00
( x ) => x . deviceParseId === device . objectId ,
) ;
2021-03-31 23:28:25 +02:00
if ( ! outputSettings ) {
logger . verbose (
` No outputSettings found for ${ device . title } ( ${ device . deviceId } ), assuming output 0 ` ,
) ;
}
2021-04-01 13:19:02 +02:00
const deviceOutput = outputSettings ? outputSettings . output : 0 ;
2021-04-28 20:07:53 +02:00
const outputAddress = this . siteDetails . outputAddress [ device . deviceId ] ;
2021-03-31 23:28:25 +02:00
2021-04-28 20:07:53 +02:00
if ( outputAddress ) {
const bleOutputAddress = outputAddress [ deviceOutput ] ;
2019-12-10 22:01:12 +01:00
2021-04-28 20:07:53 +02:00
if ( device . traits === TRAITS . NO _LOAD ) {
logger . warn (
` Device ${ device . title } ( ${ device . deviceId } ) has no load configured and will be excluded ` ,
) ;
} else {
const uniqueOutputId = this . deviceRegistry . getUniqueOutputId (
device . deviceId ,
deviceOutput ,
) ;
const plejdDevice = this . siteDetails . plejdDevices . find (
( x ) => x . deviceId === device . deviceId ,
) ;
const dimmable = device . traits === TRAITS . DIMMABLE ;
// dimmable = settings.dimCurve !== 'NonDimmable';
2022-10-06 09:35:15 +02:00
try {
2022-10-16 14:40:15 +02:00
const decodedDeviceType = this . _getDeviceType ( plejdDevice ) ;
2022-10-06 09:35:15 +02:00
2022-10-16 14:40:15 +02:00
let loadType = decodedDeviceType . type ;
2022-10-06 09:35:15 +02:00
if ( device . outputType === 'RELAY' ) {
loadType = 'switch' ;
} else if ( device . outputType === 'LIGHT' ) {
loadType = 'light' ;
}
const room = this . siteDetails . rooms . find ( ( x ) => x . roomId === device . roomId ) ;
const roomTitle = room ? room . title : undefined ;
/** @type {import('types/DeviceRegistry').OutputDevice} */
const outputDevice = {
bleOutputAddress ,
deviceId : device . deviceId ,
dimmable ,
name : device . title ,
output : deviceOutput ,
roomId : device . roomId ,
roomName : roomTitle ,
state : undefined ,
type : loadType ,
2022-10-16 14:40:15 +02:00
typeDescription : decodedDeviceType . description ,
typeName : decodedDeviceType . name ,
2022-10-06 09:35:15 +02:00
version : plejdDevice . firmware . version ,
uniqueId : uniqueOutputId ,
} ;
this . deviceRegistry . addOutputDevice ( outputDevice ) ;
} catch ( error ) {
logger . error ( ` Error trying to create output device: ${ error } ` ) ;
2023-08-16 15:32:53 +02:00
logger . warn (
` device (from API response) when error happened: ${ JSON . stringify ( device , null , 2 ) } ` ,
) ;
logger . warn (
` plejdDevice (from API response) when error happened: ${ JSON . stringify (
plejdDevice ,
null ,
2 ,
) } ` ,
) ;
2021-05-03 09:45:57 +02:00
}
2021-04-28 20:07:53 +02:00
}
2021-05-01 19:41:29 +02:00
} else {
// The device does not have an output. It can be assumed to be a WPH-01 or a WRT-01
// Filter inputSettings for available buttons
const inputSettings = this . siteDetails . inputSettings . filter (
2021-05-06 13:28:13 +02:00
( x ) => x . deviceId === device . deviceId ,
2021-05-05 19:34:34 +02:00
) ;
// For each found button, register the device as an inputDevice
inputSettings . forEach ( ( input ) => {
const bleInputAddress = this . siteDetails . deviceAddress [ input . deviceId ] ;
logger . verbose (
` Found input device ( ${ input . deviceId } ), with input ${ input . input } having BLE address ( ${ bleInputAddress } ) ` ,
) ;
const plejdDevice = this . siteDetails . plejdDevices . find (
( x ) => x . deviceId === device . deviceId ,
) ;
const uniqueInputId = this . deviceRegistry . getUniqueInputId ( device . deviceId , input . input ) ;
2022-10-06 09:35:15 +02:00
try {
2022-10-16 14:40:15 +02:00
const decodedDeviceType = this . _getDeviceType ( plejdDevice ) ;
if ( decodedDeviceType . broadcastClicks ) {
2022-10-06 09:35:15 +02:00
/** @type {import('types/DeviceRegistry').InputDevice} */
const inputDevice = {
bleInputAddress ,
deviceId : device . deviceId ,
name : device . title ,
input : input . input ,
roomId : device . roomId ,
2022-10-16 14:40:15 +02:00
type : decodedDeviceType . type ,
typeDescription : decodedDeviceType . description ,
typeName : decodedDeviceType . name ,
2022-10-06 09:35:15 +02:00
version : plejdDevice . firmware . version ,
uniqueId : uniqueInputId ,
} ;
this . deviceRegistry . addInputDevice ( inputDevice ) ;
}
} catch ( error ) {
logger . error ( ` Error trying to create input device: ${ error } ` ) ;
2023-08-16 15:32:53 +02:00
logger . warn (
` device (from API response) when error happened: ${ JSON . stringify ( device , null , 2 ) } ` ,
) ;
logger . warn (
` plejdDevice (from API response) when error happened: ${ JSON . stringify (
plejdDevice ,
null ,
2 ,
) } ` ,
) ;
2021-05-06 13:28:13 +02:00
}
2021-05-05 19:34:34 +02:00
} ) ;
}
2021-02-01 21:22:53 +01:00
} ) ;
}
2019-12-04 11:17:06 +01:00
2021-02-01 21:22:53 +01:00
_getRoomDevices ( ) {
if ( this . config . includeRoomsAsLights ) {
2021-01-21 21:31:37 +01:00
logger . debug ( 'includeRoomsAsLights is set to true, adding rooms too.' ) ;
2021-02-01 21:22:53 +01:00
this . siteDetails . rooms . forEach ( ( room ) => {
2021-01-22 15:49:02 +01:00
const { roomId } = room ;
2021-02-01 21:22:53 +01:00
const roomAddress = this . siteDetails . roomAddress [ roomId ] ;
2019-12-13 17:59:48 +01:00
2021-03-29 12:51:48 +02:00
const deviceIdsByRoom = this . deviceRegistry . getOutputDeviceIdsByRoomId ( roomId ) ;
2021-02-27 16:41:05 +01:00
2023-08-16 15:32:53 +02:00
const dimmable =
deviceIdsByRoom &&
deviceIdsByRoom . some (
2021-03-29 12:51:48 +02:00
( deviceId ) => this . deviceRegistry . getOutputDevice ( deviceId ) . dimmable ,
) ;
2021-02-27 16:41:05 +01:00
2021-03-29 12:51:48 +02:00
/** @type {import('types/DeviceRegistry').OutputDevice} */
2019-12-13 17:59:48 +01:00
const newDevice = {
2021-03-31 20:04:45 +02:00
bleOutputAddress : roomAddress ,
2021-03-29 12:51:48 +02:00
deviceId : null ,
dimmable ,
2019-12-13 17:59:48 +01:00
name : room . title ,
2021-03-29 12:51:48 +02:00
output : undefined ,
2021-05-12 11:31:43 +02:00
roomId : undefined ,
2021-05-12 10:45:27 +02:00
roomName : undefined ,
2021-03-29 12:51:48 +02:00
state : undefined ,
2019-12-13 17:59:48 +01:00
type : 'light' ,
2022-10-16 14:40:15 +02:00
typeDescription : 'A Plejd room' ,
2019-12-13 17:59:48 +01:00
typeName : 'Room' ,
2021-03-29 12:51:48 +02:00
uniqueId : roomId ,
version : undefined ,
2019-12-13 17:59:48 +01:00
} ;
2020-01-17 15:00:54 +00:00
2021-03-29 12:51:48 +02:00
this . deviceRegistry . addOutputDevice ( newDevice ) ;
2021-02-01 21:22:53 +01:00
} ) ;
2021-01-21 21:31:37 +01:00
logger . debug ( 'includeRoomsAsLights done.' ) ;
2019-12-13 17:59:48 +01:00
}
2021-02-01 21:22:53 +01:00
}
2019-12-13 17:59:48 +01:00
2021-02-01 21:22:53 +01:00
_getSceneDevices ( ) {
2021-03-31 20:04:45 +02:00
this . deviceRegistry . clearSceneDevices ( ) ;
2020-02-29 15:54:08 +00:00
// add scenes as switches
2021-06-21 12:06:08 +02:00
const scenes = [ ... this . siteDetails . scenes ] ;
2020-02-29 15:54:08 +00:00
2021-02-01 21:22:53 +01:00
scenes . forEach ( ( scene ) => {
const sceneNum = this . siteDetails . sceneIndex [ scene . sceneId ] ;
2021-03-29 12:51:48 +02:00
/** @type {import('types/DeviceRegistry').OutputDevice} */
2020-02-29 15:54:08 +00:00
const newScene = {
2021-03-31 20:04:45 +02:00
bleOutputAddress : sceneNum ,
2021-03-29 12:51:48 +02:00
deviceId : undefined ,
dimmable : false ,
2020-02-29 15:54:08 +00:00
name : scene . title ,
2021-03-29 12:51:48 +02:00
output : undefined ,
roomId : undefined ,
2021-05-07 08:52:57 +02:00
roomName : undefined ,
2021-03-29 12:51:48 +02:00
state : false ,
2021-04-01 13:19:02 +02:00
type : 'scene' ,
2022-10-16 14:40:15 +02:00
typeDescription : 'A Plejd scene' ,
2020-02-29 15:54:08 +00:00
typeName : 'Scene' ,
2021-03-29 12:51:48 +02:00
version : undefined ,
uniqueId : scene . sceneId ,
2020-02-29 15:54:08 +00:00
} ;
2021-02-01 21:22:53 +01:00
this . deviceRegistry . addScene ( newScene ) ;
} ) ;
2019-12-13 14:13:00 +01:00
}
2019-12-04 11:17:06 +01:00
}
2021-01-22 15:49:02 +01:00
module . exports = PlejdApi ;