├── test ├── AdvAir.sh ├── echoScripts │ ├── justExitWithRCof0 │ ├── justExitWithRCof1 │ ├── echo_0 │ ├── echo_1 │ ├── echo_On │ ├── runToTimeoutRcOf0 │ ├── runToTimeoutRcOf1 │ ├── echo_false │ ├── echo_nothing │ ├── echo_null │ ├── echo_true │ ├── echo_ENABLED │ ├── echo_INACTIVE │ ├── echo_quoted0 │ ├── echo_quoted1 │ ├── echo_DISABLED │ ├── echo_quotedFALSE │ ├── echo_quotedNULL │ ├── echo_quotedNothing │ ├── echo_quotedTRUE │ ├── echo_true_withRcOf1 │ ├── echo_after5seconds │ ├── echo_ACTIVE │ ├── echo_errorToStderr │ ├── echo_nullAndErrorToStderr │ ├── echo_too_much │ └── testGetSetValues.js ├── sanityTests ├── testOurConfig.json.js ├── allTests ├── async-dump.js ├── getAccessoryUUID.js ├── extractKeyValue.js ├── isCmd4Directive.js ├── versionChecker.js ├── isAccDirective.js ├── isJSON.js ├── isDevDirective.js ├── VariableTimer.js ├── loadPluginTest.js ├── isNumeric.js ├── indexOfEnum.js ├── mocha-setup ├── trueTypeOf.js ├── Cmd4Mode.js ├── CMD4_CHAR_TYPE_ENUMS.js ├── HV.js ├── getAccessoryNameFunctions.js ├── initPluginTest.js ├── getSetAllValues.js └── Logger.js ├── screenshots ├── Eve_screenshot.png └── Homekit_screenshot.png ├── Extras └── Cmd4Scripts │ ├── Examples │ ├── middleWare.sh │ ├── DoorLock.sh │ ├── basic_ping.sh │ ├── advanced_ping.sh │ ├── PS5.sh │ ├── ExampleShellScript_template.sh │ ├── PS4.sh │ ├── wakeonlan.sh │ └── ExampleJavaScript_template.js │ └── CheckYourScript.sh ├── .npmignore ├── CHANGELOG.md ├── docs ├── index.html └── AdvancedTroubleShooting.md ├── utils ├── isNumeric.js ├── extractKeyValue.js ├── indexOfEnum.js ├── lcFirst.js ├── ucFirst.js ├── isJSON.js ├── getAccessoryUUID.js ├── getAccessoryNameFunctions.js ├── trueTypeOf.js ├── isCmd4Directive.js ├── isAccDirective.js ├── createAccessorysInformationService.js ├── isDevDirective.js ├── indexOfEnumLintTest.js ├── VariableTimer.js ├── HV.js ├── versionChecker.js ├── transposeCMD4Props.js ├── Logger.js └── Cmd4Storage.js ├── .gitignore ├── cmd4Settings.js ├── commitlint.config.js ├── .github ├── pull_request_template.md └── ISSUE_TEMPLATE │ ├── feature-request.md │ ├── bug-report.md │ └── support-request.md ├── .eslintrc.json ├── postinstall.js ├── index.js ├── lib └── CMD4_CHAR_TYPE_ENUMS.js ├── tools └── whereIsConstant ├── package.json └── cmd4Constants.js /test/AdvAir.sh: -------------------------------------------------------------------------------- 1 | ../Extras/Cmd4Scripts/Examples/AnyDevice -------------------------------------------------------------------------------- /test/echoScripts/justExitWithRCof0: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exit 0; 4 | -------------------------------------------------------------------------------- /test/echoScripts/justExitWithRCof1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exit 1; 4 | -------------------------------------------------------------------------------- /test/echoScripts/echo_0: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo 0; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo "1"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_On: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo On; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/runToTimeoutRcOf0: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1000000; 4 | exit 0; 5 | -------------------------------------------------------------------------------- /test/echoScripts/runToTimeoutRcOf1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 1000000; 4 | exit 1; 5 | -------------------------------------------------------------------------------- /test/echoScripts/echo_false: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo false; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_nothing: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo ""; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_null: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo "null"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_true: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo true; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_ENABLED: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo "Enabled"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_INACTIVE: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo INACTIVE; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_quoted0: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo \"0\"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_quoted1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo \"1\"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_DISABLED: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo "DISABLED"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_quotedFALSE: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo \"False\"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_quotedNULL: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo \"NULL\"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_quotedNothing: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo \" \"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_quotedTRUE: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo \"True\"; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_true_withRcOf1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo true; 4 | 5 | exit 1; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_after5seconds: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sleep 5; 4 | stdbuf -o0 echo false; 5 | 6 | exit 0; 7 | -------------------------------------------------------------------------------- /screenshots/Eve_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztalbot2000/homebridge-cmd4/HEAD/screenshots/Eve_screenshot.png -------------------------------------------------------------------------------- /test/echoScripts/echo_ACTIVE: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "blast" > /tmp/blast; 3 | 4 | stdbuf -o0 echo ACTIVE; 5 | 6 | exit 0; 7 | -------------------------------------------------------------------------------- /screenshots/Homekit_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ztalbot2000/homebridge-cmd4/HEAD/screenshots/Homekit_screenshot.png -------------------------------------------------------------------------------- /test/echoScripts/echo_errorToStderr: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -e0 -i0 echo "This message goes to stderr" >&2; 4 | 5 | exit 0; 6 | -------------------------------------------------------------------------------- /test/echoScripts/echo_nullAndErrorToStderr: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo null 4 | stdbuf -o0 e0 echo "This message goes to stderr" >&2; 5 | 6 | exit 0; 7 | -------------------------------------------------------------------------------- /test/echoScripts/echo_too_much: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stdbuf -o0 echo ""; 4 | stdbuf -o0 echo "second extra line"; 5 | stdbuf -o0 echo "third extra line"; 6 | 7 | exit 0; 8 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/Examples/middleWare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | result=$(node .homebridge/Cmd4Scripts/State.js $* 2>&1) 3 | echo $( date ) >> /tmp/Cmd4.log 4 | echo $* >> /tmp/Cmd4.log 5 | echo $result | tee -a /tmp/Cmd4.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .eslintrc.json 2 | .gitignore 3 | .github 4 | .huskyrc 5 | .DS_Store 6 | *_save* 7 | *_bak* 8 | *.swp 9 | commitlint.config.js 10 | jsmin 11 | utils/indexOfEnumLintTest.js 12 | screenshots 13 | test 14 | node_modules 15 | local 16 | tools 17 | docs/autoGenerated 18 | docs/images 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Homebridges-cmd4 - CMD4 Plugin for Homebridge - Supports ~All Accessory Types and now all Characteristics too!! 2 | #### 8.0.3 (2024-12-11) 3 | 4 | ##### Bug Fixes 5 | 6 | * Update node requirements to satisfy the current Node.js version of v22.12.0 ([22761c89](https://github.com/ztalbot2000/homebridge-cmd4/commit/22761c8954dfdb6086023585978836b36f531f65)) 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Please follow this link.

