UNPKG

homebridge-bondbridge

Version:

Catered shell script to integrate BondBridge units by Bond

684 lines (597 loc) 20.9 kB
const fs = require('fs') const chalk = require('chalk') const commandExistsSync = require( "command-exists" ).sync; // Parse the args var args = process.argv.slice(2); const BONDBRIDGE_SH_PATH = args[0] || "/usr/local/lib/node_modules/homebridge-bondbridge/BondBridge.sh"; const homebridgeConfigPath = args[1] || "/var/lib/homebridge/config.json"; let listOfConstants = { }; var debug = false; consoleLog(`ADVAIR_path=${BONDBRIDGE_SH_PATH}`) consoleLog(`configJsonPath=${homebridgeConfigPath}`) checkInstallationButtonPressed( true ) function consoleLog( msg ) { if ( debug ) { console.log( msg ); } } function message( data ) { console.log( data ); } function checkQueueTypesForQueue( queueTypes, queue ) { for ( let queueTypesIndex = 0; queueTypesIndex < queueTypes.length; queueTypesIndex++ ) { let entry = queueTypes[ queueTypesIndex ]; if ( entry.queue == queue ) { if ( entry.queueType == "WoRm2" ) { return( { rc: true, message: `passed` }); } return( { rc: false, message: `queue ${ queue } queueType is not WoRm2. Please change to Worm2.` }); } } return( { rc: false, message: `No matching queue: "${ queue }" in queueTypes` }); } // MyPlace platform has the ability to allow constants which could be used for the IP function processConstants( constantsArgArray ) { // // Check #8A // Constants must be an Array // consoleLog( `Check #8A` ); if ( ! Array.isArray ( constantsArgArray ) ) { message( chalk.red( `ERROR: Constants must be an array of { "key": "\${SomeKey}", "value": "some replacement string" }` ) ) return false; } // Iterate over the groups of key/value constants in the array. // Note: DO NOT USE: forEach as javascript continues after a return! for ( let argIndex = 0; argIndex < constantsArgArray.length; argIndex++ ) { let argEntry = constantsArgArray[ argIndex ]; if ( argEntry.key == undefined ) { // // Check #8B // key must be defined // consoleLog( `Check #8B` ); message( chalk.red( `ERROR: Constant definition at index: "${ argIndex }" has no "key":` ) ) return false; } if ( argEntry.value == undefined ) { // // Check #8c // value must be defined // consoleLog( `Check #8C` ); message( chalk.red( `ERROR: Constant definition at index: "${ argIndex }" has no "value":` ) ) return false; } let keyToAdd = argEntry.key; let valueToAdd = argEntry.value; if ( ! keyToAdd.startsWith( "${" ) ) { // // Check #8D // key must start with ${ // consoleLog( `Check #8D` ); message( chalk.red( `ERROR: Constant definition for: "${ keyToAdd }" must start with "\${" for clarity.` ) ) return false; } if ( ! keyToAdd.endsWith( "}" ) ) { // // Check #8E // key must end with } // consoleLog( `Check #8E` ); message( chalk.red( `ERROR: Constant definition for: "${ keyToAdd }" must end with "}" for clarity.` ) ) return false; } // remove any leading and trailing single quotes // so that using it for replacement will be easier. valueToAdd.replace(/^'/, "") valueToAdd.replace(/'$/, "") if ( debug ) console.log( chalk.cyan( `CheckConfig keyToAdd:${keyToAdd} valueToAdd:${valueToAdd}` ) ); listOfConstants[ keyToAdd ] = valueToAdd; } return true; } function replaceConstantsInString( orig ) { let finalAns = orig; for ( let key in listOfConstants ) { let replacementConstant = listOfConstants[ key ]; if ( debug ) console.log( chalk.cyan( `INFO: replacing key: ${ key } with: ${ replacementConstant }` ) ); finalAns = finalAns.replace( key, replacementConstant ); } return finalAns; } function updateConfigFirstTime( firstTime ) { // // Check #1 // See if the config.json file exists // consoleLog( `Check #1` ); let configFile = homebridgeConfigPath; if ( configFile == undefined ) { message( chalk.red( `ERROR: No config.json found or specified` ) ) return false; } if ( ! fs.existsSync( configFile ) ) { if ( ! firstTime ) { message( chalk.red( `ERROR: No ${ configFile } found or specified` ) ) } return false; } // Open the config.json file for reading let config_in = fs.readFileSync( configFile, 'utf8' ); // // Check #2 // Convert the config.json into a json type // This can throw an Error so catch it. consoleLog( `Check #2` ); try { this.config = JSON.parse( config_in ); } catch ( e ) { if ( ! firstTime ) { message( chalk.red( `ERROR: Parse config.json failed: ${ e }` ) ) } return false; } let myPlaceBondBridgeConfig = this.config.platforms.find( platform => platform[ "BondBridge" ] !== null ); if ( myPlaceBondBridgeConfig && myPlaceBondBridgeConfig.debug ) { console.log( `Setting debug for BondBridge` ); debug = myPlaceBondBridgeConfig.debug; } return true; } // There is nothing really to differentiate a regular Accessory for that of // an Bond Bridge // function isAccessoryAbondBridge( accessory ) { if ( accessory.manufacturer && accessory.manufacturer.match( /Bond Bridge/ ) ) return true; // Trigger off of the state_cmd, if it exists if ( accessory.state_cmd != undefined ) { // The new BondBridge if ( accessory.state_cmd.match( /BondBridge.sh/ ) ) return true; } return false; } function checkInstallationButtonPressed( ) { // The read in config.json in JSON format if ( debug ) console.log( chalk.cyan( `INFO: CheckConfig is now in the process of checking the config.json` ) ); // Update the config, this is not the first time // return if it fails. As this is not the First time, it will // error if need be. if ( updateConfigFirstTime( false ) == false ) return; // // Check #3 // Check that jq is installed. consoleLog( `Check #3` ); if ( ! commandExistsSync( "jq" ) ) { message( chalk.red( `ERROR: jq is required globally and not installed.` ) ) return; } // // Check #4 // Check that curl is installed. consoleLog( `Check #4` ); if ( ! commandExistsSync( "curl" ) ) { message( chalk.red( `ERROR: curl is required globally and not installed.` ) ) return; } // // Check #5 is already done before calling this CheckConfig.js // Check #5A // Find Node modules // // console.log( `Check #5A` ); // let node_modules = getGlobalNodeModulesPathForFile( "" ); // if ( node_modules == null ) // { // message( chalk.red( `ERROR: Could not determine where node_modules is installed globally.` ) ) // return; // } // // Check #5B // See if MyPlace is installed from node_modules // // fileToFind = "/homebridge-myplace/index.js"; // let myPlaceIndex = getGlobalNodeModulesPathForFile( fileToFind ) // if ( myPlaceIndex == null ) // { // message( chalk.red( `ERROR: MyPlace Plugin not installed` ) ) // return; // } // // Check #6 // See if our BondBridge.sh script is present // // Create the path to the BondBridge.sh from node_modules consoleLog( `Check #6` ); let ourScript = BONDBRIDGE_SH_PATH if ( ourScript == null ) { message( chalk.red( `ERROR: No BondBridge.sh script present. Looking for: <Your Global node_modules Path>${ this.BONDBRIDGE_SH }` ) ) return; } let myPlaceAccessoriesFound = false; let bondbridgeAccessoriesFound = []; let myPlaceQueueTypesFound = []; let retVal = { }; // Iterate over the elements in the array. // Note: DO NOT USE: forEach as javascript continues after a return! for ( let entryIndex = 0; entryIndex < this.config.platforms.length; entryIndex++ ) { let entry = this.config.platforms[ entryIndex]; if ( debug ) console.log( chalk.cyan( `INFO: CheckConfig is checking Platform entry ${ entry.platform }` ) ); // // Check #7 // See if any MyPlace accessories are defined in config.json // consoleLog( `Check #7` ); if ( entry.platform != "MyPlace" ) continue; myPlaceAccessoriesFound = true; // // Check #18 // See if there are any accessory queues defined // consoleLog( `Check #18` ); if ( entry.queueTypes != undefined ) { // // Check #19 // queueTypes must be an array // consoleLog( `Check #19` ); if ( ! Array.isArray( entry.queueTypes ) ) { message( chalk.red( `ERROR: queueTypes is not an Array` ) ) return; } // Iterate over the elements in the array. // Note: DO NOT USE: forEach as javascript continues after a return! for ( let queueTypesIndex = 0; queueTypesIndex < entry.queueTypes.length; queueTypesIndex++ ) { let queueTypeEntry = entry.queueTypes[ queueTypesIndex ]; // Need to append each one retVal = checkQueueTypesForQueue( myPlaceQueueTypesFound, queueTypeEntry.queue ); if ( retVal.rc == true ) // if ( myPlaceQueueTypesFound.find( queueTypeEntry ) ) { // // Check #20 // Duplicate queue // consoleLog( `Check #20` ); message( chalk.red( `ERROR: Duplicate queue found: ${ queueTypeEntry.queue }` ) ) return; } myPlaceQueueTypesFound.push( queueTypeEntry ); } } // // Check #8 // Process Constants // consoleLog( `Check #8` ); if ( entry.constants != undefined ) if ( processConstants( entry.constants ) == false ) return; // Iterate over the elements in the array. // Note: DO NOT USE: forEach as javascript continues after a return! for ( let accessoryIndex = 0; accessoryIndex < entry.accessories.length; accessoryIndex++ ) { let accessory = entry.accessories[ accessoryIndex ]; if ( debug ) console.log( chalk.cyan( `INFO: CheckConfig is checking accessory ${ accessory.name }` ) ); // // Check #9 // See if any Bond Bridge accessories are defined in config.json // consoleLog( `Check #9` ); if ( ! isAccessoryAbondBridge( accessory ) ) continue; // // Check #10 // See if any Bond Bridge accessory has a defined name // consoleLog( `Check #10` ); if ( debug ) console.log( chalk.cyan( `INFO: CheckConfig is checking accessory ${ accessory.name }` ) ); if ( accessory.name == undefined ) { message( chalk.red( `ERROR: Accessory at index: ${ entryIndex } accessory.name is undefined` ) ) return; } // // Check #11 // See if any Bond Bridge accessory has a defined displayName // consoleLog( `Check #11` ); if ( debug ) console.log( chalk.cyan( `INFO: CheckConfig is checking accessory ${ accessory.name } for displayName` ) ); if ( accessory.displayName == undefined ) { message( chalk.red( `ERROR: Accessory at index: ${ entryIndex } "${ accessory.name }" has no displayName` ) ) return; } // // Check #12 // Polling is done by displayName, It cannot already exist. // consoleLog( `Check #12` ); if ( debug ) console.log( chalk.cyan( `INFO: CheckConfig is Checking accessory ${ accessory.displayName } for duplicate displayName` ) ); if ( bondbridgeAccessoriesFound.find( ( displayName ) => displayName == accessory.displayName ) ) { message( chalk.red( `ERROR: Accessory: "${ accessory.displayName }"'s displayName is defined twice` ) ) return; } // Add it to the Array bondbridgeAccessoriesFound.push( accessory.displayName ); if ( debug ) console.log( chalk.cyan( `INFO: CheckConfig is Checking Bond Bridge accessory ${ accessory.displayName }` ) ); // // Check #13 // The state_cmd must be defined for the Air accessory // consoleLog( `Check #13` ); if ( accessory.state_cmd == undefined ) { message( chalk.red( `ERROR: No state_cmd for: "${ accessory.displayName }"` ) ) return; } // // Check #14 // See if the state_cmd does not match the BondBridge.sh // consoleLog( `Check #14` ); if ( ! accessory.state_cmd.match( ourScript ) ) { message( chalk.red( `ERROR: Invalid state_cmd for: "${ accessory.displayName }". It should be:\n${ ourScript }` ) ) return; } // // Check #15 // See if the state_cmd_suffix is defined for the Air accessory // It must have at least an IP consoleLog( `Check #15` ); if ( accessory.state_cmd_suffix == undefined ) { message( chalk.red( `ERROR: No state_cmd_suffix for: "${ accessory.displayName }". It must at least contain an IP.` ) ) return; } if ( debug ) console.log( chalk.cyan( `INFO: Calling replaceConstantsInString` ) ); let state_cmd_suffix = replaceConstantsInString( accessory.state_cmd_suffix ); if ( debug ) console.log( chalk.cyan( `INFO: after replaceConstantsInString state_cmd_suffix=${ state_cmd_suffix }` ) ); // // Check #16A // The state_cmd_suffix must have an IP for the BondBridge accessory // if ( ! state_cmd_suffix.match( /[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*/ ) ) { this.advError( { "rc": false, "message": `state_cmd_suffix has no IP for: "${ accessory.displayName }" state_cmd_suffix: ${ state_cmd_suffix }` }); return; } // // Check #16B // The state_cmd_suffix must have a 'token: for the BondBridge accessory // if ( ! state_cmd_suffix.match( /'token:[a-f0-9]*'/ ) ) { this.advError( { "rc": false, "message": `state_cmd_suffix has no 'token' defined for: "${ accessory.displayName }" state_cmd_suffix: ${ state_cmd_suffix }` }); return; } // // Check #16C // The state_cmd_suffix must have a 'device: for the BondBridge accessory // if ( ! state_cmd_suffix.match( /'device:[a-f0-9]*'/ ) ) { this.advError( { "rc": false, "message": `state_cmd_suffix has no 'device' defined for: "${ accessory.displayName }" state_cmd_suffix: ${ state_cmd_suffix }` }); return; } // // Check #17A // The state_cmd_suffix must have a fanSwitch or a lightSwitch for a BondBridge Switch accessory // if ( accessory.type.match( /^Switch/ ) ) { if ( ! ( state_cmd_suffix.match( /fanSwitch/ ) || state_cmd_suffix.match( /lightSwitch/ ) ) ) { this.advError( { "rc": false, "message": `The state_cmd_suffix for: "${ accessory.displayName }" requires a 'fanSwitch' or a key word 'lightSwitch'.` }); return; } } // // Check #17B // The state_cmd_suffix must have a 'fan ' for a Fan accessory // if ( accessory.type.match( /^Fan/ ) ) { if ( ! state_cmd_suffix.match( /fan / ) ) { this.advError( { "rc": false, "message": `The state_cmd_suffix for: "${ accessory.displayName }" requires a key word 'fan'.` }); return; } } // // Check #17C // The state_cmd_suffix must have a 'light ', 'lightTimer', 'lightDevice', 'fanDevice' or 'fanTimer' for a Lightbulb accessory // if ( accessory.type.match( /^Lightbulb/ ) ) { if ( ! ( state_cmd_suffix.match( /light / ) || state_cmd_suffix.match( /dimmer / ) || state_cmd_suffix.match( /lightTimer/ ) || state_cmd_suffix.match( /fanTimer/ ) ) ) { this.advError( { "rc": false, "message": `The state_cmd_suffix for: "${ accessory.displayName }" requires a keyword 'light ' or 'lightTimer' or 'fanTimer'.` }); return; } // // Check #17C1 // if state_cmd_suffix has lightTimer, then it must also has lightDevice // if ( state_cmd_suffix.match( /lightTimer/ ) ) { if ( ! state_cmd_suffix.match( /'lightDevice:[a-f0-9]*'/ ) ) { this.advError( { "rc": false, "message": `The state_cmd_suffix for: "${ accessory.displayName }" requires a keyword 'lightDevice'.` }); return; } } // // Check #17C2 // if state_cmd_suffix has fanTimer, then it must also has fanDecive // if ( state_cmd_suffix.match( /fanTimer/ ) ) { if ( ! state_cmd_suffix.match( /'fanDevice:[a-f0-9]*'/ ) ) { this.advError( { "rc": false, "message": `The state_cmd_suffix for: "${ accessory.displayName }" requires a keyword 'fanDevice'.` }); return; } } } // // Check #21 // See if there is a queue defined // consoleLog( `Check #21`); if ( accessory.queue == undefined ) { message( chalk.red( `ERROR: No queue defined for: "${ accessory.displayName }"` ) ) return; } // // Check #22 // queue name must be an string // consoleLog( `Check #22`); if ( typeof accessory.queue != "string" ) { message( chalk.red( `ERROR: queue for: "${ accessory.displayName }" is not a string` ) ) return; } retVal = checkQueueTypesForQueue( myPlaceQueueTypesFound, accessory.queue ); // Check #23 // queue must be defined in queueTypes consoleLog( `Check #23`); if ( retVal.rc == false ) { message( chalk.red( `ERROR: For: "${ accessory.displayName }" ${ retVal.message }` ) ) return; } // Check #24 Polling must be defined for BondBridge accessories consoleLog( `Check #24`); if ( ! accessory.polling || ( typeof accessory.polling == "boolean" && accessory.polling != true && ! Array.isArray( accessory.polling) ) ) { message( chalk.red( `ERROR: Polling for: "${ accessory.displayName }" is not an Array or Boolean` ) ) return; } } } // // Check #32 // See if any MyPlace accessories are defined in config.json // consoleLog( `Check #32`); if ( myPlaceAccessoriesFound == false ) { message( chalk.red( `ERROR: No MyPlace Accessories found` ) ) return; } // // Check #33 // See if any Bond Bridge accessories are defined in config.json // consoleLog( `Check #33`); if ( bondbridgeAccessoriesFound.length == 0 ) { message( chalk.red( `ERROR: No Bond Bridge Accessories found` ) ) return; } // // Check #34 // See if any queueTypes were defined // ( Most likely an earlier failure will succeed this one ) // consoleLog( `Check #34`); if ( myPlaceQueueTypesFound == null ) { message( chalk.red( `ERROR: No MyPlace Queue Types were defined for Bond Bridge Accessories` ) ) return; } // PASS ! message( chalk.green( chalk.bold ( `PASSED` ) ) ) }