8 | 9 | 10 | -------------------------------------------------------------------------------- /utils/isNumeric.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Description: 4 | // Determine if a given parameter is numeric. 5 | // 6 | // @param num - The number to determine if it is actually numeric. 7 | // @returns: boolean 8 | // 9 | 10 | var isNumeric = function( num ) 11 | { 12 | num = "" + num; // coerce num to be a string 13 | return !isNaN( num ) && !isNaN( parseFloat( num ) ); 14 | } 15 | 16 | module.exports = isNumeric; 17 | -------------------------------------------------------------------------------- /utils/extractKeyValue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Description: 4 | // Extracts a key from an object given a value. 5 | // 6 | // @param obj - The object to get the key from. 7 | // @param value - The value to find. 8 | // 9 | // @returns key or undefined 10 | // 11 | 12 | var extractKeyValue = function (obj, value) { 13 | return Object.keys(obj)[Object.values(obj).indexOf(value)]; 14 | } 15 | 16 | module.exports = extractKeyValue; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac 2 | .DS_Store 3 | 4 | # Text editors 5 | *.swp 6 | 7 | # Stupid commits of npm package 8 | *.tgz 9 | 10 | # My favorite temp backup 11 | *.[0-9] 12 | *.save* 13 | *_save* 14 | *_bak* 15 | 16 | # Compiled Executables 17 | jsmin 18 | a.out 19 | 20 | # node 21 | node_modules 22 | npm-debug.log 23 | .node-version 24 | 25 | test/tmp/* 26 | 27 | # Not a fan 28 | package-lock.json 29 | 30 | # work in progress folder 31 | local 32 | -------------------------------------------------------------------------------- /test/sanityTests: -------------------------------------------------------------------------------- 1 | test/Cmd4Storage.js 2 | test/HV.js 3 | test/cmd4Constants.js 4 | test/CMD4_CHAR_TYPE_ENUMS.js 5 | test/CMD4_ACC_TYPE_ENUM.js 6 | test/CMD4_DEVICE_TYPE_ENUM.js 7 | test/isAccDirective.js 8 | test/isDevDirective.js 9 | test/isCmd4Directive.js 10 | test/configTest.js 11 | test/loadPluginTest.js 12 | test/Cmd4Accessory.js 13 | test/internalRelatedTargetTests.js 14 | test/Cmd4Platform.js 15 | test/Cmd4AccessoryGetValue.js 16 | test/Cmd4AccessorySetValue.js 17 | test/Cmd4Mode.js 18 | test/Cmd4PriorityPollingQueue.js 19 | test/initPluginTest.js 20 | test/fakeGato.js 21 | test/pollingTest.js 22 | test/Cmd4PlatformRestartTests.js 23 | -------------------------------------------------------------------------------- /test/testOurConfig.json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'), 4 | Parser = require('jsonparse'); 5 | 6 | 7 | var json = fs.readFileSync("Extras/config.json"); 8 | 9 | 10 | 11 | describe( "Testing our config.json", ( ) => 12 | { 13 | it( "isJSON should be a function", ( ) => 14 | { 15 | assert.isFunction( Parser, "Parser is not a function" ); 16 | }); 17 | 18 | it( "If our config.json is valid", ( ) => 19 | { 20 | JSON.parse(json); 21 | var p = new Parser(); 22 | 23 | p.onError = function (value) { 24 | assert("Our config.json is invalid value:", value); 25 | }; 26 | 27 | p.write( json ); 28 | 29 | }); 30 | }) 31 | -------------------------------------------------------------------------------- /utils/indexOfEnum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // Description: 5 | // Create an Object prototype to for getting an index of an enumerated type. 6 | // 7 | 8 | module.exports=Object.defineProperty(Object.prototype, "indexOfEnum", { 9 | value: function( predicate, fromIndex ) { 10 | let length = this == null ? 0 : Object.keys( this ).length; 11 | if ( !length ) 12 | return -1; 13 | 14 | let index = fromIndex == null ? 0 : fromIndex; 15 | if ( index < 0 ) 16 | { 17 | index = Math.max( length + index, 0 ); 18 | } 19 | 20 | for ( let i=index; i < length; i++) 21 | { 22 | if ( predicate( this[ i ], i, this ) ) 23 | { 24 | return i; 25 | } 26 | } 27 | return -1; 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /cmd4Settings.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // 3 | // This is the name of the platform that users will use to register 4 | // the plugin in the Homebridge config.json 5 | // 6 | exports.PLATFORM_NAME = "Cmd4"; 7 | 8 | // 9 | // This *MUST* match the name of your plugin as defined the package.json 10 | // 11 | exports.PLUGIN_NAME = "homebridge-cmd4"; 12 | 13 | // These must be global so that all characteristics are not 14 | // polled at the same time. Specifically a MyAir that has 15 | // multiple fans, switches and temperature sensors, all in 16 | // the same device of which a linkedAccessory is not an option. 17 | //exports.arrayOfAllStaggeredPollingCharacteristics = [ ]; 18 | exports.listOfCreatedPriorityQueues = { }; 19 | 20 | 21 | // By using our own Logger, we don't trigger others 22 | exports.cmd4Dbg = false; 23 | -------------------------------------------------------------------------------- /utils/lcFirst.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Description: 4 | // Convert the first character of a string to lower case. 5 | // 6 | // @param string - The string to convert. 7 | // 8 | // @returns: The string with the first letter lower cased. 9 | // 10 | // 11 | var lcFirst = function( string ) 12 | { 13 | switch( typeof string ) 14 | { 15 | case undefined: 16 | 17 | console.log( "Asked to lower case first character of NULL String" ); 18 | return undefined; 19 | 20 | case "boolean": 21 | case "number": 22 | return string; 23 | case "string": 24 | return string.charAt( 0 ).toLowerCase() + string.slice( 1 ); 25 | default: 26 | console.log( "Asked to lower case first character of non String(%s):%s", typeof string, string ); 27 | return string; 28 | } 29 | } 30 | 31 | module.exports = lcFirst; 32 | -------------------------------------------------------------------------------- /utils/ucFirst.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Description: 4 | // Convert the first character of a string to upper case. 5 | // 6 | // @param string - The string to convert. 7 | // 8 | // @returns: The string with the first letter upper cased. 9 | // 10 | // 11 | var ucFirst = function( string ) 12 | { 13 | switch( typeof string ) 14 | { 15 | case undefined: 16 | 17 | console.log( "Asked to upper case first character of NULL String" ); 18 | return undefined; 19 | 20 | case "boolean": 21 | case "number": 22 | return string; 23 | case "string": 24 | return string.charAt( 0 ).toUpperCase() + string.slice( 1 ); 25 | default: 26 | console.log( "Asked to upper case first character of non String(%s):%s", typeof string, string ); 27 | return string; 28 | } 29 | } 30 | 31 | module.exports = ucFirst; 32 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] , 3 | plugins: ['commitlint-plugin-function-rules'], 4 | rules: { 5 | 'header-case': [ 0 ], 6 | 'body-max-line-length': [0], // level: disabled 7 | 'function-rules/header-case': [ 8 | 2, // level: error 9 | 'always', 10 | () => { 11 | // I do not care about case in header 12 | return [true]; 13 | } 14 | ], 15 | 'body-case': [ 0 ], 16 | 'function-rules/body-case': [ 17 | 2, // level: error 18 | 'always', 19 | () => { 20 | // I do not care about case in body 21 | return [true]; 22 | } 23 | ], 24 | 'subject-case': [ 0 ], 25 | 'function-rules/subject-case': [ 26 | 2, // level: error 27 | 'always', 28 | () => { 29 | // I do not care about case in subject 30 | return [true]; 31 | } 32 | ] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /test/allTests: -------------------------------------------------------------------------------- 1 | test/Logger.js 2 | test/async-dump.js 3 | test/configHasCharacteristicProps.js 4 | test/isJSON.js 5 | test/indexOfEnum.js 6 | test/isNumeric.js 7 | test/extractKeyValue.js 8 | test/getAccessoryNameFunctions.js 9 | test/getAccessoryUUID.js 10 | test/getSetAllValues.js 11 | test/VariableTimer.js 12 | test/transposeCMD4Props.js 13 | test/trueTypeOf.js 14 | test/versionChecker.js 15 | test/Cmd4Storage.js 16 | test/HV.js 17 | test/cmd4Constants.js 18 | test/CMD4_CHAR_TYPE_ENUMS.js 19 | test/CMD4_ACC_TYPE_ENUM.js 20 | test/CMD4_DEVICE_TYPE_ENUM.js 21 | test/isAccDirective.js 22 | test/isDevDirective.js 23 | test/isCmd4Directive.js 24 | test/testOurConfig.json.js 25 | test/configTest.js 26 | test/loadPluginTest.js 27 | test/Cmd4Accessory.js 28 | test/internalRelatedTargetTests.js 29 | test/Cmd4Platform.js 30 | test/Cmd4AccessoryGetValue.js 31 | test/Cmd4AccessorySetValue.js 32 | test/Cmd4Mode.js 33 | test/Cmd4PriorityPollingQueue.js 34 | test/initPluginTest.js 35 | test/fakeGato.js 36 | test/pollingTest.js 37 | test/Cmd4PlatformRestartTests.js 38 | -------------------------------------------------------------------------------- /utils/isJSON.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Description: 4 | // Determine if parameter is a true JSON object, not an array, but {} 5 | // 6 | // @param m - JSON Object to check. 7 | // @returns: boolean 8 | 9 | function isJSON( m ) 10 | { 11 | if ( ! m ) 12 | { 13 | console.warn( "No parameter passed to isJSON" ); 14 | return false; 15 | } 16 | 17 | if ( ! m.constructor ) 18 | { 19 | //console.warn( "No constructor to isJSON for parameter: %s", m ); 20 | return false; 21 | } 22 | 23 | if ( m.constructor === Array ) 24 | { 25 | //console.warn( "It is an array" ); 26 | return false; 27 | } 28 | 29 | if ( typeof m == "object" ) 30 | { 31 | try{ m = JSON.stringify( m ); } 32 | catch( err ) { return false; } } 33 | 34 | if ( typeof m == "string") 35 | { 36 | try{ m = JSON.parse( m ); } 37 | catch ( err ) { return false; } } 38 | 39 | if ( typeof m != "object") { return false; } 40 | 41 | return true; 42 | } 43 | 44 | 45 | module.exports = isJSON; 46 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pull Request 3 | about: Resolve an issue or add an improvement to cmd4. 4 | title: "[Pull Request]" 5 | labels: pull-request 6 | assignees: ztalbot2000 7 | 8 | --- 9 | 10 | 11 | 12 | **Is your pull request related to a problem? Please describe:** 13 | 14 | 15 | **Describe the solution you'd have implemented:** 16 | 17 | 18 | **Do your changes pass unit testing ( npm run test ) :** 19 | - [x] Yes 20 | - [ ] No 21 | 22 | **Do your changes pass lint testing ( npm run lint ) :** 23 | - [x] Yes 24 | - [ ] No 25 | 26 | **Additional context:** 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /utils/getAccessoryUUID.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Description: 4 | // Get or create a Accessories UUID based on what it is configured as. 5 | // 6 | // @param config - The accessories config information. 7 | // @param UUIDGen - api.hap.uuid 8 | // 9 | // @returns - UUID or exits as all Accessories must have a name or displayName. 10 | // 11 | // Note: This follows the getAccessoryName logic of getting the Accessories name. 12 | 13 | var getAccessoryUUID = function ( config, UUIDGen ) 14 | { 15 | if ( config.UUID ) 16 | return config.UUID; 17 | if ( config.uuid ) 18 | return config.uuid; 19 | 20 | if ( config.name ) 21 | return UUIDGen.generate( config.name ); 22 | 23 | if ( config.Name ) 24 | return UUIDGen.generate( config.Name ); 25 | 26 | if ( config.displayName ) 27 | return UUIDGen.generate( config.displayName ); 28 | 29 | if ( config.DisplayName ) 30 | return UUIDGen.generate( config.DisplayName ); 31 | 32 | throw new Error( "You must either, 'displayName' and or 'name' per accessory." ); 33 | } 34 | 35 | module.exports = getAccessoryUUID; 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea or improvement for cmd4. 4 | title: "[Feature Request]" 5 | labels: enhancement 6 | assignees: ztalbot2000 7 | 8 | --- 9 | ** Cmd4 No longer supported:** 10 | 11 | 12 | 13 | 14 | **Is your feature request related to a problem? Please describe:** 15 | 16 | 17 | **Describe the solution you'd like:** 18 | 19 | 20 | **Describe alternatives you've considered:** 21 | 22 | 23 | **Additional context:** 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /utils/getAccessoryNameFunctions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Description: 4 | // Routines of which given an Accessory Config. Extract the Accessory Name based on 5 | // either it's displayName or name key values. 6 | // 7 | // @param config - The Accessories json config. 8 | // 9 | // @returns name as a string or dies as all accessories must have a name 10 | // 11 | 12 | var getAccessoryName = function ( config ) 13 | { 14 | if ( config.name ) return config.name; 15 | if ( config.Name ) return config.Name; 16 | if ( config.displayName ) return config.displayName; 17 | if ( config.DisplayName ) return config.DisplayName; 18 | 19 | throw new Error( "You must have a 'Name' per accessory." ); 20 | } 21 | 22 | var getAccessoryDisplayName = function ( config ) 23 | { 24 | if ( config.displayName ) return config.displayName; 25 | if ( config.DisplayName ) return config.DisplayName; 26 | 27 | if ( config.name ) return config.name; 28 | if ( config.Name ) return config.Name; 29 | 30 | throw new Error( "You must either, 'displayName' and or 'name' per accessory." ); 31 | } 32 | 33 | module.exports = { getAccessoryName, 34 | getAccessoryDisplayName 35 | }; 36 | -------------------------------------------------------------------------------- /utils/trueTypeOf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Description: 4 | // Determine the true type of an object, because typeOf is screwy 5 | // for null/undefined. 6 | // 7 | // 8 | // NOTE: "0" or any qoted number is still a string. 9 | // This function just fixes null/undefined. 10 | // Use isNumeric if needed. 11 | // 12 | // @param m - type to check 13 | // @returns: Array, Boolean, Number, String, Object, null, undefined 14 | 15 | function trueTypeOf( m ) 16 | { 17 | switch( typeof m ) 18 | { 19 | case "boolean": 20 | return Boolean; 21 | case "number": 22 | return Number; 23 | case "string": 24 | // If the string is actually a number, let the caller 25 | // deal with it as our intent is just to fix undefined 26 | // and null issues. 27 | return String; 28 | case "object": 29 | // null can be an object 30 | if ( m == null ) 31 | return null; 32 | if ( Array.isArray( m ) ) 33 | return Array; 34 | 35 | return Object; 36 | case "undefined": 37 | return undefined; 38 | default: 39 | throw new Error("OOPS"); 40 | } 41 | 42 | } 43 | 44 | module.exports = trueTypeOf; 45 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": false, 5 | "node": true, 6 | "mocha": true, 7 | "es2021": true 8 | }, 9 | "globals": { 10 | "assert" : "writeable", 11 | "expect" : "writeable", 12 | "sinon" : "writeable", 13 | "ACC_EOL" : "readonly", 14 | "DEVICE_EOL" : "readonly", 15 | "FORMAT_EOL" : "readonly", 16 | "UNITS_EOL" : "readonly", 17 | "PERMS_EOL" : "readonly", 18 | "DEVICE_DATA" : "readonly", 19 | "ACC_DATA" : "readonly", 20 | "CHAR_DATA" : "readonly", 21 | "CMD4_CHAR_TYPE_ENUMS" : "readonly", 22 | "CMD4_ACC_TYPE_ENUM" : "readonly", 23 | "CMD4_DEVICE_TYPE_ENUM" : "readonly", 24 | "cleanStatesDir" : "readonly", 25 | "accEnumIndexToC" : "readonly", 26 | "devEnumIndexToC" : "readonly", 27 | "fs" : "writeable", 28 | "HomebridgeAPI" : "writeable", 29 | "Logger" : "writeable", 30 | "platformAccessory_1" : "writeable" 31 | }, 32 | "extends": "eslint:recommended", 33 | "parserOptions": { 34 | "ecmaVersion": 12 35 | }, 36 | "rules": { 37 | "no-fallthrough": ["error", { "commentPattern": "break[\\s\\w]*omitted"}], 38 | "no-whitespace-before-property": ["error"], 39 | "arrow-spacing": ["error"] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/async-dump.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // How To use: 4 | // 5 | // 1. In package.json change scipt "test", adding: 6 | // node_modules/.bin/mocha --require ./test/async-dump.js 7 | // 2. In the failing test add at the top: 8 | // after(function () { 9 | // global.asyncDump(); 10 | // }); 11 | // 3. Run the test 12 | // npm run test test/failingTest 13 | // 14 | // 15 | // Taken from: https://gist.github.com/boneskull/7fe75b63d613fa940db7ec990a5f5843 16 | 17 | const { createHook } = require( 'async_hooks' ); 18 | const { stackTraceFilter } = require( 'mocha/lib/utils' ); 19 | const allResources = new Map(); 20 | 21 | // this will pull Mocha internals out of the stacks 22 | const filterStack = stackTraceFilter(); 23 | 24 | const hook = createHook({ 25 | init(asyncId, type, triggerAsyncId) 26 | { 27 | allResources.set(asyncId, { type, triggerAsyncId, stack: ( new Error( ) ).stack }); 28 | }, 29 | destroy( asyncId ) { 30 | allResources.delete( asyncId ); 31 | } 32 | }).enable( ); 33 | 34 | global.asyncDump = module.exports = ( ) => { 35 | hook.disable( ); 36 | console.error(` 37 | STUFF STILL IN THE EVENT LOOP:`) 38 | allResources.forEach(value => { 39 | console.error( `Type: ${value.type}` ); 40 | console.error( filterStack(value.stack ) ); 41 | console.error( '\n' ); 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /utils/isCmd4Directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const constants = require( "../cmd4Constants" ); 4 | const lcFirst = require( "./lcFirst" ); 5 | 6 | 7 | // Description: 8 | // Determine if parameter is a Cmd4 directive based on it being 9 | // in the cmd4Constants. 10 | // 11 | // @param directive - The Cmd4 Directive to check 12 | // @param allowUpper - if upper case allowed to be checked. 13 | // @returns: { key: The CORRECT directive 14 | // wasLower: If the directive was lowered to make it correct. 15 | // } or null 16 | 17 | 18 | function isCmd4Directive( directive, allowUpper = false ) 19 | { 20 | if ( ! directive ) 21 | { 22 | console.warn( "No parameter passed to isCmd4Directive" ); 23 | return null; 24 | } 25 | 26 | if ( Object.values( constants ).indexOf( directive ) >= 0 ) 27 | { 28 | //console.log("isCmd4Directive directive: %s returning: %s", directive, directive ); 29 | return { key: directive, 30 | wasLower: true }; 31 | } 32 | 33 | if ( directive == "UUID" ) 34 | return { key: "uuid", 35 | wasLower: false }; 36 | 37 | // Note: There are othes like WiFi ... but nobody uses them thankfully ! 38 | if ( allowUpper == true ) 39 | { 40 | 41 | let lcDirective = lcFirst( directive ); 42 | if ( Object.values( constants ).indexOf( lcDirective ) >= 0 ) 43 | { 44 | //console.log("isCmd4Directive directive: %s returning: %s", directive, lcDirective ); 45 | return { key: lcDirective, 46 | wasLower: false }; 47 | } 48 | } 49 | 50 | //console.log("isCmd4Directive directive: %s returning null", directive ); 51 | 52 | return null; 53 | } 54 | 55 | 56 | module.exports = isCmd4Directive; 57 | -------------------------------------------------------------------------------- /test/getAccessoryUUID.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _api = new HomebridgeAPI(); // object we feed to Plugins 4 | var pluginModule = require( "../index" ); 5 | 6 | var getAccessoryUUID = require( "../utils/getAccessoryUUID.js" ); 7 | 8 | describe("Quick Testing load of index.js", ( ) => 9 | { 10 | it("API should not be null", ( ) => 11 | { 12 | assert.isNotNull(_api, "_api is null" ); 13 | }); 14 | 15 | it("index.js loaded should not be null", ( ) => 16 | { 17 | assert.isNotNull(pluginModule, "loading resulted in null" ); 18 | }); 19 | 20 | it("UUIDGen should be found", ( ) => 21 | { 22 | var t = typeof _api.hap.uuid.generate; 23 | assert.equal(t, "function" ); 24 | }); 25 | }); 26 | 27 | describe( "Testing getAccessoryUUID.js", ( ) => 28 | { 29 | it( "getAccessoryUUID.js should be a function", ( ) => 30 | { 31 | 32 | assert.isFunction( getAccessoryUUID, "getAccessoryUUID is not a function" ); 33 | }); 34 | 35 | it( "getAccessoryUUID should return a string ", ( ) => 36 | { 37 | let config = {displayName: "Kodi", 38 | name: "blah" 39 | }; 40 | let result = getAccessoryUUID( config, _api.hap.uuid); 41 | 42 | assert.isString( result, "getAccessoryUUID should return a string. result:: " + result ); 43 | }); 44 | 45 | it( "getAccessoryUUID should return a string length 36 ", ( ) => 46 | { 47 | let config = { name: "Kodi", 48 | configuredUUID: "blah" 49 | }; 50 | let result = getAccessoryUUID( config, _api.hap.uuid); 51 | 52 | assert.isString( result, "getAccessoryUUID should return a string. result:: " + result ); 53 | 54 | assert.equal( result.length, 36, "getAccessoryUUID return a string length of 36. result:: " + result.length ); 55 | }); 56 | }) 57 | -------------------------------------------------------------------------------- /utils/isAccDirective.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let CMD4_ACC_TYPE_ENUM = require( "../lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC_TYPE_ENUM; 4 | 5 | 6 | // Description: 7 | // Determine if parameter is a Cmd4 accessory characteristic 8 | // 9 | // @param type - The characteristic type to check. i.e. "On" 10 | // @param allowUpper - if upper case allowed to be checked. 11 | // @returns: { type: The CORRECT characteristic type 12 | // accTypeEnumIndex: The index of the characteristic 13 | // } or null 14 | // 15 | 16 | function isAccDirective( type, allowUpper = false ) 17 | { 18 | // For backward compatability of testStoredValueForIndex of FakeGato 19 | // we must return a null accTypeIndex, which should be checked instead 20 | // of just rc. 21 | let defaultRc = { "type": type, 22 | "accTypeEnumIndex": null 23 | }; 24 | 25 | if ( ! type ) 26 | { 27 | console.warn( "No parameter passed to isCmd4Directive" ); 28 | return defaultRc; 29 | } 30 | 31 | let accTypeEnumIndex; 32 | 33 | // We want lower case to be correct 34 | accTypeEnumIndex = CMD4_ACC_TYPE_ENUM.properties.indexOfEnum( i => i.sche === type ) 35 | if ( accTypeEnumIndex >= 0 ) 36 | return { "type": type, 37 | "accTypeEnumIndex": accTypeEnumIndex }; 38 | 39 | // Note: There are othes like WiFi ... but nobody uses them thankfully ! 40 | if ( allowUpper == true ) 41 | { 42 | accTypeEnumIndex = CMD4_ACC_TYPE_ENUM.properties.indexOfEnum( i => i.type === type ); 43 | 44 | // We return the correct lower case 45 | if ( accTypeEnumIndex >= 0 ) 46 | return { "type": CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].sche, 47 | "accTypeEnumIndex": accTypeEnumIndex }; 48 | } 49 | 50 | return defaultRc; 51 | } 52 | 53 | 54 | module.exports = isAccDirective; 55 | -------------------------------------------------------------------------------- /utils/createAccessorysInformationService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function createAccessorysInformationService( accessory ) 4 | { 5 | // Standalone accessories do not have platforms 6 | if ( accessory.platform ) 7 | { 8 | // Platform accessories may already have information sercices if 9 | // they are restored from cache by Homebridge. 10 | let informationService = accessory.platform.getService( accessory.api.hap.Service.AccessoryInformation ) 11 | if ( informationService ) 12 | { 13 | accessory.log.debug( `Using Existing ( cached ) accessory information service for: ${ accessory.displayName }` ); 14 | accessory.informationService = informationService; 15 | } 16 | } 17 | 18 | if ( ! accessory.informationService ) 19 | { 20 | accessory.log.debug( `Creating new accessory information service for: ${ accessory.displayName }` ); 21 | 22 | // Create accessory's Information Service 23 | accessory.informationService = new accessory.api.hap.Service.AccessoryInformation( ); 24 | } 25 | 26 | 27 | // Add/update the Model characteristic, if it is defined. 28 | if ( accessory.model ) 29 | accessory.informationService 30 | .setCharacteristic( accessory.api.hap.Characteristic.Model, accessory.model ); 31 | 32 | // Add/update the Manufacturer characteristic, if it is defined. 33 | if ( accessory.manufacturer ) 34 | accessory.informationService 35 | .setCharacteristic( accessory.api.hap.Characteristic.Manufacturer, accessory.manufacturer ); 36 | 37 | // Add/update the serialNumber characteristic, if it is defined. 38 | if ( accessory.serialNumber ) 39 | accessory.informationService 40 | .setCharacteristic( accessory.api.hap.Characteristic.SerialNumber, accessory.serialNumber ); 41 | 42 | if ( accessory.services ) 43 | accessory.services.push( accessory.informationService ); 44 | } 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Raise a bug related report for cmd4. 4 | title: "[Bug]" 5 | labels: bug 6 | assignees: ztalbot2000 7 | 8 | --- 9 | ** Cmd4 No longer supported:** 10 | 11 | 12 | 13 | 14 | **Describe The Bug:** 15 | 16 | 17 | **To Reproduce:** 18 | 19 | 20 | **Expected Behaviour:** 21 | 22 | 23 | [**Link to Logs:**]() 24 | 25 | 26 | 27 | 28 | 29 | **Paste of Logs:** 30 | ``` 31 | 32 | ``` 33 | 34 | **Cmd4 Config:** 35 | 36 | 37 | 38 | ```json 39 | 40 | 41 | ``` 42 | 43 | **Screenshots:** 44 | 45 | 46 | **Environment:** 47 | 48 | * **Node.js Version**: 49 | * **NPM Version**: 50 | * **Homebridge Version**: 51 | * **homebridge-cmd4 Version**: 52 | * **Operating System**: Raspbian / Ubuntu / Debian / Windows / macOS / Docker / other 53 | * **Process Supervisor**: Systemd / init.d / pm2 / launchctl / Docker / hb-service / other / none 54 | 55 | 56 | -------------------------------------------------------------------------------- /utils/isDevDirective.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let ucFirst = require( "../utils/ucFirst" ); 4 | 5 | 6 | let CMD4_DEVICE_TYPE_ENUM = require( "../lib/CMD4_DEVICE_TYPE_ENUM" ).CMD4_DEVICE_TYPE_ENUM; 7 | 8 | 9 | // Description: 10 | // Determine if type is a Cmd4 device based CMD4_DEVICE_TYPE_ENUM.deviceName 11 | // 12 | // @param deviceName - The device name to check. i.e. "Switch" 13 | // @param allowUpper - if upper case allowed to be checked. 14 | // @returns: { deviceName: The CORRECT device name 15 | // devEnumIndex: The index of the characteristic 16 | // } or null 17 | 18 | 19 | function isDevDirective( deviceName, allowUpper = false ) 20 | { 21 | // We return similiar to isAccDirective 22 | // We must return a null devTypeIndex, which should be checked instead 23 | // of just rc, like isAccDirective 24 | let defaultRc = { "deviceName": deviceName, 25 | "devEnumIndex": null 26 | }; 27 | 28 | if ( ! deviceName ) 29 | { 30 | console.warn( "No parameter passed to isDevDirective" ); 31 | return defaultRc; 32 | } 33 | 34 | let devEnumIndex; 35 | 36 | // We want lower case to be correct 37 | devEnumIndex = CMD4_DEVICE_TYPE_ENUM.indexOfEnum( deviceName ); 38 | if ( devEnumIndex >= 0 ) 39 | return { "deviceName": deviceName, 40 | "devEnumIndex": devEnumIndex }; 41 | 42 | // Note: There are othes like WiFi ... but nobody uses them thankfully ! 43 | if ( allowUpper == true ) 44 | { 45 | let ucDeviceName = ucFirst( deviceName ); 46 | devEnumIndex = CMD4_DEVICE_TYPE_ENUM.indexOfEnum( ucDeviceName ); 47 | 48 | // We return the correct upper case 49 | if ( devEnumIndex >= 0 ) 50 | return { "deviceName": CMD4_DEVICE_TYPE_ENUM.devEnumIndexToC( devEnumIndex ), 51 | "devEnumIndex": devEnumIndex }; 52 | } 53 | 54 | return defaultRc; 55 | } 56 | 57 | 58 | module.exports = isDevDirective; 59 | -------------------------------------------------------------------------------- /postinstall.js: -------------------------------------------------------------------------------- 1 | // Post install notes 2 | 3 | 4 | // Fun colour stuff 5 | const chalk = require( "chalk" ); 6 | 7 | const myPkg = require( "./package.json" ); 8 | 9 | const { isUpgrade } = require( "./utils/versionChecker" ); 10 | 11 | // To use await you must be in an async function, so put it in one. 12 | ( async( ) => 13 | { 14 | // Wait for the Promise of isUpgrade to complete. 15 | let lv = await isUpgrade( ); 16 | 17 | if ( lv == true ) 18 | { 19 | console.log( chalk.green( `[UPDATE AVAILABLE] ` ) + `Version ${lv} of ${myPkg.name} is available. Any release notes can be found here: ` + chalk.underline( `${myPkg.changelog}` ) ); 20 | } 21 | console.log( chalk.yellow( `HomeBridge-Cmd4 5.0.0+ Important Notice:\n` ) ); 22 | console.log( `Cmd4 has been optimized for simplification and best practices. Its configuration has changed to what is recommended by Homebridge. See https://git.io/JtMGR.\n` ); 23 | console.log( `Gone are the are the very confusing Cmd4_Mode and RestartRecovery. The only changes you will see are the warnings that these options are no longer required.` ); 24 | console.log( chalk.red( `* ` ) + `RestartRecovery is now automatic; which not enabling could cause your device to turn on/off over a restart.` ); 25 | console.log( chalk.red( `* ` ) + `Cmd4_Mode is as per https://git.io/JtMGR where the callback is immediate to homebridge with the data from your device to follow.` ); 26 | console.log( chalk.red( `* ` ) + `Demo mode is still available by not defining any polling.\n` ); 27 | 28 | console.log( chalk.underline( `Cmd4 New Users` ) ); 29 | console.log( chalk.green( `* ` ) + `You will need to follow the README to continue the configuration of HomeBridge-CMD4.\n` ); 30 | 31 | console.log(`\n As always, if you like this plugin, don't forget to star it on GitHub.\n`); 32 | console.log(`\n Enjoy`); 33 | console.log(` John Talbot\n`); 34 | })( ); 35 | 36 | -------------------------------------------------------------------------------- /test/extractKeyValue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var CMD4_ACC_TYPE_ENUM = { 4 | AccessoryFlags: 0, 5 | properties: {} 6 | }; 7 | CMD4_ACC_TYPE_ENUM.properties = 8 | { 9 | 0: { type: "AccessoryFlags", 10 | // characteristic: Characteristic.AccessoryFlags, 11 | // props: {format: Characteristic.Formats.UINT32, 12 | // perms: [Characteristic.Perms.READ, 13 | // Characteristic.Perms.NOTIFY 14 | // ] 15 | // }, 16 | validValues: 17 | {"OPEN": 0, 18 | "CLOSED": 1, 19 | "OPENING": 2, 20 | "CLOSING": 3, 21 | "STOPPED": 4 22 | } 23 | } 24 | }; 25 | 26 | var extractKeyValue = require( "../utils/extractKeyValue.js" ); 27 | 28 | describe( "Testing extractKeyValue", ( ) => 29 | { 30 | it( "extractKeyValue should be a function", ( ) => 31 | { 32 | assert.isFunction( extractKeyValue, "extractKeyValue is not a function" ); 33 | }); 34 | 35 | it( "test 1 extractKeyValue should return correct key", ( ) => 36 | { 37 | let expectedKey = "CLOSING"; 38 | let value = 3; 39 | let result = extractKeyValue( CMD4_ACC_TYPE_ENUM.properties[0].validValues, value ); 40 | assert.equal( result, expectedKey, "Test 1 extractKeyValue( " + value + " ) returned:" + result + " expected:" + expectedKey ); 41 | }); 42 | 43 | it( "test 2 extractKeyValue should return undefined for no value", ( ) => 44 | { 45 | let expectedKey = undefined; 46 | let value = undefined; 47 | let result = extractKeyValue( CMD4_ACC_TYPE_ENUM.properties["0"].validValues, value ); 48 | assert.equal( result, expectedKey, "Test 1 extractKeyValue( " + value + " ) returned:" + result + " expected:" + expectedKey ); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /utils/indexOfEnumLintTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is not a utility. This is a testcase, but not a Unit test either. 4 | // Lint had picked up an error that indexOfEnum was set, but not used; though 5 | // the function worked perfectly as proven by unit testing. 6 | // What I had found is that without the Object.defineProperty within the 7 | // requiring code, like this one, lint would fail. 8 | // 9 | // This test is placed here, just to remember that reason. 10 | 11 | 12 | var HomebridgeAPI = require( "../node_modules/homebridge/lib/api" ).HomebridgeAPI; 13 | var _api = new HomebridgeAPI( ); // object we feed to Plugins 14 | 15 | 16 | 17 | var { indexOfEnum } = require( "../utils/indexOfEnum" ); 18 | 19 | Object.defineProperty(exports, "indexOfEnum", { enumerable: true, get: function () { return indexOfEnum.indexOfEnum; } }); 20 | 21 | let ACC_DATA = require('../lib/CMD4_ACC_TYPE_ENUM'); 22 | let DEVICE_DATA = require('../lib/CMD4_DEVICE_TYPE_ENUM'); 23 | 24 | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic ); 25 | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.hap.Service, _api.hap.Characteristic, _api.hap.Categories ); 26 | 27 | 28 | let i=0; 29 | let j = 63; 30 | let type; 31 | let ucKeyIndex; 32 | 33 | type = CMD4_DEVICE_TYPE_ENUM.properties[ j ].deviceName; 34 | console.log("checking type:" + type ); 35 | 36 | 37 | ucKeyIndex = CMD4_DEVICE_TYPE_ENUM.properties.indexOfEnum( i => i.deviceName === type); 38 | if ( ucKeyIndex < 0 ) 39 | { 40 | console.log("FAIL: Invalid device type:%s", type ); 41 | } else { 42 | console.log("PASS (:%s) %s = %s", type, i, ucKeyIndex ); 43 | } 44 | 45 | type="blah"; 46 | console.log("checking type:" + type ); 47 | 48 | 49 | ucKeyIndex = CMD4_DEVICE_TYPE_ENUM.properties.indexOfEnum( i => i.deviceName === type); 50 | if ( ucKeyIndex < 0 ) 51 | { 52 | console.log("PASS: Invalid device type:%s", type ); 53 | } else { 54 | console.log("FAIL: (%s) %s = %s", type, i, ucKeyIndex ); 55 | } 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support Request 3 | about: Stuck on one of the installation steps or having trouble with your script? 4 | title: "[Support]" 5 | labels: help wanted 6 | assignees: ztalbot2000 7 | 8 | --- 9 | ** Cmd4 No longer supported:** 10 | 11 | 12 | 13 | 14 | 15 | 16 | **Describe Your Problem:** 17 | 18 | 19 | [**Link to Logs:**]() 20 | 21 | 22 | 23 | 24 | 25 | **Paste of Logs:** 26 | ``` 27 | 28 | ``` 29 | 30 | **Cmd4 Config:** 31 | 32 | 33 | 34 | ```json 35 | 36 | 37 | ``` 38 | 39 | **Screenshots:** 40 | 41 | 42 | **Environment:** 43 | 44 | * **Node.js Version**: 45 | * **NPM Version**: 46 | * **Homebridge Version**: 47 | * **homebridge-cmd4 Version**: 48 | * **Operating System**: Raspbian / Ubuntu / Debian / Windows / macOS / Docker / other 49 | * **Process Supervisor**: Systemd / init.d / pm2 / launchctl / Docker / hb-service / other / none 50 | 51 | 52 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/Examples/DoorLock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # In this example we see a Raspberry Pi with a gpio pin that triggers 5 | # a lock mechanism. The lock is momentary. You should configure the GPIO by executing: 6 | # 7 | # echo 24 > /sys/class/gpio/export 8 | # echo "out" > /sys/class/gpio/gpio24/direction 9 | # 10 | # The corresponding config.json is 11 | # 12 | # { 13 | # "type": "LockMechanism", 14 | # "displayName": "Front Door", 15 | # "lockCurrentState": "SECURED", 16 | # "lockTargetState": "SECURED", 17 | # "name": "Front Door", 18 | # "manufacturer": "XYZ", 19 | # "model": "XYZ", 20 | # "serialNumber": "GPIO 24", 21 | # "polling": [ 22 | # { 23 | # "characteristic": "lockCurrentState", 24 | # "interval": 5, 25 | # "timeout": 4900 26 | # }, 27 | # { 28 | # "characteristic": "lockTargetState", 29 | # "interval": 5, 30 | # "timeout": 4900 31 | # } 32 | # ], 33 | # "stateChangeResponseTime": 0.2, 34 | # "state_cmd": "sh /homebridge/DoorLock.sh" 35 | # } 36 | 37 | #!/bin/sh 38 | 39 | STATE_FILE="/dev/shm/DoorLock.state" 40 | 41 | if [ ! -f "$STATE_FILE" ]; then 42 | echo 1 > $STATE_FILE 43 | fi 44 | 45 | STATE=$(cat $STATE_FILE) 46 | 47 | if [ "$1" = "Get" ]; then 48 | case $3 in 49 | "LockCurrentState") 50 | echo $STATE 51 | ;; 52 | "LockTargetState") 53 | echo $STATE 54 | echo 1 > $STATE_FILE 55 | ;; 56 | esac 57 | echo 0 > /sys/class/gpio/gpio24/value 58 | exit 0 59 | fi 60 | 61 | if [ "$1" = "Set" ]; then 62 | case $3 in 63 | "LockTargetState") 64 | if [ "$4" = "UNSECURED" ]; then 65 | echo 0 > $STATE_FILE 66 | echo 1 > /sys/class/gpio/gpio24/value 67 | sleep 0.1 68 | fi 69 | ;; 70 | esac 71 | echo 0 > /sys/class/gpio/gpio24/value 72 | exit 0 73 | fi 74 | 75 | exit 66 76 | -------------------------------------------------------------------------------- /test/isCmd4Directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let { indexOfEnum } = require( "../utils/indexOfEnum" ); 4 | Object.defineProperty(exports, "indexOfEnum", { enumerable: true, get: function () { return indexOfEnum.indexOfEnum; } }); 5 | 6 | let isCmd4Directive = require( "../utils/isCmd4Directive" ); 7 | 8 | 9 | var _api = new HomebridgeAPI(); // object we feed to Plugins 10 | 11 | // Init the library for all to use 12 | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.hap.Formats, _api.hap.Units, _api.hap.Perms ); 13 | 14 | 15 | // ******** QUICK TEST of SETUP ************* 16 | describe('Quick Test of Setup', ( ) => 17 | { 18 | it( `CMD4_ACC_TYPE_ENUM.EOL = ${ ACC_EOL }`, ( ) => 19 | { 20 | expect( CMD4_ACC_TYPE_ENUM.EOL ).to.equal( ACC_EOL ); 21 | }); 22 | }); 23 | 24 | // ******** TEST isCmd4Directive .************* 25 | 26 | describe( `Test isCmd4Directive import`, ( ) => 27 | { 28 | it( `isCmd4Directive should be a function `, ( ) => 29 | { 30 | assert.isFunction( isCmd4Directive, `isCmd4Directive is not a function. Found: ${ typeof isCmd4Directive }` ); 31 | }); 32 | }); 33 | 34 | describe( `Test isCmd4Directive`, ( ) => 35 | { 36 | it( `isCmd4Directive should identify a Cmd4 directive`, ( ) => 37 | { 38 | let directive = "polling"; 39 | let rc = isCmd4Directive( directive ); 40 | assert.isString( rc.key, `Unexpected result for isCmd4Directive` ); 41 | assert.equal( rc.key, directive, `Expected result to be "polling"` ); 42 | }); 43 | it( `isCmd4Directive should NOT identify an uppercase type `, ( ) => 44 | { 45 | let directive = "Polling"; 46 | let rc = isCmd4Directive( directive ); 47 | assert.isNull( rc, `Expected result to be null` ); 48 | }); 49 | it( `isCmd4Directive should NOT identify an unknown type `, ( ) => 50 | { 51 | let directive = "Blast"; 52 | let rc = isCmd4Directive( directive ); 53 | assert.isNull( rc, `Expected result to be null` ); 54 | }); 55 | it( `isCmd4Directive should identify an uppercase type if upperCase is checked`, ( ) => 56 | { 57 | let directive = "Polling"; 58 | let rc = isCmd4Directive( directive, true ); 59 | assert.isString( rc.key, `Expected result to be a string` ); 60 | assert.equal( rc.key, "polling", `Expected result to be "polling"` ); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /utils/VariableTimer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Orig 4 | var variableTimer = { 5 | running: false, 6 | iv: 5000, 7 | timeout: false, 8 | cb : function(){}, 9 | start : function(cb,iv){ 10 | var elm = this; 11 | clearInterval(this.timeout); 12 | this.running = true; 13 | if(cb) this.cb = cb; 14 | if(iv) this.iv = iv; 15 | this.timeout = setTimeout(function(){elm.execute(elm)}, this.iv); 16 | }, 17 | execute : function(e){ 18 | if(!e.running) return false; 19 | e.cb(); 20 | e.start(); 21 | }, 22 | stop : function(){ 23 | this.running = false; 24 | }, 25 | set_interval : function(iv){ 26 | clearInterval(this.timeout); 27 | this.start(false, iv); 28 | } 29 | }; 30 | */ 31 | 32 | // Taken from https://stackoverflow.com/questions/1280263/changing-the-interval-of-setinterval-while-its-running 33 | // 34 | // timer.start(function(){ 35 | // console.debug('go'); 36 | // }, 2000); 37 | // 38 | // timer.set_interval(500); 39 | // 40 | // timer.stop(); 41 | // 42 | 43 | class VariableTimer 44 | { 45 | constructor () 46 | { 47 | this.running = false; 48 | this.iv = 0; 49 | this.timeout = false; 50 | this.cb = function( ){ }; 51 | } 52 | start( cb , iv ) 53 | { 54 | var elm = this; 55 | clearInterval( this.timeout ); 56 | this.running = true; 57 | if ( cb ) this.cb = cb; 58 | if ( iv ) this.iv = iv; 59 | this.timeout = setTimeout( function( ){ elm.execute( elm ) }, this.iv ); 60 | } 61 | 62 | execute( e ) 63 | { 64 | if ( ! e.running ) return false; 65 | e.cb( ); 66 | e.start( ); 67 | } 68 | 69 | stop( ) 70 | { 71 | this.running = false; 72 | } 73 | 74 | set_interval( iv ) 75 | { 76 | // You can't change an interval when timer is not running 77 | if ( this.running == false ) 78 | { 79 | this.iv = iv; 80 | return; 81 | } 82 | 83 | // Do not change interval if less than .5 seconds difference 84 | let round = Math.trunc( iv / 500) * 500; 85 | if (round != this.iv ) 86 | { 87 | clearInterval( this.timeout ); 88 | this.start( false, round ); 89 | } 90 | } 91 | } 92 | 93 | //module.exports.VariableTimer = VariableTimer; 94 | module.exports = VariableTimer; 95 | -------------------------------------------------------------------------------- /test/versionChecker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isUpgrade, getLatestVersion, isVersionNewerThanPackagedVersion, getPackagedVersion } = require( "../utils/versionChecker" ); 4 | 5 | describe( `Testing versionChecker init`, ( ) => 6 | { 7 | it( `isUpgrade should be a function`, ( ) => 8 | { 9 | assert.isFunction( isUpgrade, `isUpgrade is not a function` ); 10 | }); 11 | 12 | it( `getLatestVersion should be a function`, ( ) => 13 | { 14 | assert.isFunction( getLatestVersion, `getLatestVersion is not a function` ); 15 | }); 16 | 17 | it( `isVersionNewerThanPackagedVersion should be a function`, ( ) => 18 | { 19 | assert.isFunction( isVersionNewerThanPackagedVersion, `isVersionNewerThanPackagedVersion is not a function` ); 20 | }); 21 | 22 | it( `getPackagedVersion should be a function`, ( ) => 23 | { 24 | assert.isFunction( getPackagedVersion, `getPackagedVersion is not a function` ); 25 | }); 26 | }); 27 | 28 | 29 | describe( `Testing versionChecker functionality`, ( ) => 30 | { 31 | it( `getPackagedVersion should return an string`, ( ) => 32 | { 33 | let result = getPackagedVersion( ); 34 | assert.isString( result, `getPackagedVersion failed: ${ typeof result }` ); 35 | }); 36 | 37 | it( `getLatestVersion should return an string`, async ( ) => 38 | { 39 | let result = await getLatestVersion( ); 40 | assert.isString( result, `getLatestVersion failed: ${ typeof result }` ); 41 | }).timeout(5000); 42 | 43 | it( `isVersionNewerThanPackagedVersion should return true for a high version`, ( ) => 44 | { 45 | let result = isVersionNewerThanPackagedVersion( "9.0.0" ); 46 | assert.isTrue( result, `isVersionNewerThanPackagedVersion expected: true: found: ${ result }` ); 47 | }); 48 | it( `isVersionNewerThanPackagedVersion should return false for a lower version`, ( ) => 49 | { 50 | let result = isVersionNewerThanPackagedVersion( "1.0.0" ); 51 | assert.isFalse( result, `isVersionNewerThanPackagedVersion expected: false: found: ${ result }` ); 52 | }); 53 | 54 | it( `isVersionNewerThanPackagedVersion should return false for same version`, async ( ) => 55 | { 56 | let latest = await getLatestVersion( ); 57 | let result = isVersionNewerThanPackagedVersion( latest ); 58 | assert.isFalse( result, `isVersionNewerThanPackagedVersion expected: false: found: ${ result }` ); 59 | }).timeout(5000); 60 | 61 | }) 62 | 63 | -------------------------------------------------------------------------------- /test/isAccDirective.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let { indexOfEnum } = require( "../utils/indexOfEnum" ); 4 | Object.defineProperty(exports, "indexOfEnum", { enumerable: true, get: function () { return indexOfEnum.indexOfEnum; } }); 5 | 6 | let isAccDirective = require( "../utils/isAccDirective" ); 7 | 8 | 9 | var _api = new HomebridgeAPI(); // object we feed to Plugins 10 | 11 | // Init the library for all to use 12 | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.hap.Formats, _api.hap.Units, _api.hap.Perms ); 13 | 14 | 15 | // ******** TEST isAccDirectivw .************* 16 | 17 | describe( `Test isAccDirective import`, ( ) => 18 | { 19 | it( `isAccDirectivw should be a function `, ( ) => 20 | { 21 | assert.isFunction( isAccDirective, `isAccDirective is not a function. Found: ${ typeof isAccDirective }` ); 22 | }); 23 | }); 24 | 25 | describe( `Test isAccDirective`, ( ) => 26 | { 27 | it( `isAccDirective should identify a type `, ( ) => 28 | { 29 | let type = CMD4_ACC_TYPE_ENUM.accEnumIndexToLC( CMD4_ACC_TYPE_ENUM.On ); 30 | let rc = isAccDirective( type ); 31 | assert.isString( rc.type, `Unexpected result for isAccDirective` ); 32 | assert.equal( rc.type, type, `Expected result to be "on"` ); 33 | assert.equal( rc.accTypeEnumIndex, CMD4_ACC_TYPE_ENUM.On, `UnExpected result` ); 34 | }); 35 | it( `isAccDirective should NOT identify an uppercase type `, ( ) => 36 | { 37 | let ucType = CMD4_ACC_TYPE_ENUM.accEnumIndexToUC( CMD4_ACC_TYPE_ENUM.On ); 38 | let rc = isAccDirective( ucType ); 39 | assert.isNull( rc.accTypeEnumIndex, `Expected result to be null` ); 40 | }); 41 | it( `isAccDirective should NOT identify an unknown type `, ( ) => 42 | { 43 | let type = "Blast"; 44 | let rc = isAccDirective( type ); 45 | assert.isNull( rc.accTypeEnumIndex, `Expected result to be null` ); 46 | }); 47 | it( `isAccDirective should identify an uppercase type if upperCase is checked`, ( ) => 48 | { 49 | let type = CMD4_ACC_TYPE_ENUM.accEnumIndexToLC( CMD4_ACC_TYPE_ENUM.On ); 50 | let ucType = CMD4_ACC_TYPE_ENUM.accEnumIndexToUC( CMD4_ACC_TYPE_ENUM.On ); 51 | 52 | let rc = isAccDirective( ucType, true ); 53 | assert.isString( rc.type, `Unexpected result for isAccDirective` ); 54 | assert.equal( rc.type, type, `Expected result to be "on"` ); 55 | assert.equal( rc.accTypeEnumIndex, CMD4_ACC_TYPE_ENUM.On, `UnExpected result` ); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/isJSON.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isJSON = require( "../utils/isJSON.js" ); 4 | 5 | describe( "Testing isJSON", ( ) => 6 | { 7 | it( "isJSON should be a function", ( ) => 8 | { 9 | assert.isFunction( isJSON, "isJSON is not a function" ); 10 | }); 11 | 12 | it( "isJSON should correctly identify a JSON object", ( ) => 13 | { 14 | let data = { "name": "John"}; 15 | let expectedResult = true; 16 | let result = isJSON( data ); 17 | assert.equal( result, expectedResult, "isJSON( " + data + " ) returned: " + result + " expected: " + expectedResult ); 18 | }); 19 | 20 | it( "isJSON should return false for a string", ( ) => 21 | { 22 | let data = "abcdef"; 23 | let expectedResult = false; 24 | let result = isJSON( data ); 25 | assert.equal( result, expectedResult, "isJSON( " + data + " ) returned: " + result + " expected: " + expectedResult ); 26 | }); 27 | 28 | it( "isJSON should return false for a number", ( ) => 29 | { 30 | let data = 12345; 31 | let expectedResult = false; 32 | let result = isJSON( data ); 33 | assert.equal( result, expectedResult, "isJSON( " + data + " ) returned: " + result + " expected: " + expectedResult ); 34 | }); 35 | 36 | it( "isJSON should return false for a float", ( ) => 37 | { 38 | let data = 3.1415; 39 | let expectedResult = false; 40 | let result = isJSON( data ); 41 | assert.equal( result, expectedResult, "isJSON( " + data + " ) returned: " + result + " expected: " + expectedResult ); 42 | }); 43 | 44 | it( "isJSON should return false for a boolean", ( ) => 45 | { 46 | let data = false; 47 | let expectedResult = false; 48 | let result = isJSON( data ); 49 | assert.equal( result, expectedResult, "isJSON( " + data + " ) returned: " + result + " expected: " + expectedResult ); 50 | }); 51 | 52 | it( "isJSON should return false for an array", ( ) => 53 | { 54 | let data = [1,2,3,4]; 55 | let expectedResult = false; 56 | let result = isJSON( data ); 57 | assert.equal( result, expectedResult, "isJSON( " + data + " ) returned: " + result + " expected: " + expectedResult ); 58 | }); 59 | 60 | it( "isJSON should return false for a undefined", ( ) => 61 | { 62 | let data = undefined; 63 | let expectedResult = false; 64 | let result = isJSON( data ); 65 | assert.equal( result, expectedResult, "isJSON( " + data + " ) returned: " + result + " expected: " + expectedResult ); 66 | }); 67 | }) 68 | 69 | -------------------------------------------------------------------------------- /test/echoScripts/testGetSetValues.js: -------------------------------------------------------------------------------- 1 | #!/opt/homebrew/bin/node 2 | 3 | const fs = require( "fs" ); 4 | 5 | function showHelp() 6 | { 7 | console.log(` 8 | Syntax: Get Device < fn > 9 | Set Device [< value >] < fn > 10 | 11 | 12 | Set writes stuff to < fn > as a required fn with 13 | INPUTS.IO 14 | INPUTS.DEVICE 15 | INPUTS.CHARACTERISTIC 16 | INPUTS.VALUE 17 | 18 | Get echos what Set would have sent 19 | 20 | 21 | ` ); 22 | process.exit( 666 ); 23 | } 24 | 25 | function createDeviceFileName( fn, DEVICE, CHARACTERISTIC ) 26 | { 27 | return `${ fn }_${ DEVICE }_${ CHARACTERISTIC }`; 28 | } 29 | 30 | function doGet( ) 31 | { 32 | if ( ELEMENTS != 6 ) { showHelp( ); process.exit( 1 ); } 33 | 34 | let fn = process.argv[5]; 35 | 36 | let deviceFile = createDeviceFileName( fn, DEVICE, CHARACTERISTIC ); 37 | 38 | let INPUTS; 39 | 40 | try { 41 | INPUTS = require( deviceFile ); 42 | } catch ( e ) { 43 | console.log(`Cannot load fn: ${ deviceFile }, error: ${ e }`); 44 | process.exit( 666 ); 45 | } 46 | 47 | console.log( `${ INPUTS.VALUE }` ); 48 | 49 | process.exit( 0 ); 50 | } 51 | 52 | function doSet( ) 53 | { 54 | if ( ELEMENTS != 7 ) { showHelp( ); process.exit( 1 ); }; 55 | 56 | let VALUE = process.argv[5]; 57 | let fn = process.argv[6]; 58 | 59 | let deviceFile = createDeviceFileName( fn, DEVICE, CHARACTERISTIC ); 60 | 61 | // Skip the fn 62 | ELEMENTS--; 63 | 64 | // Remove the old fn 65 | try { 66 | fs.unlinkSync( deviceFile ); 67 | } catch(err) { 68 | // Don't care 69 | // console.error( err ) 70 | // process.exit( 666 ); 71 | } 72 | 73 | let data = `exports.IO="${ IO }"; 74 | exports.DEVICE="${ DEVICE }"; 75 | exports.CHARACTERISTIC="${ CHARACTERISTIC }"; 76 | exports.VALUE="${ VALUE }";\n`; 77 | 78 | // Write the arguments as requires 79 | fs.writeFileSync( deviceFile, data, function( err ) 80 | { 81 | console.log(` Error writing to fn: ${ deviceFile } error: ${ err }\n`); 82 | process.exit( 666 ); 83 | }); 84 | 85 | process.exit( 0 ); 86 | } 87 | 88 | let ELEMENTS = process.argv.length; 89 | if ( ELEMENTS < 4 ) { showHelp( ); process.exit( 1 ); } 90 | 91 | let IO = process.argv[2]; 92 | let DEVICE = process.argv[3]; 93 | let CHARACTERISTIC = process.argv[4]; 94 | 95 | switch( IO ) 96 | { 97 | case "Get": 98 | doGet( ); 99 | break; 100 | case "Set": 101 | doSet( ); 102 | break; 103 | default: 104 | console.log(`Invalid Get/Set: ${IO}` ); 105 | showHelp(); 106 | } 107 | process.exit( 1 ); 108 | -------------------------------------------------------------------------------- /test/isDevDirective.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let { indexOfEnum } = require( "../utils/indexOfEnum" ); 4 | Object.defineProperty(exports, "indexOfEnum", { enumerable: true, get: function () { return indexOfEnum.indexOfEnum; } }); 5 | 6 | let isDevDirective = require( "../utils/isDevDirective" ); 7 | let lcFirst = require( "../utils/lcFirst" ); 8 | 9 | 10 | var _api = new HomebridgeAPI(); // object we feed to Plugins 11 | 12 | // Init the library for all to use 13 | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.hap.Formats, _api.hap.Units, _api.hap.Perms ); 14 | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.hap.Service, _api.hap.Characteristic, _api.hap.Categories ); 15 | 16 | 17 | 18 | // ******** TEST isDevDirectivw .************* 19 | 20 | describe( `Test isDevDirective import`, ( ) => 21 | { 22 | it( `isDevDirectivw should be a function `, ( ) => 23 | { 24 | assert.isFunction( isDevDirective, `isDevDirective is not a function. Found: ${ typeof isDevDirective }` ); 25 | }); 26 | }); 27 | 28 | describe( `Test isDevDirective`, ( ) => 29 | { 30 | it( `isDevDirective should identify a device `, ( ) => 31 | { 32 | let deviceName = CMD4_DEVICE_TYPE_ENUM.devEnumIndexToC( CMD4_DEVICE_TYPE_ENUM.Switch ); 33 | let rc = isDevDirective( deviceName ); 34 | assert.isString( rc.deviceName, `Unexpected result for isDevDirective` ); 35 | assert.equal( rc.deviceName, deviceName, `Expected result to be "on"` ); 36 | assert.equal( rc.devEnumIndex, CMD4_DEVICE_TYPE_ENUM.Switch, `UnExpected result` ); 37 | }); 38 | it( `isDevDirective should NOT identify an lowercase deviceName `, ( ) => 39 | { 40 | let deviceName = CMD4_DEVICE_TYPE_ENUM.devEnumIndexToC( CMD4_DEVICE_TYPE_ENUM.Switch ); 41 | let lcDeviceName = lcFirst( deviceName ); 42 | let rc = isDevDirective( lcDeviceName ); 43 | assert.isNull( rc.devEnumIndex, `Expected result to be null` ); 44 | }); 45 | it( `isDevDirective should NOT identify an unknown deviceName `, ( ) => 46 | { 47 | let deviceName = "Blast"; 48 | let rc = isDevDirective( deviceName ); 49 | assert.isNull( rc.devEnumIndex, `Expected result to be null` ); 50 | }); 51 | it( `isDevDirective should identify an lower case deviceName if lowerCase is checked`, ( ) => 52 | { 53 | let deviceName = CMD4_DEVICE_TYPE_ENUM.devEnumIndexToC( CMD4_DEVICE_TYPE_ENUM.Switch ); 54 | let lcDeviceName = lcFirst( deviceName ); 55 | let rc = isDevDirective( lcDeviceName, true ); 56 | 57 | assert.isString( rc.deviceName, `Unexpected result for isDevDirective` ); 58 | assert.equal( rc.deviceName, deviceName, `Expected result to be "on"` ); 59 | assert.equal( rc.devEnumIndex, CMD4_DEVICE_TYPE_ENUM.Switch, `UnExpected result` ); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /utils/HV.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const constants = require( "../cmd4Constants" ); 4 | 5 | // These would already be initialized by index.js 6 | let CMD4_DEVICE_TYPE_ENUM = require( "../lib/CMD4_DEVICE_TYPE_ENUM" ).CMD4_DEVICE_TYPE_ENUM; 7 | 8 | class HV 9 | { 10 | constructor () 11 | { 12 | this.allowTLV8 = constants.DEFAULT_ALLOW_TLV8; 13 | this.debug = constants.DEFAULT_DEBUG; 14 | this.outputConstants = constants.DEFAULT_OUTPUTCONSTANTS; 15 | this.interval = constants.DEFAULT_INTERVAL; 16 | this.stateChangeResponseTime = constants.DEFAULT_STATE_CHANGE_RESPONSE_TIME; 17 | this.statusMsg = constants.DEFAULT_STATUSMSG; 18 | this.timeout = constants.DEFAULT_TIMEOUT; 19 | 20 | this.stateChangeResponseTimeHasBeenUpdated = false; 21 | } 22 | 23 | update( entity ) 24 | { 25 | // Heirarchy Next the Device Properties 26 | if ( entity.typeIndex != undefined && 27 | this.stateChangeResponseTimeHasBeenUpdated == false ) 28 | { 29 | this.stateChangeResponseTime = CMD4_DEVICE_TYPE_ENUM.properties[ entity.typeIndex ].devicesStateChangeDefaultTime; 30 | } 31 | 32 | // FakeGato Hierarchy 33 | if ( entity.storage != undefined ) 34 | this.storage = entity.storage; 35 | if ( entity.storagePath != undefined ) 36 | this.storagePath = entity.storagePath; 37 | if ( entity.folder != undefined ) 38 | this.folder = entity.folder; 39 | if ( entity.keyPath != undefined ) 40 | this.keyPath = entity.keyPath; 41 | 42 | // Heirarchy 43 | if ( entity.allowTLV8 != undefined ) 44 | this.allowTLV8 = entity.allowTLV8; 45 | 46 | 47 | if ( entity.debug != undefined ) 48 | this.debug = entity.debug; 49 | if ( entity.definitions != undefined ) 50 | this.definitions = entity.definitions; 51 | if ( entity.interval != undefined ) 52 | this.interval = entity.interval; 53 | if ( entity.outputConstants != undefined ) 54 | this.outputConstants = entity.outputConstants; 55 | if ( entity.queueTypes != undefined ) 56 | this.queueTypes = entity.queueTypes; 57 | if ( entity.stateCmd != undefined ) 58 | this.stateCmd = entity.stateCmd; 59 | if ( entity.state_cmd_prefix != undefined ) 60 | this.state_cmd_prefix = entity.state_cmd_prefix; 61 | if ( entity.stateCmdSuffix != undefined ) 62 | this.state_cmd_suffix = entity.state_cmd_suffix; 63 | if ( entity.stateChangeResponseTime != undefined ) 64 | { 65 | this.stateChangeResponseTimeHasBeenUpdated = true; 66 | this.stateChangeResponseTime = entity.stateChangeResponseTime; 67 | } 68 | if ( entity.statusMsg != undefined ) 69 | this.statusMsg = entity.statusMsg; 70 | if ( entity.timeout != undefined ) 71 | this.timeout = entity.timeout; 72 | } 73 | 74 | } 75 | 76 | module.exports = HV; 77 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/Examples/basic_ping.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # This Cmd4 example demonstrates how you can test if an accessory is on the network using ping. 5 | # 6 | # Your Cmd4 .homebridge/.config.json file would have a state_cmd like: 7 | # state_cmd: ".homebridge/Cmd4Scripts/Examples/ping.sh" 8 | # 9 | # 10 | # Testing from the shell prompt: 11 | # ./basic_ping.sh Get My_TV On 12 | # or 13 | # ./basic_ping.sh Set My_TV On 1 14 | 15 | 16 | 17 | # Exit immediately if a command exits with a non-zero status 18 | set -e 19 | 20 | # Define the accessories IP you wish to test 21 | ip="192.168.2.1" 22 | 23 | # Check if the first parameter to this script was "Get" for getting an accessory's 24 | # specific attribute. 25 | if [ "$1" = "Get" ]; then 26 | 27 | # Normally we would exit immediately if a command fails with a non-zero status. 28 | # In this case ping can fail and we would rely on the failing exit status to 29 | # tell Cmd4 that the accessory is not on the network. That would be the prefered 30 | # thing to do. However for this example we are going to output '0' (false) so 31 | # that you can see the '0' on the console telling us that the accessory is not 32 | # on the network. 33 | set +e 34 | 35 | # $2 would be the name of the accessory 36 | # $3 would be the accessory's charactersistic 'On' 37 | # On OSX the string is returned differently than on linux. 38 | ping -c 2 -W 1 "${ip}" | sed -E 's/2 packets received/2 received/g' | grep -i '2 received' >> /dev/null 39 | rc=$? 40 | 41 | # Exit immediately if a command exits with a non-zero status 42 | set -e 43 | 44 | # Check if we got the message '2 packets recieved' meaning the accessory is 45 | # on the network by seeing if the return code of the above command passed or 46 | # failed. 47 | if [ "$rc" = "0" ]; then 48 | # The message was recieved so the target is up, sending a '1' (true), like 49 | # a binary number is, back to Cmd4. 50 | echo "1" 51 | 52 | # Exit this script positivitely. 53 | exit 0 54 | else 55 | # The message was not recieved so the target must be down, sending a '0' (false), like 56 | # a binary number is, back to Cmd4. 57 | echo "0" 58 | 59 | # Exit this script positivitely, even though ping failed. 60 | exit 0 61 | fi 62 | fi 63 | 64 | # Check if the first parameter to this script was "Set" for setting an accessory's 65 | # specific attribute. 66 | if [ "$1" = "Set" ]; then 67 | 68 | # $2 would be the name of the accessory. 69 | # $3 would be the accessory's charactersistic 'On'. 70 | # $4 would be '1' for 'On' and '0' for 'Off', like a binary number is. 71 | # $4 would be 'true' for 'On' and 'false' for 'Off' with 72 | # outputConstants=true in your .homebridge/.config.json file. 73 | 74 | # This ping script does not do anything for set so just exit successfully. 75 | exit 0 76 | fi 77 | 78 | # The proper arguments to this script were not passed to it so end with a failure exit status. 79 | exit 66 80 | -------------------------------------------------------------------------------- /test/VariableTimer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | let VariableTimer = require( "../utils/VariableTimer" ); 5 | 6 | 7 | describe('A Variable Timer Test', ( ) => 8 | { 9 | it( "Test creation of variableTimer", ( ) => 10 | { 11 | const timer = new VariableTimer( ); 12 | 13 | assert.instanceOf( timer , VariableTimer, "Expected timer to be instance of VariableTimer. Found %s" , timer ); 14 | assert.isFunction( timer.start, ".start is not a function" ); 15 | assert.isFunction( timer.stop, ".stop is not a function" ); 16 | assert.isFunction( timer.set_interval, ".set_interval is not a function" ); 17 | 18 | }); 19 | 20 | // Only skip this test because it takes 20 seconds 21 | it.skip( "Test timer can change intervals, stop start ...", ( done ) => 22 | { 23 | const timer = new VariableTimer( ); 24 | var start = new Date(); 25 | var total = 0; 26 | 27 | timer.start( ( ) => 28 | { 29 | var end = new Date(); 30 | var seconds = ( end - start) / 1000 31 | total += Math.trunc( seconds ); 32 | start = end; 33 | console.log(`done ${ seconds } ${ total }`); 34 | }, 1000); 35 | assert.equal( timer.iv, 1000, "iv is set incorrectly" ); 36 | setTimeout(() => 37 | { 38 | console.log("changing timer to 2 seconds."); 39 | timer.set_interval( 2000 ); 40 | assert.equal( timer.iv, 2000, "iv is reset incorrectly" ); 41 | assert.equal( total, 4, "total is incorrect" ); 42 | setTimeout(() => 43 | { 44 | assert.equal( total, 12, "total is incorrect" ); 45 | timer.stop(); 46 | done(); 47 | }, 10000); 48 | }, 5000); 49 | 50 | }).timeout(20000); 51 | 52 | it( "Test timer can change intervals, only by .5s increments", ( done ) => 53 | { 54 | const timer = new VariableTimer( ); 55 | var start = new Date(); 56 | var total = 0; 57 | 58 | timer.start( ( ) => 59 | { 60 | var end = new Date(); 61 | var seconds = ( end - start) / 1000 62 | total += Math.trunc( seconds ); 63 | start = end; 64 | console.log(`done ${ seconds } ${ total }`); 65 | }, 1000); 66 | assert.equal( timer.iv, 1000, "iv is set incorrectly" ); 67 | timer.set_interval( 2000 ); 68 | assert.equal( timer.iv, 2000, "iv reset to 2000 incorrectly" ); 69 | timer.set_interval( 2100 ); 70 | assert.equal( timer.iv, 2000, "iv reset incorrectly to 2100" ); 71 | timer.set_interval( 2500 ); 72 | assert.equal( timer.iv, 2500, "iv reset incorrectly to 2500" ); 73 | timer.set_interval( 2999 ); 74 | assert.equal( timer.iv, 2500, "iv reset incorrectly to 2999" ); 75 | timer.set_interval( 3000 ); 76 | assert.equal( timer.iv, 3000, "iv reset incorrectly to 3000" ); 77 | timer.set_interval( 2500 ); 78 | assert.equal( timer.iv, 2500, "iv reset incorrectly down to 2500" ); 79 | timer.stop(); 80 | done( ); 81 | 82 | }).timeout(20000); 83 | }); 84 | 85 | -------------------------------------------------------------------------------- /utils/versionChecker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // These routines are used to get this packages 4 | // version information. 5 | // It not only uses Promises but creates Promises 6 | // and is documented as such. 7 | 8 | const latestVersion = require( "latest-version" ); 9 | 10 | // Retrieve the package information that contains 11 | // things like the current version. 12 | const myPkg = require( "../package.json" ); 13 | 14 | // Split the version into its sub components. 15 | function splitVersion( version ) 16 | { 17 | let parts = version.split( "." ); 18 | return { "version": parts[ 0 ], "major": parts[ 1 ], "minor": parts[ 2 ] }; 19 | } 20 | 21 | // getLatestVersion could just be defined as: 22 | // function getLatestVersion ( ) 23 | // However, defining it this way signifies it 24 | // returns a Promise. In this case the Promise 25 | // given to us by latestVersion. 26 | const getLatestVersion = async ( ) => 27 | { 28 | return latestVersion( myPkg.name ); 29 | } 30 | 31 | function getPackagedVersion( ) 32 | { 33 | return myPkg.version; 34 | } 35 | 36 | // Check that there is a possible upgrade out there. 37 | function isVersionNewerThanPackagedVersion( version ) 38 | { 39 | // The default return code. 40 | let rc = false; 41 | 42 | // Split the version components into their sub components 43 | let installedVersionInfo = splitVersion( myPkg.version ); 44 | let gitVersionInfo = splitVersion( version ); 45 | 46 | // Set the return code appropriately 47 | if ( Number( gitVersionInfo.version ) > Number( installedVersionInfo.version ) ) 48 | return true; 49 | if ( Number( gitVersionInfo.version ) < Number( installedVersionInfo.version ) ) 50 | return false; 51 | if ( Number( gitVersionInfo.major ) > Number( installedVersionInfo.major ) ) 52 | return true; 53 | if ( Number( gitVersionInfo.major ) < Number( installedVersionInfo.major ) ) 54 | return false; 55 | if ( Number( gitVersionInfo.minor ) > Number( installedVersionInfo.minor ) ) 56 | return true; 57 | 58 | return rc; 59 | } 60 | 61 | 62 | // Check that there is a possible upgrade out there. 63 | function isUpgrade( ) 64 | { 65 | // Create a new Promise that will be fufilled when we processed the 66 | // information provided to us by the Promise of getLatestVersion. 67 | // You cannot take an asynchronous call and convert it to a synchronous 68 | // call unless you would create a timer and wait forever? for it 69 | // to complete, which defeats the purpose of node.js. 70 | return new Promise( ( resolve ) => 71 | { 72 | // To use the promise of getLatestVersion, it must be in an async function 73 | // so put it in one. 74 | ( async( ) => 75 | { 76 | // Wait for the Promise of getLatestVersion to complete 77 | let lv = await getLatestVersion( ); 78 | 79 | resolve( isVersionNewerThanPackagedVersion( lv ) ); 80 | } 81 | )( ); 82 | }); 83 | } 84 | 85 | // Export the internal functions we wish to expose. 86 | module.exports = { isUpgrade, getLatestVersion, isVersionNewerThanPackagedVersion, getPackagedVersion }; 87 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/Examples/advanced_ping.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # This Cmd4 example demonstrates a little more advanced way of using ping to test if an 5 | # accessory is on the network by passing in the IP address to be used with the Cmd4 option 6 | # of state_cmd_suffix. 7 | # 8 | # Your Cmd4 .homebridge/.config.json file would have a state_cmd like: 9 | # state_cmd: ".homebridge/Cmd4Scripts/Examples/ping.sh" 10 | # state_cmd_suffix: "192.168.2.1" 11 | # 12 | # Testing from the shell prompt: 13 | # ./advanced_ping.sh Get My_TV On 192.168.2.1 14 | # or 15 | # ./advanced_ping.sh Set My_TV On 1 192.168.2.1 16 | 17 | # Exit immediately if a command exits with a non-zero status 18 | set -e 19 | 20 | # Check if the first parameter to this script was "Get" for getting an accessory's 21 | # specific attribute. 22 | if [ "$1" = "Get" ]; then 23 | 24 | # Cmd4 will pass the IP in the config.json defined by state_cmd_suffix as the fourth 25 | # parameter to a Get command. 26 | ip="${4}" 27 | 28 | # Normally we would exit immediately if a command fails with a non-zero status. 29 | # In this case ping can fail and we would rely on the failing exit status to 30 | # tell Cmd4 that the accessory is not on the network. That would be the prefered 31 | # thing to do. However for this example we are going to output '0' (false) so 32 | # that you can see the '0' on the console telling us that the accessory is not 33 | # on the network. 34 | set +e 35 | 36 | # $2 would be the name of the accessory 37 | # $3 would be the accessory's charactersistic 'On' 38 | # On OSX the string is returned differently than on linux. 39 | ping -c 2 -W 1 "${ip}" | sed -E 's/2 packets received/2 received/g' | grep -i '2 received' >> /dev/null 40 | rc=$? 41 | 42 | # Exit immediately if a command exits with a non-zero status 43 | set -e 44 | 45 | # Check if we got the message '2 packets recieved' meaning the accessory is 46 | # on the network by seeing if the return code of the above command passed or 47 | # failed. 48 | if [ "$rc" = "0" ]; then 49 | # The message was recieved so the target is up, sending a '1' (true), like 50 | # a binary number is, back to Cmd4. 51 | echo "1" 52 | 53 | # Exit this script positivitely. 54 | exit 0 55 | else 56 | # The message was not recieved so the target must be down, sending a '0' (false), like 57 | # a binary number is, back to Cmd4. 58 | echo "0" 59 | 60 | # Exit this script positivitely, even though ping failed. 61 | exit 0 62 | fi 63 | fi 64 | 65 | # Check if the first parameter to this script was "Set" for setting an accessory's 66 | # specific attribute. 67 | if [ "$1" = "Set" ]; then 68 | 69 | # $2 would be the name of the accessory. 70 | # $3 would be the accessory's charactersistic 'On'. 71 | # $4 would be '1' for 'On' and '0' for 'Off', like a binary number is. 72 | # $4 would be 'true' for 'On' and 'false' for 'Off' with 73 | # outputConstants=true in your .homebridge/.config.json file. 74 | 75 | # Cmd4 will pass the IP in the config.json defined by state_cmd_suffix as the fifth 76 | # parameter to a Set command. 77 | ip="${5}" 78 | 79 | # This ping script does not do anything for set so just exit successfully. 80 | exit 0 81 | fi 82 | 83 | # The proper arguments to this script were not passed to it so end with a failure exit status. 84 | exit 66 85 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // 4 | // Homebridge 5 | // Flow / \ 6 | // / \ 7 | // api.registerPlatform api.registerAccessory 8 | // forEach Accessories{ } Any { } before/after Accessories{ } 9 | // Cmd4Platform Cmd4Accessory 10 | // Cmd4Accessory 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // 17 | // 18 | // 19 | // 20 | // 21 | 22 | // The Cmd4 Classes 23 | const Cmd4Accessory = require( "./Cmd4Accessory" ).Cmd4Accessory; 24 | const Cmd4Platform = require( "./Cmd4Platform" ).Cmd4Platform; 25 | 26 | const settings = require( "./cmd4Settings" ); 27 | 28 | // Pretty colors 29 | const chalk = require( "chalk" ); 30 | 31 | // The Library files that know all. 32 | var CHAR_DATA = require( "./lib/CMD4_CHAR_TYPE_ENUMS" ); 33 | var ACC_DATA = require( "./lib/CMD4_ACC_TYPE_ENUM" ); 34 | var DEVICE_DATA = require( "./lib/CMD4_DEVICE_TYPE_ENUM" ); 35 | 36 | module.exports = 37 | { 38 | default: function ( api ) 39 | { 40 | // Init the libraries for all to use 41 | let CMD4_CHAR_TYPE_ENUMS = CHAR_DATA.init( api.hap.Formats, api.hap.Units, api.hap.Perms ); 42 | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( api.hap.Characteristic, api.hap.Formats, api.hap.Units, api.hap.Perms ); 43 | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( 44 | CMD4_ACC_TYPE_ENUM, api.hap.Service, api.hap.Characteristic, api.hap.Categories ); 45 | 46 | api.registerAccessory( settings.PLATFORM_NAME, Cmd4Accessory ); 47 | api.registerPlatform( settings.PLATFORM_NAME, Cmd4Platform ); 48 | 49 | setTimeout( checkForUpdates, 1800 ); 50 | 51 | // This is not required by homebridge and does not affect it. I use it for 52 | // unit testing. 53 | return { CMD4_CHAR_TYPE_ENUMS, 54 | CMD4_ACC_TYPE_ENUM, 55 | CMD4_DEVICE_TYPE_ENUM, 56 | api 57 | }; 58 | }, 59 | // These would be the uninitialized values, 60 | // used for unit testing 61 | CHAR_DATA: CHAR_DATA, // properties would be { } empty. 62 | ACC_DATA: ACC_DATA, // properties would be { } empty. 63 | DEVICE_DATA: DEVICE_DATA // properties would be { } empty. 64 | } 65 | 66 | function checkForUpdates( ) 67 | { 68 | // Don't show the updates message in mocha test mode 69 | if ( process.argv.includes( "test/mocha-setup" ) ) 70 | return; 71 | 72 | const { getLatestVersion, isVersionNewerThanPackagedVersion } = require( "./utils/versionChecker" ); 73 | const myPkg = require( "./package.json" ); 74 | 75 | ( async( ) => 76 | { 77 | // Fix for #127, constant crash loops when no internet connection 78 | // trying to get latest Cmd4 version. 79 | // thx nano9g 80 | try 81 | { 82 | let lv = await getLatestVersion( ); 83 | 84 | if ( isVersionNewerThanPackagedVersion( lv ) ) 85 | { 86 | console.log( chalk.green( `[UPDATE AVAILABLE] ` ) + `Version ${lv} of ${myPkg.name} is available. Any release notes can be found here: ` + chalk.underline( `${myPkg.changelog}` ) ); 87 | } 88 | 89 | } 90 | catch( error ) 91 | { 92 | console.log( chalk.yellow( `[UPDATE CHECK FAILED] ` ) + `Could not check for newer versions of ${myPkg.name} due to error ${error.name}: ${error.message}`) 93 | } 94 | })( ); 95 | } 96 | -------------------------------------------------------------------------------- /test/loadPluginTest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // ***************** TEST LOADING ********************** 4 | 5 | 6 | var pluginModule = require( "../index" ); 7 | var CMD4_ACC_TYPE_ENUM = pluginModule.ACC_DATA.CMD4_ACC_TYPE_ENUM; 8 | var CMD4_DEVICE_TYPE_ENUM = pluginModule.DEVICE_DATA.CMD4_DEVICE_TYPE_ENUM; 9 | 10 | // While the above method is better, It doesn't check that 11 | // the key values are sequential. 12 | // You cannot break out of forEach .... 13 | function getIndexOfValue( obj, value ) 14 | { 15 | let count = 0; 16 | let found = -1; 17 | Object.keys( obj ).forEach( function( key ) 18 | { 19 | // console.log( "Checking: " + key + " count: " + count + " obj[key]: " + obj[key] + " for: " + value + " t1: " + typeof obj[key] + " t2: " + typeof value ); 20 | if ( obj[key] == value && value == count ) { found = value } 21 | count+=1; 22 | }); 23 | return found; 24 | } 25 | 26 | // ************ TEST PLUGIN WAS Loaded Successfully ************** 27 | describe( "Testing load of index.js", ( ) => 28 | { 29 | it( "Testing require of index.js", ( ) => 30 | { 31 | expect( pluginModule ).not.to.be.a( "null", "loaded plugin was null" ); 32 | }); 33 | 34 | it( "index.js default initializer should be found", ( ) => 35 | { 36 | expect( pluginModule.default ).to.be.a( "function", "plugin has no default init function t: " + typeof pluginModule.default); 37 | }); 38 | }); 39 | 40 | // ************ TEST UNINITIALIZED PLUGIN ************** 41 | describe( "Testing uninitialized plugin", ( ) => 42 | { 43 | // DEVICE_TYPE Testing 44 | it( "CMD4_DEVICE_TYPE_ENUM is defined", ( ) => 45 | { 46 | expect( CMD4_DEVICE_TYPE_ENUM ).not.to.be.a( "null", "CMD4_DEVICE_TYPE_ENUM is null" ); 47 | }); 48 | it( "CMD4_DEVICE_TYPE_ENUM has EOL", ( ) => 49 | { 50 | expect( CMD4_DEVICE_TYPE_ENUM.EOL ).not.to.be.a( "null", "CMD4_DEVICE_TYPE_ENUM.EOL is null" ); 51 | }); 52 | 53 | it( "CMD4_DEVICE_TYPE_ENUM.EOL = " + DEVICE_EOL, ( ) => 54 | { 55 | expect( CMD4_DEVICE_TYPE_ENUM.EOL ).to.equal( DEVICE_EOL, "CMD4_DEVICE_TYPE_ENUM.EOL. Expected: " + DEVICE_EOL + " found: " + CMD4_DEVICE_TYPE_ENUM.EOL ); 56 | }); 57 | 58 | it( "CMD4_DEVICE_TYPE_ENUM[ 0-" + CMD4_DEVICE_TYPE_ENUM.EOL + " ] to have a valid value", ( ) => 59 | { 60 | for ( let index=0; index < CMD4_DEVICE_TYPE_ENUM.EOL; index ++) 61 | { 62 | let keyIndex = getIndexOfValue( CMD4_DEVICE_TYPE_ENUM, index ); 63 | expect( keyIndex ).to.equal( index, "Expected value at index: " + index + " to be: " + index + " found: " + keyIndex ); 64 | } 65 | }); 66 | 67 | // ACC_TYPE Testing 68 | it( "CMD4_ACC_TYPE_ENUM is defined", ( ) => 69 | { 70 | expect( CMD4_ACC_TYPE_ENUM ).not.to.be.a( "null", "CMD4_ACC_TYPE_ENUM is null" ); 71 | }); 72 | it( "CMD4_ACC_TYPE_ENUM has EOL", ( ) => 73 | { 74 | expect( CMD4_ACC_TYPE_ENUM.EOL ).not.to.be.a( "null", "CMD4_ACC_TYPE_ENUM.EOL is null" ); 75 | }); 76 | it( "CMD4_ACC_TYPE_ENUM.EOL =" + ACC_EOL, ( ) => 77 | { 78 | expect( CMD4_ACC_TYPE_ENUM.EOL ).to.equal( ACC_EOL, "CMD4_ACC_TYPE_ENUM.EOL. Expected: " + ACC_EOL + " found: " + CMD4_ACC_TYPE_ENUM.EOL ); 79 | }); 80 | 81 | it( "CMD4_ACC_TYPE_ENUM[ 0-" + CMD4_ACC_TYPE_ENUM.EOL + " ] to have a valid value", ( ) => 82 | { 83 | for ( let index=0; index < CMD4_ACC_TYPE_ENUM.EOL; index ++ ) 84 | { 85 | let keyIndex = getIndexOfValue( CMD4_ACC_TYPE_ENUM, index ); 86 | expect( keyIndex ).to.equal( index, "Expected ACC ENUM at index: " + index + " to be: " + index + " found: " + keyIndex ); 87 | } 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/isNumeric.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isNumeric = require( "../utils/isNumeric.js" ); 4 | 5 | describe( "Testing isNumeric", ( ) => 6 | { 7 | it( "isNumeric should be a function", ( ) => 8 | { 9 | assert.isFunction( isNumeric, "isNumeric is not a function" ); 10 | }); 11 | 12 | it( "isNumeric should correctly identify a string number", ( ) => 13 | { 14 | let data = "12345"; 15 | let expectedResult = true; 16 | let result = isNumeric( data ); 17 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 18 | }); 19 | 20 | it( "isNumeric should correctly identify a string float", ( ) => 21 | { 22 | let data = "3.1415"; 23 | let expectedResult = true; 24 | let result = isNumeric( data ); 25 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 26 | }); 27 | 28 | it( "isNumeric should correctly identify a number", ( ) => 29 | { 30 | let data = 12345; 31 | let expectedResult = true; 32 | let result = isNumeric( data ); 33 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 34 | }); 35 | 36 | it( "isNumeric should correctly identify a float", ( ) => 37 | { 38 | let data = 3.1415; 39 | let expectedResult = true; 40 | let result = isNumeric( data ); 41 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 42 | }); 43 | 44 | it( "isNumeric should correctly identify a negative float", ( ) => 45 | { 46 | let data = -3.1415; 47 | let expectedResult = true; 48 | let result = isNumeric( data ); 49 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 50 | }); 51 | 52 | it( "isNumeric should correctly identify a 0", ( ) => 53 | { 54 | let data = 0; 55 | let expectedResult = true; 56 | let result = isNumeric( data ); 57 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 58 | }); 59 | 60 | it( `isNumeric should correctly identify a "0"`, ( ) => 61 | { 62 | let data = "0"; 63 | let expectedResult = true; 64 | let result = isNumeric( data ); 65 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 66 | }); 67 | 68 | 69 | 70 | it( "isNumeric should correctly fail a character string", ( ) => 71 | { 72 | let data = "One"; 73 | let expectedResult = false; 74 | let result = isNumeric( data ); 75 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 76 | }); 77 | 78 | it( "isNumeric should correctly fail an undefined", ( ) => 79 | { 80 | let data = undefined; 81 | let expectedResult = false; 82 | let result = isNumeric( data ); 83 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 84 | }); 85 | it( "isNumeric should correctly fail a null", ( ) => 86 | { 87 | let data = null; 88 | let expectedResult = false; 89 | let result = isNumeric( data ); 90 | assert.equal( result, expectedResult, "isNumeric( " + data + " ) returned: " + result + " expected: " + expectedResult ); 91 | }); 92 | }) 93 | 94 | -------------------------------------------------------------------------------- /test/indexOfEnum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let { indexOfEnum } = require( "../utils/indexOfEnum" ); 4 | Object.defineProperty(exports, "indexOfEnum", { enumerable: true, get: function () { return indexOfEnum.indexOfEnum; } }); 5 | 6 | // ***************** TEST Plugin Initialized Variables *************** 7 | 8 | describe( "Initializing our plugin module", ( ) => {}); 9 | 10 | let _api = new HomebridgeAPI( ); // object we feed to Plugins 11 | 12 | // Init the library for all to use 13 | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.hap.Formats, _api.hap.Units, _api.hap.Perms ); 14 | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.hap.Service, _api.hap.Characteristic, _api.hap.Categories ); 15 | 16 | 17 | // ******** QUICK TEST CMD4_DEVICE_TYPE_ENUM ************* 18 | describe( "Quick Test load of CMD4_DEVICE_TYPE_ENUM", ( ) => 19 | { 20 | it( "CMD4_DEVICE_TYPE_ENUM.EOL =" + DEVICE_EOL, ( ) => 21 | { 22 | expect( CMD4_DEVICE_TYPE_ENUM.EOL ).to.equal( DEVICE_EOL ); 23 | }); 24 | }); 25 | describe( "Quick Test load of CMD4_ACC_TYPE_ENUM", ( ) => 26 | { 27 | it( "CMD4_ACC_TYPE_ENUM.EOL =" + ACC_EOL, ( ) => 28 | { 29 | expect( CMD4_ACC_TYPE_ENUM.EOL ).to.equal( ACC_EOL ); 30 | }); 31 | }); 32 | 33 | // ******** TEST indexOfEnum .************* 34 | 35 | describe( "Test indexOfEnum import", ( ) => 36 | { 37 | it( "indexOfEnum should be a function ", ( ) => 38 | { 39 | assert.isFunction( indexOfEnum, "index of enum is not a function. Found: %s", typeof indexOfEnum ); 40 | }); 41 | }); 42 | 43 | describe( "Test indexOfEnum", ( ) => 44 | { 45 | it( "indexOfEnum should be identify a deviceName ", ( ) => 46 | { 47 | let j = CMD4_DEVICE_TYPE_ENUM.WiFiSatellite; 48 | let name = CMD4_DEVICE_TYPE_ENUM.properties[ j ].deviceName; 49 | let ucKeyIndex = CMD4_DEVICE_TYPE_ENUM.properties.indexOfEnum( i => i.deviceName === name ); 50 | assert.equal( j, ucKeyIndex, "index of enum should identify the device %s(%s). Found: %s", name, j, ucKeyIndex ); 51 | }); 52 | 53 | it( "indexOfEnum should identify a Characteristic Name ", ( ) => 54 | { 55 | let j = CMD4_ACC_TYPE_ENUM.BatteryLevel; 56 | let type = CMD4_ACC_TYPE_ENUM.properties[ j ].type; 57 | let ucKeyIndex = CMD4_ACC_TYPE_ENUM.properties.indexOfEnum( i => i.type === type); 58 | assert.equal( j, ucKeyIndex, "index of enum should identify the characteristic " + type + "(" + j + "). Found: " + ucKeyIndex ); 59 | }); 60 | it( "indexOfEnum should identify a Characteristic Name ", ( ) => 61 | { 62 | let j = CMD4_ACC_TYPE_ENUM.CurrentRelativeHumidity; 63 | //let j = CMD4_ACC_TYPE_ENUM.CurrentHumidifierDehumidifierState; 64 | //let j = CMD4_ACC_TYPE_ENUM.blast; 65 | console.log("j=" + j); 66 | let type = CMD4_ACC_TYPE_ENUM.properties[ j ].type; 67 | let ucKeyIndex = CMD4_ACC_TYPE_ENUM.properties.indexOfEnum( i => i.type === type); 68 | assert.equal( j, ucKeyIndex, "index of enum should identify the characteristic " + type + "(" + j + "). Found: " + ucKeyIndex ); 69 | }); 70 | describe( "Test each characteristic type", ( ) => 71 | { 72 | for ( let index = 0; index < ACC_EOL; index++ ) 73 | { 74 | it( "indexOfEnum should be identify a Characteristic Type ", ( ) => 75 | { 76 | let type = CMD4_ACC_TYPE_ENUM.properties[ index ].type; 77 | let ucKeyIndex = CMD4_ACC_TYPE_ENUM.properties.indexOfEnum( i => i.type === type); 78 | assert.equal( index, ucKeyIndex, "index of enum should identify the characteristic " + type + "(" + index + "). Found: " + ucKeyIndex ); 79 | }); 80 | } 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/mocha-setup: -------------------------------------------------------------------------------- 1 | // Define common functions and values for all unit tests. 2 | 3 | const which = require('which'); 4 | const path = require( "path" ); 5 | 6 | // Get the real path of homebridge instead of a dev dependancy, 7 | // which caused issues if you forget to update dependancies but 8 | // upgrade homebridge. 9 | const homebridgePath = which.sync( 'homebridge', { nothrow: true } ) 10 | 11 | let apiPath; 12 | if ( homebridgePath ) 13 | { 14 | let dirname = path.dirname( homebridgePath ); 15 | 16 | console.log( "Found homebridge in path %s", dirname ); 17 | apiPath = `${ dirname }/../lib/node_modules/homebridge/lib/api`; 18 | global.HomebridgeAPI = require( apiPath ).HomebridgeAPI; 19 | 20 | if ( ! global.HomebridgeAPI ) 21 | { 22 | console.log( "homebridgeAPI not available !!!" ); 23 | process.exit( 10 ); 24 | } 25 | 26 | // For serializing/deserializing arrays of accessories 27 | let platformAccessoryPath = `${ dirname }/../lib/node_modules/homebridge/lib/platformAccessory`; 28 | global.platformAccessory_1 = require( platformAccessoryPath ); 29 | 30 | } else 31 | { 32 | console.log( "homebridge not found !!!" ); 33 | process.exit( 10 ); 34 | } 35 | console.log( "Found api in %s", apiPath ); 36 | 37 | // IMPORTANT - ALL GLOBALS MUST BE DEFINED IN .eslintrc.json for lint to work 38 | global.fs = require( 'fs' ); 39 | global.assert = require( "chai" ).assert; 40 | global.expect = require( "chai" ).expect; 41 | global.sinon = require( "sinon" ); 42 | 43 | global.ACC_EOL = 255; 44 | global.DEVICE_EOL = 81; 45 | global.FORMAT_EOL = 11; 46 | global.UNITS_EOL = 5; 47 | global.PERMS_EOL = 9; 48 | global.ACCESS_EOL = 3; 49 | 50 | // These would be the uninitialized values, used for unit testing 51 | global.ACC_DATA = require( '../lib/CMD4_ACC_TYPE_ENUM' ); 52 | global.CMD4_ACC_TYPE_ENUM = ACC_DATA.CMD4_ACC_TYPE_ENUM; 53 | 54 | global.DEVICE_DATA = require( '../lib/CMD4_DEVICE_TYPE_ENUM' ); 55 | global.CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.CMD4_DEVICE_TYPE_ENUM; 56 | 57 | global.CHAR_DATA = require( `../lib/CMD4_CHAR_TYPE_ENUMS` ); 58 | global.CMD4_CHAR_TYPE_ENUMS = CHAR_DATA.CMD4_CHAR_TYPE_ENUMS; 59 | 60 | 61 | global.Logger = require("../utils/Logger"); 62 | 63 | 64 | // A true sleep ( blocking ). 65 | const moment = require( "moment" ); 66 | global.sleep = function( secondsToSleep = 1 ) 67 | { 68 | let sleepUntill = moment( ).add( secondsToSleep, 'seconds'); 69 | while( moment ( ).isBefore( sleepUntill ) ) { /* block the process */ } 70 | } 71 | 72 | global.accEnumIndexToC = function( index ) 73 | { 74 | return CMD4_ACC_TYPE_ENUM.properties[ index ].type; 75 | } 76 | global.devEnumIndexToC = function( index ) 77 | { 78 | return CMD4_DEVICE_TYPE_ENUM.properties[ index ].deviceName; 79 | } 80 | 81 | // How it's used 82 | // sleep( 10 ); 83 | 84 | global.cleanStatesDir = function( ) 85 | { 86 | const os = require( "os" ); 87 | const cmd4StateDir = os.homedir( ) + "/.homebridge/Cmd4Scripts/Cmd4States/" 88 | 89 | var glob = require( "glob" ); 90 | 91 | 92 | glob( cmd4StateDir + "Status_Device_*", null, function ( er, files ) 93 | { 94 | for ( var file of files ) 95 | { 96 | // To use the promise of unlink, it must be in an async function 97 | // so put it in one. Why not unLinkSync, because for whatever reason 98 | // some files were notbremoved synchronously. 99 | ( async( ) => 100 | { 101 | await fs.unlink( file, function( err, result ) 102 | { 103 | if ( err && err.code != 'ENOENT' ) 104 | console.log( 'file not removed err: ' + err + " result: " + result ); 105 | }); 106 | }); 107 | } 108 | }) 109 | } 110 | 111 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/Examples/PS5.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # This Cmd4 script uses playactor to turn on/off a PS5. 5 | # 6 | # You will need to install playactor and have the command in a global PATH 7 | # or modify this script with its actual PATH. 8 | # 9 | # Your Cmd4 .homebridge/.config.json file would have a state_cmd like: 10 | # state_cmd: ".homebridge/Cmd4Scripts/Examples/PS5.sh" 11 | # 12 | # Testing from the shell prompt: 13 | # ./PS5.sh Get PS5 On 14 | # or 15 | # ./PS5.sh Set PS5 On 1 16 | # or 17 | # ./PS5.sh Set PS5 On 0 18 | 19 | 20 | set -e 21 | 22 | # Exit immediately for unbound variables. 23 | set -u 24 | 25 | # Passed in Args 26 | length=$# 27 | device="" 28 | io="" 29 | characteristic="" 30 | 31 | 32 | if [ $length -le 2 ]; then 33 | echo "Usage: $0 Get < AccessoryName > < characteristic >" 34 | echo "Usage: $0 Set < AccessoryName > < characteristic > < Value >" 35 | exit 199 36 | fi 37 | 38 | 39 | if [ $length -ge 1 ]; then 40 | io=$1 41 | fi 42 | if [ $length -ge 2 ]; then 43 | device=$2 44 | fi 45 | if [ $length -ge 3 ]; then 46 | characteristic=$3 47 | fi 48 | 49 | 50 | # For "Get" Directives 51 | if [ "$io" = "Get" ]; then 52 | case "$characteristic" in 53 | 54 | On ) 55 | 56 | # Normally we would exit immediately if a command fails with a non-zero status. 57 | # In this case playactor can fail and we would rely on the failing exit status to 58 | # tell Cmd4 that the accessory is not on the network. That would be the prefered 59 | # thing to do. However for this example we are going to output '0' (false) so 60 | # that you can see the '0' on the console telling us that the accessory is not 61 | # on the network. 62 | set +e 63 | 64 | # Check if we got the message '200 OK' meaning the accessory is 65 | # on the network by seeing if the return code of the above command passed or 66 | # failed. 67 | playactor check | grep -i '200 Ok'>> /dev/null 2>&1 68 | rc=$? 69 | set -e 70 | 71 | if [ "$rc" = "0" ]; then 72 | # The message was recieved so the target is up, sending a '1' (true), like 73 | # a binary number is, back to Cmd4. 74 | stdbuf -o0 -e0 echo 1 75 | exit 0 76 | else 77 | # The message was not recieved so the target must be down, sending a '0' (false), like 78 | # a binary number is, back to Cmd4. 79 | stdbuf -o0 -e0 echo 0 80 | exit 0 81 | fi 82 | ;; 83 | *) 84 | echo "Unhandled Get characteristic $characteristic" >&2 85 | exit 109 86 | ;; 87 | esac 88 | fi 89 | 90 | # For "Set" Directives 91 | if [ "$io" = "Set" ]; then 92 | value="1" 93 | if [ $length -ge 4 ]; then 94 | value=$4 95 | else 96 | echo "No value specified for set" >&2 97 | exit 199 98 | fi 99 | 100 | case "$characteristic" in 101 | On ) 102 | # Normally we would exit immediately if a command fails with a non-zero status. 103 | # In this case playactor can fail and we would rely on the failing exit status to 104 | # tell Cmd4 that the accessory is not on the network. That would be the prefered 105 | # thing to do. However for this example we are going to output '0' (false) so 106 | # that you can see the '0' on the console telling us that the accessory is not 107 | # on the network. 108 | set +e 109 | 110 | if [ "$value" = "1" ]; then 111 | 112 | # Execute the on command 113 | sudo playactor wake 114 | 115 | exit 0 116 | else 117 | # Execute the off command 118 | sudo playactor standby 119 | 120 | exit 0 121 | fi 122 | ;; 123 | *) 124 | echo "Unhandled Set characteristic $characteristic" >&2 125 | exit 109 126 | ;; 127 | esac 128 | fi 129 | 130 | 131 | echo "Unhandled $io $device $characteristic" >&2 132 | 133 | exit 150 134 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/Examples/ExampleShellScript_template.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ExampleScript_template.sh 4 | # 5 | # Description: 6 | # This script is a goood starting place for you to create Cmd4 Scripts 7 | # of your own 8 | # 9 | # Parameters are: 10 | # Get < Any accessory name > < Characteristic> 11 | # Set < Any accessory name > < Characteristic> < value > 12 | # 13 | # Note 1: These paramaters match the those of the Cmd4 plugin. 14 | # A full lost of supported devices and characteristics can be 15 | # found at: 16 | # https://ztalbot2000.github.io/homebridge-cmd4 17 | # 18 | # How it works: 19 | # 20 | # The Cmd4 plugin will call this script to retrieve those states 21 | # you have defined as not Cached to Get/Set your devices characteristic 22 | # states. 23 | # 24 | # For example: 25 | # bash ExampleScript_template.sh Set My_Door TargetDoorState 0 26 | # or 27 | # bash ExampleScript.sh Get My_Door CurrentDoorState 28 | # 29 | 30 | set -e 31 | 32 | # Exit immediately for unbound variables. 33 | set -u 34 | 35 | 36 | length=$# 37 | device="" 38 | io="" 39 | characteristic="" 40 | option="" 41 | 42 | if [ $length -le 1 ]; then 43 | printf "Usage: $0 Get < AccessoryName > < Characteristic >\n" 44 | printf "Usage: $0 Set < AccessoryName > < Characteristic > < Value >\n" 45 | exit -1 46 | fi 47 | 48 | # printf "args =$#\n" # debug 49 | # printf "arg1 =$1\n" # debug 50 | 51 | if [ $length -ge 1 ]; then 52 | io=$1 53 | # printf "io=$io\n" # debug 54 | fi 55 | if [ $length -ge 2 ]; then 56 | device=$2 57 | # printf "device = ${device}\n" # debug 58 | fi 59 | if [ $length -ge 3 ]; then 60 | characteristic=$3 61 | # printf "Characteristic = ${characteristic}\n" # debug 62 | fi 63 | if [ $length -ge 4 ]; then 64 | option=$4 65 | # printf "option = ${option}\n" # debug 66 | fi 67 | 68 | if [ "${io}" == "Get" ]; then 69 | case $characteristic in 70 | 'CurrentDoorState') 71 | 72 | printf "0\n" # Door is open 73 | 74 | # See https://ztalbot2000.github.io/homebridge-cmd4 75 | # For the possible values and characteristics 76 | # available per device. It will show somethink like: 77 | # Valid Values: 78 | # 0 - "Open. The door is fully open." 79 | # 1 - "Closed. The door is fully closed." 80 | # 2 - "Opening. The door is actively opening." 81 | # 3 - "Closing. The door is actively closing." 82 | # 4 - "Stopped. The door is not moving, and it is not fully 83 | # open nor fully closed." 84 | # 5-255 - "Reserved" 85 | exit 0 86 | ;; 87 | 'TargetDoorState') 88 | printf "0\n" 89 | exit 0 90 | ;; 91 | 'ObstructionDetected') 92 | printf "0\n" 93 | exit 0 94 | ;; 95 | 'LockCurrentState') 96 | printf "0\n" 97 | exit 0 98 | ;; 99 | *) 100 | printf "UnHandled Get ${device} Characteristic ${characteristic}\n" 101 | exit -1 102 | ;; 103 | esac 104 | fi 105 | if [ "${io}" == 'Set' ]; then 106 | case $characteristic in 107 | 'CurrentDoorState') 108 | # Current Door State is not settable. The 109 | # call would be to TargetDoorState. This is here 110 | # for debugging only. 111 | 112 | exit 0 113 | ;; 114 | 'TargetDoorState') 115 | # Do something of your own here. 116 | exit 0 117 | ;; 118 | 'ObstructionDetected') 119 | # Obstruction Detected is not settable. It 120 | # call is a read-only characteristic. This is here 121 | # for debugging only. 122 | exit 0 123 | ;; 124 | 'LockCurrentState') 125 | # Lock Current State is not settable. It 126 | # call is a read-only characteristic. This is here 127 | # for debugging only. 128 | exit 0 129 | ;; 130 | *) 131 | printf "UnHandled Set GarageDoorOpenner Characteristic ${characteristic}" 132 | exit -1 133 | ;; 134 | esac 135 | fi 136 | printf "Unknown io command ${io}\n" 137 | exit -1 138 | 139 | 140 | -------------------------------------------------------------------------------- /lib/CMD4_CHAR_TYPE_ENUMS.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var CMD4_CHAR_TYPE_ENUMS = 4 | { 5 | CMD4_FORMAT_TYPE_ENUM: 6 | { 7 | BOOL: 0, 8 | INT: 1, 9 | FLOAT: 2, 10 | STRING: 3, 11 | UINT8: 4, 12 | UINT16: 5, 13 | UINT32: 6, 14 | UINT64: 7, 15 | DATA: 8, 16 | TLV8: 9, 17 | DICTIONARY: 10, 18 | EOL: 11, 19 | properties: { } 20 | }, 21 | 22 | CMD4_UNITS_TYPE_ENUM: 23 | { 24 | CELSIUS: 0, 25 | PERCENTAGE: 1, 26 | ARC_DEGREE: 2, 27 | LUX: 3, 28 | SECONDS: 4, 29 | EOL: 5, 30 | 31 | properties: { } 32 | }, 33 | 34 | CMD4_PERMS_TYPE_ENUM: 35 | { 36 | READ: 0, 37 | WRITE: 1, 38 | PAIRED_READ: 2, 39 | PAIRED_WRITE: 3, 40 | NOTIFY: 4, 41 | ADDITIONAL_AUTHORIZATION: 5, 42 | TIMED_WRITE: 6, 43 | HIDDEN: 7, 44 | WRITE_RESPONSE: 8, 45 | EOL: 9, 46 | properties: { } 47 | } 48 | } 49 | 50 | // Export both the init function and the uninitialized data for unit testing 51 | module.exports = 52 | { 53 | init: function ( hapFormats, hapUnits, hapPerms ) 54 | { 55 | 56 | // Fill in the properties of all possible characteristics 57 | // props was added because calling getCharacteridtic().props.perms adds 58 | // the characteristic in by default. This costs some lines, but is advantageous. 59 | CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.properties = 60 | { 61 | 0: { type: hapFormats.BOOL // "bool" 62 | }, 63 | 1: { type: hapFormats.INT // "int" 64 | }, 65 | 2: { type: hapFormats.FLOAT // "float" 66 | }, 67 | 3: { type: hapFormats.STRING // "string" 68 | }, 69 | 4: { type: hapFormats.UINT8 // "uint8" 70 | }, 71 | 5: { type: hapFormats.UINT16 // "uint16" 72 | }, 73 | 6: { type: hapFormats.UINT32 // "uint32" 74 | }, 75 | 7: { type: hapFormats.UINT64 // "uint64" 76 | }, 77 | 8: { type: hapFormats.DATA // "data" 78 | }, 79 | 9: { type: hapFormats.TLV8 // "tlv8" 80 | }, 81 | 10: { type: hapFormats.ARRAY // "array" 82 | }, 83 | 11: { type: hapFormats.DICTIONARY // "dict" 84 | } 85 | }; 86 | 87 | CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.properties = 88 | { 89 | 0: { type: hapUnits.CELSIUS // "celsius" 90 | }, 91 | 1: { type: hapUnits.PERCENTAGE // "percentage" 92 | }, 93 | 2: { type: hapUnits.ARC_DEGREE // "arcdegrees" 94 | }, 95 | 3: { type: hapUnits.LUX // "lux" 96 | }, 97 | 4: { type: hapUnits.SECONDS // "seconds" 98 | } 99 | }; 100 | 101 | CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.properties = 102 | { 103 | 0: { type: hapPerms.READ // "pr" 104 | }, 105 | 1: { type: hapPerms.WRITE // "pw" 106 | }, 107 | 2: { type: hapPerms.PAIRED_READ // "pr" 108 | }, 109 | 3: { type: hapPerms.PAIRED_WRITE // "pw" 110 | }, 111 | 4: { type: hapPerms.NOTIFY // "ev" 112 | }, 113 | 5: { type: hapPerms.EVENTS // "ev" 114 | }, 115 | 6: { type: hapPerms.ADDITIONAL_AUTHORIZATION // "aa" 116 | }, 117 | 7: { type: hapPerms.TIMED_WRITE // "tw" 118 | }, 119 | 8: { type: hapPerms.HIDDEN // "hd" 120 | }, 121 | 9: { type: hapPerms.WRITE_RESPONSE // "wr" 122 | } 123 | }; 124 | 125 | return CMD4_CHAR_TYPE_ENUMS; 126 | 127 | }, CMD4_CHAR_TYPE_ENUMS 128 | } 129 | 130 | -------------------------------------------------------------------------------- /utils/transposeCMD4Props.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Description: 4 | // Routines to convert Cmd4 values to constants and back. 5 | // 6 | // @param CMD4_ACC_TYPE_ENUM - Just that 7 | // @param accTypeEnumIndex - The Accessory Type Enumerated index. 8 | // @param constantString - The string to change into a HAP value. 9 | // @param constantValue - The value to change into a HAP String. 10 | // 11 | // @returns Value of transposition or nothing. 12 | // 13 | 14 | var extractKeyValue = function( obj, value ) 15 | { 16 | for ( let key in obj ) 17 | { 18 | // In case value given is a string, compare that as well. 19 | if ( obj[ key ] == value || obj[ key ] + "" == value ) 20 | return key; 21 | } 22 | return undefined; 23 | } 24 | 25 | // Used to convet ValidValus from a Constant to their corresponding value. 26 | var transposeConstantToValidValue = function ( CMD4_ENUM_properties_obj, accTypeEnumIndex, constantString ) 27 | { 28 | if ( Object.keys( CMD4_ENUM_properties_obj[ accTypeEnumIndex ].validValues ).length <= 0 ) 29 | { 30 | // Return the original as it should be used instead of nothing 31 | // This is not a failure 32 | //return { "value": constantString, "rc": true, "msg": `Non Convertible characteristic ${ constantString } for ${ CMD4_ENUM_properties_obj[ accTypeEnumIndex ].type }` }; 33 | return constantString; 34 | } 35 | 36 | // In case constantString is not a string, ie false 37 | let lookupString = "" + constantString; 38 | let ucConstantString = lookupString.toUpperCase(); 39 | 40 | if ( Object.prototype.hasOwnProperty.call( CMD4_ENUM_properties_obj[ accTypeEnumIndex ].validValues, ucConstantString ) ) 41 | { 42 | // let value = CMD4_ENUM_properties_obj[ accTypeEnumIndex ].validValues[ ucConstantString ]; 43 | return CMD4_ENUM_properties_obj[ accTypeEnumIndex ].validValues[ ucConstantString ]; 44 | 45 | //return { "value": value, "rc": true, "msg": "Transpose success" }; 46 | } 47 | 48 | // What if it is already transposed correctly? 49 | // let constant = extractKeyValue( CMD4_ENUM_properties_obj[ accTypeEnumIndex ].validValues, constantString ); 50 | // if ( constant == undefined || constant == null ) 51 | // return { "value": constantString, "rc": false, "msg": `Cannot convert ${ constantString } to a value for ${ CMD4_ENUM_properties_obj[ accTypeEnumIndex ].type }` }; 52 | //else 53 | // return { "value": constantString, "rc": true, "msg": "Already transposed" }; 54 | return constantString; 55 | } 56 | 57 | // Used to convet ValidValues Value to its corresponding Constant. 58 | var transposeValueToValidConstant = function ( CMD4_ENUM_properties_obj, accTypeEnumIndex, valueString ) 59 | { 60 | if ( Object.keys( CMD4_ENUM_properties_obj[ accTypeEnumIndex ].validValues ).length <= 0) 61 | { 62 | // Return the original as it should be used instead of nothing 63 | // This is not a failure 64 | //return { "value": valueString, "rc": true, "msg": `Non Convertible characteristic ${ valueString } for ${ CMD4_ENUM_properties_obj[ accTypeEnumIndex ].type }` }; 65 | return valueString; 66 | } 67 | 68 | let constant = extractKeyValue( CMD4_ENUM_properties_obj[ accTypeEnumIndex ].validValues, valueString ); 69 | 70 | if ( constant == undefined || constant == null ) 71 | { 72 | // What if it is already transposed correctly? 73 | //let value = CMD4_ENUM_properties_obj[ accTypeEnumIndex ].validValues[ valueString ]; 74 | //if ( value == undefined || value == null ) 75 | // return { "value": valueString, "rc": false, "msg": `Cannot convert ${ valueString } to a constant for ${ CMD4_ENUM_properties_obj[ accTypeEnumIndex ].type }` }; 76 | //else 77 | // return { "value": valueString, "rc": true, "msg": "Already transposed" }; 78 | 79 | return valueString; 80 | } 81 | 82 | // return { "value": constant, "rc": true, "msg": "Transpose success" }; 83 | return constant; 84 | } 85 | 86 | // SendValue does not send true/false for historical reasons 87 | var transposeBoolToValue = function ( valueString ) 88 | { 89 | if ( valueString == true ) 90 | return 1; 91 | if ( valueString == false ) 92 | return 0; 93 | 94 | return valueString; 95 | } 96 | 97 | module.exports = { 98 | transposeConstantToValidValue, 99 | transposeValueToValidConstant, 100 | transposeBoolToValue, 101 | extractKeyValue 102 | }; 103 | -------------------------------------------------------------------------------- /tools/whereIsConstant: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | 4 | // File System utilities 5 | let fs = require("fs"); 6 | 7 | // Command line parser 8 | const { Command } = require( "commander" ); 9 | const program = new Command; 10 | 11 | const constants = require( "../cmd4Constants" ); 12 | 13 | var cmd4Files = [ "./Cmd4Platform.js", 14 | "./Cmd4Accessory.js", 15 | "./Cmd4PriorityPollingQueue.js", 16 | "./utils/HV.js", 17 | "./index.js", 18 | "./lib/CMD4_DEVICE_TYPE_ENUM.js", 19 | "./tools/Cmd4AccDocGenerator"]; 20 | 21 | // A nice little getOpt node.js package 22 | program 23 | .description( 'Determine where a given constant is in our source.' ) 24 | .requiredOption( '-c, --constant ', 'Constant to search for>' ) 25 | .option( '-r, --reverse', 'Look for key of constant' ) 26 | .option( '-v, --verbose', 'Show verbose search results' ); 27 | 28 | // Parse the arguments passed into this program. 29 | program.parse( process.argv ); 30 | 31 | 32 | // Get the options passed in based on the commander getOpts definitions. 33 | let options = program.opts( ); 34 | 35 | // This is the count of the found constants in result 36 | let foundCount=0; 37 | 38 | // Stop after finding this many 39 | const MAX_FOUND_BEFORE_EXIT = 3 40 | 41 | // The regex to find the constant in the source files 42 | // A great regex site: 43 | // https://regex101.com/r/bE3c0x/5 44 | let reg; 45 | 46 | let lookingFor = ""; 47 | 48 | if ( options.reverse ) 49 | { 50 | // WORKS. Ex: whereIsConstant -r -v -c PUBLISHEXTERNALLY 51 | lookingFor = constants[ options.constant ]; 52 | if ( lookingFor == undefined ) 53 | { 54 | console.error( `Constant key: ${ options.constant } not defined in cmd4Constants.js` ); 55 | process.exit( 1 ); 56 | } 57 | reg = `.*constants.${ options.constant }.*$`; 58 | // My logic seems reversed, but that is not true 59 | console.log( `Looking for: constants.${ options.constant }` ); 60 | if ( options.verbose ) 61 | console.log( `using regex: ${ reg }` ); 62 | } else 63 | { 64 | // Works. Ex: whereIsConstant -r -v -c publishExternally 65 | lookingFor = Object.keys( constants ).find(key => constants[ key ] === options.constant ); 66 | if ( lookingFor == undefined ) 67 | { 68 | console.error( `Constant value: ${ options.constant } not defined in cmd4Constants.js` ); 69 | process.exit( 1 ); 70 | } 71 | reg = `\\w*.${ options.constant }.*$`; 72 | // My logic seems reversed, but that is not true 73 | console.log( `Looking for: .${ lookingFor }` ); 74 | if ( options.verbose ) 75 | console.log( `using regex: ${ reg }` ); 76 | } 77 | const regex = new RegExp( reg ); 78 | 79 | 80 | // The constant must be in one of the Cmd4 source files 81 | for ( let fileIndex = 0; 82 | (fileIndex < cmd4Files.length ); 83 | fileIndex++ ) 84 | { 85 | let cmd4File = cmd4Files[ fileIndex ]; 86 | if ( options.verbose ) 87 | console.log( `Checking file: ${ cmd4File }` ); 88 | 89 | // Read in all the code from the source file 90 | let code = fs.readFileSync( cmd4File, "utf8" ); 91 | 92 | // If I could grep the source file I would, so 93 | // check the regex against each line 94 | var codeLines = code.split( '\n' ); 95 | let lineCount = 0; 96 | for ( let lineIndex = 0; 97 | lineIndex < codeLines.length; 98 | lineIndex++, lineCount++ ) 99 | { 100 | let line = codeLines[ lineIndex ]; 101 | 102 | // Check the regex 103 | let t = regex.test( line ); 104 | if ( t == true ) 105 | { 106 | foundCount++; 107 | if ( foundCount == 1 ) 108 | { 109 | process.stdout.write( `Found in ${ cmd4File }` ); 110 | } 111 | else if ( foundCount <= MAX_FOUND_BEFORE_EXIT ) 112 | { 113 | process.stdout.write( `, ${ cmd4File }` ); 114 | } 115 | else 116 | { 117 | process.stdout.write(` ...\n` ); 118 | process.exit( 0 ); 119 | } 120 | } 121 | } 122 | if (foundCount == MAX_FOUND_BEFORE_EXIT ) 123 | break; 124 | } 125 | if (foundCount == 0 ) 126 | { 127 | console.log( `constant: ${ options.constant } not found in Cmd4 source files` ); 128 | process.exit( 1 ); 129 | } else 130 | { 131 | // Trailing \n 132 | console.log(""); 133 | } 134 | process.exit( 0 ); 135 | 136 | -------------------------------------------------------------------------------- /test/trueTypeOf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var trueTypeOf = require( "../utils/trueTypeOf.js" ); 4 | 5 | describe( "Testing trueTypeOf", ( ) => 6 | { 7 | it( "trueTypeOf should be a function", ( ) => 8 | { 9 | assert.isFunction( trueTypeOf, "trueTypeOf is not a function" ); 10 | }); 11 | 12 | it( "trueTypeOf should correctly identify a String object", ( ) => 13 | { 14 | let data = "Cmd4"; 15 | let expectedResult = String; 16 | let result = trueTypeOf( data ); 17 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 18 | }); 19 | 20 | it( "trueTypeOf should correctly identify a Boolean false object", ( ) => 21 | { 22 | let data = false; 23 | let expectedResult = Boolean; 24 | let result = trueTypeOf( data ); 25 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 26 | }); 27 | 28 | it( "trueTypeOf should correctly identify a Boolean true object", ( ) => 29 | { 30 | let data = true; 31 | let expectedResult = Boolean; 32 | let result = trueTypeOf( data ); 33 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 34 | }); 35 | 36 | it( "trueTypeOf should correctly identify a Number object", ( ) => 37 | { 38 | let data = 42; 39 | let expectedResult = Number; 40 | let result = trueTypeOf( data ); 41 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 42 | }); 43 | 44 | it( "trueTypeOf should correctly identify a Array object", ( ) => 45 | { 46 | let data = [ 1, 2, 3 ]; 47 | let expectedResult = Array; 48 | let result = trueTypeOf( data ); 49 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 50 | }); 51 | 52 | it( "trueTypeOf should correctly identify a polling config", ( ) => 53 | { 54 | let data = [ { "characteristic": "active", "timeout": 5, "interval": 3}, 55 | { "characteristic": "On", "timeout": 8, "interval": 4} 56 | ]; 57 | let expectedResult = Array; 58 | let result = trueTypeOf( data ); 59 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 60 | }); 61 | 62 | it( "trueTypeOf should correctly identify an empty object ", ( ) => 63 | { 64 | let data = undefined 65 | let expectedResult = undefined; 66 | let result = trueTypeOf( ); 67 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 68 | }); 69 | 70 | it( "trueTypeOf should correctly identify a undefined object ", ( ) => 71 | { 72 | let data = undefined 73 | let expectedResult = undefined; 74 | let result = trueTypeOf( data ); 75 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 76 | }); 77 | 78 | it( "trueTypeOf should correctly identify a null object", ( ) => 79 | { 80 | let data = null; 81 | let expectedResult = null; 82 | let result = trueTypeOf( data ); 83 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 84 | }); 85 | 86 | it( "trueTypeOf should correctly identify a 0 as a Number", ( ) => 87 | { 88 | let data = 0; 89 | let expectedResult = Number; 90 | let result = trueTypeOf( data ); 91 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 92 | }); 93 | 94 | it( "trueTypeOf should correctly identify a \"0\" as a String", ( ) => 95 | { 96 | let data = "0"; 97 | let expectedResult = String; 98 | let result = trueTypeOf( data ); 99 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 100 | }); 101 | 102 | it( "trueTypeOf should correctly identify a \"1\" as a String", ( ) => 103 | { 104 | let data = "0"; 105 | let expectedResult = String; 106 | let result = trueTypeOf( data ); 107 | assert.equal( result, expectedResult, "trueTypeOf( " + data + " ) returned: " + result + " expected: " + expectedResult ); 108 | }); 109 | }) 110 | 111 | -------------------------------------------------------------------------------- /test/Cmd4Mode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // ***************** TEST LOADING ********************** 4 | 5 | 6 | let { Cmd4Platform } = require( "../Cmd4Platform" ); 7 | let { Cmd4Accessory } = require( "../Cmd4Accessory" ); 8 | 9 | // Settings, Globals and Constants 10 | let settings = require( "../cmd4Settings" ); 11 | 12 | 13 | var _api = new HomebridgeAPI( ); // object we feed to Plugins 14 | 15 | 16 | // Init the library for all to use 17 | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.hap.Formats, _api.hap.Units, _api.hap.Perms ); 18 | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.hap.Service, _api.hap.Characteristic, _api.hap.Categories ); 19 | 20 | 21 | 22 | // ******** QUICK TEST CMD4_ACC_TYPE_ENUM ************* 23 | describe( "Quick Test load of CMD4_ACC_TYPE_ENUM", ( ) => 24 | { 25 | it( "CMD4_ACC_TYPE_ENUM.EOL =" + ACC_EOL, ( ) => 26 | { 27 | expect( CMD4_ACC_TYPE_ENUM.EOL ).to.equal( ACC_EOL ); 28 | }); 29 | }); 30 | 31 | 32 | 33 | // ******** QUICK TEST CMD4_DEVICE_TYPE_ENUM ************* 34 | describe( "Quick Test load of CMD4_DEVICE_TYPE_ENUM", ( ) => 35 | { 36 | it( "CMD4_DEVICE_TYPE_ENUM.EOL =" + DEVICE_EOL, ( ) => 37 | { 38 | expect( CMD4_DEVICE_TYPE_ENUM.EOL ).to.equal( DEVICE_EOL ); 39 | }); 40 | }); 41 | 42 | describe( "Testing Demo Mode", function( ) 43 | { 44 | 45 | beforeEach( function( ) 46 | { 47 | settings.listOfCreatedPriorityQueues = { }; 48 | }); 49 | afterEach( function( ) 50 | { 51 | // Clear any timers created for any polling queue 52 | Object.keys(settings.listOfCreatedPriorityQueues).forEach( (queueName) => 53 | { 54 | let queue = settings.listOfCreatedPriorityQueues[ queueName ]; 55 | Object.keys(queue.listOfRunningPolls).forEach( (key) => 56 | { 57 | let timer = queue.listOfRunningPolls[ key ]; 58 | clearTimeout( timer ); 59 | }); 60 | 61 | clearTimeout( queue.pauseTimer ); 62 | }); 63 | 64 | // Put back the polling queues 65 | settings.listOfCreatedPriorityQueues = { }; 66 | }); 67 | 68 | 69 | it( "Test if Cmd4Accessory exists", function ( ) 70 | { 71 | expect( Cmd4Accessory ).not.to.be.a( "null", "Cmd4Accessory was null" ); 72 | }); 73 | 74 | it( "V2 Crippled Test that getValue (Cached) occurs in Demo mode", function( done ) 75 | { 76 | let platformConfig = 77 | { 78 | accessories: 79 | [{ 80 | name: "MySwitch", 81 | type: "Switch", 82 | on: false, 83 | }] 84 | }; 85 | 86 | const log = new Logger( ); 87 | log.setBufferEnabled( ); 88 | log.setOutputEnabled( false ); 89 | log.setDebugEnabled( true ); 90 | 91 | let cmd4Platform = new Cmd4Platform( log, platformConfig, _api ); 92 | 93 | expect( cmd4Platform ).to.be.a.instanceOf( Cmd4Platform, "cmd4Platform is not an instance of Cmd4Platform" ); 94 | 95 | cmd4Platform.discoverDevices( ); 96 | 97 | assert.include( log.logBuf, `[35mConfiguring platformAccessory: \u001b[39mMySwitch`, ` cmd4Accessory incorrect stdout": ${ log.logBuf }` ); 98 | assert.include( log.logBuf, `[33mAdding getCachedValue for MySwitch characteristic: On`, ` cmd4Accessory incorrect stdout": ${ log.logBuf }` ); 99 | 100 | let cmd4Accessory = cmd4Platform.createdCmd4Accessories[0]; 101 | 102 | expect( cmd4Accessory ).to.be.a.instanceOf( Cmd4Accessory, "Cmd4Accessory is not an instance of Cmd4Accessory" ); 103 | 104 | //log.reset( ); 105 | log.setOutputEnabled( false ); 106 | log.setDebugEnabled( true ); 107 | 108 | /* Characteristic.getValue() is deprecated in V2 and Characteristic.value does not call the 109 | * get functions. Worked last in homebridge-1.8 110 | // Call the getValue bound function, which is priorityGetValue 111 | cmd4Accessory.service.getCharacteristic( 112 | CMD4_ACC_TYPE_ENUM.properties[ CMD4_ACC_TYPE_ENUM.On ] 113 | .characteristic ).getValue( "On", function dummyCallback( ) { } ); 114 | 115 | setTimeout( ( ) => 116 | { 117 | assert.include( log.logBuf, `[90mgetCachedValue On for: MySwitch returned (CACHED) value: false`, ` getValue incorrect stdout: ${ log.logBuf }` ); 118 | assert.equal( log.errBuf, "", ` getValue Unexpected stderr: ${ log.errBuf }` ); 119 | 120 | done( ); 121 | }, 1000 ); 122 | 123 | */ 124 | // Added for homebridge v2 .getValue lines commented out above. 125 | done(); 126 | }).timeout( 2000 ); 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/CheckYourScript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash --noprofile --norc 2 | 3 | # Fun colour & cursor stuff 4 | TCR=$(tput cr) 5 | TCLR=$(tput clear) 6 | TBLD=$(tput bold) 7 | TNRM=$(tput sgr0) 8 | TBLK=$(tput setaf 0) 9 | TRED=$(tput setaf 1) 10 | TGRN=$(tput setaf 2) 11 | TYEL=$(tput setaf 3) 12 | TBLU=$(tput setaf 4) 13 | TMAG=$(tput setaf 5) 14 | TCYN=$(tput setaf 6) 15 | TWHT=$(tput setaf 7) 16 | 17 | # OS type for flavours of different commands (like date on OSX) 18 | case $(uname | tr '[:upper:]' '[:lower:]') in 19 | solaris*) 20 | date_cmd="date -u +%s.%N" 21 | ;; 22 | darwin*) 23 | # OSX does not have msec 24 | date_cmd="date -u +%s" 25 | ;; 26 | linux*) 27 | date_cmd="date -u +%s.%N" 28 | ;; 29 | bsd*) 30 | date_cmd="date -u +%s.%N" 31 | ;; 32 | msys*) 33 | date_cmd="date -u +%s.%N" 34 | ;; 35 | *) 36 | echo "unknown: OSTYPE:$OSTYPE" 37 | exit -1 38 | ;; 39 | esac 40 | 41 | printf "${TCLR}" 42 | 43 | if [ "${1}" = '-h' ]; then 44 | printf "${TBLU}Usage:${TNRM}" 45 | printf "${TNRM} SHELL> cd \n" 46 | printf "${TNRM} SHELL> bash --noprofile --norc \n" 47 | printf "\n" 48 | printf "${TBLU}Syntax:${TNRM}\n" 49 | printf " ${TBLU}${0}${TNRM} 'full state command'\n" 50 | printf "${TBLU}i.e.${TNRM}\n" 51 | printf " '.homebridge/Cmd4Scripts/CheckYourScript.sh' 'bin/MyExec' 'Get' 'MyDevice' 'On'\n" 52 | printf " or\n" 53 | printf " '.homebridge/Cmd4Scripts/CheckYourScript.sh' 'bash bin/YourScript.sh' 'Get' 'MyDevice' 'On'\n" 54 | printf " or\n" 55 | printf " '.homebridge/Cmd4Scripts/CheckYourScript.sh' 'bin/YourScript.sh 'Get 'MyDevice' 'On'\n" 56 | printf "\n" 57 | printf "Note: Add the '' around the command to prevent globbing, which is not done by homebridge-cmd4\n" 58 | exit 0 59 | fi 60 | 61 | # Processes are run from your home directory, so go there first. 62 | cd "${HOME}" 63 | printf "${TBLU}Changing to:${TNRM}'${HOME}' ${TBLU}where processes are run from${TNRM}\n" 64 | 65 | # $HOME is expanded by the shell and not scripts, so do not rely on it. 66 | unset HOME 67 | 68 | printf "${TBLU}Enviroment in shell is limited to these variables:${TNRM}\n" 69 | env 70 | 71 | printf "\n" 72 | 73 | printf "${TBLU}Command will be run from the directory: ${TNRM}${PWD}\n" 74 | 75 | output="" 76 | rc=0 77 | 78 | if [ "$2" = 'Set' ] || [ "$3" = 'Set' ]; then 79 | printf "${TBLU}(Set) Cmd4 would execute:${TNRM} $* " 80 | printf "\n" 81 | 82 | start_time="$( $date_cmd )" 83 | output=$("$@") 84 | rc="$?" 85 | end_time="$( $date_cmd )" 86 | 87 | # The elapsed time (in microseconds) 88 | # We add 1 second as OSX does not have msec dates. 89 | # One second will make no difference, but might help a newbie. 90 | elapsed=( $(/usr/bin/bc <<<"($end_time-$start_time) * 1000 + 1000") ) 91 | 92 | printf "\n" 93 | if [ $rc = 0 ]; then 94 | printf "${TGRN}Command passed with returned code:${TNRM}'${rc}'\n" 95 | if [ "${output}" != "" ]; then 96 | printf "Output: '${output}' would be ignored\n" 97 | fi 98 | printf "$TBLU}The timeout value should be at least :${TNRM} ${elapsed} (microseconds)\n" 99 | printf "$TBLU}Multiply by 5 for safety.${TNRM}\n" 100 | else 101 | printf "${TRED}Command given did not exit with a ${TNRM}0${TRED} return code and would fail in homebridge-cmd4 rc=${TNRM}'${rc}'\n" 102 | printf "Understand that the error given is a result of running your command in a basic shell environment. Head the errors given\n" 103 | fi 104 | else 105 | printf "${TBLU}(Get) Cmd4 would execute:${TNRM} $*" 106 | printf "\n" 107 | 108 | start_time="$( $date_cmd )" 109 | output=$("$@") 110 | rc="$?" 111 | end_time="$( $date_cmd )" 112 | 113 | # The elapsed time (in microseconds) 114 | # We add 1 second as OSX does not have msec dates. 115 | elapsed=( $(/usr/bin/bc <<<"($end_time-$start_time) * 1000 + 1000") ) 116 | 117 | printf "\n" 118 | wordCount=0 119 | if [ $rc = 0 ]; then 120 | printf "${TGRN}Command passed with returned code:${TNRM}'${rc}'\n" 121 | printf "${TBLU}Output: of command was:${TNRM}'${output}'\n" 122 | wordCount=$(IFS=' '; set -f; set -- "${output}"; echo $#) 123 | if [ "${wordCount}" != '1' ]; then 124 | printf "${TYEL}Word count of output should only be 1, not ${TNRM}'${wordCount}'\n" 125 | fi 126 | printf "$TBLU}The timeout value should be at least :${TNRM} ${elapsed} (microseconds)\n" 127 | printf "$TBLU}Multiply by 5 for safety.${TNRM}\n" 128 | else 129 | printf "${TRED}Command given did not exit with a ${TNRM}0${TRED} return code and would fail in homebridge-cmd4 rc=${TNRM}'${rc}'\n" 130 | printf "Understand that the error given is a result of running your command in a basic shell environment. Head the errors given\n" 131 | fi 132 | fi 133 | 134 | 135 | exit ${rc} 136 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/Examples/PS4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | # 5 | # This Cmd4 example demonstrates a script that is compatible with 6 | # cmdSwitch2's example of controlling a PS4. 7 | # 8 | # Your Cmd4 .homebridge/.config.json file would have a state_cmd like: 9 | # state_cmd: ".homebridge/Cmd4Scripts/Examples/PS4.sh" 10 | # 11 | # Testing from the shell prompt: 12 | # ./PS4.sh Get PS4 On 13 | # or 14 | # ./PS4.sh Set PS4 On 1 15 | # or 16 | # ./PS4.sh Set PS4 On 0 17 | 18 | # Exit immediately if a command exits with a non-zero status 19 | set -e 20 | 21 | # Check if the first parameter to this script was "Get" for getting an accessory's 22 | # specific attribute. 23 | if [ "$1" = "Get" ]; then 24 | 25 | # Normally we would exit immediately if a command fails with a non-zero status. 26 | # In this case ps4-waker can fail and we would rely on the failing exit status to 27 | # tell Cmd4 that the accessory is not on the network. That would be the prefered 28 | # thing to do. However for this example we are going to output '0' (false) so 29 | # that you can see the '0' on the console telling us that the accessory is not 30 | # on the network. 31 | set +e 32 | 33 | ps4-waker search | grep -i '200 Ok' >> /dev/null 2>&1 34 | rc=$? 35 | 36 | # Exit immediately if a command exits with a non-zero status 37 | set -e 38 | 39 | # Check if we got the message '200 OK' meaning the accessory is 40 | # on the network by seeing if the return code of the above command passed or 41 | # failed. 42 | if [ "$rc" = "0" ]; then 43 | # The message was recieved so the target is up, sending a '1' (true), like 44 | # a binary number is, back to Cmd4. 45 | echo "1" 46 | 47 | # Exit this script positivitely. 48 | exit 0 49 | else 50 | # The message was not recieved so the target must be down, sending a '0' (false), like 51 | # a binary number is, back to Cmd4. 52 | echo "0" 53 | 54 | # Exit this script positivitely, even though ps4-waker failed. 55 | exit 0 56 | fi 57 | fi 58 | 59 | # Check if the first parameter to this script was "Set" for setting an accessory's 60 | # specific attribute. 61 | if [ "$1" = "Set" ]; then 62 | 63 | # $2 would be the name of the accessory. 64 | # $3 would be the accessory's charactersistic 'On'. 65 | # $4 would be '1' for 'On' and '0' for 'Off', like a binary number is. 66 | # $4 would be 'true' for 'On' and 'false' for 'Off' with 67 | # outputConstants=true in your .homebridge/.config.json file. 68 | 69 | # Handle the Set 'On' attribute of the accessory 70 | if [ "$3" = "On" ]; then 71 | 72 | # If the accessory is to be set on 73 | if [ "$4" = "1" ]; then 74 | 75 | # Normally we would exit immediately if a command fails with a non-zero status. 76 | # In this case ps4-waker can fail and we would rely on the failing exit status to 77 | # tell Cmd4 that the accessory is not on the network. That would be the prefered 78 | # thing to do. However for this example we are going to output '0' (false) so 79 | # that you can see the '0' on the console telling us that the accessory is not 80 | # on the network. 81 | set +e 82 | 83 | # Execute the on command 84 | ps4-waker >> /dev/null 2>&1 85 | 86 | # keep the result of the on/off command 87 | rc=$? 88 | 89 | # Exit immediately if a command exits with a non-zero status 90 | set -e 91 | 92 | else 93 | 94 | # Normally we would exit immediately if a command fails with a non-zero status. 95 | # In this case ps4-waker can fail and we would rely on the failing exit status to 96 | # tell Cmd4 that the accessory is not on the network. That would be the prefered 97 | # thing to do. However for this example we are going to output '0' (false) so 98 | # that you can see the '0' on the console telling us that the accessory is not 99 | # on the network. 100 | set +e 101 | 102 | # Execute the off command 103 | ps4-waker standby >> /dev/null 2>&1 104 | 105 | # keep the result of the on/off command 106 | rc=$? 107 | 108 | # Exit immediately if a command exits with a non-zero status 109 | set -e 110 | fi 111 | 112 | # Check if the on/off command had a positive return status. 113 | if [ "$rc" = "0" ]; then 114 | 115 | # The on/off command was successful, so exit successfully. 116 | exit 0 117 | 118 | else 119 | # The on/off comand had a failure result. Exit with that result. 120 | 121 | # Exit this script positivitely, even though ping failed. 122 | exit $rc 123 | fi 124 | fi 125 | fi 126 | 127 | # The proper arguments to this script were not passed to it so end with a failure exit status. 128 | exit 666 129 | -------------------------------------------------------------------------------- /utils/Logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function ( mod ) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | 6 | const settings = require( "../cmd4Settings" ); 7 | 8 | const util_1 = __importDefault( require( "util" ) ); 9 | const chalk_1 = __importDefault( require( "chalk" ) ); 10 | // 11 | // Log levels to indicate importance of the logged message. 12 | // Every level corresponds to a certain color. 13 | // 14 | // - INFO: no color 15 | // - WARN: yellow 16 | // - ERROR: red 17 | // - DEBUG: gray 18 | // 19 | 20 | // 21 | // Gets the prefix 22 | // @param prefix 23 | // 24 | function getLogPrefix( prefix ) 25 | { 26 | return chalk_1.default.cyan( `[${prefix}]` ); 27 | } 28 | 29 | // 30 | // Logger class 31 | // 32 | class Logger 33 | { 34 | constructor( ) 35 | { 36 | // Note: log.info goes to logBuf 37 | this.logLineCount = 0; 38 | this.logBuf = ""; 39 | // Note: log.warn goes to errBuf 40 | this.errLineCount = 0; 41 | this.errBuf = ""; 42 | 43 | this.outputEnabled = true; 44 | this.bufferEnabled = false; 45 | this.debugEnabled = false; 46 | 47 | // Added as was set outside the class 48 | this.timestampEnabled = true; 49 | this.prefix = settings.PLATFORM_NAME; 50 | this.forceColor( ); 51 | } 52 | // 53 | // Turns on debug level logging. Off by default. 54 | // 55 | // @param enabled { boolean } 56 | // 57 | setDebugEnabled( enabled = true ) 58 | { 59 | this.debugEnabled = enabled; 60 | settings.cmd4Dbg = enabled; 61 | } 62 | setOutputEnabled( enabled = true ) 63 | { 64 | this.outputEnabled = enabled; 65 | } 66 | // 67 | // Turns on buffered logging. Off by default. 68 | // 69 | // @param enabled { boolean } 70 | // 71 | setBufferEnabled( enabled = true ) 72 | { 73 | this.bufferEnabled = enabled; 74 | } 75 | // 76 | // Turns on inclusion of timestamps in log messages. On by default. 77 | // 78 | // @param enabled { boolean } 79 | // 80 | setTimestampEnabled( enabled = true ) 81 | { 82 | this.timestampEnabled = enabled; 83 | } 84 | // 85 | // Forces color in logging output, even if it seems like color is unsupported. 86 | // 87 | forceColor( ) 88 | { 89 | chalk_1.default.level = 1; // `1` - Basic 16 colors support. 90 | } 91 | info( message, ...parameters ) 92 | { 93 | this.log( "info" /* INFO */, message, ...parameters ); 94 | } 95 | warn( message, ...parameters ) 96 | { 97 | this.log( "warn" /* WARN */, message, ...parameters ); 98 | } 99 | error( message, ...parameters ) 100 | { 101 | this.log( "error" /* ERROR */, message, ...parameters ); 102 | } 103 | debug( message, ...parameters ) 104 | { 105 | if ( this.debugEnabled ) 106 | this.log( "debug" /* DEBUG */, message, ...parameters ); 107 | } 108 | log( level, message, ...parameters ) 109 | { 110 | if ( level === "debug" /* DEBUG */ && ! this.debugEnabled ) 111 | { 112 | return; 113 | } 114 | message = util_1.default.format( message, ...parameters ); 115 | switch ( level ) 116 | { 117 | case "warn": 118 | message = chalk_1.default.yellow( message ); 119 | break; 120 | case "error": 121 | message = chalk_1.default.red( message ); 122 | break; 123 | case "debug": 124 | message = chalk_1.default.gray( message ); 125 | break; 126 | } 127 | if ( this.prefix ) 128 | { 129 | message = getLogPrefix( this.prefix ) + " " + message; 130 | } 131 | if ( this.timestampEnabled ) 132 | { 133 | const date = new Date(); 134 | message = chalk_1.default.white( `[${date.toLocaleString()}] ` ) + message; 135 | } 136 | switch ( level ) 137 | { 138 | // Homebridge puts warninggs to errorbuf, so do the same. 139 | case "error": 140 | case "warn": 141 | if ( this.bufferEnabled == true ) 142 | { 143 | this.errLineCount++; 144 | this.errBuf += message; 145 | this.errBuf += "\n"; 146 | } 147 | if ( this.outputEnabled == true ) 148 | console.log( message ); 149 | break; 150 | case "debug": 151 | case "info": 152 | default: 153 | if ( this.bufferEnabled == true ) 154 | { 155 | this.logLineCount++; 156 | this.logBuf += message; 157 | this.logBuf += "\n"; 158 | } 159 | if ( this.outputEnabled == true ) 160 | console.log( message ); 161 | } 162 | 163 | } 164 | reset() 165 | { 166 | this.logLineCount = 0; 167 | this.logBuf = ""; 168 | this.errLineCount = 0; 169 | this.errBuf = ""; 170 | } 171 | } 172 | module.exports=Logger; 173 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/Examples/wakeonlan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # This Cmd4 example demonstrates a script that can be used for a wakeonlan 5 | # scenario. It is a port of the cmdSwitch2 example and is more for a Windows 6 | # PC. 7 | # 8 | # Your Cmd4 .homebridge/.config.json file would have a state_cmd like: 9 | # state_cmd: ".homebridge/Cmd4Scripts/Examples/wakeonlan.sh" 10 | # state_cmd_suffix: "192.168.2.66 dc:a6:32:40:de:7c" 11 | # 12 | # 13 | # Testing from the shell prompt: 14 | # ./wakeonlan.sh Get HTPC On 192.168.2.66 dc:a6:32:40:de:7c 15 | # or 16 | # ./wakeonlan.sh Set HTPC On 1 192.168.2.66 dc:a6:32:40:de:7c 17 | # or 18 | # ./wakeonlan.sh Set HTPC On 0 192.168.2.66 dc:a6:32:40:de:7c 19 | 20 | # Exit immediately if a command exits with a non-zero status 21 | set -e 22 | 23 | # Check if the first parameter to this script was "Get" for getting an accessory's 24 | # specific attribute. 25 | if [ "$1" = "Get" ]; then 26 | 27 | # Cmd4 will pass the IP in the config.json defined by state_cmd_suffix as the fourth 28 | # parameter to a Get command. 29 | ip="${4}" 30 | 31 | # Cmd4 will pass the MAC Address in the config.json defined by state_cmd_suffix as the fifth 32 | # parameter to a Get command. 33 | macAddress="${5}" 34 | 35 | # Normally we would exit immediately if a command fails with a non-zero status. 36 | # In this case ping can fail and we would rely on the failing exit status to 37 | # tell Cmd4 that the accessory is not on the network. That would be the prefered 38 | # thing to do. However for this example we are going to output '0' (false) so 39 | # that you can see the '0' on the console telling us that the accessory is not 40 | # on the network. 41 | set +e 42 | 43 | # On OSX the string is returned differently than on linux. 44 | ping -c 2 -W 1 "${ip}" | sed -E 's/2 packets received/2 received/g' | grep -i '2 received' >> /dev/null 45 | rc=$? 46 | 47 | # Exit immediately if a command exits with a non-zero status 48 | set -e 49 | 50 | # Check if we got the message '2 packets recieved' meaning the accessory is 51 | # on the network by seeing if the return code of the above command passed or 52 | # failed. 53 | if [ "$rc" = "0" ]; then 54 | # The message was recieved so the target is up, sending a '1' (true), like 55 | # a binary number is, back to Cmd4. 56 | echo "1" 57 | 58 | # Exit this script positivitely. 59 | exit 0 60 | else 61 | # The message was not recieved so the target must be down, sending a '0' (false), like 62 | # a binary number is, back to Cmd4. 63 | echo "0" 64 | 65 | # Exit this script positivitely, even though ping failed. 66 | exit 0 67 | fi 68 | fi 69 | 70 | # Check if the first parameter to this script was "Set" for setting an accessory's 71 | # specific attribute. 72 | if [ "$1" = "Set" ]; then 73 | 74 | # $2 would be the name of the accessory. 75 | # $3 would be the accessory's charactersistic 'On'. 76 | # $4 would be '1' for 'On' and '0' for 'Off', like a binary number is. 77 | # $4 would be 'true' for 'On' and 'false' for 'Off' with 78 | # outputConstants=true in your .homebridge/.config.json file. 79 | 80 | # Cmd4 will pass the IP in the config.json defined by state_cmd_suffix as the fifth 81 | # parameter to a Set command. 82 | ip="${5}" 83 | 84 | # Cmd4 will pass the MAC Address in the config.json defined by state_cmd_suffix as the sixth 85 | # parameter to a Set command. 86 | macAddress="${6}" 87 | 88 | # Handle the Set 'On' attribute of the accessory 89 | if [ "$3" = "On" ]; then 90 | 91 | # If the accessory is to be set on 92 | if [ "$4" = "1" ]; then 93 | # Execute the on command 94 | wakeonlan -i ${ip} ${macAddress} >> /dev/null 2>&1 95 | 96 | # keep the result of the on/off command 97 | rc=$? 98 | else 99 | # Execute the off command 100 | # The password is harad coded here. We use the default 101 | # Raspberry Pi password asli an example. How you handle 102 | # this unencrypted password is up to you. Note that 103 | # this command only works to a Windows box, but is the 104 | # exmple given in cmdSwitch2. 105 | # net rpc shutdown -I "${ip}" -U user%password, 106 | net rpc shutdown -I "${ip}" -U pi%raspberry >> /dev/null 2>&1 107 | 108 | # keep the result of the on/off command 109 | rc=$? 110 | fi 111 | 112 | # Check if the on/off command had a positive return status. 113 | if [ "$rc" = "0" ]; then 114 | 115 | # The on/off command was successful, so exit successfully. 116 | exit 0 117 | 118 | else 119 | # The on/off comand had a failure result. Exit with that result. 120 | 121 | # Exit this script positivitely, even though ping failed. 122 | exit $rc 123 | fi 124 | fi 125 | fi 126 | 127 | # The proper arguments to this script were not passed to it so end with a failure exit status. 128 | exit 66 129 | -------------------------------------------------------------------------------- /Extras/Cmd4Scripts/Examples/ExampleJavaScript_template.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // ExampleScript_template.js 3 | // 4 | // Description: 5 | // This script is a goood starting place for you to create Cmd4 Scripts 6 | // of your own 7 | // 8 | // Parameters are: 9 | // Get < Any accessory name > < Characteristic > 10 | // Set < Any accessory name > < Characteristic > < value > 11 | // 12 | // Note 1: These paramaters match the those of the Cmd4 plugin. 13 | // A full lost of supported devices and characteristics can be 14 | // found at: 15 | // https://ztalbot2000.github.io/homebridge-cmd4 16 | // 17 | // How it works: 18 | // The Cmd4 plugin will call this script to retrieve those states 19 | // you have defined as not Cached to Get/Set your devices characteristic 20 | // states. 21 | // 22 | // For example: 23 | // node ExampleScript_template.js Set My_Door TargetDoorState 0 24 | // or 25 | // node ExampleScript.js Get My_Door CurrentDoorState 26 | // 27 | // 28 | 29 | 'use strict'; 30 | 31 | var length = process.argv.length; 32 | var device = "My_Door"; 33 | var io = ""; 34 | var characteristic = ""; 35 | var option = ""; 36 | 37 | if ( length == 2 ) process.exit( 0 ); 38 | 39 | if ( length <= 2 ) { 40 | console.log( "Usage: " + process.argv[0] + " < Get > < AccessoryName > < Characteristic >" ); 41 | console.log( " " + process.argv[0] + " < Set > < AccessoryName > < Characteristic > < Value >" ); 42 | process.exit( -1 ); 43 | } 44 | 45 | if ( length >= 2 ) io = process.argv[2]; 46 | if ( length >= 3 ) device = process.argv[3]; 47 | if ( length >= 4 ) characteristic = process.argv[4]; 48 | if ( length >= 5 ) option = process.argv[5]; 49 | 50 | var c = ""; 51 | 52 | switch( io ) 53 | { 54 | case "Get": 55 | { 56 | switch( characteristic ) 57 | { 58 | case "CurrentDoorState": 59 | { 60 | console.log( 0 ); 61 | 62 | // See https://ztalbot2000.github.io/homebridge-cmd4 63 | // For the possible values and characteristics 64 | // available per device. It will show somethink like: 65 | // Valid Values: 66 | // 0 - "Open. The door is fully open." 67 | // 1 - "Closed. The door is fully closed." 68 | // 2 - "Opening. The door is actively opening." 69 | // 3 - "Closing. The door is actively closing." 70 | // 4 - "Stopped. The door is not moving, and it is not fully 71 | // open nor fully closed." 72 | // 5-255 - "Reserved" 73 | 74 | break; 75 | } 76 | case "TargetDoorState": 77 | { 78 | console.log( 0 ); 79 | break; 80 | } 81 | case "ObstructionDetected": 82 | { 83 | console.log ( 0 ); 84 | break; 85 | } 86 | case "LockCurrentState": 87 | { 88 | console.log ( 0 ); 89 | break; 90 | } 91 | default: 92 | console.error( "Unhandled characteristic for:" + io + " Device:" + device + " Characteristic:" + characteristic ); 93 | process.exit( -1 ); 94 | } 95 | 96 | break; 97 | 98 | } // End of Switch for "Get" 99 | case "Set": 100 | { 101 | switch( characteristic ) 102 | { 103 | case "CurrentDoorState": 104 | { 105 | // Current Door State is not settable. The 106 | // call would be to TargetDoorState. This is here 107 | // for debugging only. 108 | 109 | break; 110 | } 111 | case "TargetDoorState": 112 | { 113 | // Do something of your own here. 114 | 115 | break; 116 | } 117 | case "ObstructionDetected": 118 | { 119 | // Obstruction Detected is not settable. It 120 | // call is a read-only characteristic. This is here 121 | // for debugging only. 122 | break; 123 | } 124 | case "LockCurrentState": 125 | { 126 | // Lock Current State is not settable. It 127 | // call is a read-only characteristic. This is here 128 | // for debugging only. 129 | break; 130 | } 131 | default: 132 | console.error( "UnHandled Characteristic for:" + io + " Device:" + device + " Characteristic:" + characteristic ); 133 | process.exit( -1 ); 134 | } 135 | 136 | break; 137 | } // End of Switch Device for "Set" 138 | default: 139 | console.error( "Unknown IO" + io ); 140 | process.exit( -1 ); 141 | } 142 | 143 | //console.log( "Say What Device:" + device + " Characteristic:" + characteristic + " Option:" + option ); 144 | 145 | // You must exit with a zero status, confirming the script rannsuccessfully. 146 | process.exit( 0 ); 147 | 148 | 149 | -------------------------------------------------------------------------------- /utils/Cmd4Storage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // These would already be initialized by index.js 4 | let CMD4_ACC_TYPE_ENUM = require( "../lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC_TYPE_ENUM; 5 | 6 | class Cmd4Storage 7 | { 8 | constructor( log, cmd4Storage ) 9 | { 10 | this.CLASS_VERSION = 1; 11 | this.DATA = [ ]; 12 | 13 | if ( cmd4Storage == undefined ) 14 | { 15 | log.debug("Init new cmd4Storage" ); 16 | 17 | // init new 18 | this.DATA = new Array( CMD4_ACC_TYPE_ENUM.EOL ).fill( null ); 19 | 20 | } else if ( cmd4Storage instanceof Cmd4Storage ) 21 | { 22 | log.debug("Class is Cmd4Storage version: %s", cmd4Storage.version ); 23 | if ( cmd4Storage.CLASS_VERSION == this.CLASS_VERSION ) 24 | { 25 | // The same. I can just load its data 26 | this.loadLatestData( cmd4Storage.DATA ); 27 | } else { 28 | throw new Error( `Do not know how to handle Cmd4_Storage Class version: ${ cmd4Storage.CLASS_VERSION }` ); 29 | } 30 | } else if ( Array.isArray( cmd4Storage ) ) 31 | { 32 | log.debug("Cmd4Storage is Array" ); 33 | // Init original unversioned 34 | let data = this.upgradeDataArray( 0, cmd4Storage ); 35 | this.loadLatestData( data ); 36 | 37 | } else if ( cmd4Storage.constructor === Object ) 38 | { 39 | log.debug("Cmd4Storage is Object version %s", cmd4Storage.CLASS_VERSION ); 40 | if ( cmd4Storage.CLASS_VERSION == this.CLASS_VERSION ) 41 | { 42 | // The same. I can just load its data 43 | this.loadLatestData( cmd4Storage.DATA ); 44 | } else { 45 | throw new Error( `Do not know how to handle Cmd4_Storage Class version: ${ cmd4Storage.CLASS_VERSION }` ); 46 | } 47 | } else 48 | { 49 | // Woops init new 50 | log.error( "cmd4Storage is %s", cmd4Storage ); 51 | console.error( "cmd4Storage.constructor.name is %s", cmd4Storage.constructor.name ); 52 | throw new Error( `Do not know how to handle typeof: ${ typeof cmd4Storage } Cmd4_Storage parm: ${ cmd4Storage }` ); 53 | } 54 | } 55 | 56 | upgradeDataArray( fromVersion, fromData) 57 | { 58 | let data = [ ]; 59 | 60 | if ( fromVersion != 0 ) 61 | throw new Error( `Do not know how to handle Cmd4_Storage version: ${ fromVersion }` ); 62 | 63 | // Version 0 ACC_DATA went from 0-122 64 | // This version goes from 1-123 and changes to 65 | // Assoc array so that index changes like this will no longer 66 | // impact the storage schema as much 67 | let i=0; 68 | for ( i=0; i < CMD4_ACC_TYPE_ENUM.ListPairings; i++ ) 69 | { 70 | data[ i ] = fromData[ i ]; 71 | } 72 | data[ CMD4_ACC_TYPE_ENUM.ListPairing ] = null; 73 | for ( i = CMD4_ACC_TYPE_ENUM.ListPairings +1; i < CMD4_ACC_TYPE_ENUM.EOL; i++ ) 74 | { 75 | data[ i ] = fromData[ i - 1 ]; 76 | } 77 | return data; 78 | } 79 | 80 | loadLatestData( data ) 81 | { 82 | this.DATA = data; 83 | } 84 | 85 | getStoredValueForIndex( accTypeEnumIndex ) 86 | { 87 | if ( accTypeEnumIndex < 0 || accTypeEnumIndex >= CMD4_ACC_TYPE_ENUM.EOL ) 88 | throw new Error( `getStoredValue - Characteristic index: ${ accTypeEnumIndex } not between 0 and ${ CMD4_ACC_TYPE_ENUM.EOL }\nCheck your config.json file for unknown characteristic.` ); 89 | 90 | 91 | return this.DATA[ accTypeEnumIndex ]; 92 | } 93 | 94 | getStoredValueForCharacteristic( characteristicString ) 95 | { 96 | let accTypeEnumIndex = CMD4_ACC_TYPE_ENUM.indexOfEnum( characteristicString ); 97 | 98 | return this.getStoredValueForIndex( accTypeEnumIndex ); 99 | } 100 | setStoredValueForIndex( accTypeEnumIndex, value ) 101 | { 102 | if ( accTypeEnumIndex < 0 || accTypeEnumIndex >= CMD4_ACC_TYPE_ENUM.EOL ) 103 | throw new Error( `setStoredValue - Characteristic index: ${ accTypeEnumIndex } not between 0 and ${ CMD4_ACC_TYPE_ENUM.EOL }\nCheck your config.json file for unknown characteristic.` ); 104 | 105 | this.DATA[ accTypeEnumIndex ] = value; 106 | } 107 | setStoredValueForCharacteristic( characteristicString, value ) 108 | { 109 | let accTypeEnumIndex = CMD4_ACC_TYPE_ENUM.indexOfEnum( characteristicString ); 110 | 111 | this.setStoredValueForIndex( accTypeEnumIndex, value ); 112 | } 113 | 114 | // Unlike get/set, testStoredValueForIndex does not call process.exit, 115 | // but undefined for an illegal range, in the case that rogue runtime data 116 | // dies not take down CMD4. 117 | testStoredValueForIndex( accTypeEnumIndex ) 118 | { 119 | if ( accTypeEnumIndex < 0 || accTypeEnumIndex > CMD4_ACC_TYPE_ENUM.EOL ) 120 | return undefined; 121 | 122 | return this.DATA[ accTypeEnumIndex ]; 123 | } 124 | testStoredValueForCharacteristic( characteristicString ) 125 | { 126 | let accTypeEnumIndex = CMD4_ACC_TYPE_ENUM.indexOfEnum( characteristicString ); 127 | 128 | return this.testStoredValueForIndex( accTypeEnumIndex ); 129 | } 130 | } 131 | 132 | module.exports = Cmd4Storage; 133 | -------------------------------------------------------------------------------- /test/CMD4_CHAR_TYPE_ENUMS.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _api = new HomebridgeAPI(); // object we feed to Plugins 4 | 5 | let { indexOfEnum } = require( "../utils/indexOfEnum" ); 6 | Object.defineProperty(exports, "indexOfEnum", { enumerable: true, get: function () { return indexOfEnum.indexOfEnum; } }); 7 | 8 | 9 | describe( "Testing require of CMD4_CHAR_TYPE_ENUMS.js", ( ) => 10 | { 11 | it( "CMD4_CHAR_TYPE_ENUMS should be defined ( required correctly )", ( ) => 12 | { 13 | assert.isNotNull( CMD4_CHAR_TYPE_ENUMS, "CMD4_CHAR_TYPE_ENUMS is null" ); 14 | }); 15 | 16 | // ************ TEST UNINITIALIZED CMD4_FORMAT_TYPE_ENUM EOL ************** 17 | describe( "Testing CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.EOL", ( ) => 18 | { 19 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM has EOL", ( ) => 20 | { 21 | assert.isNotNull( CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.EOL, "EOL is null" ); 22 | }); 23 | 24 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.EOL = " + FORMAT_EOL, ( ) => 25 | { 26 | assert.equal( CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.EOL, FORMAT_EOL, "CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.EOL FOUND: " + CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.EOL ); 27 | }); 28 | 29 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM[ 0-" + CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.EOL + " ] should equal value at index", ( ) => 30 | { 31 | for ( let index=0; index < CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.EOL; index ++ ) 32 | { 33 | assert.notEqual( CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM[index], index ); 34 | } 35 | }); 36 | 37 | }); 38 | 39 | // ************ TEST UNINITIALIZED CMD4_UNITS_TYPE_ENUM EOL ************** 40 | describe( "Testing CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.EOL", ( ) => 41 | { 42 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM has EOL", ( ) => 43 | { 44 | assert.isNotNull( CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.EOL, "EOL is null" ); 45 | }); 46 | 47 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.EOL = " + UNITS_EOL, ( ) => 48 | { 49 | assert.equal( CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.EOL, UNITS_EOL, "CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.EOL FOUND: " + CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.EOL ); 50 | }); 51 | 52 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM[ 0-" + CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.EOL + " ] should equal value at index", ( ) => 53 | { 54 | for ( let index=0; index < CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.EOL; index ++ ) 55 | { 56 | assert.notEqual( CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM[index], index ); 57 | } 58 | }); 59 | 60 | }); 61 | 62 | 63 | // ************ TEST UNINITIALIZED CMD4_PERMS_TYPE_ENUM EOL ************** 64 | describe( "Testing CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.EOL", ( ) => 65 | { 66 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM has EOL", ( ) => 67 | { 68 | assert.isNotNull( CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.EOL, "EOL is null" ); 69 | }); 70 | 71 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.EOL = " + PERMS_EOL, ( ) => 72 | { 73 | assert.equal( CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.EOL, PERMS_EOL, "CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.EOL FOUND: " + CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.EOL ); 74 | }); 75 | 76 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM[ 0-" + CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.EOL + " ] should equal value at index", ( ) => 77 | { 78 | for ( let index=0; index < CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.EOL; index ++ ) 79 | { 80 | assert.notEqual( CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM[index], index ); 81 | } 82 | }); 83 | 84 | }); 85 | }) 86 | 87 | 88 | 89 | 90 | 91 | describe( "Testing INITIALIZED CMD4_CHAR_TYPE_ENUMS", ( ) => 92 | { 93 | // Init the library for all to use 94 | let CMD4_CHAR_TYPE_ENUMS = CHAR_DATA.init( _api.hap.Formats, _api.hap.Units, _api.hap.Perms ); 95 | 96 | 97 | describe("Testing Initialized CMD4_CHAR_TYPE_ENUMS.", ( ) => 98 | { 99 | // Test a format type 100 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.float should equal expected value", ( ) => 101 | { 102 | let formatIndex = CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM.properties.indexOfEnum( i => i.type === "float" ); 103 | assert.equal( formatIndex, 2, `CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM "float" not equal to expected index`); 104 | }); 105 | 106 | // Test a units type 107 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.lux should equal expected value", ( ) => 108 | { 109 | let unitsIndex = CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM.properties.indexOfEnum( i => i.type === "lux" ); 110 | assert.equal( unitsIndex, 3, `CMD4_CHAR_TYPE_ENUMS.CMD4_UNITS_TYPE_ENUM "lux" not equal to expected index`); 111 | }); 112 | 113 | // Test a perms type 114 | it( "CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.ev should equal expected value", ( ) => 115 | { 116 | let permsIndex = CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM.properties.indexOfEnum( i => i.type === "ev" ); 117 | assert.equal( permsIndex, 4, `CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM "ev" not equal to expected index`); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-cmd4", 3 | "description": "Exec Plugin for Homebridge supporting all accessorys and characteristics", 4 | "version": "8.0.3", 5 | "license": "MIT", 6 | "author": { 7 | "name": "John Talbot" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/ztalbot2000/homebridge-cmd4.git" 12 | }, 13 | "homepage": "https://github.com/ztalbot2000/homebridge-cmd4#readme", 14 | "changelog": "https://github.com/ztalbot2000/homebridge-cmd4/blob/master/ChangeLog.md", 15 | "bugs": { 16 | "url": "https://github.com/ztalbot2000/homebridge-cmd4/issues", 17 | "email": "ztalbot2000@gmail.com" 18 | }, 19 | "dependencies": { 20 | "chalk": "^4.1.0", 21 | "command-exists": "^1.2.9", 22 | "fakegato-history": ">=0.6.5", 23 | "latest-version": "^5.1.0", 24 | "moment": "*" 25 | }, 26 | "devDependencies": { 27 | "@commitlint/cli": "^11.0.0", 28 | "@commitlint/config-conventional": "^11.0.0", 29 | "chai": "^4.2.0", 30 | "commander": "^8.2.0", 31 | "commitlint-plugin-function-rules": "^1.1.20", 32 | "eslint": "^7.17.0", 33 | "generate-changelog": "^1.8.0", 34 | "husky": "^4.3.8", 35 | "link-checker": "^1.4.2", 36 | "markdown-link-check": "^3.8.6", 37 | "mocha": "^8.0.1", 38 | "node-persist": "^0.0.11", 39 | "sinon": "^9.2.4", 40 | "which": "^2.0.2" 41 | }, 42 | "directories": { 43 | "test": "test", 44 | "lib": "lib", 45 | "utils": "utils", 46 | "docs": "docs" 47 | }, 48 | "engines": { 49 | "node": "^18.20.4 || ^20.18.0 || ^22.10.0", 50 | "homebridge": "^1.8.0 || ^2.0.0-beta.0" 51 | }, 52 | "keywords": [ 53 | "homebridge-plugin", 54 | "homebridge", 55 | "exec", 56 | "command", 57 | "switch", 58 | "light", 59 | "door", 60 | "thermostat", 61 | "security", 62 | "temperature", 63 | "virtual", 64 | "Eve", 65 | "Homekit", 66 | "siri", 67 | "home", 68 | "Eve" 69 | ], 70 | "husky": { 71 | "hooks": { 72 | "pre-commit": "npm run lint && npm run test test/cmd4Constants", 73 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 74 | } 75 | }, 76 | "scripts": { 77 | "debug": "DEBUG=* homebridge --debug --plugin-path .", 78 | "pretest": "printf ' test is only done in a development environment\n';sleep 2 ", 79 | "testDebug": "node_modules/.bin/mocha --require async-dump.js --file test/mocha-setup --ignore test/versionChecker.js", 80 | "test": "node_modules/.bin/mocha --file test/mocha-setup --ignore test/versionChecker.js", 81 | "singleTest": "node_modules/.bin/mocha --bail --file test/mocha-setup --ignore test/versionChecker.js", 82 | "allTests": "node_modules/.bin/mocha --bail --file test/mocha-setup $( cat test/allTests )", 83 | "sanityTests": "node_modules/.bin/mocha --bail --file test/mocha-setup $( cat test/sanityTests )", 84 | "_Note0": "Excluded from commit as may fail for probing GitHub to much", 85 | "testMarkDown": "markdown-link-check ./README.md && markdown-link-check ./CHANGELOG.md && markdown-link-check ./docs/AdvancedTroubleShooting.md && markdown-link-check ./docs/Developers.md", 86 | "testAutoGeneratedDocLinks": "link-checker docs/autoGenerated/CMD4_AccessoryDescriptions.html", 87 | "commitTests": "npm run test && npm run testMarkDown", 88 | "_Note": "Excluded only because they sometimes fail, but pass alone. ", 89 | "testCmd4Accessory": "node_modules/.bin/mocha --file test/mocha-setup test/Cmd4Accessory.js", 90 | "testVersionChecker": "node_modules/.bin/mocha --file test/mocha-setup test/versionChecker.js", 91 | "lint": "eslint --ext .js *.js utils/*.js lib/*.js tools/* test/*.js", 92 | "postinstall": "node postinstall.js", 93 | "_Note01": "Auto change the version based on the commit history. ", 94 | "_Note02": "Where: ", 95 | "_Note03": "To trigger a version update ", 96 | "_Note04": " npm run release ", 97 | "_Note05": " ", 98 | "_Note06": "To trigger a patch update (1.0.0 → 1.0.1) ", 99 | "_Note07": " git commit -m 'fix: …' ", 100 | "_Note08": "To trigger a minor update (1.0.0 → 1.1.0) ", 101 | "_Note09": " git commit -m 'feat: …' ", 102 | "_Note10": "To trigger a major update (1.0.0 → 2.0.0) ", 103 | "_Note11": " A BREAKING CHANGE: in the commit body ", 104 | "_Note12": " ", 105 | "_Note13": "As a summary, generate-changelog will do ", 106 | "_Note14": " * Bumps the version in package.json ", 107 | "_Note15": " * Updates CHANGELOG.md ", 108 | "_Note16": " ", 109 | "release": "tools/generateChangeLog", 110 | "_Note17": " ", 111 | "_Note18": "Overide default behaviour with: ", 112 | "release:patch": "tools/generateChangeLog --type patch --cleanup", 113 | "release:minor": "tools/generateChangeLog --type minor --cleanup", 114 | "release:major": "tools/generateChangeLog --type major --cleanup" 115 | } 116 | } -------------------------------------------------------------------------------- /test/HV.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | let HV = require( "../utils/HV" ); 5 | let constants = require( "../cmd4Constants" ); 6 | 7 | 8 | describe(`A Hierarchy Variable Test`, ( ) => 9 | { 10 | it( `Test creation of HV`, ( ) => 11 | { 12 | const hv = new HV( ); 13 | 14 | assert.instanceOf( hv , HV, "Expected hv to be instance of HV. Found %s" , hv ); 15 | assert.isFunction( hv.update, ".update is not a function" ); 16 | 17 | assert.equal( hv.allowTLV8, constants.DEFAULT_ALLOW_TLV8, `default not created` ); 18 | assert.equal( hv.debug, constants.DEFAULT_DEBUG, `default not created` ); 19 | assert.equal( hv.outputConstants, constants.DEFAULT_OUTPUTCONSTANTS, `default not created` ); 20 | assert.equal( hv.interval, constants.DEFAULT_INTERVAL, `default not created` ); 21 | assert.equal( hv.stateChangeResponseTime, constants.DEFAULT_STATE_CHANGE_RESPONSE_TIME, `default not created` ); 22 | assert.equal( hv.statusMsg, constants.DEFAULT_STATUSMSG, `default not created` ); 23 | assert.equal( hv.timeout, constants.DEFAULT_TIMEOUT, `default not created` ); 24 | 25 | 26 | }); 27 | 28 | it( `HV can update variables`, ( done ) => 29 | { 30 | let data = 31 | { 32 | allowTLV8: false, 33 | debug: true, 34 | outputConstants: true, 35 | interval: 100, 36 | stateChangeResponseTime: 122, 37 | statusMsg: false, 38 | timeout: 141 39 | }; 40 | let hv = new HV( ); 41 | 42 | // Simple check previously tested 43 | assert.equal( hv.allowTLV8, constants.DEFAULT_ALLOW_TLV8, `default not created` ); 44 | 45 | hv.update( data ); 46 | 47 | assert.equal( hv.allowTLV8, data.allowTLV8, `hv not updated` ); 48 | assert.equal( hv.debug, data.debug, `hv not updated` ); 49 | assert.equal( hv.outputConstants, data.outputConstants, `hv not updated` ); 50 | assert.equal( hv.interval, data.interval, `hv not updated` ); 51 | assert.equal( hv.stateChangeResponseTime, data.stateChangeResponseTime, `hv not updated` ); 52 | assert.equal( hv.statusMsg, data.statusMsg, `hv not updated` ); 53 | assert.equal( hv.timeout, data.timeout, `hv not updated` ); 54 | 55 | done( ); 56 | }); 57 | 58 | it( `Test HV can update updated variables`, ( done ) => 59 | { 60 | let data = 61 | { 62 | allowTLV8: false, 63 | debug: true, 64 | outputConstants: true, 65 | interval: 100, 66 | stateChangeResponseTime: 122, 67 | statusMsg: false, 68 | timeout: 141 69 | }; 70 | let hv = new HV( ); 71 | 72 | hv.update( data ); 73 | 74 | let data2 = 75 | { 76 | allowTLV8: true, 77 | debug: false, 78 | outputConstants: false, 79 | interval: 300, 80 | // stateChangeResponseTime: 122, 81 | statusMsg: true, 82 | timeout: 333 83 | }; 84 | 85 | hv.update( data2 ); 86 | 87 | assert.equal( hv.allowTLV8, data2.allowTLV8, `hv not updated` ); 88 | assert.equal( hv.debug, data2.debug, `hv not updated` ); 89 | assert.equal( hv.outputConstants, data2.outputConstants, `hv not updated` ); 90 | assert.equal( hv.interval, data2.interval, `hv not updated` ); 91 | assert.equal( hv.stateChangeResponseTime, data.stateChangeResponseTime, `hv not updated` ); 92 | assert.equal( hv.statusMsg, data2.statusMsg, `hv not updated` ); 93 | assert.equal( hv.timeout, data2.timeout, `hv not updated` ); 94 | 95 | done( ); 96 | }); 97 | 98 | it( `Test HV copy does not change original`, ( done ) => 99 | { 100 | const hv = new HV( ); 101 | 102 | let rHV = Object.assign( {}, hv ); 103 | assert.equal( hv.timeout, constants.DEFAULT_TIMEOUT, `hv not initialized` ); 104 | 105 | rHV.timeout = 1500; 106 | 107 | assert.equal( rHV.timeout, 1500, `rhv not changed` ); 108 | assert.equal( hv.timeout, constants.DEFAULT_TIMEOUT, `hv was changed` ); 109 | 110 | done( ); 111 | }); 112 | 113 | it( `Test HV copy does not change original`, ( done ) => 114 | { 115 | const hv = new HV( ); 116 | 117 | let rHV = Object.assign( {}, hv ); 118 | rHV.update = hv.update; 119 | 120 | assert.equal( hv.timeout, constants.DEFAULT_TIMEOUT, `hv not initialized` ); 121 | 122 | let data = 123 | { 124 | allowTLV8: true, 125 | debug: false, 126 | outputConstants: false, 127 | interval: 300, 128 | // stateChangeResponseTime: 122, 129 | statusMsg: true, 130 | timeout: 333 131 | }; 132 | 133 | rHV.update( data ); 134 | 135 | // Check changed 136 | assert.equal( rHV.allowTLV8, data.allowTLV8, `rHV not updated` ); 137 | assert.equal( rHV.debug, data.debug, `rHV not updated` ); 138 | assert.equal( rHV.outputConstants, data.outputConstants, `rHV not updated` ); 139 | assert.equal( rHV.interval, data.interval, `rHV not updated` ); 140 | assert.equal( rHV.stateChangeResponseTime, constants.DEFAULT_STATE_CHANGE_RESPONSE_TIME, `default not created` ); 141 | assert.equal( rHV.statusMsg, data.statusMsg, `rHV not updated` ); 142 | assert.equal( rHV.timeout, data.timeout, `rHV not updated` ); 143 | 144 | // Check original 145 | assert.equal( hv.allowTLV8, constants.DEFAULT_ALLOW_TLV8, `default was changed` ); 146 | assert.equal( hv.debug, constants.DEFAULT_DEBUG, `default was changed` ); 147 | assert.equal( hv.outputConstants, constants.DEFAULT_OUTPUTCONSTANTS, `default was changed` ); 148 | assert.equal( hv.interval, constants.DEFAULT_INTERVAL, `default was changed` ); 149 | assert.equal( hv.stateChangeResponseTime, constants.DEFAULT_STATE_CHANGE_RESPONSE_TIME, `default was changed` ); 150 | assert.equal( hv.statusMsg, constants.DEFAULT_STATUSMSG, `default was changed` ); 151 | assert.equal( hv.timeout, constants.DEFAULT_TIMEOUT, `default was changed` ); 152 | 153 | 154 | done( ); 155 | 156 | }); 157 | }); 158 | 159 | -------------------------------------------------------------------------------- /test/getAccessoryNameFunctions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { getAccessoryName, getAccessoryDisplayName } = require( "../utils/getAccessoryNameFunctions" ); 4 | 5 | describe( "Testing getAccessoryName LC", ( ) => 6 | { 7 | it( "getAccessoryName.js should be a function", ( ) => 8 | { 9 | 10 | assert.isFunction( getAccessoryName, "getAccessoryName is not a function" ); 11 | }); 12 | 13 | it( "getAccessoryName should choose name first LC ", ( ) => 14 | { 15 | let config = {displayName: "Kodi", 16 | name: "blah" 17 | }; 18 | let expectedResult = "blah"; 19 | let result = getAccessoryName( config ); 20 | 21 | assert.equal( result, expectedResult, "getAccessoryName should return name over displayName. result: " + result + " expected: " + expectedResult ); 22 | }); 23 | 24 | it( "getAccessoryName should choose displayName second LC ", ( ) => 25 | { 26 | let config = { noName: "blah", 27 | displayName: "Kodi" 28 | }; 29 | let expectedResult = "Kodi"; 30 | let result = getAccessoryName( config ); 31 | 32 | assert.equal( result, expectedResult, "getAccessoryName should return displayName over name. result: " + result + " expected: " + expectedResult ); 33 | }); 34 | }) 35 | 36 | describe( "Testing getAccessoryDisplayName LC", ( ) => 37 | { 38 | it( "getAccessoryDisplayName.js should be a function LC", ( ) => 39 | { 40 | 41 | assert.isFunction( getAccessoryDisplayName, "getAccessoryDisplayName is not a function" ); 42 | }); 43 | 44 | it( "getAccessoryDisplayName should choose displayName first LC ", ( ) => 45 | { 46 | let config = {displayName: "Kodi", 47 | name: "blah" 48 | }; 49 | let expectedResult = "Kodi"; 50 | let result = getAccessoryDisplayName( config ); 51 | 52 | assert.equal( result, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + result + " expected: " + expectedResult ); 53 | }); 54 | 55 | it( "getAccessoryDisplayName should choose name second LC ", ( ) => 56 | { 57 | let config = { name: "Kodi", 58 | configuredName: "blah" 59 | }; 60 | let expectedResult = "Kodi"; 61 | let result = getAccessoryDisplayName( config ); 62 | 63 | assert.equal( result, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + result + " expected: " + expectedResult ); 64 | }); 65 | }) 66 | 67 | 68 | describe( "Testing getAccessoryName UC", ( ) => 69 | { 70 | it( "getAccessoryName should choose name first UC ", ( ) => 71 | { 72 | let config = {displayName: "Kodi", 73 | name: "blah" 74 | }; 75 | let expectedResult = "blah"; 76 | let result = getAccessoryName( config ); 77 | 78 | assert.equal( result, expectedResult, "getAccessoryName should return name over displayName. result: " + result + " expected: " + expectedResult ); 79 | }); 80 | 81 | it( "getAccessoryName should choose displayName second UC ", ( ) => 82 | { 83 | let config = { NoName: "blah", 84 | displayName: "Kodi" 85 | }; 86 | let expectedResult = "Kodi"; 87 | let result = getAccessoryName( config ); 88 | 89 | assert.equal( result, expectedResult, "getAccessoryName should return displayName over name. result: " + result + " expected: " + expectedResult ); 90 | }); 91 | }) 92 | 93 | describe( "Testing getAccessoryDisplayName.js", ( ) => 94 | { 95 | it( "getAccessoryDisplayName.js should be a function UC", ( ) => 96 | { 97 | 98 | assert.isFunction( getAccessoryDisplayName, "getAccessoryDisplayName is not a function" ); 99 | }); 100 | 101 | it( "getAccessoryDisplayName should choose displayName first UC ", ( ) => 102 | { 103 | let config = {displayName: "Kodi", 104 | name: "blah" 105 | }; 106 | let expectedResult = "Kodi"; 107 | let result = getAccessoryDisplayName( config ); 108 | 109 | assert.equal( result, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + result + " expected: " + expectedResult ); 110 | }); 111 | 112 | it( "getAccessoryDisplayName should choose name second UC ", ( ) => 113 | { 114 | let config = { name: "Kodi", 115 | configuredName: "blah" 116 | }; 117 | let expectedResult = "Kodi"; 118 | let result = getAccessoryDisplayName( config ); 119 | 120 | assert.equal( result, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + result + " expected: " + expectedResult ); 121 | }); 122 | }) 123 | 124 | /* Mucking about. Result, just use getAccessoryName 125 | describe( "Testing logic of config.name", ( ) => 126 | { 127 | it.skip( "name should equal whatever config.[Nn]ame is", ( ) => 128 | { 129 | let config = { Name: "Np", 130 | ConfiguredName: "Np" 131 | }; 132 | 133 | let configPassedToGetAccessoryNane = { Name: "Kodi", 134 | ConfiguredName: "blah" 135 | }; 136 | 137 | // This does not do what I thought 138 | let name = config.name = config.name = config.Name || getAccessoryName( configPassedToGetAccessoryNane ); 139 | 140 | let expectedResult = "Kodi"; 141 | 142 | // This fails with: 143 | // AssertionError: getAccessoryDisplayName should return displayName over name. result: Np expected: Kodi: expected 'Np' to equal 'Kodi' 144 | // + expected - actual 145 | // -Np 146 | // +Kodi 147 | assert.equal( name, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + name + " expected: " + expectedResult ); 148 | assert.equal( config.name, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + name + " expected: " + expectedResult ); 149 | assert.equal( config.Name, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + name + " expected: " + expectedResult ); 150 | 151 | }); 152 | 153 | it.skip( "name should equal whatever config.[Nn]ame is", ( ) => 154 | { 155 | let config = { Name: "Np", 156 | ConfiguredName: "Np" 157 | }; 158 | 159 | let configPassedToGetAccessoryNane = { Name: "Kodi", 160 | ConfiguredName: "blah" 161 | }; 162 | 163 | // This does do what is expected, but config.Name never gets set. 164 | let name = config.name = config.name || getAccessoryName( configPassedToGetAccessoryNane ); 165 | 166 | let expectedResult = "Kodi"; 167 | 168 | assert.equal( name, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + name + " expected: " + expectedResult ); 169 | assert.equal( config.name, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + name + " expected: " + expectedResult ); 170 | 171 | // This fails with: 172 | // AssertionError: getAccessoryDisplayName should return displayName over name. result: Kodi expected: Kodi: expected 'Np' to equal 'Kodi' 173 | // + expected - actual 174 | // -Np 175 | // +Kodi 176 | assert.equal( config.Name, expectedResult, "getAccessoryDisplayName should return displayName over name. result: " + name + " expected: " + expectedResult ); 177 | }); 178 | }) 179 | */ 180 | -------------------------------------------------------------------------------- /test/initPluginTest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | 5 | // ***************** TEST LOADING ********************** 6 | 7 | 8 | var pluginModule = require( "../index" ); 9 | 10 | 11 | describe(`Testing load of index.js`, ( ) => 12 | { 13 | it( `index.js loaded should not be null`, ( ) => 14 | { 15 | assert.isNotNull(pluginModule, `loading resulted in null` ); 16 | }); 17 | 18 | var t = typeof pluginModule.default; 19 | it( `index.js default initializer should be found`, ( ) => 20 | { 21 | assert.equal(t, `function` ); 22 | }); 23 | }); 24 | 25 | describe( `Testing homebridge API`, ( ) => 26 | { 27 | it( `API should not be null`, ( ) => 28 | { 29 | let apiInstance = new HomebridgeAPI(); 30 | assert.isNotNull( apiInstance, ` apiInstance is null` ); 31 | }); 32 | }); 33 | 34 | describe( `Testing homebridge setup`, ( ) => 35 | { 36 | it( `HAP Categories should not be null`, ( ) => 37 | { 38 | let apiInstance = new HomebridgeAPI(); 39 | assert.isNotNull( apiInstance.hap.Categories, `Categories is null` ); 40 | }); 41 | 42 | it( `HAP Characteristic should be a function`, ( ) => 43 | { 44 | let apiInstance = new HomebridgeAPI(); 45 | assert.isFunction( apiInstance.hap.Characteristic, "Characteristic is not an function" ); 46 | }); 47 | it( `HAP Accessory should be a function`, ( ) => 48 | { 49 | let apiInstance = new HomebridgeAPI(); 50 | assert.isFunction( apiInstance.hap.Accessory, `apiInstance.hap.Accessory is not an function` ); 51 | }); 52 | it( `HAP Service should be a function`, ( ) => 53 | { 54 | let apiInstance = new HomebridgeAPI(); 55 | assert.isFunction( apiInstance.hap.Service, ` apiInstance.hap.Service is not an function` ); 56 | }); 57 | }); 58 | 59 | 60 | // ***************** TEST Plugin Un Initialized Variables *************** 61 | 62 | describe( `Testing index.js plugin unInitialized variables.`, ( ) => 63 | { 64 | it( `Plugin CMD4_DEVICE_TYPE_ENUM should be a object`, ( ) => 65 | { 66 | assert.isObject(CMD4_DEVICE_TYPE_ENUM, `CMD4_DEVICE_TYPE_ENUM is not an object` ); 67 | }); 68 | it( `Plugin CMD4_ACC_TYPE_ENUM should be a object`, ( ) => 69 | { 70 | assert.isObject(CMD4_ACC_TYPE_ENUM, "CMD4_ACC_TYPE_ENUM is not an object" ); 71 | }); 72 | it( `Plugin CMD4_ACC_TYPE_ENUM.EOL should be defined`, ( ) => 73 | { 74 | assert.equal(CMD4_DEVICE_TYPE_ENUM.EOL, DEVICE_EOL, `CMD4_DEVICE_TYPE_ENUM.EOL is incorrect` ); 75 | }); 76 | it( `Plugin CMD4_ACC_TYPE_ENUM.EOL should be defined`, ( ) => 77 | { 78 | assert.equal(CMD4_ACC_TYPE_ENUM.EOL, ACC_EOL, "CMD4_ACC_TYPE_ENUM.EOL is incorrect" ); 79 | }); 80 | }); 81 | 82 | // ***************** TEST Plugin Initialized Variables *************** 83 | 84 | describe( `Testing index.js plugin Initialized variables.`, ( ) => 85 | { 86 | it( `Initialized Plugin CMD4_DEVICE_TYPE_ENUM.EOL should be correct`, ( ) => 87 | { 88 | let apiInstance = new HomebridgeAPI(); 89 | let cmd4 = pluginModule.default( apiInstance ); 90 | 91 | assert.equal(cmd4.CMD4_DEVICE_TYPE_ENUM.EOL, DEVICE_EOL, "returned CMD4_DEVICE_TYPE_ENUM.EOL is incorrect" ); 92 | }); 93 | it( `Initialized Plugin returned CMD4_ACC_TYPE_ENUM.EOL should be correct`, ( ) => 94 | { 95 | let apiInstance = new HomebridgeAPI(); 96 | let cmd4 = pluginModule.default( apiInstance ); 97 | 98 | assert.equal(cmd4.CMD4_ACC_TYPE_ENUM.EOL, ACC_EOL, `returned CMD4_ACC_TYPE_ENUM.EOL is incorrect` ); 99 | }); 100 | 101 | it( `Initialized Plugin returned CMD4_DEVICE_TYPE_ENUM.properties length should be correct`, ( ) => 102 | { 103 | let apiInstance = new HomebridgeAPI(); 104 | let cmd4 = pluginModule.default( apiInstance ); 105 | let properties = cmd4.CMD4_DEVICE_TYPE_ENUM.properties; 106 | 107 | assert.equal(Object.keys(properties).length, DEVICE_EOL, 'returned CMD4_DEVICE_TYPE_ENUM.properties length is incorrect' ); 108 | }); 109 | 110 | it( `Initialized Plugin returned CMD4_ACC_TYPE_ENUM.properties length should be correct`, ( ) => 111 | { 112 | let apiInstance = new HomebridgeAPI(); 113 | let cmd4 = pluginModule.default( apiInstance ); 114 | let properties = cmd4.CMD4_ACC_TYPE_ENUM.properties; 115 | 116 | assert.equal(Object.keys(properties).length, ACC_EOL, 'returned CMD4_ACC_TYPE_ENUM.properties length is incorrect' ); 117 | }); 118 | 119 | it( `Initialized Plugin returned CMD4_ACC_TYPE_ENUM.properties[0-${ ACC_EOL }].Characteristic should be defined`, ( ) => 120 | { 121 | let apiInstance = new HomebridgeAPI(); 122 | let cmd4 = pluginModule.default( apiInstance ); 123 | let properties = cmd4.CMD4_ACC_TYPE_ENUM.properties; 124 | 125 | for ( let accTypeEnumIndex = 0; accTypeEnumIndex < ACC_EOL; accTypeEnumIndex++ ) 126 | { 127 | assert.isNotNull( properties[ accTypeEnumIndex ].characteristic, `Characteristic at index: ${ accTypeEnumIndex } is null. found ${ properties[ accTypeEnumIndex ].characteristic }` ); 128 | } 129 | }); 130 | 131 | it( `Initialized Plugin returned CMD4_DEVICE_TYPE_ENUM.properties[0-${ DEVICE_EOL }].service should be defined`, ( ) => 132 | { 133 | let apiInstance = new HomebridgeAPI(); 134 | let cmd4 = pluginModule.default( apiInstance ); 135 | let properties = cmd4.CMD4_DEVICE_TYPE_ENUM.properties; 136 | 137 | for ( let deviceTypeEnumIndex = 0; deviceTypeEnumIndex < DEVICE_EOL; deviceTypeEnumIndex++ ) 138 | { 139 | if ( properties[ deviceTypeEnumIndex ].deprecated == true ) 140 | continue; 141 | 142 | assert.isNotNull( properties[ deviceTypeEnumIndex ].service, `service at index: ${ deviceTypeEnumIndex } is null.. found ${ properties[ deviceTypeEnumIndex ].service }` ); 143 | } 144 | }); 145 | /* 146 | // While this works individualy, it intercepts loading from other unit tests 147 | it( `Initialized Plugin returned CMD4_DEVICE_TYPE_ENUM.properties[0-${ DEVICE_EOL }].service should be defined`, ( ) => 148 | { 149 | let Cmd4Platform = require( "../Cmd4Platform" ).Cmd4Platform; 150 | 151 | 152 | var apiInstance = new HomebridgeAPI(); // object we feed to Plugins 153 | 154 | let cmd4 = pluginModule.default( apiInstance ); 155 | let CMD4_DEVICE_TYPE_ENUM = cmd4.CMD4_DEVICE_TYPE_ENUM; 156 | let CMD4_ACC_TYPE_ENUM = cmd4.CMD4_ACC_TYPE_ENUM; 157 | assert.equal( CMD4_DEVICE_TYPE_ENUM.EOL, DEVICE_EOL, "returned CMD4_DEVICE_TYPE_ENUM.EOL is incorrect" ); 158 | assert.equal( CMD4_ACC_TYPE_ENUM.EOL, ACC_EOL, `returned CMD4_ACC_TYPE_ENUM.EOL is incorrect` ); 159 | 160 | let config = 161 | { 162 | "platform": "Cmd4", 163 | "name": "Cmd4", 164 | "accessories": 165 | [ 166 | { 167 | "type": "Switch", 168 | "name": "PS_4", 169 | "displayName": "PS_4", 170 | "on": false, 171 | } 172 | ] 173 | } 174 | 175 | let cmd4Platform = new Cmd4Platform( null, config, apiInstance ); 176 | let log = cmd4Platform.log; 177 | log.setBufferEnabled( ); 178 | 179 | 180 | expect( cmd4Platform ).to.be.a.instanceOf( Cmd4Platform, "cmd4Platform is not an instance of Cmd4Platform" ); 181 | assert.equal( log.logBuf, "", ` cmd4Platform unexpected output received: ${ log.logBuf }` ); 182 | assert.equal( log.errBuf, "", ` cmd4Platform unexpected error output received: ${ log.errBuf }` ); 183 | log.reset( ); 184 | 185 | 186 | apiInstance.emit("didFinishLaunching"); 187 | 188 | assert.include( log.logBuf, `Cmd4Platform didFinishLaunching`, `didFinishLaunching Incorrect stdout: ${ log.logBuf }` ); 189 | assert.equal( log.errBuf, "", ` cmd4Platform Unexpected stderr: ${ log.errBuf }` ); 190 | 191 | }); 192 | */ 193 | }); 194 | -------------------------------------------------------------------------------- /docs/AdvancedTroubleShooting.md: -------------------------------------------------------------------------------- 1 | # Homebridges-cmd4 - Advanced Trouble Shooting. 2 | 3 | ## Table of Contents 4 | * [**About Advanced Trouble Shooting**](#about-advanced-trouble-shooting) 5 | * [**The #1 Thing to Remember**](#the-1-thing-to-remember) 6 | * [**The Parameters sent by Cmd4**](#the-parameters-sent-by-cmd4) 7 | * [**Troubleshooting your own scripts**](#troubleshooting-your-own-scripts) 8 | * [***Create a middleWare shell script***](#create-a-middleware-shell-script) 9 | * [**Debug mode is your best friend**](#debug-mode-is-your-best-friend) 10 | * [**Debugging Fakegato history**](#debugging-fakegato-history) 11 | * [**Missing icons**](#missing-icons) 12 | * [**Child process error message**](#child-process-error-message) 13 | * [**License**](#license) 14 | 15 | ## About Advanced Trouble Shooting 16 |     Unlike Basic Trouble Shooting, this guide is more for those who are having problems with their own scripts and what problems can arise when integrating them with Cmd4. 17 | 18 | ## The #1 Thing to Remember 19 |     Cmd4 runs your script in the background *WITHOUT ANY ENVIRONMENT* defined. Any variables, alias, special paths are not seen by your script so even if you run the script from the command line and it works, it may not from within Cmd4. Create a bash session without any environment set up like Cmd4 does with the command:
20 | 21 | ```bash 22 | *SHELL*> env -i bash --noprofile --norc 23 | ``` 24 |
25 |     From within this environment test your script like:
26 | 27 | ```bash 28 | *SHELL*> node .homebridge/YourScriptHere.js Get My_Fan On 29 | ``` 30 | 31 | ### The Parameters Sent by Cmd4 32 |     The second most important thing to remember is what Cmd4 sends for Get/Set requests. Your script must meet these requirements. These are defined as:
33 | 34 | ``` 35 | Get < Accessory Name > < Characteristic > 36 | Set < Accessory Name > < Characteristic > < Value > 37 | ``` 38 | 39 | ## Troubleshooting your own scripts 40 | 41 | ### Execute your script from the command line interface for *Get* 42 |     Remembering that Cmd4 executes your script in a No environment setting. First execute your scripts from the CLI.
43 | 44 | ```bash 45 | *SHELL*> env -i bash --noprofile --norc 46 | *SHELL*> Get < Accessory Name > < Characteristic > 47 | ``` 48 |
49 | The script must output a one word answer or multiple quoted words.
50 | Note: Your script must also exit with a 0 return code. 51 |
52 | ### Execute your script from the command line interface for *Set* 53 |    Your script must respond to the Set command.
54 | 55 | ```bash 56 | *SHELL*> env -i bash --noprofile --norc 57 | *SHELL*> Set < Accessory Name > < Characteristic > < value > 58 | ``` 59 |
60 | Note: Your script must also exit with a 0 return code. 61 | 62 | ### Debug mode is your best friend 63 |     As with Basic Troubleshooting, if your script passes at the CLI, run homebridge in debug mode:
64 |     New in Cmd4 v4.0.0 is how to enable Debug mode. The logs are 100% the same, except that now that Cmd4 has its own logging system ( Copied from Homebridge for compatability ); Enabling Debug logs will not enable Debug logs in other plugins.
65 | There are two ways to enable Cmd4 Debug logs. 66 | 67 | #### Method 1. Modifying the Cmd4 Platform section 68 |     The Cmd4 Platform section can contain the enable Debug directive. 69 | 70 | ```json 71 | { 72 | "platform": "Cmd4", 73 | "name": "Cmd4", 74 | "debug": true 75 | } 76 | ``` 77 | 78 | #### Method 2. Add DEBUG environental variable 79 | 80 | ```bash 81 | *SHELL*> DEBUG=Cmd4 82 | ``` 83 | Note: For Homebridge-config-ui-x, you only need to write Cmd4 in the Environmental variable section. 84 | 85 | 86 | ### Create a middleWare shell script 87 |     To see when and what both Cmd4 is sending/receiving as well as what your script is sending and receiving, create a middleWare.sh script that is called from the config.json and then calls your script. A script similiar to:
88 | 89 | ``` 90 | #!/bin/bash 91 | echo $( date ) >> /tmp/Cmd4.log 92 | echo $* >> /tmp/Cmd4.log 93 | node .homebridge/Cmd4Scripts/State.js $* 2>&1 | tee -a /tmp/Cmd4.log 94 | ``` 95 | 96 | Running a "tail -f /tmp/Cmd4.log" in a seperate terminal window will show you everything that is going on between the processes. 97 |
98 | 99 | 100 | ## Debugging Fakegato history 101 | See [fakegato-history](https://github.com/simont77/fakegato-history) 102 | 103 |     if you have added fakegato history, but no history is showing, there are things you can check. 104 | 105 | ### Step 1. Check that the characteristic is polled. 106 |     Only polled characteristics are recorded. For history to be collected you will have to enable polling and interval for the accessory, and according to the fakegato-history documents it should be less than 10 minutes (600 seconds). The new polling config section allows for each characteristic to be polled at their individual times. Check your config.json for this setup. An example of polling is: 107 | ```json 108 | "polling": [{"characteristic": "currentHeatingCoolingState", 109 | "interval": 540, "timeout": 4000}, 110 | {"characteristic": "currentTemperature", 111 | "interval": 60, "timeout": 4000} 112 | ], 113 | ``` 114 | 115 | ### Step 2. Check the fakegato logs. 116 |     The storagePath stores the history records. If there are no logs, run Cmd4 in Debug mode to see if the records are being created. 117 | 118 | ### Step 3. Check your fakegato configuration. 119 |     In the following configuration, the value polled for the characteristic "currentTemperature" will be used for the designation "currentTemp". Similarly the value polled for the characteristic "targetTemperature" will be used for the designation "setTemp". This follows the fakegato-history spec and this model is used for the other fakegato-history records. 120 | 121 | ```json 122 | "fakegato":{"eve":"thermo", 123 | "currentTemp": "currentTemperature", 124 | "setTemp": "targetTemperature", 125 | "valvePosition": "0", 126 | "storage": "fs", 127 | "storagePath": ".homebridge/FakegatoStorage", 128 | "folder": "folderName", 129 | "keyPath": "/place/to/store/my/keys/" 130 | } 131 | ``` 132 | 133 | Note: The value "0" should be used for any characteristics value which is not possible to retrieve. 134 | 135 | ## Missing icons 136 |    IOS 14 added a new category characteristic to give a hint to any GUI of which icon to use. The big impact was found in missing icons for Televisions. For Televisions you must add:
137 | 138 | ```json 139 | "category": "TELEVISION" 140 | ``` 141 | This category only takes effect for Platform Accessories. 142 | 143 | 144 | ## Child process error message 145 |     If you happen to see this error message:
146 | ``` 147 | Error: Command failed: /homebridge/Server.sh Get 'Server' 'On' 148 | 149 | at ChildProcess.exithandler (child_process.js:297:12) 150 | at ChildProcess.emit (events.js:193:13) 151 | at maybeClose (internal/child_process.js:1001:16) 152 | at Process.ChildProcess._handle.onexit (internal/child_process.js:266:5) 153 | killed: true 154 | code: null 155 | signal: SIGTERM, 156 | cmd: "/homebridge/Server.sh Get Server On" 157 | 158 | ``` 159 | 160 | The command may not exist, but also the timeout value in your config.json for that accessory may be too low. 161 | 162 | 163 | ## License 164 | See [LICENSE](https://github.com/ztalbot2000/homebridge-cmd4/blob/master/LICENSE) 165 | 166 | 167 | 168 | 171 | 172 | [homebridge]:https://github.com/nfarina/homebridge 173 | [ztalbot2000]:https://github.com/ztalbot2000 174 | -------------------------------------------------------------------------------- /test/getSetAllValues.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const os = require( "os" ); 4 | const cmd4StateDir = os.homedir( ) + "/.homebridge/Cmd4Scripts/Cmd4States/" 5 | 6 | var glob = require( "glob" ); 7 | 8 | 9 | describe( 'Cleaning up any old Cmd4States/Status_Device_* files ...', ( ) => 10 | { 11 | glob( cmd4StateDir + "Status_Device_*", null, function ( er, files ) 12 | { 13 | for ( var file of files ) 14 | { 15 | // To use the promise of unlink, it must be in an async function 16 | // so put it in one. Why not unLinkSync, because for whatever reason 17 | // some files were notbremoved synchronously. 18 | ( async( ) => 19 | { 20 | await fs.unlink( file, function( err, result ) 21 | { 22 | it('file:' + file +' should be removed', function ( done ) 23 | { 24 | if ( err && err.code != 'ENOENT' ) 25 | assert.isNull( err, 'file not removed err: ' + err + " result: " + result ); 26 | done( ); 27 | }); 28 | }); 29 | }); 30 | } 31 | }) 32 | }); 33 | 34 | 35 | 36 | function removeStateFileThatShouldAlreadyBeRemoved( characteristicString ) 37 | { 38 | let stateFile = cmd4StateDir + "Status_Device_" + characteristicString; 39 | if (fs.existsSync( stateFile )) 40 | { 41 | // console.log(' *** BLAST! The path exists.' + stateFile ); 42 | // To use the promise of unlink, it must be in an async function 43 | // so put it in one. Why not unLinkSync, because for whatever reason 44 | // some files were notbremoved synchronously. 45 | ( async( ) => 46 | { 47 | fs.unlink( stateFile, function( err, result ) 48 | { 49 | if ( err && err.code != 'ENOENT' ) 50 | assert.isNull( err, 'file not removed err: ' + err + " result: " + result ); 51 | }); 52 | }); 53 | } 54 | } 55 | 56 | 57 | // ***************** TEST LOADING ********************** 58 | 59 | // This would be the plugin un-initialized 60 | var pluginModule = require( "../index" ); 61 | 62 | describe( "Testing load of index.js", ( ) => 63 | { 64 | it( "index.js loaded should not be null", ( ) => 65 | { 66 | assert.isNotNull( pluginModule, "loading resulted in null" ); 67 | }); 68 | 69 | var t = typeof pluginModule.default; 70 | it( "index.js default initializer should be found", ( ) => 71 | { 72 | assert.equal( t, "function"); 73 | }); 74 | }); 75 | 76 | // ************ TEST UNINITIALIZED CMD4_DEVICE_TYPE_ENUM EOL ************** 77 | describe( "Quick Testing CMD4_DEVICE_TYPE_ENUM EOL", ( ) => 78 | { 79 | it( "CMD4_DEVICE_TYPE_ENUM has EOL", ( ) => 80 | { 81 | assert.isNotNull( pluginModule.DEVICE_DATA.CMD4_DEVICE_TYPE_ENUM.EOL, "EOL is null" ); 82 | }); 83 | 84 | it( "CMD4_DEVICE_TYPE_ENUM.EOL = " + DEVICE_EOL, ( ) => 85 | { 86 | assert.equal( pluginModule.DEVICE_DATA.CMD4_DEVICE_TYPE_ENUM.EOL, DEVICE_EOL ); 87 | }); 88 | }); 89 | 90 | var _api = new HomebridgeAPI( ); // object we feed to Plugins 91 | 92 | describe( "Testing homebridge API", ( ) => 93 | { 94 | it( "API should not be null", ( ) => 95 | { 96 | assert.isNotNull( _api, "_api is null"); 97 | }); 98 | }); 99 | 100 | describe( "Initializing our plugin module", ( ) => 101 | {}); 102 | var cmd4 = pluginModule.default( _api ); 103 | 104 | 105 | 106 | 107 | // ***************** TEST State.js ********************** 108 | const child_process = require( "child_process" ); 109 | 110 | 111 | // *** TEST Get of Characteristic properties of default written data ******* 112 | describe( "Testing State.js Get Characteristics default written data ( " + CMD4_ACC_TYPE_ENUM.EOL + " of them )", ( ) => 113 | { 114 | for ( let accTypeEnumIndex=0; accTypeEnumIndex < CMD4_ACC_TYPE_ENUM.EOL; accTypeEnumIndex ++ ) 115 | { 116 | let characteristicString = cmd4.CMD4_ACC_TYPE_ENUM.properties[accTypeEnumIndex].type; 117 | let cmd = "./Extras/Cmd4Scripts/State.js"; 118 | let args2 = " Get Device " + characteristicString + " "; 119 | let args = ["Get", "Device", characteristicString ]; 120 | 121 | it( accTypeEnumIndex + ": " + cmd + args2 + "should return something", ( ) => 122 | { 123 | 124 | // Why twice with two awaits, beats me ! 125 | removeStateFileThatShouldAlreadyBeRemoved( characteristicString ); 126 | 127 | const ps = child_process.spawnSync( cmd, args ); 128 | 129 | var data="not set by me"; 130 | 131 | if ( ps.status !== 0 ) 132 | { 133 | assert.equal( ps.status, 0, "Process error stdout: " + ps.stdout + " stderr: " + ps.stderr + " status: " + ps.status + " signal: " + ps.signal ); 134 | } else { 135 | data = ps.stdout; 136 | assert( data != "", "data returned '" + data + "'" ); 137 | 138 | assert( data.length != 0, "data returned: " + data.length ); 139 | } 140 | }); 141 | } 142 | }); 143 | 144 | // *** TEST Set of Characteristic properties ******* 145 | 146 | let dummyData = "X0X0Test"; 147 | let dummyDataR = "\"" + dummyData + "\""; 148 | 149 | describe( `Testing State.js Set Characteristics ( ${ CMD4_ACC_TYPE_ENUM.EOL } of them )`, ( ) => 150 | { 151 | for ( let accTypeEnumIndex=0; accTypeEnumIndex < CMD4_ACC_TYPE_ENUM.EOL; accTypeEnumIndex ++ ) 152 | { 153 | let characteristicString = CMD4_ACC_TYPE_ENUM.properties[accTypeEnumIndex].type; 154 | let cmd = "./Extras/Cmd4Scripts/State.js"; 155 | let args2 = " Set Device " + characteristicString + " " + dummyData; 156 | let args = ["Set", "Device", characteristicString, dummyData]; 157 | 158 | const ps = child_process.spawnSync( cmd, args ); 159 | 160 | it( accTypeEnumIndex + ": " + cmd + args2 + " should return no data", ( ) => 161 | { 162 | var data="not set by me"; 163 | 164 | // console.log( "status is '%s'", ps.status ); 165 | if ( ps.status !== 0 ) 166 | { 167 | assert.equal( ps.status, 0, "Process error stdout: " + ps.stdout + " stderr: " + ps.stderr + " status: " + ps.status + " signal: " + ps.signal ); 168 | } else { 169 | data = ps.stdout; 170 | assert( data == "", "No data should be returned: '" + data + "'" ); 171 | 172 | } 173 | }); 174 | } 175 | }); 176 | 177 | // *** TEST Get of Characteristic properties after set of dummy data ******* 178 | 179 | describe( "Begin Testing", ( ) => 180 | {}); 181 | 182 | 183 | // *** TEST Get of Characteristic properties of written data ******* 184 | describe( "Testing State.js Get Characteristics written data ( " + cmd4.CMD4_ACC_TYPE_ENUM.EOL + " of them ). Note: May fail even number of times ????", ( ) => 185 | { 186 | for ( let accTypeEnumIndex=0; accTypeEnumIndex < CMD4_ACC_TYPE_ENUM.EOL; accTypeEnumIndex ++ ) 187 | { 188 | let characteristicString = CMD4_ACC_TYPE_ENUM.properties[accTypeEnumIndex].type; 189 | let cmd = "./Extras/Cmd4Scripts/State.js"; 190 | let args2 = " Get Device " + characteristicString + " "; 191 | let args = ["Get", "Device", characteristicString]; 192 | 193 | it( accTypeEnumIndex + ": " + cmd + args2 + "should return: " + dummyDataR , ( ) => 194 | { 195 | const ps = child_process.spawnSync( cmd, args ); 196 | 197 | var data="not set by me"; 198 | 199 | if ( ps.status !== 0 ) 200 | { 201 | assert.equal( ps.status, 0, "Process error stdout: " + ps.stdout + " stderr: " + ps.stderr + " status: " + ps.status + " signal: " + ps.signal ); 202 | } else { 203 | data = ps.stdout; 204 | // fixme ??? assert( data == dummyData, "data returned: '" + data + "'" ); 205 | // The carriage return is stripped by getValue 206 | assert( data == dummyDataR, "data returned:" + data ); 207 | 208 | assert( data.length != 0, "data returned: " + data.length ); 209 | } 210 | }); 211 | } 212 | }); 213 | -------------------------------------------------------------------------------- /cmd4Constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Naming convention 4 | // DEFAULT_ => Default values 5 | // _l => Lower Case 6 | // _lv => Lower Case Variable of same name 7 | 8 | // Triggers which Array CMD4Accessory will be placed 9 | // Either cmd4Accessories or cmd4StandaloneAccessories 10 | // 11 | 12 | 13 | var cmd4Constants = { 14 | STANDALONE: "Standalone", 15 | PLATFORM: "Platform", 16 | 17 | // Default intervals 18 | // 10 seconds 19 | SLOW_STATE_CHANGE_RESPONSE_TIME: 10000, 20 | // 3 seconds 21 | MEDIUM_STATE_CHANGE_RESPONSE_TIME: 3000, 22 | // 3 seconds 23 | DEFAULT_STATE_CHANGE_RESPONSE_TIME: 3000, 24 | // 1 second 25 | FAST_STATE_CHANGE_RESPONSE_TIME: 1000, 26 | 27 | // 1 minute 28 | DEFAULT_INTERVAL: 60000, 29 | // 1 minute 30 | DEFAULT_TIMEOUT: 60000, 31 | 32 | // Not a Bool, otherwise conditional check fails 33 | DEFAULT_STATUSMSG: "TRUE", 34 | 35 | DEFAULT_QUEUE_TYPE: "WoRm", 36 | // 10 seconds 37 | DEFAULT_QUEUE_PAUSE_TIMEOUT: 10000, 38 | 39 | // No debug 40 | DEFAULT_DEBUG: false, 41 | // No funny TLV8 characteristics 42 | DEFAULT_ALLOW_TLV8: false, 43 | DEFAULT_OUTPUTCONSTANTS: false, 44 | 45 | // Fakegato Constants 46 | FAKEGATO_TYPE_ENERGY: "energy", 47 | FAKEGATO_TYPE_ROOM: "room", 48 | FAKEGATO_TYPE_WEATHER: "weather", 49 | FAKEGATO_TYPE_DOOR: "door", 50 | FAKEGATO_TYPE_MOTION: "motion", 51 | FAKEGATO_TYPE_THERMO: "thermo", 52 | FAKEGATO_TYPE_AQUA: "aqua", 53 | EVE: "eve", 54 | STORAGE: "storage", 55 | STORAGEPATH: "storagePath", 56 | FOLDER: "folder", 57 | KEYPATH: "keyPath", 58 | STATUS: "status", 59 | TEMP: "temp", 60 | SETTEMP: "setTemp", 61 | HUMIDITY: "humidity", 62 | PPM: "ppm", 63 | POWER: "power", 64 | PRESSURE: "pressure", 65 | CURRENTTEMP: "currentTemp", 66 | VALVEPOSITION: "valvePosition", 67 | WATERAMOUNT: "waterAmount", 68 | TIME: "time", 69 | PATH: "path", 70 | 71 | FS: "fs", 72 | GOOGLE_DRIVE: "googleDrive", 73 | 74 | // Config Constants 75 | DEBUG: "debug", 76 | OUTPUTCONSTANTS: "outputConstants", 77 | STATUSMSG: "statusMsg", 78 | QUEUETYPE: "queueType", 79 | QUEUETYPES: "queueTypes", 80 | 81 | // Queue Types 82 | QUEUETYPE_SEQUENTIAL: "Sequential", 83 | QUEUETYPE_WORM: "WoRm", 84 | QUEUETYPE_WORM2: "WoRm2", 85 | // Used internally to mean only polled entries go straight through the queue 86 | QUEUETYPE_STANDARD: "StandarD", 87 | // Used internally to mean entries go straight through the queue 88 | QUEUETYPE_PASSTHRU: "None", 89 | DEFAULT_STANDARD_QUEUE_RETRY_COUNT: 0, 90 | DEFAULT_WORM_QUEUE_RETRY_COUNT: 0, 91 | QUEUE_RETRIES: "retries", 92 | 93 | 94 | 95 | // Platform/Accessory Config Constants 96 | TYPE: "type", 97 | SUBTYPE: "subType", 98 | DISPLAYNAME: "displayName", 99 | UUID: "uuid", 100 | ACCESSORY: "accessory", 101 | CATEGORY: "category", 102 | PUBLISHEXTERNALLY: "publishExternally", 103 | CHARACTERISTIC: "characteristic", 104 | TIMEOUT: "timeout", 105 | QUEUE: "queue", 106 | POLLING: "polling", 107 | INTERVAL: "interval", 108 | STATECHANGERESPONSETIME: "stateChangeResponseTime", 109 | STATE_CMD_PREFIX: "state_cmd_prefix", 110 | STATE_CMD_SUFFIX: "state_cmd_suffix", 111 | STATE_CMD: "state_cmd", 112 | FAKEGATO: "fakegato", 113 | REQUIRES: "requires", 114 | CONSTANTS: "constants", 115 | VARIABLES: "variables", 116 | LINKEDTYPES: "linkedTypes", 117 | ACCESSORIES: "accessories", 118 | URL: "url", 119 | ALLOWTLV8: "allowTLV8", 120 | 121 | DEFINITIONS: "definitions", 122 | PROPS: "props", 123 | 124 | // While also characteristics, they are also used by 125 | // the infomation service 126 | MANUFACTURER: "manufacturer", 127 | SERIALNUMBER: "serialNumber", 128 | MODEL: "model", 129 | 130 | // Internal list variables 131 | ACCESSORY_lv: "accessory", 132 | CHARACTERISTIC_STRING_lv: "characteristicString", 133 | CALLBACK_lv: "callback", 134 | ACC_TYPE_ENUM_INDEX_lv: "accTypeEnumIndex", 135 | INTERVAL_lv: "interval", 136 | IS_SET_lv: "isSet", 137 | QUEUE_NAME_lv: "queueName", 138 | QUEUE_GET_IS_UPDATE_lv: "queueGetIsUpdate", 139 | STATE_CHANGE_RESPONSE_TIME_lv: "stateChangeResponseTime", 140 | TIMEOUT_lv: "timeout", 141 | VALUE_lv: "value", 142 | CMD4_STORAGE_lv: "cmd4Storage", 143 | 144 | ERROR_STRING_MIN: -151, 145 | ERROR_TIMER_EXPIRED: -151, 146 | // ERROR_CMD_FAILED_REPLY: -152, 147 | ERROR_NULL_REPLY: -153, 148 | ERROR_NULL_STRING_REPLY: -154, 149 | ERROR_EMPTY_STRING_REPLY: -155, 150 | ERROR_2ND_NULL_STRING_REPLY: -156, 151 | ERROR_NON_CONVERTABLE_REPLY: -157, 152 | ERROR_NO_DATA_REPLY: -158, 153 | ERROR_STRING_MAX: -158, 154 | ERROR_STRINGS: 155 | [ // cmd4Constants.ERROR_TIMER_EXPIRED -151 156 | "Timer expired contacting accessory", 157 | // cmd4Constants.ERROR_CMD_FAILED_REPLY -152 158 | "Command failed", 159 | // cmd4Constants.ERROR_NULL_REPLY -153 160 | "Reply is NULL", 161 | // cmd4Constants.ERROR_NULL_STRING_REPLY -154 162 | "Reply is NULL string", 163 | // cmd4Constants.ERROR_EMPTY_STRING_REPLY -155 164 | "Reply is an empty string", 165 | // cmd4Constants.ERROR_2ND_NULL_STRING_REPLY -156 166 | "Reply is still NULL", 167 | // cmd4Constants.ERROR_NON_CONVERTABLE_REPLY -157 168 | "Cannot convert characteristic string", 169 | // cmd4Constants.ERROR_NO_DATA_REPLY -158 170 | "No data returned from accessory" 171 | ], 172 | 173 | // Convert our known Error Codes to strings 174 | errorString: function( index ) 175 | { 176 | let offset = - index + cmd4Constants.ERROR_STRING_MIN ; 177 | let max = cmd4Constants.ERROR_STRING_MIN - cmd4Constants.ERROR_STRING_MAX; 178 | 179 | //console.log(" index is " + index ); 180 | //console.log(" offset is " + offset ); 181 | //console.log(" max is " + max ); 182 | // i.e 0-7 183 | if ( index == 0 ) 184 | return "Device returned SUCCESS; " + index; 185 | 186 | //if ( offset < 0 || offset > max ) 187 | if ( index > cmd4Constants.ERROR_STRING_MIN ) 188 | return "Device returned its own error; " + index; 189 | if ( index < cmd4Constants.ERROR_STRING_MAX ) 190 | return "Device returned its own error; " + index; 191 | 192 | // Should not happen because of the above checks 193 | if ( offset < 0 ) 194 | return "Invalid Error min index: " + index; 195 | 196 | // Should not happen because of the above checks 197 | if ( offset > max ) 198 | return "Invalid Error max index: " + index; 199 | 200 | return cmd4Constants.ERROR_STRINGS[ offset ]; 201 | }, 202 | 203 | 204 | 205 | // Static Messages 206 | DBUSY: "Perhaps your device is busy or unavailable?" 207 | }; 208 | module.exports = cmd4Constants; 209 | -------------------------------------------------------------------------------- /test/Logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Settings, Globals and Constants 4 | let settings = require( "../cmd4Settings" ); 5 | // Let logger control logs for Unit Testing 6 | settings.cmd4Dbg = true; 7 | 8 | var _api = new HomebridgeAPI( ); // object we feed to Plugins 9 | 10 | // Init the library for all to use 11 | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.hap.Formats, _api.hap.Units, _api.hap.Perms ); 12 | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.hap.Service, _api.hap.Characteristic, _api.hap.Categories ); 13 | 14 | 15 | let { Cmd4Accessory } = require( "../Cmd4Accessory" ); 16 | 17 | 18 | // ******** QUICK TEST CMD4_ACC_TYPE_ENUM ************* 19 | describe( "Quick Test load of CMD4_ACC_TYPE_ENUM", ( ) => 20 | { 21 | it( "CMD4_ACC_TYPE_ENUM.EOL =" + ACC_EOL, ( ) => 22 | { 23 | expect( CMD4_ACC_TYPE_ENUM.EOL ).to.equal( ACC_EOL ); 24 | }); 25 | }); 26 | 27 | 28 | // ******** QUICK TEST CMD4_DEVICE_TYPE_ENUM ************* 29 | describe( "Quick Test load of CMD4_DEVICE_TYPE_ENUM", ( ) => 30 | { 31 | it( "CMD4_DEVICE_TYPE_ENUM.EOL =" + DEVICE_EOL, ( ) => 32 | { 33 | expect( CMD4_DEVICE_TYPE_ENUM.EOL ).to.equal( DEVICE_EOL ); 34 | }); 35 | }); 36 | 37 | 38 | // ******** TEST logger ************* 39 | describe('A simple logger Test', ( ) => 40 | { 41 | it( "Test bufferEnabled is off by default", ( ) => 42 | { 43 | const log = new Logger( ); 44 | log.setOutputEnabled( false ); 45 | 46 | let STDOUT_DATA="stdout_data"; 47 | log.info( STDOUT_DATA ); 48 | 49 | // Logger adds a \n so use include 50 | assert.equal( log.logBuf , "", `Expected no logs to stdout` ); 51 | assert.equal( log.logLineCount, 0 , `unexpected number of lines to stdout` ); 52 | assert.equal( log.errBuf , "", `Expected no logs to stderr` ); 53 | assert.equal( log.errLineCount, 0 , `unexpected number of lines to stderr` ); 54 | 55 | }); 56 | 57 | it( "Test info log to stdout gets captured and mothing else", ( ) => 58 | { 59 | const log = new Logger( ); 60 | log.setBufferEnabled( true ); 61 | log.setOutputEnabled( false ); 62 | log.setDebugEnabled( false ); 63 | 64 | let STDOUT_DATA="stdout_data"; 65 | log.info( STDOUT_DATA ); 66 | 67 | // Logger adds a \n so use include 68 | assert.include( log.logBuf , STDOUT_DATA, `Expected logs to stdout for log.info ` ); 69 | assert.equal( log.logLineCount, 1 , `unexpected number of lines to stdout` ); 70 | assert.equal( log.errBuf , "", `Expected no logs to stderr` ); 71 | assert.equal( log.errLineCount, 0 , `unexpected number of lines to stderr` ); 72 | 73 | }); 74 | 75 | it( "Test warn log to stderr gets captured and mothing else", ( ) => 76 | { 77 | const log = new Logger( ); 78 | log.setBufferEnabled( true ); 79 | log.setOutputEnabled( false ); 80 | log.setDebugEnabled( false ); 81 | 82 | let STDERR_DATA="stderr_data"; 83 | log.warn( STDERR_DATA ); 84 | 85 | assert.equal( log.logBuf , "", `Expected no logs to stout` ); 86 | assert.equal( log.logLineCount, 0 , `unexpected number of lines to stdout` ); 87 | // Logger adds a \n so use include 88 | assert.include( log.errBuf , STDERR_DATA, `Expected logs to stderr for log.warn ` ); 89 | assert.equal( log.errLineCount, 1 , `unexpected number of lines to stderr` ); 90 | 91 | }); 92 | 93 | it( "Test log to stderr gets captured and mothing else", ( ) => 94 | { 95 | const log = new Logger( ); 96 | log.setBufferEnabled( true ); 97 | log.setOutputEnabled( false ); 98 | log.setDebugEnabled( false ); 99 | 100 | let STDERR_DATA="stderr_data"; 101 | log.error( STDERR_DATA ); 102 | 103 | assert.equal( log.logBuf , "", `Expected no logs to stdout` ); 104 | assert.equal( log.logLineCount, 0 , `unexpected number of lines to stdout` ); 105 | // Logger adds a \n so use include 106 | assert.include( log.errBuf , STDERR_DATA, `Expected logs to stderr` ); 107 | assert.equal( log.errLineCount, 1 , `unexpected number of lines to stderr` ); 108 | 109 | }); 110 | 111 | it( "Test log to stderr and different to stdout", ( ) => 112 | { 113 | const log = new Logger( ); 114 | log.setBufferEnabled( true ); 115 | log.setOutputEnabled( false ); 116 | log.setDebugEnabled( false ); 117 | 118 | let STDERR_DATA="stderr_data"; 119 | let STDOUT_DATA="stdout_data"; 120 | log.info( STDOUT_DATA ); 121 | log.error( STDERR_DATA ); 122 | 123 | // Logger adds a \n so use include 124 | assert.include( log.logBuf , STDOUT_DATA, `Expected logs to stdout for log.info ` ); 125 | assert.equal( log.logLineCount, 1 , `unexpected number of lines to stdout` ); 126 | // Logger adds a \n so use include 127 | assert.include( log.errBuf , STDERR_DATA, `Expected logs to stderr` ); 128 | assert.equal( log.errLineCount, 1 , `unexpected number of lines to stderr` ); 129 | 130 | }); 131 | 132 | 133 | it( "Test can create an instance of Cmd4Accessory with new logger", ( ) => 134 | { 135 | let config = 136 | { 137 | name: "Test Switch", 138 | type: "Switch", 139 | on: false, 140 | polling: true, 141 | state_cmd: "./test/echoScripts/echo_1" 142 | }; 143 | 144 | const log = new Logger( ); 145 | log.setBufferEnabled( true ); 146 | log.setOutputEnabled( false ); 147 | log.setDebugEnabled( false ); 148 | 149 | 150 | let accessory = new Cmd4Accessory( log, config, _api, [ ] ); 151 | 152 | assert.instanceOf( accessory , Cmd4Accessory, "Expected accessory to be instance of Cmd4Accessory. Found %s" , accessory ); 153 | assert.equal( log.logBuf , "", `unexpected logs to stdout for a simple instance of Cmd4Accessory` ); 154 | assert.equal( log.logLineCount, 0 , `unexpected number of lines to stdout` ); 155 | assert.equal( log.errBuf , "", `Expected no logs to stderr for a simple instance of Cmd4Accessory` ); 156 | assert.equal( log.errLineCount, 0 , `unexpected number of lines to stderr` ); 157 | 158 | log.reset(); 159 | }); 160 | 161 | it( "Test can create an instance of Cmd4Accessory with a debug log", ( ) => 162 | { 163 | let config = 164 | { 165 | name: "Test Switch", 166 | type: "Switch", 167 | on: false, 168 | polling: true, 169 | state_cmd: "./test/echoScripts/echo_1" 170 | }; 171 | 172 | let log = new Logger( ); 173 | log.setBufferEnabled( true ); 174 | log.setOutputEnabled( false ); 175 | log.setDebugEnabled( true ); 176 | 177 | 178 | new Cmd4Accessory( log, config, _api, [ ] ); 179 | 180 | assert.include( log.logBuf, "Creating Standalone Accessory type for ", `Expected debug logs to stdout with setDebugEnabled` ); 181 | //assert.equal( log.logLineCount, 17 , `unexpected number of lines to stdout` ); 182 | assert.equal( log.errBuf, "", `Expected no logs to stderr for a simple instance of Cmd4Accessory` ); 183 | assert.equal( log.errLineCount, 0 , `unexpected number of lines to stderr` ); 184 | 185 | log.reset(); 186 | }); 187 | 188 | it( "Test logger performance of NOT enabled message", ( ) => 189 | { 190 | const log = new Logger( ); 191 | log.setBufferEnabled( false ); 192 | log.setOutputEnabled( false ); 193 | log.setDebugEnabled( false ); 194 | let entry = { characteristicString: "testCharacteristic", 195 | accessory: 196 | { displayName: "testDevice", 197 | queue: 198 | { inProgressGets: 0, 199 | inProgressSets: 0 200 | } 201 | } 202 | }; 203 | 204 | let logStartTime = process.hrtime( ); 205 | 206 | log.debug( `OUTPUT FOR MEASUREMENT High priority "Get" queue interrupt attempted: ${ entry.accessory.displayName } ${ entry.characteristicString } inProgressSets:${ entry.accessory.queue.inProgressSets } inProgressGets: ${ entry.accessory.queue.inProgressGets }` ); 207 | let logTotalTime = process.hrtime( logStartTime ); 208 | 209 | let debug = false; 210 | let debugStartTime = process.hrtime( ); 211 | if ( debug ) log.debug( `OUTPUT FOR MEASUREMENT High priority "Get" queue interrupt attempted: ${ entry.accessory.displayName } ${ entry.characteristicString } inProgressSets:${ entry.accessory.queue.inProgressSets } inProgressGets: ${ entry.accessory.queue.inProgressGets }` ); 212 | let debugTotalTime = process.hrtime( debugStartTime ); 213 | 214 | let diff = logTotalTime[1] - debugTotalTime[1]; 215 | console.log( `logTotalTime: ${ logTotalTime[1] } debugTotalTime: ${ debugTotalTime[1] } diff: ${ diff }` ); 216 | 217 | assert.isAbove( logTotalTime[1], debugTotalTime[1], `Expected log total time to be greater than debug total time` ); 218 | 219 | }); 220 | }); 221 | 222 | --------------------------------------------------------------------------------