Showing preview only (2,185K chars total). Download the full file or copy to clipboard to get everything.
Repository: ztalbot2000/homebridge-cmd4
Branch: master
Commit: 85928c56f4fe
Files: 136
Total size: 2.1 MB
Directory structure:
gitextract_pta7yrh3/
├── .eslintrc.json
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.md
│ │ ├── feature-request.md
│ │ └── support-request.md
│ └── pull_request_template.md
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── Cmd4Accessory.js
├── Cmd4Platform.js
├── Cmd4PriorityPollingQueue.js
├── Extras/
│ ├── Cmd4Scripts/
│ │ ├── CheckYourScript.sh
│ │ ├── Examples/
│ │ │ ├── AirPurifier.js
│ │ │ ├── AnyDevice
│ │ │ ├── DoorLock.sh
│ │ │ ├── ExampleJavaScript_template.js
│ │ │ ├── ExampleShellScript_template.sh
│ │ │ ├── PS4.sh
│ │ │ ├── PS5.sh
│ │ │ ├── SecuritySystem.js
│ │ │ ├── advanced_ping.sh
│ │ │ ├── basic_ping.sh
│ │ │ ├── middleWare.sh
│ │ │ └── wakeonlan.sh
│ │ └── State.js
│ ├── config.json
│ ├── config.min.json
│ └── jsmin.c
├── LICENSE
├── README.md
├── RUNNING_CHANGELOG.md
├── cmd4Constants.js
├── cmd4Settings.js
├── commitlint.config.js
├── docs/
│ ├── AdvancedTroubleShooting.md
│ ├── Developers.md
│ ├── autoGenerated/
│ │ └── CMD4_AccessoryDescriptions.html
│ └── index.html
├── index.js
├── lib/
│ ├── CMD4_ACC_TYPE_ENUM.js
│ ├── CMD4_CHAR_TYPE_ENUMS.js
│ └── CMD4_DEVICE_TYPE_ENUM.js
├── package.json
├── postinstall.js
├── test/
│ ├── CMD4_ACC_TYPE_ENUM.js
│ ├── CMD4_CHAR_TYPE_ENUMS.js
│ ├── CMD4_DEVICE_TYPE_ENUM.js
│ ├── Cmd4Accessory.js
│ ├── Cmd4AccessoryGetValue.js
│ ├── Cmd4AccessorySetValue.js
│ ├── Cmd4Mode.js
│ ├── Cmd4Platform.js
│ ├── Cmd4PlatformRestartTests.js
│ ├── Cmd4PriorityPollingQueue.js
│ ├── Cmd4Storage.js
│ ├── HV.js
│ ├── Logger.js
│ ├── VariableTimer.js
│ ├── allTests
│ ├── async-dump.js
│ ├── cmd4Constants.js
│ ├── configHasCharacteristicProps.js
│ ├── configTest.js
│ ├── echoScripts/
│ │ ├── echo_0
│ │ ├── echo_1
│ │ ├── echo_ACTIVE
│ │ ├── echo_DISABLED
│ │ ├── echo_ENABLED
│ │ ├── echo_INACTIVE
│ │ ├── echo_On
│ │ ├── echo_after5seconds
│ │ ├── echo_errorToStderr
│ │ ├── echo_false
│ │ ├── echo_nothing
│ │ ├── echo_null
│ │ ├── echo_nullAndErrorToStderr
│ │ ├── echo_quoted0
│ │ ├── echo_quoted1
│ │ ├── echo_quotedFALSE
│ │ ├── echo_quotedNULL
│ │ ├── echo_quotedNothing
│ │ ├── echo_quotedTRUE
│ │ ├── echo_too_much
│ │ ├── echo_true
│ │ ├── echo_true_withRcOf1
│ │ ├── justExitWithRCof0
│ │ ├── justExitWithRCof1
│ │ ├── runToTimeoutRcOf0
│ │ ├── runToTimeoutRcOf1
│ │ └── testGetSetValues.js
│ ├── extractKeyValue.js
│ ├── fakeGato.js
│ ├── getAccessoryNameFunctions.js
│ ├── getAccessoryUUID.js
│ ├── getSetAllValues.js
│ ├── indexOfEnum.js
│ ├── initPluginTest.js
│ ├── internalRelatedTargetTests.js
│ ├── isAccDirective.js
│ ├── isCmd4Directive.js
│ ├── isDevDirective.js
│ ├── isJSON.js
│ ├── isNumeric.js
│ ├── loadPluginTest.js
│ ├── mocha-setup
│ ├── pollingTest.js
│ ├── sanityTests
│ ├── systemTest.js
│ ├── testAdvAirGetSet.js
│ ├── testOurConfig.json.js
│ ├── transposeCMD4Props.js
│ ├── trueTypeOf.js
│ └── versionChecker.js
├── tools/
│ ├── Cmd4AccDocGenerator
│ ├── generateChangeLog
│ └── whereIsConstant
└── utils/
├── Cmd4Storage.js
├── HV.js
├── Logger.js
├── VariableTimer.js
├── createAccessorysInformationService.js
├── extractKeyValue.js
├── getAccessoryNameFunctions.js
├── getAccessoryUUID.js
├── indexOfEnum.js
├── indexOfEnumLintTest.js
├── isAccDirective.js
├── isCmd4Directive.js
├── isDevDirective.js
├── isJSON.js
├── isNumeric.js
├── lcFirst.js
├── transposeCMD4Props.js
├── trueTypeOf.js
├── ucFirst.js
└── versionChecker.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"env": {
"browser": false,
"commonjs": false,
"node": true,
"mocha": true,
"es2021": true
},
"globals": {
"assert" : "writeable",
"expect" : "writeable",
"sinon" : "writeable",
"ACC_EOL" : "readonly",
"DEVICE_EOL" : "readonly",
"FORMAT_EOL" : "readonly",
"UNITS_EOL" : "readonly",
"PERMS_EOL" : "readonly",
"DEVICE_DATA" : "readonly",
"ACC_DATA" : "readonly",
"CHAR_DATA" : "readonly",
"CMD4_CHAR_TYPE_ENUMS" : "readonly",
"CMD4_ACC_TYPE_ENUM" : "readonly",
"CMD4_DEVICE_TYPE_ENUM" : "readonly",
"cleanStatesDir" : "readonly",
"accEnumIndexToC" : "readonly",
"devEnumIndexToC" : "readonly",
"fs" : "writeable",
"HomebridgeAPI" : "writeable",
"Logger" : "writeable",
"platformAccessory_1" : "writeable"
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12
},
"rules": {
"no-fallthrough": ["error", { "commentPattern": "break[\\s\\w]*omitted"}],
"no-whitespace-before-property": ["error"],
"arrow-spacing": ["error"]
}
}
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: Bug Report
about: Raise a bug related report for cmd4.
title: "[Bug]"
labels: bug
assignees: ztalbot2000
---
** Cmd4 No longer supported:**
<!-- As of September 2025, I will no longer be supporting Cmd4. Any bugs will most likely go into the bit bucket -->
<!-- Provide a general summary in the Title above -->
**Describe The Bug:**
<!-- A clear and concise description of what problem you are experiencing. -->
**To Reproduce:**
<!-- Steps to reproduce the behaviour. -->
**Expected Behaviour:**
<!-- A clear and concise description of what you expected to happen. -->
[**Link to Logs:**]()
<!-- If using a gist, hastebin or pastebin; paste the link between the two () above -->
<!-- If pasting log files directly, please instead do so between the ``` lines below -->
<!-- If using hastebin/pastebin or other text sharing website please make the lifespan long-->
<!-- Remove any sensitive information, passwords, etc. -->
**Paste of Logs:**
```
```
**Cmd4 Config:**
<!-- Paste relevant output between the two ``` lines below -->
<!-- Remove any sensitive information, passwords, etc. -->
```json
```
**Screenshots:**
<!-- If applicable, add screenshots to help explain your problem. -->
**Environment:**
* **Node.js Version**: <!-- node -v -->
* **NPM Version**: <!-- npm -v -->
* **Homebridge Version**: <!-- homebridge -V -->
* **homebridge-cmd4 Version**: <!-- Check on homebridge-config-ui-x -->
* **Operating System**: Raspbian / Ubuntu / Debian / Windows / macOS / Docker / other
* **Process Supervisor**: Systemd / init.d / pm2 / launchctl / Docker / hb-service / other / none
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.md
================================================
---
name: Feature Request
about: Suggest an idea or improvement for cmd4.
title: "[Feature Request]"
labels: enhancement
assignees: ztalbot2000
---
** Cmd4 No longer supported:**
<!-- As of September 2025, I will no longer be supporting Cmd4. Any issues will most likely go into the bit bucket -->
<!-- Provide a general summary in the Title above -->
**Is your feature request related to a problem? Please describe:**
<!-- A clear and concise description of what the problem is. E.g. John there needs to be a button to buy you a coffee. -->
**Describe the solution you'd like:**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered:**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context:**
<!-- Add any other context or screenshots about the feature request here. -->
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->
================================================
FILE: .github/ISSUE_TEMPLATE/support-request.md
================================================
---
name: Support Request
about: Stuck on one of the installation steps or having trouble with your script?
title: "[Support]"
labels: help wanted
assignees: ztalbot2000
---
** Cmd4 No longer supported:**
<!-- As of September 2025, I will no longer be supporting Cmd4. Any requests will most likely go into the bit bucket -->
<!-- Provide a general summary in the Title above -->
<!-- Before opening an issue, please review the README for Installation Details and Basic Troubleshooting to ensure that this is a new issue, and alternatively search the closed issues for similar problems. -->
**Describe Your Problem:**
<!-- A clear and concise description of what problem you are experiencing or what installation step you are stuck on. -->
[**Link to Logs:**]()
<!-- If using a gist, hastebin or pastebin; paste the link between the two () above -->
<!-- If pasting log files directly, please instead do so between the ``` lines below -->
<!-- If using hastebin/pastebin or other text sharing website please make the lifespan long-->
<!-- Remove any sensitive information, passwords, etc. -->
**Paste of Logs:**
```
```
**Cmd4 Config:**
<!-- Paste relevant output between the two ``` lines below -->
<!-- Remove any sensitive information, passwords, etc. -->
```json
```
**Screenshots:**
<!-- If applicable, add screenshots to help explain your problem. -->
**Environment:**
* **Node.js Version**: <!-- node -v -->
* **NPM Version**: <!-- npm -v -->
* **Homebridge Version**: <!-- homebridge -V -->
* **homebridge-cmd4 Version**: <!-- Check on homebridge-config-ui-x -->
* **Operating System**: Raspbian / Ubuntu / Debian / Windows / macOS / Docker / other
* **Process Supervisor**: Systemd / init.d / pm2 / launchctl / Docker / hb-service / other / none
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->
================================================
FILE: .github/pull_request_template.md
================================================
---
name: Pull Request
about: Resolve an issue or add an improvement to cmd4.
title: "[Pull Request]"
labels: pull-request
assignees: ztalbot2000
---
<!-- Provide a general summary in the Title above -->
**Is your pull request related to a problem? Please describe:**
<!-- A clear and concise description of what the problem is. E.g. John there needs to be a button to buy you a coffee. -->
**Describe the solution you'd have implemented:**
<!-- A clear and concise description of what you your pull request is for. Explain the technical solution you have provided and how it addresses the issue. -->
**Do your changes pass unit testing ( npm run test ) :**
- [x] Yes
- [ ] No
**Do your changes pass lint testing ( npm run lint ) :**
- [x] Yes
- [ ] No
**Additional context:**
<!-- Add any other context or screenshots about the pull request here. -->
<!-- Click the "Preview" tab before you submit to ensure the formatting is correct. -->
================================================
FILE: .gitignore
================================================
# Mac
.DS_Store
# Text editors
*.swp
# Stupid commits of npm package
*.tgz
# My favorite temp backup
*.[0-9]
*.save*
*_save*
*_bak*
# Compiled Executables
jsmin
a.out
# node
node_modules
npm-debug.log
.node-version
test/tmp/*
# Not a fan
package-lock.json
# work in progress folder
local
================================================
FILE: .npmignore
================================================
.eslintrc.json
.gitignore
.github
.huskyrc
.DS_Store
*_save*
*_bak*
*.swp
commitlint.config.js
jsmin
utils/indexOfEnumLintTest.js
screenshots
test
node_modules
local
tools
docs/autoGenerated
docs/images
================================================
FILE: CHANGELOG.md
================================================
# Homebridges-cmd4 - CMD4 Plugin for Homebridge - Supports ~All Accessory Types and now all Characteristics too!!
#### 8.0.4 (2026-02-03)
##### Bug Fixes
* Support Node v24 ([feb476b5](https://github.com/ztalbot2000/homebridge-cmd4/commit/feb476b5bbea98099f7ebaf4a8dcea03572dac82))
================================================
FILE: Cmd4Accessory.js
================================================
'use strict';
const moment = require( "moment" );
// Settings, Globals and Constants
let settings = require( "./cmd4Settings" );
const constants = require( "./cmd4Constants" );
let Logger = require( "./utils/Logger" );
const { getAccessoryName, getAccessoryDisplayName
} = require( "./utils/getAccessoryNameFunctions" );
let getAccessoryUUID = require( "./utils/getAccessoryUUID" );
const { addQueue, queueExists } = require( "./Cmd4PriorityPollingQueue" );
// Hierarchy variables
let HV = require( "./utils/HV" );
let createAccessorysInformationService = require( "./utils/createAccessorysInformationService" );
let lcFirst = require( "./utils/lcFirst" );
let trueTypeOf = require( "./utils/trueTypeOf" );
// The sObject.defineProperty is to resolve a lint issue.
// See utils/indexOfEnumLintTest.js for further information.
let indexOfEnum = require( "./utils/indexOfEnum" );
Object.defineProperty( exports, "indexOfEnum", { enumerable: true, get: function ( ){ return indexOfEnum.indexOfEnum; } });
// For changing validValue Constants to Values and back again
var { transposeConstantToValidValue,
} = require( "./utils/transposeCMD4Props" );
let isJSON = require( "./utils/isJSON" );
let isCmd4Directive = require( "./utils/isCmd4Directive" );
let isAccDirective = require( "./utils/isAccDirective" );
let isDevDirective = require( "./utils/isDevDirective" );
// Pretty Colors
var chalk = require( "chalk" );
// These would already be initialized by index.js
let CMD4_ACC_TYPE_ENUM = require( "./lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC_TYPE_ENUM;
let CMD4_DEVICE_TYPE_ENUM = require( "./lib/CMD4_DEVICE_TYPE_ENUM" ).CMD4_DEVICE_TYPE_ENUM;
const Cmd4Storage = require( "./utils/Cmd4Storage" );
let FakeGatoHistoryService = null;
// Only one TV is allowed per bridge. Circumvented by
// publishing the TV externally.
let numberOfTVsPerBridge = 0;
// Array Remove
function removeFromArray( arr, val )
{
for (let i = arr.length - 1; i >= 0; i--)
{
if (arr[i] === val)
{
// console.log("Removing %s", val );
arr.splice(i, 1);
}
}
return arr;
}
// Accessory definitions - THE GOOD STUFF STARTs HERE
//
// An Homebridge accessory by default is passed the following params
//
// @params:
// log - Logging functionality.
// config - The JSON description of the accessory.
// api - Homebridge API.
//
// @Optional params
// parentInfo - Optionally passed from a parent as if this is a linked accessory,
// or from a CMD4 Platform.
//
//
class Cmd4Accessory
{
constructor( log, config, api, STORED_DATA_ARRAY, parentInfo )
{
// Non Platform accessories get called with homebridges Logger
// replace with ours
if ( typeof log.setOutputEnabled === "function" )
{
this.log = log;
// Carry the debug flag from the platform
settings.cmd4Dbg = log.debugEnabled;
}
else
{
this.log = new Logger( );
if ( config[ constants.DEBUG ] == true ||
config[ "Debug" ] == true ||
process.env.DEBUG == settings.PLATFORM_NAME )
{
settings.cmd4Dbg = true;
}
}
this.log.setDebugEnabled( settings.cmd4Dbg );
this.config = config;
this.api = api;
// keep a copy because traversing it for format checking can be slow.
this.Characteristic = api.hap.Characteristic;
this.parentInfo = parentInfo;
// Use parent values ( if any ) or these defaults.
// LEVEL is a number, possibly 0 which must be handled more precisely.
this.CMD4 = ( parentInfo && parentInfo.CMD4 ) ? parentInfo.CMD4 : constants.STANDALONE;
this.LEVEL = ( parentInfo && parentInfo.LEVEL !== undefined ) ? parentInfo.LEVEL + 1 : 0;
this.createdCmd4Accessories = ( parentInfo && parentInfo.createdCmd4Accessories ) ? parentInfo.createdCmd4Accessories : [ ];
let typeMsg = [ "", "Linked ", "Added " ][ this.LEVEL ] || "";
if ( settings.cmd4Dbg ) log.debug( chalk.blue ( `Creating ${ typeMsg }${ this.CMD4 } Accessory type for : ${ config.displayName } LEVEL: ${ this.LEVEL }` ) );
this.services = [ ];
this.linkedAccessories = [ ];
this.listOfVariables = { };
this.listOfConstants = { };
// Determines if the accessory is communicable
this.errorValue = 0;
this.errorString = "init";
// Used to determine missing related characteristics and
// to determine if the related characteristic is also polled.
this.listOfPollingCharacteristics = { };
// An extra flag
this.ServiceCreated = false;
// DisplayName and/or Name must be defined.
// No need to update config anymore as it is no longer cached, only the Characteristic values are.
this.name = getAccessoryName( this.config );
this.displayName = getAccessoryDisplayName( this.config );
// Everything that needs to talk to the device now goes through the queue
this.queue = null;
// Use the Hierarhy variables from the parent, if not create it.
this.hV = new HV( );
if ( parentInfo && parentInfo.hV )
{
this.hV.update( parentInfo.hV );
}
// In case it is not passed in.
if ( STORED_DATA_ARRAY == undefined || STORED_DATA_ARRAY == null )
this.STORED_DATA_ARRAY = [ ];
else
this.STORED_DATA_ARRAY = STORED_DATA_ARRAY;
let parseConfigShouldUseCharacteristicValues = true;
if ( ! Array.isArray( this.STORED_DATA_ARRAY ) )
{
this.log.warn( "STORED_DATA_ARRAY passed in is not an array and should be reported." );
this.STORED_DATA_ARRAY = [ ];
}
// generate a unique id for the accessory this should be generated from
// something globally unique, but constant, for example, the device serial
// number or MAC address.
let uuid = getAccessoryUUID( config, this.api.hap.uuid );
// Handle case change
let existingDataU = this.STORED_DATA_ARRAY.find( data => data[ "UUID" ] === uuid );
if ( existingDataU )
{
//Z this.log.info( chalk.blue ( `THIS MSG TO BE REMOVED. RENAMING UUID for: ${ config.displayName } LEVEL: ${ this.LEVEL }` ) );
existingDataU[ "uuid" ] = existingDataU[ "UUID" ];
delete existingDataU[ "UUID" ];
}
// NOTE: We saved the data via lower case uuid.
let existingData = this.STORED_DATA_ARRAY.find( data => data[ constants.UUID ] === uuid );
if ( existingData )
{
//Z this.log.info( chalk.blue ( `THIS MSG TO BE REMOVED. Found existing data for: ${ this.displayName }` ) );
if ( settings.cmd4Dbg ) this.log.debug(`Cmd4Accessory: found existingData for ${ this.displayName }` );
if ( existingData.storedValuesPerCharacteristic )
{
//Z this.log.info( chalk.blue ( `THIS MSG TO BE REMOVED. Found old storedValuesPerCharacteristic for: ${ this.displayName }` ) );
if ( settings.cmd4Dbg ) this.log.debug( `Upgrading to cmd4Storage` );
this.cmd4Storage = new Cmd4Storage( this.log, existingData.storedValuesPerCharacteristic );
this.STORED_DATA_ARRAY.push( { [ constants.UUID ]: uuid,
[ constants.CMD4_STORAGE_lv ]: this.cmd4Storage
}
);
//this.STORED_DATA_ARRAY.remove( existingData );
removeFromArray( this.STORED_DATA_ARRAY, existingData );
} else if ( existingData.cmd4Storage )
{
//Z this.log.info( chalk.blue ( `THIS MSG TO BE REMOVED. Using existing cmd4Storage for: ${ this.displayName }` ) );
if ( settings.cmd4Dbg ) this.log.debug( `Using existing cmd4Storage` );
this.cmd4Storage = new Cmd4Storage( this.log, existingData.cmd4Storage );
this.STORED_DATA_ARRAY.push( { [ constants.UUID ]: uuid,
[ constants.CMD4_STORAGE_lv ]: this.cmd4Storage
}
);
//this.STORED_DATA_ARRAY.remove( existingData );
removeFromArray( this.STORED_DATA_ARRAY, existingData );
} else {
//Z log.info( chalk.blue ( `THIS MSG TO BE REMOVED. Unexpected empty cmd4Storage for: ${ this.displayName }` ) );
this.log.warn( `Unexpected empty cmd4Storage` );
this.cmd4Storage = new Cmd4Storage( this.log );
this.STORED_DATA_ARRAY.push( { [ constants.UUID ]: uuid,
[ constants.CMD4_STORAGE_lv ]: this.cmd4Storage
}
);
//this.STORED_DATA_ARRAY.remove( existingData );
removeFromArray( this.STORED_DATA_ARRAY, existingData );
}
// Do not read stored values from config.json
parseConfigShouldUseCharacteristicValues = false;
} else
{
//Z log.info( chalk.blue ( `THIS MSG TO BE REMOVED. Creating new cmd4Storage for: ${ this.displayName }` ) );
if ( settings.cmd4Dbg ) this.log.debug(`Cmd4Accessory: creating new cmd4Storage for ${ this.displayName }` );
// Instead of local variables for every characteristic, create an array to
// hold values for all characteristics based on the size of all possible
// characteristics. Placing them in .config will make them be cached over
// restarts.
this.cmd4Storage = new Cmd4Storage( this.log );
this.STORED_DATA_ARRAY.push( { [ constants.UUID ]: uuid,
[ constants.CMD4_STORAGE_lv ]: this.cmd4Storage
}
);
}
// Add the global constants to the listOfConstants
if ( this.parentInfo && this.parentInfo.globalConstants != null )
{
this.processConstants( this.parentInfo.globalConstants );
// Since linked accessories get processed first, The parentInfo they
// get is actually "this" and we need for the linked accessory to
// process the constants first in order to see them. i.e. ${IP}
this.globalConstants = this.parentInfo.globalConstants;
}
// Direct if polling should be set or false.
// You cannot copy polling from the parent because you would be copying the array
// of polled characteristics that the child does not have, or turning on polling
// for linked accessories too.
//this.polling = false;
// Init the Global Fakegato service once !
if ( FakeGatoHistoryService == null )
FakeGatoHistoryService = require( "fakegato-history" )( api );
// Get the supplied values from the accessory config.
this.parseConfig( this.config, parseConfigShouldUseCharacteristicValues );
// Update the accessories namespace for stored variables
// like timeout, stateChangeResponseTime ... As it may require
// changes from parseConfig.
this.hV.update( this );
// Add any required characteristics of a device that are missing from
// a users config.json file.
this.addRequiredCharacteristicStoredValues( );
// The accessory cannot have the same UUID as any other
checkAccessoryForDuplicateUUID( this, this.uuid );
// The default response time is in seconds
if ( ! this.stateChangeResponseTime )
this.stateChangeResponseTime = CMD4_DEVICE_TYPE_ENUM.properties[ this.typeIndex ].devicesStateChangeDefaultTime;
// Check the polling config for characteristics that may be set there
// and not in the config.json.
this.checkPollingConfigForUnsetCharacteristics( this.polling );
// Convert the accessoriesConfig ( if any ) to an array of Cmd4Accessory
if ( this.accessoriesConfig && this.CMD4 == constants.PLATFORM && this.LEVEL == 0 )
{
log.info( `Creating accessories for: ${ this.displayName }` );
// Let me explain.
// Level 0 are standalone or platform.
// Level 1 is linked.
// Added accessories are on the same level as linked,
// but they are not linkedTypes, just added to the platform.
// For Example: TelevisionSpeaker.
let savedLevel = this.LEVEL;
this.LEVEL = 1; // will be incremented to 2.
this.accessories = this.accessoryTypeConfigToCmd4Accessories( this.accessoriesConfig, this );
this.LEVEL = savedLevel;
}
// Convert the linkedTypes ( if any ) to an array of Cmd4Accessory
// Linked Accessories can be on Standalone or Platform Accessories.
if ( this.linkedAccessoriesConfig && this.LEVEL == 0 )
{
log.info( `Creating linked accessories for: ${ this.displayName }` );
this.linkedAccessories = this.accessoryTypeConfigToCmd4Accessories( this.linkedAccessoriesConfig, this );
}
// This sets up which characteristics, if any, will be polled
// This can be done for only LEVEL 0 accessories and itself
if ( this.LEVEL == 0 )
{
// if ( settings.cmd4Dbg ) log.debug( "CMD4=%s LEVEL=%s for %s", accessory.CMD4, accessory.LEVEL, accessory.displayName );
// The linked accessory children are at different levels of recursion, so only
// allow what is posssible.
if ( this.linkedAccessories && this.linkedAccessories.length > 0 )
{
if ( settings.cmd4Dbg ) this.log.debug( `Setting up which characteristics will be polled for Linked Accessories of ${ this.displayName }` );
this.linkedAccessories.forEach( ( linkedAccessory ) =>
{
if ( linkedAccessory.polling != false )
{
linkedAccessory.determineCharacteristicsToPollForAccessory( linkedAccessory );
}
});
}
// The Television Speaker Platform Example
if ( this.accessories && this.accessories.length > 0 )
{
if ( settings.cmd4Dbg ) this.log.debug( `Setting up which characteristics will be polled for Added Accessories of ${ this.displayName }` );
this.accessories.forEach( ( addedAccessory ) =>
{
if ( addedAccessory.polling )
{
addedAccessory.determineCharacteristicsToPollForAccessory( addedAccessory );
}
});
}
if ( settings.cmd4Dbg ) this.log.debug( `Setting up which characteristics will be polled for ${ this.displayName }` );
this.determineCharacteristicsToPollForAccessory( this );
}
// Create all the services for the accessory, including fakegato and polling
// Only true Standalone accessories can have their services created and
// polling started. Otherwise the platform will have to do this.
if ( this.CMD4 == constants.STANDALONE && this.LEVEL == 0 )
{
if ( settings.cmd4Dbg ) log.debug( `Creating Standalone service for: ${ this.displayName }` );
this.createServicesForStandaloneAccessoryAndItsChildren( this )
}
} // Cmd4Accessory ( log, config, api, STORED_DATA_ARRAY, parentInfo )
identify( callback )
{
callback( );
}
getServices( )
{
//if ( this.services )
//{
// if ( settings.cmd4Dbg ) this.log.debug( Fg.Red + "ZZZZ Returning:%s number of services for:%s" + Fg.Rm, this.services.length, this.displayName );
//} else {
// if ( settings.cmd4Dbg ) this.log.debug( Fg.Red + "ZZZZ Returning this.services:%s for:%s" + Fg.Rm, this.services, this.displayName );
//}
return this.services;
}
// Any required characteristic of an accessory that is not in the accessories
// config will be added later by the existance of its stored value, so
// find the missing characteristics and add their value s here.
addRequiredCharacteristicStoredValues ( )
{
// Get the properties for this accessories device type
let properties = CMD4_DEVICE_TYPE_ENUM.properties[ this.typeIndex ];
// Check if required characteristics should be added, or TLV8 removed.
for ( let accTypeEnumIndex = 0 ; accTypeEnumIndex < CMD4_ACC_TYPE_ENUM.EOL; accTypeEnumIndex++ )
{
// Get the properties for this accessories device type
let devProperties = CMD4_DEVICE_TYPE_ENUM.properties[ this.typeIndex ];
// See if the characteristic index is in the required characteristics of the device
let requiredIndex = devProperties.requiredCharacteristics.indexOfEnum( i => i.type === accTypeEnumIndex );
let format = CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].props.format;
// No matter what, remove it
if ( format == this.api.hap.Formats.TLV8 && this.hV.allowTLV8 == false )
{
if ( this.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex ) != null )
{
this.cmd4Storage.setStoredValueForIndex( accTypeEnumIndex, null );
this.log.warn( `****** Removing TLV8 required characteristic: ${ CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].type }` );
}
continue;
}
// if it is required and not stored, add it
if ( requiredIndex != -1 && this.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex ) == null )
{
this.log.warn( `**** Adding required characteristic ${ CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].type } for ${ this.displayName }` );
this.log.warn( `Not defining a required characteristic can be problematic` );
// Get the default value to store
let defaultValue = properties.requiredCharacteristics[ requiredIndex ].defaultValue;
// If ConfiguredName was not defined, then use the Accessories Name
if ( accTypeEnumIndex == CMD4_ACC_TYPE_ENUM.ConfiguredName )
defaultValue = getAccessoryName( this.config );
if ( settings.cmd4Dbg ) this.log.debug( `*****Adding default value ${ defaultValue } for: ${ this.displayName }` );
this.cmd4Storage.setStoredValueForIndex( accTypeEnumIndex, defaultValue );
}
}
}
checkPollingConfigForUnsetCharacteristics( pollingConfig )
{
if ( trueTypeOf( pollingConfig ) != Array )
return;
if ( settings.cmd4Dbg ) this.log.debug( `Checking ${ this.displayName } for polling of unset characteristics.` );
pollingConfig.forEach( ( jsonPollingConfig ) =>
{
let value;
let valueToStore = null;
let accTypeEnumIndex = -1;
let key;
for ( key in jsonPollingConfig )
{
value = jsonPollingConfig[ key ];
let rcDirective = isCmd4Directive( key );
if ( rcDirective == null )
{
rcDirective = isCmd4Directive( key, true );
if ( rcDirective != null )
{
// warn now
this.log.warn( `The config.json Cmd4 Polling Directive: ${ key } is Capitalized. It should be: ${ rcDirective.key }. In the near future this will be an error for homebridge-ui integration.\nTo remove this Warning, Please fix your config.json.` );
// create the proper lower case value
jsonPollingConfig[ rcDirective.key ] = value;
// delete the upper case value
delete jsonPollingConfig[ key ];
//set the key
key = rcDirective.key;
}
}
// Not finding the key is not an error as it could be a Characteristic
switch ( key )
{
case constants.TIMEOUT:
case constants.INTERVAL:
// break omitted
case constants.QUEUE:
{
break;
}
case constants.QUEUETYPES:
{
// This whole record is not a characteristic polling entry
// continue to next ( via return )
return;
}
case constants.CHARACTERISTIC:
{
//2 checkPollingOfUnsetCharacteristics
valueToStore = null;
let rcDirective = isAccDirective( value, false );
if ( rcDirective.accTypeEnumIndex == null )
{
rcDirective = isAccDirective( value, true );
if ( rcDirective.accTypeEnumIndex == null )
throw new Error( `No such polling characteristic: "${ value }" for: "${ this.displayName }".` );
this.log.warn( `The config.json Polling characteristic: ${ value } is Capitalized it should be: ${ rcDirective.type }. In the near future this will be an Error so that Cmd4 can use homebridge-ui.\nTo remove this Warning, Please fix your config.json.` );
}
accTypeEnumIndex = rcDirective.accTypeEnumIndex;
// We can do this as this is a new way to do things.
let storedValue = this.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex );
if ( storedValue == undefined )
throw new Error( `Polling for: "${ value }" requested, but characteristic is not in your config.json file for: "${ this.displayName }".` );
// This makes thinks nice down below.
valueToStore = storedValue;
break;
}
default:
{
// Is this still useful?
accTypeEnumIndex = CMD4_ACC_TYPE_ENUM.indexOfEnum( key );
if ( accTypeEnumIndex < 0 )
// throw new Error( `OOPS: "${ key }" not found while parsing for characteristic polling. There something wrong with your config.json file?` );
throw new Error( `OOPS: "${ key }" not found while parsing for characteristic polling of "${ this.displayName }". There something wrong with your config.json file?` );
valueToStore = value;
}
}
}
if ( accTypeEnumIndex == -1 )
throw new Error( `No characteristic found while parsing for characteristic polling of: "${ this.displayName }". There something wrong with your config.json file?` );
if ( this.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex ) == undefined )
{
this.log.warn( `Polling for: "${ key }" requested, but characteristic` );
this.log.warn( `is not in your config.json file for: ${ this.displayName }` );
this.log.warn( `This will be an error in the future.` );
}
this.cmd4Storage.setStoredValueForIndex( accTypeEnumIndex, valueToStore );
});
}
createServicesForStandaloneAccessoryAndItsChildren( accessory )
{
if ( settings.cmd4Dbg ) accessory.log.debug( chalk.blue( `createServicesFor${ this.CMD4 }AccessoryAndItsChildren` ) );
if ( accessory.ServiceCreated == true )
{
if ( settings.cmd4Dbg ) accessory.log.debug( chalk.red( `SERVICES ALREADY CREATED FOR ${ this.displayName } ${ this.CMD4 } ${ this.LEVEL }` ) );
return;
} else {
accessory.ServiceCreated = true;
}
let properties = CMD4_DEVICE_TYPE_ENUM.properties[ accessory.typeIndex ];
//
// Standalone Accessory
//
// Create the accessory's service
accessory.service = new properties.service( accessory.name, accessory.subType )
if ( settings.cmd4Dbg ) accessory.log.debug( `Creating information service for standalone accessory: ${ accessory.displayName }` );
// Create the Standalone accessory's information service.
createAccessorysInformationService( accessory );
// Create the Standalone accessory's services for all its linked children
if ( accessory.linkedAccessories )
{
accessory.linkedAccessories.forEach( ( linkedAccessory ) =>
{
let properties = CMD4_DEVICE_TYPE_ENUM.properties[ linkedAccessory.typeIndex ];
// Standalone Step 4.
// const hdmi1InputService = this.tvAccessory.addService( this.Service.InputSource, `hdmi1', 'HDMI 1' );
if ( settings.cmd4Dbg ) accessory.log.debug( `Standalone Step 4. linkedAccessory( ${ accessory.displayName } ).service = new Service( ${ linkedAccessory.name }, ${ linkedAccessory.subType } )` );
linkedAccessory.service = new properties.service( linkedAccessory.name, linkedAccessory.subType )
accessory.services.push( linkedAccessory.service );
// Hmmm Double Check this !!
// Create Information Service
//if ( settings.cmd4Dbg ) linkedAccessory.log.debug( "Creating information service for linkedAccessory:%s", linkedAccessory.displayName );
//createAccessorysInformationService( linkedAccessory );
if ( settings.cmd4Dbg ) accessory.log.debug( `Standalone Step 5. ${ accessory.displayName }.service.addLinkedService( ${ linkedAccessory.displayName }.service` );
// Standalone Step 5.
// tvService.addLinkedService( hdmi1InputService ); // link to tv service
accessory.service.addLinkedService( linkedAccessory.service );
linkedAccessory.addAllServiceCharacteristicsForAccessory( linkedAccessory );
// Setup the fakegato service if defined in the config.json file
linkedAccessory.setupAccessoryFakeGatoService( linkedAccessory.fakegatoConfig );
// Move the information service to the top of the list
linkedAccessory.services.unshift( linkedAccessory.informationService );
});
}
accessory.addAllServiceCharacteristicsForAccessory( accessory );
// Setup the fakegato service if defined in the config.json file
accessory.setupAccessoryFakeGatoService( accessory.fakegatoConfig );
accessory.services.push( accessory.service );
// Move the information service to the top of the list
accessory.services.unshift( accessory.informationService );
}
// ***********************************************
//
// setCachedValue:
// This methos will update the cached value of a
// characteristic of a accessory.
//
// ***********************************************
setCachedValue( accTypeEnumIndex, characteristicString, value, callback )
{
let self = this;
if ( self.hV.statusMsg == "TRUE" )
self.log.info( chalk.blue( `Setting (Cached) ${ self.displayName } ${ characteristicString }` ) + ` ${ value }` );
else
if ( settings.cmd4Dbg ) self.log.debug( `setCachedvalue accTypeEnumIndex:( ${ accTypeEnumIndex } )-"${ characteristicString }" function for: ${ self.displayName } value: ${ value }` );
// Save the cached value.
// Fakegato does not need to be updated as that is done on a "Get".
self.cmd4Storage.setStoredValueForIndex( accTypeEnumIndex, value );
let relatedCurrentAccTypeEnumIndex = this.getDevicesRelatedCurrentAccTypeEnumIndex( accTypeEnumIndex );
// We are currently tring to set a cached characteristics
// like "Target*".
// There is no way for its relatedCurrentAccTypeEnumIndex characteristic like "Current*"
// to be set if cached or Polled (with the exception below).
if ( relatedCurrentAccTypeEnumIndex != null )
{
// We are in a "Set" but this applies to the "Get" for why we would need to
// set the relatedCurrentAccTypeEnumIndex Characteristic as well.
if ( self.listOfPollingCharacteristics[ relatedCurrentAccTypeEnumIndex ])
{
let relatedCharacteristicString = CMD4_ACC_TYPE_ENUM.properties[ relatedCurrentAccTypeEnumIndex ].type;
self.log.info( chalk.blue( `Also Setting (Cached) ${ self.displayName } ${ relatedCharacteristicString }` ) + ` ${ value }` );
self.cmd4Storage.setStoredValueForIndex( relatedCurrentAccTypeEnumIndex, value );
}
}
callback( null );
}
// ***********************************************
//
// GetCachedValue:
// This methos will return an accessories cached
// characteristic value.
//
// ***********************************************
getCachedValue( accTypeEnumIndex, characteristicString, callback )
{
let self = this;
let storedValue = self.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex );
if ( storedValue == null || storedValue == undefined )
{
self.log.warn( `getCachedValue ${ characteristicString } for: ${ self.displayName } has no cached value` );
callback( 10, null );
}
if ( settings.cmd4Dbg ) self.log.debug( `getCachedValue ${ characteristicString } for: ${ self.displayName } returned (CACHED) value: ${ storedValue }` );
callback( 0, storedValue );
// Store history using fakegato if set up
self.updateAccessoryAttribute( accTypeEnumIndex, storedValue );
}
// Check props to see if any characteristic properties
// are to be changed. For example, currentTemperature
// minValue to be below zero.
configHasCharacteristicProps( accTypeEnumIndex )
{
if ( this.props == undefined )
return undefined;
if ( ! isJSON( this.props ) )
return undefined;
let characteristicProps = CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].props;
let type = CMD4_ACC_TYPE_ENUM.accEnumIndexToLC( accTypeEnumIndex );
let ucType = CMD4_ACC_TYPE_ENUM.accEnumIndexToUC( accTypeEnumIndex );
let definitions;
if ( this.props[ type ] )
definitions = this.props[ type ];
if ( this.props[ ucType ] )
definitions = this.props[ ucType ];
if ( ! definitions )
return undefined;
let rc = definitions;
for ( let key in definitions )
{
// warn now
if ( key.charAt( 0 ) === key.charAt( 0 ).toUpperCase() )
{
this.log.warn( `The property definition key: ${ key } is Capitalized. In the near future all characteristics will start with a lower case character for homebridge-ui integration.\nTo remove this Warning, Please fix your config.json.` );
}
let lcKey = lcFirst ( key );
if ( characteristicProps[ lcKey ] == undefined )
throw new Error( `props for key "${ key }" not in definition of "${ type }"` );
if ( typeof characteristicProps[ lcKey ] != typeof definitions[ lcKey ] )
throw new Error( `props for key "${ key }" type "${ typeof definitions[ key ] }" Not equal to definition of "${ typeof characteristicProps[ key ] }"` );
}
return rc;
}
checkCharacteristicNeedsFixing( accessory, accTypeEnumIndex )
{
// Hap keeps changing this where Current and Target don't match.
// We fix this here.
if ( accTypeEnumIndex == CMD4_ACC_TYPE_ENUM.CurrentHeatingCoolingState )
{
if ( settings.cmd4Dbg ) this.log.debug( "fixing heatingCoolingState" );
accessory.service.getCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].
characteristic ).setProps(
{
maxValue: 3,
validValues: [ 0, 1, 2, 3 ]
});
}
return;
}
// ***********************************************
//
// addAllServiceCharacteristicsForAccessory:
// Method to set up all services for those characteristics in the
// config.json file.
//
//
// Explanation:
// If you are wondering why this is done this way as compared to
// other plugins that do the switch and a bind in their getServices
// section; It took a week to figure out why the security
// system was not getting updated after setting the target state.
// The get currentState needs to be called after the set targetState,
// but that was not enough. Something is different with their
// getServices bind implementation. While everything works, for
// some reason the IOS HomeKit app and even the Eve app never gets
// the result of the get currentState.
// I could delve further into their implementation, but this works.
// It was one of many methods I tried after examining and trying
// many plugins.
// This method was taken from homebridge-real-fake-garage-doors by
// plasticrake.
// P.S - This is probably more documentation of code anywhere
// in Homebridge :-) If you find it useful, send
// me a like ;-)
//
//
// Note: This code wipes out 5K of duplicate code.
// by using a bound function. It appears
// to work on my iMac.
//
// ***********************************************
addAllServiceCharacteristicsForAccessory( accessory )
{
if ( settings.cmd4Dbg ) accessory.log.debug( `Adding All Service Characteristics for: ${ accessory.displayName }` );
let perms = "";
// Check every possible characteristic
for ( let accTypeEnumIndex = 0; accTypeEnumIndex < CMD4_ACC_TYPE_ENUM.EOL; accTypeEnumIndex++ )
{
// For "Get" or "Set" commands, we send uppercase
let uCCharacteristicString = CMD4_ACC_TYPE_ENUM.accEnumIndexToUC( accTypeEnumIndex );
// If there is a stored value for this characteristic ( defined by the config file )
// Then we need to add the characteristic too
let storedValue = accessory.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex );
if ( storedValue != undefined )
{
if ( settings.cmd4Dbg ) accessory.log.debug( "Found characteristic:%s value:%s for:%s",
uCCharacteristicString,
storedValue,
this.displayName );
// Find out if the characteristic is not part of the service
// and needs to be added.
if ( ! accessory.service.testCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].characteristic ) )
{
//if ( settings.cmd4Dbg ) accessory.log.debug( "Adding optional characteristic:%s for: %s", CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].type, this.displayName );
if ( settings.cmd4Dbg ) accessory.log.debug( "Adding optional characteristic:%s for: %s", uCCharacteristicString, this.displayName );
accessory.service.addCharacteristic( CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].characteristic );
}
this.checkCharacteristicNeedsFixing( accessory, accTypeEnumIndex );
let props = accessory.configHasCharacteristicProps( accTypeEnumIndex );
if ( props )
{
if ( settings.cmd4Dbg ) accessory.log.debug( "Overriding characteristic %s props for: %s ", CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].type, this.displayName );
accessory.service.getCharacteristic( CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].
characteristic )
.setProps(
// props is an object of name value pairs (characteristics)
props
);
}
// Get the permissions of characteristic ( Read/Write ... )
// Both are 100% the same.
// perms = CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].props.perms
perms = accessory.service.getCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ]
.characteristic ).props.perms;
// Comment before change
// "Read and or write, we need to set the value once.
// If the characteristic was optional and read only, this will add
// it with the correct value. You cannot add and set a read characteristic."
//
// What was happening was at startup all writeable characteristics were calling
// setValue and the MyAir was getting hammered.
// We need to check if the characteristic is readable but not writeable.
// Things this will set are like:
// - Name
// - CurrentTemperature
// - CurrentHeatingCoolingState
// - StatusFault
// Homebridge V2 removes Perms.READ && Perms.WRITE
if ( //perms.indexOf( this.api.hap.Perms.READ ) >= 0 &&
//perms.indexOf( this.api.hap.Perms.WRITE ) == -1 ||
perms.indexOf( this.api.hap.Perms.PAIRED_READ ) >= 0 &&
perms.indexOf( this.api.hap.Perms.PAIRED_WRITE ) == -1 )
{
accessory.service.setCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].characteristic,
this.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex ) );
}
// Add getValue via getCachedValue funtion to service
if ( accessory.service.getCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ]
.characteristic ).listeners( "get" ).length == 0 )
{
// Add Read services for characterisitcs, if possible
// Homebridge v2 removed Perms.READ
if ( // perms.indexOf( this.api.hap.Perms.READ ) != -1 ||
perms.indexOf( this.api.hap.Perms.PAIRED_READ ) != -1 )
{
// getCachedValue or getValue
if ( ! accessory.polling ||
accessory.listOfPollingCharacteristics[ accTypeEnumIndex ] == undefined
)
{
if ( settings.cmd4Dbg ) this.log.debug( chalk.yellow( `Adding getCachedValue for ${ accessory.displayName } characteristic: ${ uCCharacteristicString } ` ) );
//Get cachedValue
accessory.service.getCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ]
.characteristic )
.on( "get", accessory.getCachedValue.bind( accessory, accTypeEnumIndex, uCCharacteristicString ) );
} else
{
if ( settings.cmd4Dbg ) this.log.debug( chalk.yellow( `Adding priorityGetValue for ${ accessory.displayName } characteristic: ${ uCCharacteristicString }` ) );
let details = accessory.lookupAccessoryHVForPollingCharacteristic( accessory, accTypeEnumIndex );
// Set parms are accTypeEnumIndex, value, callback
// Get parms are accTypeEnumIndex, callback
accessory.service.getCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ]
.characteristic )
.on( "get", accessory.queue.priorityGetValue.bind( accessory, accTypeEnumIndex, uCCharacteristicString, details.timeout ) );
}
}
}
// Add setValue function to service
if ( accessory.service.getCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ]
.characteristic ).listeners( "set" ).length == 0 )
{
// Add Write services for characterisitcs, if possible
// Homebridge V2 removes Perms.WRITE
if ( // perms.indexOf( this.api.hap.Perms.WRITE ) != -1 ||
perms.indexOf( this.api.hap.Perms.PAIRED_WRITE ) != -1 )
{
// setCachedValue or setValue
if ( ! accessory.polling ||
accessory.listOfPollingCharacteristics[ accTypeEnumIndex ] == undefined)
{
if ( settings.cmd4Dbg ) this.log.debug( chalk.yellow( `Adding setCachedValue for ${ accessory.displayName } characteristic: ${ uCCharacteristicString } ` ) );
// setCachedValue has parameters:
// accTypeEnumIndex, value, callback
// The first bound value though is "this"
let boundSetCachedValue = accessory.setCachedValue.bind( this, accTypeEnumIndex, uCCharacteristicString );
accessory.service.getCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ]
.characteristic ).on( "set", ( value, callback ) => {
boundSetCachedValue( value, callback );
});
} else {
if ( settings.cmd4Dbg ) this.log.debug( chalk.yellow( `Adding prioritySetValue for ${ accessory.displayName } characteristic: ${ uCCharacteristicString } ` ) );
let details = accessory.lookupAccessoryHVForPollingCharacteristic( accessory, accTypeEnumIndex );
// Set parms are accTypeEnumIndex, value, callback
// Get parms are accTypeEnumIndex, callback
let boundSetValue = accessory.queue.prioritySetValue.bind( this, accTypeEnumIndex, uCCharacteristicString, details.timeout, details.stateChangeResponseTime );
accessory.service.getCharacteristic(
CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ]
.characteristic ).on( "set", ( value, callback ) => {
boundSetValue( value, callback );
});
}
}
}
}
}
}
updateAccessoryAttribute( accTypeEnumIndex, value )
{
if ( accTypeEnumIndex < 0 || accTypeEnumIndex > CMD4_ACC_TYPE_ENUM.EOL )
{
this.log.error( `Internal error: updateAccessoryAttribute - accTypeEnumIndex: ${ accTypeEnumIndex } for: ${ this.displayName } not found` );
return;
}
this.cmd4Storage.setStoredValueForIndex( accTypeEnumIndex, value );
if ( this.loggingService )
{
let firstParm, secondParm, thirdParm;
let firstParmValue, secondParmValue, thirdParmValue = 0;
let firstParmDirective, secondParmDirective, thirdParmDirective;
switch ( this.eve )
{
case constants.FAKEGATO_TYPE_ENERGY:
{
firstParm = this.fakegatoConfig[ constants.POWER ] || "0";
firstParmDirective = isAccDirective( firstParm, true );
firstParmValue = ( this.cmd4Storage.testStoredValueForIndex( firstParmDirective.accTypeEnumIndex ) == undefined ) ?
firstParmValue : this.cmd4Storage.getStoredValueForIndex( firstParmDirective.accTypeEnumIndex );
if ( settings.cmd4Dbg ) this.log.debug( `Logging ${ constants.POWER }: ${ firstParmValue }` );
// Eve Energy ( Outlet service )
this.loggingService.addEntry(
{ [ constants.TIME ] : moment( ).unix( ),
[ constants.POWER ] : firstParmValue
});
break;
}
case constants.FAKEGATO_TYPE_ROOM:
{
firstParm = this.fakegatoConfig[ constants.TEMP ] || "0";
firstParmDirective = isAccDirective( firstParm, true );
secondParm = this.fakegatoConfig[ constants.HUMIDITY ] || "0";
secondParmDirective = isAccDirective( secondParm, true );
thirdParm = this.fakegatoConfig[ constants.PPM ] || "0";
thirdParmDirective = isAccDirective( thirdParm, true );
firstParmValue = ( this.cmd4Storage.testStoredValueForIndex( firstParmDirective.accTypeEnumIndex ) == undefined ) ?
firstParmValue : this.cmd4Storage.getStoredValueForIndex( firstParmDirective.accTypeEnumIndex );
secondParmValue = ( this.cmd4Storage.testStoredValueForIndex( secondParmDirective.accTypeEnumIndex ) == undefined ) ?
secondParmValue : this.cmd4Storage.getStoredValueForIndex( secondParmDirective.accTypeEnumIndex );
thirdParmValue = ( this.cmd4Storage.testStoredValueForIndex( thirdParmDirective.accTypeEnumIndex ) == undefined ) ?
thirdParmValue : this.cmd4Storage.getStoredValueForIndex( thirdParmDirective.accTypeEnumIndex );
if ( settings.cmd4Dbg ) this.log.debug( `Logging ${ constants.TEMP }:${ firstParmValue } ${constants.HUMIDITY }:${ secondParmValue } ${ constants.PPM }:${ thirdParmValue }` );
// Eve Room ( TempSensor, HumiditySensor and AirQuality Services )
this.loggingService.addEntry(
{ [ constants.TIME ] : moment( ).unix( ),
[ constants.TEMP ] : firstParmValue,
[ constants.HUMIDITY ] : secondParmValue,
[ constants.PPM ] : thirdParmValue
});
break;
}
case constants.FAKEGATO_TYPE_WEATHER:
{
firstParm = this.fakegatoConfig[ constants.TEMP ] || "0";
firstParmDirective = isAccDirective( firstParm, true );
secondParm = this.fakegatoConfig[ constants.PRESSURE ] || "0";
secondParmDirective = isAccDirective( secondParm, true );
thirdParm = this.fakegatoConfig[ constants.HUMIDITY ] || "0";
thirdParmDirective = isAccDirective( thirdParm, true );
firstParmValue = ( this.cmd4Storage.testStoredValueForIndex( firstParmDirective.accTypeEnumIndex ) == undefined ) ?
firstParmValue : this.cmd4Storage.getStoredValueForIndex( firstParmDirective.accTypeEnumIndex );
secondParmValue = ( this.cmd4Storage.testStoredValueForIndex( secondParmDirective.accTypeEnumIndex ) == undefined ) ?
secondParmValue : this.cmd4Storage.getStoredValueForIndex( secondParmDirective.accTypeEnumIndex );
thirdParmValue = ( this.cmd4Storage.testStoredValueForIndex( thirdParmDirective.accTypeEnumIndex ) == undefined ) ?
thirdParmValue : this.cmd4Storage.getStoredValueForIndex( thirdParmDirective.accTypeEnumIndex );
if ( settings.cmd4Dbg ) this.log.debug( `Logging ${ constants.TEMP }: ${ firstParmValue } ${ constants.PRESSURE }: ${ secondParmValue } ${ constants.HUMIDITY }: ${ thirdParmValue }` );
// Eve Weather ( TempSensor Service )
this.loggingService.addEntry(
{ [ constants.TIME ] : moment( ).unix( ),
[ constants.TEMP ] : firstParmValue,
[ constants.PRESSURE ] : secondParmValue,
[ constants.HUMIDITY ] : thirdParmValue
});
break;
}
case constants.FAKEGATO_TYPE_DOOR:
{
firstParm = this.fakegatoConfig[ constants.STATUS ] || "0";
firstParmDirective = isAccDirective( firstParm, true );
firstParmValue = ( this.cmd4Storage.testStoredValueForIndex( firstParmDirective.accTypeEnumIndex ) == undefined ) ?
firstParmValue : this.cmd4Storage.getStoredValueForIndex( firstParmDirective.accTypeEnumIndex );
if ( settings.cmd4Dbg ) this.log.debug( `Logging ${ constants.STATUS } status: ${ firstParmValue }` );
this.loggingService.addEntry(
{ [ constants.TIME ] : moment( ).unix( ),
[ constants.STATUS ] : firstParmValue
});
break;
}
case constants.FAKEGATO_TYPE_MOTION:
{
firstParm = this.fakegatoConfig[ constants.STATUS ] || "0";
firstParmDirective = isAccDirective( firstParm, true );
firstParmValue = ( this.cmd4Storage.testStoredValueForIndex( firstParmDirective.accTypeEnumIndex ) == undefined ) ?
firstParmValue : this.cmd4Storage.getStoredValueForIndex( firstParmDirective.accTypeEnumIndex );
if ( settings.cmd4Dbg ) this.log.debug( `Logging ${ constants.STATUS }: ${ firstParmValue }` );
this.loggingService.addEntry(
{ [ constants.TIME ] : moment( ).unix( ),
[ constants.STATUS ] : firstParmValue
});
break;
}
case constants.FAKEGATO_TYPE_THERMO:
{
firstParm = this.fakegatoConfig[ constants.CURRENTTEMP ] || "0";
firstParmDirective = isAccDirective( firstParm, true );
secondParm = this.fakegatoConfig[ constants.SETTEMP ] || "0";
secondParmDirective = isAccDirective( secondParm, true );
thirdParm = this.fakegatoConfig[ constants.VALVEPOSITION ] || "0";
thirdParmDirective = isAccDirective( thirdParm, true );
firstParmValue = ( this.cmd4Storage.testStoredValueForIndex( firstParmDirective.accTypeEnumIndex ) == undefined ) ?
firstParmValue : this.cmd4Storage.getStoredValueForIndex( firstParmDirective.accTypeEnumIndex );
secondParmValue = ( this.cmd4Storage.testStoredValueForIndex( secondParmDirective.accTypeEnumIndex ) == undefined ) ?
secondParmValue : this.cmd4Storage.getStoredValueForIndex( secondParmDirective.accTypeEnumIndex );
thirdParmValue = ( this.cmd4Storage.testStoredValueForIndex( thirdParmDirective.accTypeEnumIndex ) == undefined ) ?
thirdParmValue : this.cmd4Storage.getStoredValueForIndex( thirdParmDirective.accTypeEnumIndex );
if ( settings.cmd4Dbg ) this.log.debug( `Logging ${ constants.CURRENTTEMP }: ${ firstParmValue } ${ constants.SETTEMP }:${ secondParmValue } ${constants.VALVEPOSITION }:${ thirdParmValue } ` );
// Eve Thermo ( Thermostat service )
this.loggingService.addEntry(
{ [ constants.TIME ] : moment( ).unix( ),
[ constants.CURRENTTEMP ] : firstParmValue,
[ constants.SETTEMP ] : secondParmValue,
[ constants.VALVEPOSITION ] : thirdParmValue
});
break;
}
case constants.FAKEGATO_TYPE_AQUA:
{
firstParm = this.fakegatoConfig[ constants.STATUS ] || "0";
firstParmDirective = isAccDirective( firstParm, true );
secondParm = this.fakegatoConfig[ constants.WATERAMOUNT ] || "0";
secondParmDirective = isAccDirective( secondParm, true );
firstParmValue = ( this.cmd4Storage.testStoredValueForIndex( firstParmDirective.accTypeEnumIndex ) == undefined ) ?
firstParmValue : this.cmd4Storage.getStoredValueForIndex( firstParmDirective.accTypeEnumIndex );
secondParmValue = ( this.cmd4Storage.testStoredValueForIndex( secondParmDirective.accTypeEnumIndex ) == undefined ) ?
secondParmValue : this.cmd4Storage.getStoredValueForIndex( secondParmDirective.accTypeEnumIndex );
if ( settings.cmd4Dbg ) this.log.debug( `Logging ${ constants.STATUS }: ${ firstParmValue } ${ constants.WATERAMOUNT }: ${ secondParmValue }` );
// Eve Aqua ( Valve service set to Irrigation Type )
this.LoggingService.addEntry(
{ [ constants.TIME ] : moment( ).unix( ),
[ constants.STATUS ] : firstParmValue,
[ constants.WATERAMOUNT ] : secondParmValue
});
break;
}
}
}
}
setupAccessoryFakeGatoService( fakegatoConfig )
{
if ( fakegatoConfig == undefined )
return;
for ( let key in fakegatoConfig )
{
let value = fakegatoConfig[ key ];
let rcDirective = isCmd4Directive( key, false );
if ( rcDirective == null )
{
rcDirective = isCmd4Directive( key, true );
if ( rcDirective != null )
{
// warn now
this.log.warn( `The config.json FakeGato key: ${ key } is Capitalized. It should be: ${ rcDirective.key }. In the near future this will be an error for homebridge-ui integration.\nTo remove this Warning, Please fix your config.json.` );
// create the proper lower case value
fakegatoConfig[ rcDirective.key ] = value;
// delete the upper case value
fakegatoConfig[ key ].remove();
//set the key
key = rcDirective.key;
}
}
switch ( key )
{
case constants.EVE:
this.eve = fakegatoConfig[ key ];
switch( value )
{
case constants.FAKEGATO_TYPE_ENERGY:
case constants.FAKEGATO_TYPE_ROOM:
case constants.FAKEGATO_TYPE_WEATHER:
case constants.FAKEGATO_TYPE_DOOR:
case constants.FAKEGATO_TYPE_MOTION:
case constants.FAKEGATO_TYPE_THERMO:
case constants.FAKEGATO_TYPE_AQUA:
break;
default:
throw new Error( `Invalid fakegato eve type: "${ value }". It must be one of ( ${ constants.FAKEGATO_TYPE_ENERGY }, ${ constants.FAKEGATO_TYPE_ROOM }, ${ constants.FAKEGATO_TYPE_WEATHER }, ${ constants.FAKEGATO_TYPE_DOOR }, ${ constants.FAKEGATO_TYPE_MOTION }, ${ constants.FAKEGATO_TYPE_THERMO }, ${ constants.FAKEGATO_TYPE_AQUA } ). Check the Cmd4 README at: "https://github.com/simont77/fakegato-history".` );
}
break;
case constants.STORAGE:
this.storage = fakegatoConfig[ key ];
break;
case constants.STORAGEPATH:
this.storagePath = fakegatoConfig[ key ];
break;
case constants.KEYPATH:
this.keyPath = fakegatoConfig[ key ];
break;
case constants.FOLDER:
this.Folder = fakegatoConfig[ key ];
break;
case constants.STATUS:
case constants.TEMP:
case constants.SETTEMP:
case constants.HUMIDITY:
case constants.PPM:
case constants.POWER:
case constants.PRESSURE:
case constants.CURRENTTEMP:
case constants.VALVEPOSITION:
{
if ( value != "0" )
{
let rcDirective = isAccDirective( value, false );
if ( rcDirective.accTypeEnumIndex == null )
{
rcDirective = isAccDirective( value, true );
if ( rcDirective.accTypeEnumIndex == null )
throw new Error( `Invalid characteristic "${ value }" for fakegato to log of "${ key }".` );
this.log.warn( `The config.json FakeGato characteristic: ${ value } is Capitalized it should be: ${ rcDirective.type }. In the near future this will be an Error so that Cmd4 can use homebridge-ui.\nTo remove this Warning, Please fix your config.json.` );
}
// Make sure the characteristic is being polled (Changing) so I do
// not get any more tickets.
if ( this.queue.isCharacteristicPolled( rcDirective.accTypeEnumIndex, this.queue, this ) == false )
throw new Error(`Characteristic: "${ value }" for fakegato to log of "${ key }" is not being polled.\nHistory can not be updated continiously.` );
}
break;
}
default:
throw new Error( `Invalid fakegato key: "${ key }" in json.config for: "${ this.displayName }".` );
}
}
// Optional
if ( this.storage != undefined )
{
if ( this.storage == constants.FS )
{
this.loggingService = new FakeGatoHistoryService
(
this.eve,
this.platform,
{ [ constants.STORAGE ] : constants.FS,
[ constants.PATH ] : this.storagePath
}
);
this.services.push( this.loggingService );
} else if ( this.storage == constants.GOOGLE_DRIVE )
{
this.loggingService = new FakeGatoHistoryService
(
this.eve,
this.platform,
{ [ constants.STORAGE ] : constants.GOOGLE_DRIVE,
[ constants.FOLDER ] : this.folder,
[ constants.KEYPATH ] : this.keyPath }
);
this.services.push( this.loggingService );
} else
{
this.log.warn( chalk.yellow( "WARNING" ) + `: Cmd4 Unknown accessory config.storage:{ this.storage } Expected:${ constants.FS } or ${ constants.GOOGLEDRIVE } for: ${ this.displayName }` );
}
}
if ( this.loggingService )
{
if ( ! this.polling )
{
this.log.warn( `config.storage: ${ this.storage } for: ${ this.displayName } set but polling is not enabled.` );
this.log.warn( ` History will not be updated continiously.` );
}
}
}
validateStateCmd( state_cmd )
{
if ( typeof state_cmd != "string" )
throw new Error( `No state_cmd for: "${ this.displayName }".` );
// This was messy, did not like spaces. let the user be the judge of it.
return true;
}
// We are looking for things like Volume: <value>, Name: <value>, Mute: <value>
parseKeyForCharacteristics( key, value, parseConfigShouldUseCharacteristicValues )
{
// fix the their scripts, fix it here.
let rcDirective = isAccDirective( key, false );
if ( rcDirective.accTypeEnumIndex == null )
{
rcDirective = isAccDirective( key, true );
if ( rcDirective.accTypeEnumIndex == null )
throw new Error( `OOPS: "${ key }" not found for parsing characteristics in: "${ this.displayName }".` );
this.log.warn( `The config.json characteristic key: ${ key } is Capitalized. It should be: ${ rcDirective.type }. In the near future this will be an error for homebridge-ui integration.\nTo remove this Warning, Please fix your config.json.` );
}
let characteristicString = rcDirective.type;
let accTypeEnumIndex = rcDirective.accTypeEnumIndex;
if ( CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].deprecated == true )
{
this.log.warn( `The config.json characteristic: ${ characteristicString } is deprecated. It will be ignored.\nTo remove this Warning, Please fix your config.json.` );
return;
}
// Do not update the stored values as it is being restored from cache
if ( parseConfigShouldUseCharacteristicValues == false )
return;
if ( Object.keys( CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].validValues ).length > 0 )
{
// Even if outputConsts is not set, just in case, transpose it anyway.
value = transposeConstantToValidValue( CMD4_ACC_TYPE_ENUM.properties, accTypeEnumIndex, value ) ;
}
// Return the appropriate type, by seeing what it is defined as in Homebridge,
let properValue = CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].stringConversionFunction( value );
if ( properValue == undefined )
{
// If the value is not convertable, just return it.
this.log.warn( `parseKeyForCharacterisitcs: ${ this.displayName } ` + chalk.red( `Cannot convert value: ${ value } to ${ CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].props.format } for ${ characteristicString }` ) );
return;
}
//this.log.debug("Setting %s to %s", characteristicString, properValue );
this.cmd4Storage.setStoredValueForIndex( accTypeEnumIndex, properValue );
}
processRequires( requiresArray )
{
if ( ! Array.isArray ( requiresArray ) )
throw new Error( `requires must be an array of { "require": "some requires string" }` );
// Iterate over the groups of key/value constants in the array.
for ( let argIndex = 0; argIndex < requiresArray.length; argIndex++ )
{
let argEntry = requiresArray[ argIndex ];
if ( argEntry.require == undefined )
throw new Error( `Requires definition at index: "${ argIndex }" has no "require":"SomeRequire"` );
let required = argEntry.require;
if ( typeof required != "string" )
throw new Error( `Requires definition: "${ required }" must be a string.` );
required = this.replaceConstantsInString( required );
if ( settings.cmd4Dbg ) this.log.debug( `Requiring ${ required }` );
require( required );
}
}
processConstants( constantsArgArray )
{
if ( ! Array.isArray ( constantsArgArray ) )
throw new Error( `Constants must be an array of { "key": "\${SomeKey}", "value": "some replacement string" }` );
// Iterate over the groups of key/value constants in the array.
for ( let argIndex = 0; argIndex < constantsArgArray.length; argIndex++ )
{
let argEntry = constantsArgArray[ argIndex ];
if ( argEntry.key == undefined )
throw new Error( `Constant definition at index: "${ argIndex }" has no "key":"\${SomeKey}"` );
if ( argEntry.value == undefined )
throw new Error( `Constant definition at index: "${ argIndex }" has no "value":"Some replacement string` );
let keyToAdd = argEntry.key;
let valueToAdd = argEntry.value;
if ( ! keyToAdd.startsWith( "${" ) )
throw new Error( `Constant definition for: "${ keyToAdd }" must start with "\${" for clarity.` );
if ( ! keyToAdd.endsWith( "}" ) )
throw new Error( `Constant definition for: "${ keyToAdd }" must end with "}" for clarity.` );
// remove any leading and trailing single quotes
// so that using it for replacement will be easier.
valueToAdd.replace(/^'/, "")
valueToAdd.replace(/'$/, "")
// Do not check for duplicates. Linked accessories add the same
// ${IP} for example
this.listOfConstants[ keyToAdd ] = valueToAdd;
}
}
processVariables( variablesArgArray )
{
if ( ! Array.isArray ( variablesArgArray ) )
throw new Error( `Variables must be an array of { "key": "\${SomeKey}", "value": "some replacement string" }` );
// Iterate over the groups of key/value constants in the array.
for ( let argIndex = 0; argIndex < variablesArgArray.length; argIndex++ )
{
let argEntry = variablesArgArray[ argIndex ];
if ( argEntry.key == undefined )
throw new Error( `Variable definition at index: "${ argIndex }" has no "key":"\${SomeKey}"` );
if ( argEntry.value == undefined )
throw new Error( `Variable definition at index: "${ argIndex }" has no "value":"Some replacement string` );
let keyToAdd = argEntry.key;
let valueToAdd = argEntry.value;
if ( ! keyToAdd.startsWith( "${" ) )
throw new Error( `Variable definition for: "${ keyToAdd }" must start with "\${" for clarity.` );
if ( ! keyToAdd.endsWith( "}" ) )
throw new Error( `Variable definition for: "${ keyToAdd }" must end with "}" for clarity.` );
// remove any leading and trailing single quotes
// so that using it for replacement will be easier.
valueToAdd.replace(/^'/, "")
valueToAdd.replace(/'$/, "")
this.listOfVariables[ keyToAdd ] = valueToAdd;
}
}
accessoryTypeConfigToCmd4Accessories( config, parentInfo )
{
if ( ! config )
return undefined;
let that = this;
if ( Array.isArray ( config ) )
{
let accessories = config.map( ( accessoryConfig ) => { return new Cmd4Accessory( that.log, accessoryConfig, this.api, this.STORED_DATA_ARRAY, this ) } );
// Put the accessories into their correct collection array.
parentInfo.createdCmd4Accessories.push( ...accessories );
return accessories;
}
let accessory = new Cmd4Accessory( that.log, config, this.api, this.STORED_DATA_ARRAY, this );
// Put the accessory into its correct collection array.
parentInfo.createdCmd4Accessories.push( accessory );
return [ accessory ];
}
processURL( url )
{
if ( typeof url != "string" )
throw new Error( `url must be a string: "${ url }".` );
this.url = this.replaceConstantsInString( url );
}
replaceConstantsInString( orig )
{
let finalAns = orig;
for ( let key in this.listOfConstants )
{
let replacementConstant = this.listOfConstants[ key ];
finalAns = finalAns.replace( key, replacementConstant );
}
return finalAns;
}
parseConfig( config, parseConfigShouldUseCharacteristicValues )
{
let cmd4Mode = null;
for ( let key in config )
{
let value = config[ key ];
// warn now - Cmd4 Directives
let rcDirective = isCmd4Directive( key, false );
if ( rcDirective == null )
{
rcDirective = isCmd4Directive( key, true );
if ( rcDirective != null )
{
this.log.warn( `The config.json Cmd4 Directive: ${ key } is Capitalized. It should be: ${ rcDirective.key }. In the near future this will be an error for homebridge-ui integration.\nTo remove this Warning, Please fix your config.json.` );
// create the proper lower case value
config[ rcDirective.key ] = value;
// delete the upper case value
delete config[ key ];
//set the key
key = rcDirective.key;
}
}
// Not finding the key is not an error as it could be a Characteristic
switch ( key )
{
case constants.TYPE:
{
this.type = value;
let rcValue = isDevDirective( value, false );
if ( rcValue.devEnumIndex == null )
{
rcValue = isDevDirective( value, true );
if ( rcValue.devEnumIndex == null )
throw new Error( `Unknown device type: "${ value }" given in: "${ this.displayName }".` );
// warn now
this.log.warn( `The config.json Cmd4 device type: ${ value } is lowerCase. It should be: ${ rcValue.deviceName }. In the near future this will be an error for homebridge-UI integration.\nTo remove this Warning, Please fix your config.json.` );
this.type = rcValue.deviceName;
}
this.typeIndex = rcValue.devEnumIndex;
if ( CMD4_DEVICE_TYPE_ENUM.properties[ this.typeIndex ].deprecated == true )
throw new Error( `Error: device type: "${ this.type }" is now deprecated in Homebridge.` );
break;
}
case constants.SUBTYPE:
this.subType = value;
break;
case constants.MODEL:
// createAccessorysInformationService uses these
// to add to existing information service, when defined
this.model = value;
break;
case constants.MANUFACTURER:
// createAccessorysInformationService uses these
// to add to existing information service, when defined
this.manufacturer = value;
break;
case constants.SERIALNUMBER:
// createAccessorysInformationService uses these
// to add to existing information service, when defined
this.serialNumber = value;
break;
case constants.DISPLAYNAME:
// DisplayName is not a characteristic but used as a parm when
// creating the Service. This has already been parsed, but
// here so that parseConfig passes.
this.displayName = value;
break;
case constants.UUID:
// For those who define there own UUID
this.uuid = value;
break;
case constants.ACCESSORY:
// For 8.0 this is no longer supported. Homebridge has deprecated
// the template and only wants Platform templates which makes
// it easier for Cmd4 to support homebridge-ui as well..
throw new Error( `Accessory: has been deprecated by homebridge. Only Platform confgurations are supported by Cmd4 >= 8.0 for: "${ this.displayName }".` );
case constants.CATEGORY:
// For those who define there own Category
// Uppercase the category to be nice. Why do I know
// this will come back to bite me.
this.category = this.api.hap.Categories[ String( value ).toUpperCase( ) ];
if ( ! this.category )
throw new Error( `Category specified: "${ value }" is not a valid homebridge category for: "${ this.displayName }".` );
break;
case constants.PUBLISHEXTERNALLY:
// The user can make the accessory be published externally.
this.publishExternally = value;
break;
case constants.PROPS:
// Allow characteristic property changes.
this.props = value;
break;
case constants.OUTPUTCONSTANTS:
// Define if we should ouput constant strings
this.outputConstants = value;
break;
case constants.STATUSMSG:
// During state change, display a message or not
if ( value === true )
this.statusMsg = "TRUE";
else
this.statusMsg = "FALSE";
break;
case constants.QUEUE:
{
let queue = queueExists( value );
if ( queue == undefined )
throw new Error( `"QueueType" must be defined first for queue "${ value }" in: "${ this.displayName }"` );
this.queue = queue;
break;
}
case constants.TIMEOUT:
// Timers are in milliseconds. A low value can result in failure to get/set values
this.timeout = parseInt( value, 10 );
if ( this.timeout < 500 )
{
this.log.warn( `Timeout for: ${ this.displayName } is in milliseconds. A value of "${ this.timeout }" seems pretty low` );
}
break;
case constants.POLLING:
// Do not parse it yet as characteristics must be set first.
this.polling = value;
break;
case constants.INTERVAL:
// Intervals are in seconds
this.interval = parseInt( value, 10 ) * 1000;
break;
case constants.STATECHANGERESPONSETIME:
// respnse time is in seconds
this.stateChangeResponseTime = value * 1000;
break;
case constants.STATE_CMD_PREFIX:
// Not 100% sure why this would be needed, but
// added anyway since we have a suffix
this.state_cmd_prefix = value;
break;
case constants.STATE_CMD_SUFFIX:
// This gets added after any Get/Set <value>
// We replace any constants already defined
this.state_cmd_suffix = value;
break;
case constants.STATE_CMD:
// What this plugin is all about
this.state_cmd = value;
break;
case constants.FAKEGATO:
// Do not parse it yet as characteristics must be set first.
this.fakegatoConfig = value;
break;
case constants.REQUIRES:
this.processRequires( value );
break;
case constants.CONSTANTS:
this.log.warn( `Warning: ${ key } will soon been deprecated at the Accessory level. Please move it to where "Platform: "Cmd4" is located in your config.json.` );
this.log.warn( `This message will disappear when you have done so.` );
this.processConstants( value );
break;
case constants.VARIABLES:
this.processVariables( value );
break;
case constants.LINKEDTYPES:
if ( settings.cmd4Dbg ) this.log.debug( `parseConfig. Found linked Accessories` );
this.linkedAccessoriesConfig = value;
break;
case constants.ACCESSORIES:
if ( settings.cmd4Dbg ) this.log.debug( `parseConfig. Found Accessories` );
this.accessoriesConfig = value;
break;
case constants.URL:
this.processURL( value );
break;
case constants.ALLOWTLV8:
this.log.warn( `Warning: ${ key } will soon been deprecated at the Accessory level. Please move it to where "Platform: "Cmd4" is located.` );
this.log.warn( `This message will disappear when you have done so.` );
this.allowTLV8 = value;
break;
default:
{
this.parseKeyForCharacteristics( key, value, parseConfigShouldUseCharacteristicValues );
}
}
}
if ( cmd4Mode != null && this.polling )
{
if ( cmd4Mode == "Demo" )
throw new Error("Demo mode is achieved when there are no polling entries in your config.json");
this.log.warn( `Cmd4 has been simplified and optimized as per: https://git.io/JtMGR.` );
this.log.warn( `To remove this message, just remove "Cmd4_Mode" from your config.json` );
}
// A device type must be specified
if ( this.typeIndex == undefined || this.typeIndex < 0 )
throw new Error( `Unknown device type: "${ this.type }" given in: "${ this.displayName }".` );
// Create a subType to delimit services with multiple accessories of
// the same type and possibly the same accessory.name.
this.subType = this.subType || this.displayName;
// UUID must be defined or created.
this.uuid = this.uuid || getAccessoryUUID( config, this.api.hap.uuid );
// Solve some issues people have encounterred who
// have had problems with shell completion which is
// only available from shell expansion.
// State_cmd is only required when polling is enabled.
if ( this.polling )
{
// throws its own exceptio if it fails
this.validateStateCmd( this.state_cmd );
} else
{
// Added accessories like input have no polled configuristics and therefore don't
// deserve this message
if ( this.level != 1)
this.log.info( chalk.blue( `Cmd4 is running in Demo Mode for ${ this.displayName }` ) );
}
// Handle seperation of strings of state_cmd for a prefix
if ( this.state_cmd_prefix )
this.state_cmd_prefix = this.state_cmd_prefix + " ";
else
this.state_cmd_prefix = "";
// Handle seperation of strings of state_cmd for a suffix
if ( this.state_cmd_suffix )
{
if ( typeof this.state_cmd_suffix != "string" )
throw new Error( `state_cmd_suffix must be a string: "${ this.state_cmd_suffix }".` );
this.state_cmd_suffix = " " + this.replaceConstantsInString( this.state_cmd_suffix );
} else
{
this.state_cmd_suffix = "";
}
if ( this.typeIndex == CMD4_DEVICE_TYPE_ENUM.Television )
{
if ( this.CMD4 == constants.PLATFORM && ( ! this.publishExternally || ! this.category ) ||
this.CMD4 == constants.STANDALONE )
{
this.log.warn( `Televisions should be Platform Accessories with "${ constants.PUBLISHEXTERNALLY }": true, "${ constants.CATEGORY }": "TELEVISION"` );
}
if ( this.CMD4 == constants.PLATFORM && ! this.publishExternally && ( numberOfTVsPerBridge += 1 ) > 1 )
{
this.log.warn( `Only one unpublished TV is allowed per bridge` );
}
}
// Convert polling to an Array now so that it does not need to be done multiple times later.
let pollingType = trueTypeOf( this.polling );
switch( pollingType )
{
case null:
case undefined:
if ( settings.cmd4Dbg ) this.log.debug( `No polling configured.` );
return;
case Boolean:
if ( settings.cmd4Dbg ) this.log.debug( `Polling config is Default Polling. Nothing to check for unset polling characteristics` );
return;
case String:
throw new Error( `Unknown type for Polling "${ this.polling }" given in: "${ this.displayName }".` );
case Array:
break;
case Object:
this.log.warn( `Polling config for ${ this.displayName } should be an array.` );
this.log.warn( `Converting to array, but this may be an error in the future.` );
// Convert the object to an Array and try again.
this.polling = [ this.polling ];
return;
default:
throw new Error( `Do not know how to handle polling type of: "${ pollingType }" for: "${ this.displayName }".` );
}
// We need to check for removed characteristic Strings in the config
for ( let accTypeEnumIndex = 0; accTypeEnumIndex < CMD4_ACC_TYPE_ENUM.EOL; accTypeEnumIndex ++ )
{
let storedValue = this.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex );
if ( storedValue != null )
{
// connect the accTypeEnumIndex to its characteristic string
let lcCharacteristicString = CMD4_ACC_TYPE_ENUM.accEnumIndexToLC( accTypeEnumIndex );
let ucCharacteristicString = CMD4_ACC_TYPE_ENUM.accEnumIndexToUC( accTypeEnumIndex );
if ( config[ lcCharacteristicString ] != undefined ||
config[ ucCharacteristicString ] != undefined )
{
continue;
} else
{
// There was a previously stored characteristic, if it was not initialized
this.log.warn( `Removing previously configured characteristic: ${ lcCharacteristicString }` );
this.cmd4Storage.setStoredValueForIndex( accTypeEnumIndex, null );
}
}
}
}
// HV may change with polling characteristics
lookupAccessoryHVForPollingCharacteristic( accessory, accTypeEnumIndex )
{
// Heirarchy is first the default
let timeout = accessory.hV.timeout;
let interval = accessory.hV.interval;
let stateChangeResponseTime = accessory.hV.stateChangeResponseTime;
// For testing purposes where there is no queue
let queueName = null;
if ( accessory.queue )
queueName = accessory.queue.queueName;
let pollingEntry = accessory.listOfPollingCharacteristics[ accTypeEnumIndex ];
// There should only be one, if any
if ( pollingEntry != undefined )
{
if ( pollingEntry.timeout )
timeout = pollingEntry.timeout;
if ( pollingEntry.interval )
interval = pollingEntry.interval;
if ( pollingEntry.stateChangeResponseTime )
stateChangeResponseTime = pollingEntry.stateChangeResponseTime;
if ( pollingEntry.queueName )
queueName = pollingEntry.queueName;
}
return { [ constants.TIMEOUT_lv ]: timeout, [ constants.INTERVAL_lv ]: interval, [ constants.STATE_CHANGE_RESPONSE_TIME_lv ]: stateChangeResponseTime, [ constants.QUEUE_NAME_lv ]: queueName };
}
getDevicesRelatedTargetAccTypeEnumIndex( accCurrentEnumIndex )
{
// Get the Devices required characteristics
let requiredCharacteristicsArray = CMD4_DEVICE_TYPE_ENUM.properties[ this.typeIndex ].requiredCharacteristics;
if ( requiredCharacteristicsArray.length == 0 )
return null;
let found = requiredCharacteristicsArray.find( entry => entry.type == accCurrentEnumIndex );
if ( found && found.relatedTargetAccTypeEnumArray.length > 0 )
return found.relatedTargetAccTypeEnumArray[ 0 ];
return null;
// For Optional, the *Target* characteristic does not have to be
// defined with the *Current* characteristic as *Current* is
// optional, so may be *Target*
}
getDevicesRelatedCurrentAccTypeEnumIndex( accTargetEnumIndex )
{
// Get the Devices required characteristics
let requiredCharacteristicsArray = CMD4_DEVICE_TYPE_ENUM.properties[ this.typeIndex ].requiredCharacteristics;
if ( requiredCharacteristicsArray.length == 0 )
return null;
let found = requiredCharacteristicsArray.find( entry => entry.type == accTargetEnumIndex );
if ( found && found.relatedCurrentAccTypeEnumArray.length > 0 )
return found.relatedCurrentAccTypeEnumArray[ 0 ];
return null;
}
determineCharacteristicsToPollForAccessory( accessory )
{
// Get the values based on their hierarchy.
let timeout = accessory.hV.timeout;
let interval = accessory.hV.interval;
let stateChangeResponseTime = accessory.hV.stateChangeResponseTime;
// We need to create the listOfPollingCharacteristics, even in Demo mode because
// this list is also used to determine if the related characteristic should be set
// which happens in the Demo mode without polling.
if ( typeof accessory.polling == "object" )
{
if ( settings.cmd4Dbg ) this.log.debug( `Characteristic polling for: ${ accessory.displayName }` );
accessory.polling.forEach( ( jsonPollingConfig ) =>
{
// Characteristic polling is a json type
// let jsonPollingConfig = accessory.polling[ jsonIndex ];
let value;
let accTypeEnumIndex = -1;
// All this code disappears in the next major release.
for ( let key in jsonPollingConfig )
{
value = jsonPollingConfig[ key ];
let rcDirective = isCmd4Directive( key );
if ( rcDirective == null )
{
rcDirective = isCmd4Directive( key, true );
if ( rcDirective != null )
{
this.log.warn( `The config.json Cmd4 Polling Directive: ${ key } is Capitalized. It should be: ${ rcDirective.key }. In the near future this will be an error for homebridge-ui integration.\nTo remove this Warning, Please fix your config.json.` );
// create the proper lower case value
jsonPollingConfig[ rcDirective.key ] = value;
// delete the upper case value
delete jsonPollingConfig[ key ];
//set the key
key = rcDirective.key;
}
}
// Not finding the key is not an error as it could be a Characteristic
switch ( key )
{
case constants.TIMEOUT:
// Timers are in milliseconds. A low value can result in failure to get/set values
timeout = parseInt( value, 10 );
if ( timeout < 500 )
this.log.warn( `Timeout for: ${ accessory.displayName } is in milliseconds. A value of: ${ timeout } seems pretty low.` );
break;
case constants.INTERVAL:
// Intervals are in seconds
interval = parseInt( value, 10 ) * 1000;
break;
case constants.STATECHANGERESPONSETIME:
// respnse time is in seconds
stateChangeResponseTime = value * 1000;
break;
case constants.CHARACTERISTIC:
{
// The key must be a characteristic property
// but first check if one has already been defined as we can only handle one at a time.
if ( accTypeEnumIndex != -1 )
throw new Error( `For charateristic polling, you can only define one characteristic per array item.\nCannot add "${ key }" as "${ value }" is already defined for: ${ accessory.displayName } ${accTypeEnumIndex}` );
rcDirective = isAccDirective( value, false );
if ( rcDirective.accTypeEnumIndex == null )
{
rcDirective = isAccDirective( value, true );
if ( rcDirective.accTypeEnumIndex == null )
throw new Error( `No such polling characteristic: "${ value }" for: "${ this.displayName }".` );
this.log.warn( `1. The config.json Polling characteristic: ${ value } is Capitalized it should be: ${ rcDirective.type }. In the near future this will be an Error so that Cmd4 can use homebridge-ui.\nTo remove this Warning, Please fix your config.json.` );
}
accTypeEnumIndex = rcDirective.accTypeEnumIndex;
// We can do this as this is a new way to do things.
if ( this.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex ) == undefined )
throw new Error( `CCC Polling for: "${ value }" requested, but characteristic is not in your config.json file for: "${ this.displayName }".` );
break;
}
default: // Switching polling key but key is unknown
{
throw new Error( `No such polling characteristic: "${ key }" for: "${ accessory.displayName }".` );
}
}
}
// Everything now goes through the queue
if ( this.queue == null )
{
this.queue = addQueue( this.log, "Q:" + this.displayName, constants.QUEUETYPE_STANDARD, constants.DEFAULT_STANDARD_QUEUE_RETRY_COUNT );
}
// This has to be UC as it gets passed to the getValue cmd string
let characteristicString = CMD4_ACC_TYPE_ENUM.accEnumIndexToUC( accTypeEnumIndex );
if ( settings.cmd4Dbg ) this.log.debug( `Setting up accessory: ${ accessory.displayName } for polling of: ${ characteristicString } timeout: ${ timeout } interval: ${ interval } queueName: "${ this.queue.queueName }"` );
if ( timeout == undefined )
throw new Error( `determineCharacteristicsToPollForAccessory Timeout is undefined ` );
let record = { [ constants.ACCESSORY_lv ]: accessory, [ constants.ACC_TYPE_ENUM_INDEX_lv ]: accTypeEnumIndex, [ constants.CHARACTERISTIC_STRING_lv ]: characteristicString, [ constants.INTERVAL_lv ]: interval, [ constants.TIMEOUT_lv ]: timeout, [ constants.STATE_CHANGE_RESPONSE_TIME_lv ]: stateChangeResponseTime, [ constants.QUEUE_NAME_lv ]: this.queue.queueName };
// Used to determine missing related characteristics and
// to determine if the related characteristic is enabled.
this.listOfPollingCharacteristics[ accTypeEnumIndex ] = record;
this.queue.addLowPriorityGetPolledQueueEntry(
record.accessory,
record.accTypeEnumIndex,
record.characteristicString,
record.interval,
record.timeout )
});
} else
{
// Even though polling might == undefined, we need to create a list of
// would be polled characteristics for "Demo" mode
// This list is also used to determine if the related characteristic should be set
// which happens in the Demo mode without polling.
if ( this.queue == null )
{
this.queue = addQueue( this.log, "Q:" + this.displayName, constants.QUEUETYPE_STANDARD, constants.DEFAULT_STANDARD_QUEUE_RETRY_COUNT );
}
// Make sure the defined characteristics will be polled
CMD4_DEVICE_TYPE_ENUM.properties[ accessory.typeIndex ].defaultPollingCharacteristics.forEach( defaultPollingAccTypeEnumIndex =>
{
// This has to be UC as it gets passed to the getValue cmd string
let characteristicString = CMD4_ACC_TYPE_ENUM.accEnumIndexToUC( defaultPollingAccTypeEnumIndex );
let record = { [ constants.ACCESSORY_lv ]: accessory, [ constants.ACC_TYPE_ENUM_INDEX_lv ]: defaultPollingAccTypeEnumIndex, [ constants.CHARACTERISTIC_STRING_lv ]: characteristicString, [ constants.INTERVAL_lv ]: interval, [ constants.TIMEOUT_lv ]: timeout, [ constants.STATE_CHANGE_RESPONSE_TIME_lv ]: stateChangeResponseTime, [ constants.QUEUE_NAME_lv ]: this.queue.queueName };
// Used to determine missing related characteristics and
// to determine if the related characteristic is also polled.
this.listOfPollingCharacteristics[ record.accTypeEnumIndex ] = record;
// Do not create the polling record or it will start polling
if ( accessory.polling == true )
{
if ( settings.cmd4Dbg ) this.log.debug( `Adding ${ record.accessory.displayName } ${ CMD4_ACC_TYPE_ENUM.properties[ record.accTypeEnumIndex ].type } record.timeout: ${ record.timeout } record.interval: ${ record.interval } to Polled Queue ${ record.queueName }` );
this.queue.addLowPriorityGetPolledQueueEntry(
record.accessory,
record.accTypeEnumIndex,
record.characteristicString,
record.interval,
record.timeout )
}
});
}
for( let accTypeEnumIndex in this.listOfPollingCharacteristics )
{
// Look to see if currently polled characteristics are like "Current*" and have
// a related characteristic like "Target*"
let relatedTargetAccTypeEnumIndex =
this.getDevicesRelatedTargetAccTypeEnumIndex(
accTypeEnumIndex );
if ( relatedTargetAccTypeEnumIndex != null )
{
// Check that the characteristic like "Target*" is also requested to be polled
// We are in a "Set" but this applies to the "Get" for why we would need to
// set the relatedCurrentAccTypeEnumIndex Characteristic as well.
// - isRelated must be checked, because TemperatureSensors do
// not have *Target* characteristics.
if ( this.listOfPollingCharacteristics[ relatedTargetAccTypeEnumIndex ] == undefined
)
{
let characteristicString = CMD4_ACC_TYPE_ENUM.accEnumIndexToLC( accTypeEnumIndex );
let relatedCharacteristicString = CMD4_ACC_TYPE_ENUM.accEnumIndexToLC( relatedTargetAccTypeEnumIndex );
this.log.warn( `Warning, With polling for "${ characteristicString }" requested, you also must do polling of "${ relatedCharacteristicString }" or things will not function properly` );
}
}
}
}
}
// Compare accessory's UUID with those already created for possible duplicates
function checkAccessoryForDuplicateUUID( accessory, uuid )
{
// check for UUID+subtype conflict
if ( settings.cmd4Dbg ) accessory.log.debug( `Checking ${ accessory.name } for Duplicate UUID: ${ accessory.uuid }` );
for ( let existingAccessory in accessory.createdCmd4Accessories )
{
if ( uuid == existingAccessory.uuid )
{
// This is the same check as what is in
// hap-nodejs/dist/lib/Accessory.js
if ( accessory.service.subtype == existingAccessory.service.subtype )
{
accessory.log.error( chalk.red( `Error` ) + `: Cannot add a bridged Accessory with the same UUID as another bridged Accessory: ${ getAccessoryName( existingAccessory ) }` );
if ( accessory.name == existingAccessory.name )
accessory.log.error( chalk.red( `Duplicate accessory names can cause this issue.` ) );
throw new Error( `It is wiser to define the second accessory in a different bridge.` );
}
}
// Check for duplicates in Added accessories.
if ( accessory.addedAccessories && accessory.LEVEL == 0 )
{
accessory.accessories.forEach( ( addedAccessory ) =>
{
checkAccessoryForDuplicateUUID( addedAccessory, uuid );
});
}
// Check for duplicates in Linked accessories.
if ( accessory.linkedAccessories && accessory.LEVEL == 0 )
{
accessory.linkedAccessories.forEach( ( linkedAccessory ) =>
{
checkAccessoryForDuplicateUUID( linkedAccessory, uuid );
});
}
}
if ( settings.cmd4Dbg ) accessory.log.debug( `No Duplicate UUID's for this Accessory - ` + chalk.green( `OK` ) + `. Using: ${ accessory.uuid }` );
}
exports.Cmd4Accessory = Cmd4Accessory;
================================================
FILE: Cmd4Platform.js
================================================
'use strict';
// Cmd4 includes seperated out for Unit testing
const { getAccessoryName,
getAccessoryDisplayName } = require( "./utils/getAccessoryNameFunctions" );
const { parseAddQueueTypes } = require( "./Cmd4PriorityPollingQueue" );
let Logger = require( "./utils/Logger" );
let getAccessoryUUID = require( "./utils/getAccessoryUUID" );
let lcFirst = require( "./utils/lcFirst" );
let isNumeric = require( "./utils/isNumeric" );
let trueTypeOf = require( "./utils/trueTypeOf" );
// Hierarchy variables
let HV = require( "./utils/HV" );
let createAccessorysInformationService = require( "./utils/createAccessorysInformationService" );
// Pretty Colors
var chalk = require( "chalk" );
// These would already be initialized by index.js
let CMD4_CHAR_TYPE_ENUMS = require( "./lib/CMD4_CHAR_TYPE_ENUMS" ).CMD4_CHAR_TYPE_ENUMS;
let CMD4_DEVICE_TYPE_ENUM = require( "./lib/CMD4_DEVICE_TYPE_ENUM" ).CMD4_DEVICE_TYPE_ENUM;
let CMD4_ACC_TYPE_ENUM = require( "./lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC_TYPE_ENUM;
let CMD4_FORMAT_TYPE_ENUM = CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM;
let CMD4_UNITS_TYPE_ENUM = CMD4_CHAR_TYPE_ENUMS.CMD4_UNIT_TYPE_ENUM;
let CMD4_PERMS_TYPE_ENUM = CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM;
// The Cmd4 Classes
const Cmd4Accessory = require( "./Cmd4Accessory" ).Cmd4Accessory;
// Settings, Globals and Constants
let settings = require( "./cmd4Settings" );
const constants = require( "./cmd4Constants" );
// Platform definition
class Cmd4Platform
{
constructor( log, config, api )
{
// Unit testing passes their own logger with debug enabled/disabled
// replace with ours
if ( typeof log.setOutputEnabled === "function" )
{
this.log = log;
// Carry the debug flag from the platform
settings.cmd4Dbg = log.debugEnabled;
}
else
{
// By using our own Logger, we don't trigger others
this.log = new Logger( );
if ( config[ constants.DEBUG ] == true ||
config[ "Debug" ] == true ||
process.env.DEBUG == settings.PLATFORM_NAME )
{
settings.cmd4Dbg = true;
}
}
this.log.setDebugEnabled( settings.cmd4Dbg );
if ( settings.cmd4Dbg ) this.log.debug( chalk.blue( `Class Cmd4Platform` ) );
if ( config === undefined )
return;
this.config = config;
this.api = api;
this.Service = this.api.hap.Service;
// Pass along the trigger when creating the Cmd4Accessory.
// Note: The LEVEL starts at -1 as the first one gets incremented to Zero.
//
// LEVEL 0 Accessories are Platform or Standalone Accessories.
// LEVEL 1 Accessories are linked accessories.
// LEVEL 2 Accessories are added Platform accessories coerced to
// level 2 as a distinction. i.e. TelevisionSpeaker.
this.CMD4 = constants.PLATFORM;
this.LEVEL = -1;
this.toBeRestoredPlatforms = [ ];
this.createdCmd4Accessories = [ ];
this.createdCmd4Platforms = [ ];
this.globalConstants = null;
this.services = [ ];
// These would be queues of Characteristics to be polled or get/set via IOS.
settings.listOfCreatedPriorityQueues = { };
// Track the polling timers only so that unit testing can cancel them.
this.pollingTimers = [ ];
// Create the hierarhy variables
this.hV = new HV();
this.parseConfigForCmd4Directives( this.config );
// Update the namespace for stored variables
// like timeout, stateChangeResponseTime ... As it may require
// changes from parseConfig.
this.hV.update( this );
this.processNewCharacteristicDefinitions( );
// didFinishLaunching is only called after the
// registerPlatform completes.
api.on( "didFinishLaunching", ( ) =>
{
this.log.info( chalk.blue( "Cmd4Platform didFinishLaunching" ) );
this.discoverDevices( this.log );
// Any accessory not reachable must have been removed, find them
this.toBeRestoredPlatforms.forEach( ( accessory ) =>
{
if ( ! accessory.reachable )
this.removeAccessory( accessory );
});
// Let the Polling Begin
this.startPolling();
});
}
// Platforms do not use getServices. Good to know.
//getServices( )
//{
// return this.services;
//}
// As Per HomeBridge:
// This function is invoked when homebridge restores cached accessories
// from disk at startup. It should be used to setup event handlers
// for characteristics and update respective values.
//
// We do not handle restoring cached accessories ( Yet? ). Remove them
// as we regenerate everything.
configureAccessory( platformAccessory )
{
if ( platformAccessory )
{
if ( settings.cmd4Dbg ) this.log.debug( `Found cached accessory: ${ platformAccessory.displayName }` );
this.toBeRestoredPlatforms.push( platformAccessory );
}
}
removeAccessory( platformAccessory )
{
if ( ! platformAccessory )
return;
this.log.info( `Accessory ${ platformAccessory.displayName } will be removed.` );
this.api.unregisterPlatformAccessories( settings.PLUGIN_NAME, settings.PLATFORM_NAME, [ platformAccessory ] );
}
// Hmmmm does not happen
//configurationRequestHandler( context, request, callback )
//{
// this.log( chalk.red( `In ConfigRequestHandler context: ${ context }` ) );
// switch( context.step )
// {
// case 5:
// this.log( chalk.red( `Asked to remove` ) );
// break;
// }
//}
// Only parse those CMD4 directives we care about
parseConfigForCmd4Directives( config )
{
for ( let key in config )
{
let lcKey = lcFirst( key );
if ( key == "UUID" )
{
lcKey = "uuid";
} else
{
// warn now
if ( key.charAt( 0 ) === key.charAt( 0 ).toUpperCase( ) )
{
this.log.warn( `The config.json Platform key: ${ key } is Capitalized. All keys in the near future will ALWAYS start with a lower case character for homebridge-ui integration.\nTo remove this Warning, Please fix your config.json.` );
}
}
let value = config[ key ];
switch ( lcKey )
{
case constants.TIMEOUT:
// Timers are in milliseconds. A low value can result in
// failure to get/set values
this.timeout = parseInt( value, 10 );
if ( this.timeout < 500 )
this.log.warn( `Default Timeout is in milliseconds. A value of "${ this.timeout }" seems pretty low.` );
break;
case constants.INTERVAL:
// Intervals are in seconds
this.interval = parseInt( value, 10 ) * 1000;
break;
case constants.STATECHANGERESPONSETIME:
// respnse time is in seconds
this.stateChangeResponseTime = value * 1000;
break;
case constants.STATE_CMD_PREFIX:
// Not 100% sure why this would be needed, but
// added anyway since we have a suffix
this.state_cmd_prefix = value;
break;
case constants.STATE_CMD_SUFFIX:
// This gets added after any Get/Set <value>
this.state_cmd_suffix = value;
break;
case constants.STATE_CMD:
// What this plugin is all about
this.state_cmd = value;
break;
case constants.OUTPUTCONSTANTS:
this.outputConstants = value;
break;
case constants.STATUSMSG:
if ( value === false )
this.statusMsg = "FALSE";
break;
case constants.QUEUETYPES:
parseAddQueueTypes( this.log, value );
break;
case constants.DEFINITIONS:
this.definitions = value;
break;
case constants.CONSTANTS:
// Save the constants defined globally so the accessory
// can parse it.
this.globalConstants = value;
break
case constants.PLATFORM:
// Noop
break;
case constants.ACCESSORIES:
// Noop
break;
default:
// This cannot be because all the Cmd4Accessory directives
// for Standalone would have to be added.
//this.log.error( chalk.red( `Error: Unknown Cmd4 Platform config option: "${ key }"` ) );
//process.exit( 448 ) ;
}
}
}
// The purpose here is not to duplicate what is in homebridge. Just to
// do a little checking to make sure that what is defined won't cause
// Cmd4 to balk. It is okay if homebridge does though ;-)
processNewCharacteristicDefinitions( )
{
if ( this.definitions == undefined )
return;
if ( trueTypeOf( this.definitions ) != Array )
throw new Error( `${ constants.DEFINITIONS } is not a array.` );
this.definitions.forEach( ( definition, definitionIndex ) =>
{
if ( settings.cmd4Dbg ) this.log.debug( `Processing definition index: ${ definitionIndex }` );
if ( trueTypeOf( definition.type ) != String )
throw new Error( `definition.type at index: ${ definitionIndex } is not a String.` );
if ( trueTypeOf( definition.description ) != String )
throw new Error( `definition.description at index: ${ definitionIndex } is not a String.` );
if ( trueTypeOf( definition.props ) != Object )
throw new Error( `definition.props at index: ${ definitionIndex } is not an Object.` );
if ( trueTypeOf( definition.props.format ) != String )
throw new Error( `definition.props.format at index: ${ definitionIndex } is not a String.` );
// Need to check if format is correct
let formatIndex = CMD4_FORMAT_TYPE_ENUM.properties.indexOfEnum( i => i.type === definition.props.format );
if ( formatIndex < 0 )
throw new Error( `definition.props.format at index: ${ definitionIndex } is not a valid format.` );
if ( definition.props.units )
{
if ( trueTypeOf( definition.props.units ) != String )
throw new Error( `definition.props.units at index: ${ definitionIndex } is not a String.` );
// Need to check if units is correct
let unitsIndex = CMD4_UNITS_TYPE_ENUM.properties.indexOfEnum( i => i.type === definition.props.units );
if ( unitsIndex < 0 )
throw new Error( `definition.props.units at index: ${ definitionIndex } is not a valid unit.` );
}
if ( definition.props.maxValue &&
isNumeric( definition.props.maxValue ) != true )
throw new Error( `definition.props.maxValue at index: ${ definitionIndex } is not numeric.` );
if ( definition.props.minValue &&
! Number.isFinite( definition.props.minValue ) )
throw new Error( `definition.props.minValue at index: ${ definitionIndex } is not finite.` );
if ( definition.props.minStep &&
isNumeric( definition.props.minStep ) != true )
throw new Error( `definition.props.minStep at index: ${ definitionIndex } is not numeric.` );
if ( trueTypeOf( definition.props.perms ) != Array )
throw new Error( `definition.props.perms at index: ${ definitionIndex } is not an Array.` );
if ( definition.props.perms.length == 0 )
throw new Error( `definition.props.perms at index: ${ definitionIndex } cannot be an empty Array.` );
definition.props.perms.forEach( ( perm ) =>
{
let permIndex = CMD4_PERMS_TYPE_ENUM.properties.indexOfEnum( i => i.type === perm );
if ( permIndex < 0 )
throw new Error( `definition.props.perms at index: ${ definitionIndex } ${ perm } is not a valid perm.` );
});
if ( definition.validValues )
{
if ( trueTypeOf( definition.validValues ) != Object )
throw new Error( `definition.validValues at index: ${ definitionIndex } is not an Object.` );
} else
{
definition.validValues = { };
}
CMD4_ACC_TYPE_ENUM.add( this.api, definition.type, definition.description, definition.props, definition.validValues );
if ( settings.cmd4Dbg ) this.log.debug( `Created definition type: "${ definition.type }".` );
});
}
// These would be platform accessories with/without linked accessories
discoverDevices( )
{
let platform;
let accessory;
// loop over the config.json devices and register each one if it has not
// already been registered.
this.config.accessories && this.config.accessories.forEach( ( device ) =>
{
if ( settings.cmd4Dbg ) this.log.debug( `Fetching config.json Platform accessories.` );
this.Service=this.api.hap.Service;
device.name = getAccessoryName( device );
let displayName = device.displayName = getAccessoryDisplayName( device );
// generate a unique id for the accessory this should be generated from
// something globally unique, but constant, for example, the device serial
// number or MAC address.
let uuid = getAccessoryUUID( device, this.api.hap.uuid );
// See if an accessory with the same UUID has already been registered and
// restored from the cached devices we stored in the `configureAccessory`
// method above
// NOTE: HOMEBRIDGE EXAMPLES HAVE THIS AS UPPERCASE UUID.
// lower case uuid will not be found.
const existingAccessory = this.toBeRestoredPlatforms.find(accessory => accessory.UUID === uuid);
if (existingAccessory)
{
// NOTE: HOMEBRIDGE EXAMPLES HAVE THIS AS UPPERCASE UUID.
// lower case uuid will not be found.
let duplicatePlatformAccessory = this.createdCmd4Platforms.find(accessory => accessory.UUID === existingAccessory.UUID);
if ( duplicatePlatformAccessory )
{
this.log( chalk.red( `Error duplicate platform accessory: ${ duplicatePlatformAccessory.name } uuid:${ duplicatePlatformAccessory.UUID }` ) );
// Next in for.Each object iteration
return;
}
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
// if you need to update the accessory.context then you should run
// `api.updatePlatformAccessories`. eg.:
// existingAccessory.context.device = device;
// this.api.updatePlatformAccessories( [ existingAccessory ] );
//existingAccessory.context.device = device;
//this.api.updatePlatformAccessories( [ existingAccessory ] );
// create the accessory handler for the restored accessory
// this is imported from `platformAccessory.ts`
// new ExamplePlatformAccessory( this, existingAccessory );
platform = existingAccessory;
platform.Service = this.Service;
// This is how we keep the device status information over restart.
// Version 0
// Within STORED_DATA_ARRAY is a list of UUID identified objects
// of storedValuesPerCharacteristic.
// Version 1
// Within STORED_DATA_ARRAY is a list of UUID identified objects
// of Cmd4Storage.
// If the accessory has linked accessories or standalone accessories,
// there infomation gets put in this as well. I'm pretty sure
// that previosly these states were lost.
// Init the STORED_DATA_ARRAY to empty [ ]. If there was never any
// type to use, this is okay to.
let STORED_DATA_ARRAY = [ ];
// If the saved context has our STORED_DATA_ARRAY, then use it.
if ( existingAccessory.context.STORED_DATA_ARRAY )
{
if ( settings.cmd4Dbg ) this.log.debug(`Cmd4Platform: Using context.STORED_DATA_ARRAY` );
STORED_DATA_ARRAY = existingAccessory.context.STORED_DATA_ARRAY;
}
let that = this;
accessory = new Cmd4Accessory( that.log, device, this.api, STORED_DATA_ARRAY, this );
accessory.platform = platform;
// Put the accessory into its correct collection array.
this.createdCmd4Accessories.push( accessory );
// Store a copy of the device object in the `accessory.context`
// the `context` property can be used to store any data about the
// accessory you may need
accessory.platform.context.STORED_DATA_ARRAY = accessory.STORED_DATA_ARRAY;
// Get the properties for this accessories device type
let devProperties = CMD4_DEVICE_TYPE_ENUM.properties[ accessory.typeIndex ];
if ( settings.cmd4Dbg ) this.log.debug( `Step 2. ${ accessory.displayName }.service = platform.getService( Service.${ devProperties.deviceName }, ${ accessory.subType })` );
accessory.service = platform.getService( devProperties.service, accessory.name, accessory.subType );
// Determine which characteristics, if any, will be polled. This
// information is also used to define which service.getValue is
// used, either immediate, cached or polled.
// Already done by new Cmd4Acc
// accessory.determineCharacteristicsToPollOfAccessoryAndItsChildren( accessory );
// set up all services for those characteristics in the
// config.json file
accessory.addAllServiceCharacteristicsForAccessory( accessory );
// Create all the services for the accessory, including fakegato
// true = from existing.
this.createServicesForAccessoriesChildren( accessory, true )
// Since 'updateReachability()' function and the key 'reachable' are deprecated in
// Homebridge_v2, We will define the key 'reachable' and set the locally read in copy
// of the value to 'true' explicitly to flag that this accessory is restored from cache.
existingAccessory.reachable = true;
} else
{
// The accessory does not yet exist, so we need to create it.
this.log.info('Adding new platformAccessory:', displayName);
// Create the new PlatformAccessory
if ( device.category == undefined )
{
if ( settings.cmd4Dbg ) this.log.debug( `Step 1. platformAccessory = new platformAccessory( ${ displayName }, ${ uuid } )` );
platform = new this.api.platformAccessory( displayName, uuid );
} else
{
// Uppercase the category to be nice. Why do I know
// this will come back to bite me.
let category = this.api.hap.Categories[ String( device.category ).toUpperCase( ) ];
if ( ! category )
throw new Error( `Category specified: ${ device.category } is not a valid homebridge category.` );
if ( settings.cmd4Dbg ) this.log.debug( `Step 1. platformAccessory = new platformAccessory( ${ displayName }, ${ uuid }, ${ category } )` );
platform = new this.api.platformAccessory( displayName, uuid, category );
}
platform.Service = this.Service;
this.log.info( chalk.magenta( `Configuring platformAccessory: ` ) + `${ device.displayName }` );
let that = this;
accessory = new Cmd4Accessory( that.log, device, this.api, [ ], this );
accessory.platform = platform
// Put the accessory into its correct collection array.
this.createdCmd4Accessories.push( accessory );
if ( settings.cmd4Dbg ) this.log.debug( `Created platformAccessory: ${ accessory.displayName }` );
// Store a copy of the device object in the `accessory.context`
// the `context` property can be used to store any data about the
// accessory you may need
accessory.platform.context.STORED_DATA_ARRAY = accessory.STORED_DATA_ARRAY;
// Get the properties for this accessories device type
let devProperties = CMD4_DEVICE_TYPE_ENUM.properties[ accessory.typeIndex ];
// MOVE OUSTSIDE
// Platform Step 2. const tvService = this.tvAccessory.addService( this.Service.Television );
if ( settings.cmd4Dbg ) this.log.debug( `Step 2. ${ accessory.displayName }.service = platform.addService( this.Service.${ devProperties.deviceName }, ${ accessory.name }, ${ accessory.subType })` );
accessory.service = platform.addService( new devProperties.service( accessory.name, accessory.subType ) );
// Create all the services for the accessory, including fakegato
// false = not from existing.
this.createServicesForAccessoriesChildren( accessory, false )
// Step 6. this.api.publishExternalAccessories( PLUGIN_NAME, [ this.tvAccessory ] );
if ( accessory.publishExternally )
{
if ( settings.cmd4Dbg ) this.log.debug( `Step 6. publishExternalAccessories( ${ settings.PLUGIN_NAME }, [ ${accessory.displayName } ] )` );
this.api.publishExternalAccessories( settings.PLUGIN_NAME, [ platform ] );
} else {
if ( settings.cmd4Dbg ) this.log.debug( `Step 6. registerPlatformAccessories( ${ settings.PLUGIN_NAME }, ${ settings.PLATFORM_NAME }, [ ${ accessory.displayName } ] ) `);
this.api.registerPlatformAccessories( settings.PLUGIN_NAME, settings.PLATFORM_NAME, [ platform ] );
}
}
// Just a flag to say we have processed this existing platform accessory.
// @deprecated homebridge v2
//platform.updateReachability( true );
// For Unit testing only
this.createdCmd4Platforms.push( platform );
});
}
createServicesForAccessoriesChildren( cmd4PlatformAccessory, fromExisting )
{
// Create the information Service for the platform itself
// Unlike Standalone Accessories; The Platform information service is created
// for us and the getService hangs off the platform, not the accessory.
if ( cmd4PlatformAccessory.model )
{
if ( settings.cmd4Dbg ) cmd4PlatformAccessory.log.debug( `Adding model( ${ cmd4PlatformAccessory.model } ) to information service of ${ cmd4PlatformAccessory.displayName }` );
cmd4PlatformAccessory.platform.getService( cmd4PlatformAccessory.platform.Service.AccessoryInformation )
.setCharacteristic( this.api.hap.Characteristic.Model, cmd4PlatformAccessory.model );
}
if ( cmd4PlatformAccessory.manufacturer )
{
if ( settings.cmd4Dbg ) cmd4PlatformAccessory.log.debug( `Adding manufacturer( ${ cmd4PlatformAccessory.manufacturer } ) to information service of ${ cmd4PlatformAccessory.displayName }` );
cmd4PlatformAccessory.platform.getService( cmd4PlatformAccessory.platform.Service.AccessoryInformation )
.setCharacteristic( this.api.hap.Characteristic.Manufacturer, cmd4PlatformAccessory.manufacturer );
}
if ( cmd4PlatformAccessory.serialNumber )
{
if ( settings.cmd4Dbg ) cmd4PlatformAccessory.log.debug( `Adding serial Number( ${ cmd4PlatformAccessory.serialNumber } ) to information service of ${ cmd4PlatformAccessory.displayName }` );
cmd4PlatformAccessory.platform.getService( cmd4PlatformAccessory.platform.Service.AccessoryInformation )
.setCharacteristic( this.api.hap.Characteristic.SerialNumber, cmd4PlatformAccessory.serialNumber );
}
if ( cmd4PlatformAccessory.firmwareRevision )
{
if ( settings.cmd4Dbg ) cmd4PlatformAccessory.log.debug( `Adding Firmware Revision( ${ cmd4PlatformAccessory.firmwareRevision } ) to information service of ${ cmd4PlatformAccessory.displayName }` );
cmd4PlatformAccessory.platform.getService( cmd4PlatformAccessory.platform.Service.AccessoryInformation )
.setCharacteristic( this.api.hap.Characteristic.FirmwareRevision, cmd4PlatformAccessory.firmwareRevision );
}
// Create the service for all the accessories. i.e. Speaker Service
// Step 3.
// const speakerService = this.tvAccessory.addService( this.Service.TelevisionSpeaker );
cmd4PlatformAccessory.accessories && cmd4PlatformAccessory.accessories.forEach( ( addedAccessory ) =>
{
// Set the platform of the added accessories so that the accessories can call methods
// like this one.
addedAccessory.platform = cmd4PlatformAccessory.platform;
// Get the properties for this accessory's device type
let devProperties = CMD4_DEVICE_TYPE_ENUM.properties[ addedAccessory.typeIndex ];
// Existing Accessories would have existing services
if ( fromExisting == true )
{
if ( settings.cmd4Dbg ) this.log.debug( `Platform (AddedAccessory-existing) Step 3, ${ addedAccessory.displayName }.service = accessory.platform.getService( Service.${ devProperties.deviceName }, ${ addedAccessory.name }, ${ addedAccessory.subType }` );
// If you have added more than one service of the same type to an accessory, you will need to get the service using the name you defined when adding it.
//addedAccessory.service = addedAccessory.platform.getService( devProperties.service, addedAccessory.name, addedAccessory.subType );
addedAccessory.service = addedAccessory.platform.getService( addedAccessory.name, addedAccessory.subType );
} else
{
if ( settings.cmd4Dbg ) this.log.debug( `Platform (AddedAccessory-new) Step 3, ${ addedAccessory.displayName }.service = PlatformAccessory: ${ cmd4PlatformAccessory.displayName } addService( Service:${ devProperties.deviceName }, ${ addedAccessory.name }, ${ addedAccessory.subType } )` );
addedAccessory.service = cmd4PlatformAccessory.platform.addService( new devProperties.service( addedAccessory.name, addedAccessory.subType ) );
}
addedAccessory.addAllServiceCharacteristicsForAccessory( addedAccessory );
// Create Information Service for the addedAccessory
if ( settings.cmd4Dbg ) addedAccessory.log.debug( `Creating information service for AddedAccessory: ${ addedAccessory.displayName }` );
createAccessorysInformationService( addedAccessory );
// Setup the fakegato service if defined in the config.json file
addedAccessory.setupAccessoryFakeGatoService( addedAccessory.fakegatoConfig );
// Move the information service to the top of the list
addedAccessory.services.unshift( addedAccessory.informationService );
});
// Create the service for all the linked accessories. i.e. HDMI Service
cmd4PlatformAccessory.linkedAccessories && cmd4PlatformAccessory.linkedAccessories.forEach( ( linkedAccessory ) =>
{
// Set the platform of the linked accessories so that the accessories can call methods
// like this one.
linkedAccessory.platform = cmd4PlatformAccessory.platform;
// Get the properties for this linked Accessory device type
let devProperties = CMD4_DEVICE_TYPE_ENUM.properties[ linkedAccessory.typeIndex ];
// Child accessories can have linked accessories. i.e. HDMI accessory
// Step 4.
// const hdmi1InputService = this.tvAccessory.addService( this.Service.InputSource, `hdmi1', 'HDMI 1' );
// Existing Accessories would have existing services
if ( fromExisting == true )
{
if ( settings.cmd4Dbg ) this.log.debug( `Platform (LinkedAccessory-existing) Step 4. ${ linkedAccessory.displayName }.service = ${ cmd4PlatformAccessory.displayName }.getService:( ${ devProperties.deviceName }.service, ${linkedAccessory.name }, ${linkedAccessory.subType } )` );
// If you have added more than one service of the same type to an accessory, you will need to get the service using the name you defined when adding it.
//linkedAccessory.service = linkedAccessory.platform.getService( devProperties.service, linkedAccessory.name, linkedAccessory.subType );
linkedAccessory.service = linkedAccessory.platform.getService( linkedAccessory.name, linkedAccessory.subType );
} else
{
if ( settings.cmd4Dbg ) this.log.debug( `Platform (LinkedAccessory-new) Step 4. ${ linkedAccessory.displayName }.service = ${ cmd4PlatformAccessory.displayName }.addService:( ${ devProperties.deviceName }.service, ${linkedAccessory.name }, ${linkedAccessory.subType } )` );
linkedAccessory.service = cmd4PlatformAccessory.platform.addService( new devProperties.service( linkedAccessory.name, linkedAccessory.subType ) );
}
linkedAccessory.addAllServiceCharacteristicsForAccessory( linkedAccessory );
if ( fromExisting == false )
{
if ( settings.cmd4Dbg ) this.log.debug( `Platform Step 5. ${ cmd4PlatformAccessory.displayName }.service.addLinkedService( ${ linkedAccessory.displayName }.service )` );
cmd4PlatformAccessory.service.addLinkedService( linkedAccessory.service );
}
// Create Information Service for the linkedAccessory
if ( settings.cmd4Dbg ) linkedAccessory.log.debug( `Creating information service for Linked Platform Accessory: ${ linkedAccessory.displayName }` );
createAccessorysInformationService( linkedAccessory );
// Setup the fakegato service if defined in the config.json file
linkedAccessory.setupAccessoryFakeGatoService( linkedAccessory.fakegatoConfig );
// Move the information service to the top of the list
linkedAccessory.services.unshift( linkedAccessory.informationService );
});
// Setup all the characteristics for the platform accessory itself
cmd4PlatformAccessory.addAllServiceCharacteristicsForAccessory( cmd4PlatformAccessory );
// Setup the fakegato service for the platform accessory istelf.
cmd4PlatformAccessory.setupAccessoryFakeGatoService( cmd4PlatformAccessory.fakegatoConfig );
}
// The delay definitions are not meant to be changed, except for unit testing
// ==========================================================================
// staggeredStartDelay - These would be for just polling and to be nice to the system.
// queuedStartDelay - As this is both IOS and polling, the delay only happens to
// the low priority polling.
startPolling( queuedStartDelay = 40000 )
{
// Check for any queued characteristics
if ( Object.keys( settings.listOfCreatedPriorityQueues ).length == 0 )
{
if ( settings.cmd4Dbg ) this.log.debug( `No queued polling characteristics` );
return;
}
// Start polling of each queue of characteristics
let lastIndex = Object.keys( settings.listOfCreatedPriorityQueues ).length;
Object.keys( settings.listOfCreatedPriorityQueues ).forEach( ( queueName, index ) =>
{
let queue = settings.listOfCreatedPriorityQueues[ queueName ];
let queuedPollingTimer = setTimeout( ( ) =>
{
if ( index == lastIndex -1 )
this.log.info( chalk.magenta( `*** Starting Polling` ) );
queue.startQueue( queue, ( ) =>
{
if ( index == 0 )
this.log.info( chalk.magenta( `*** All characteristics are now being polled` ) );
});
}, queuedStartDelay );
this.pollingTimers.push( queuedPollingTimer );
});
}
}
exports.Cmd4Platform = Cmd4Platform;
================================================
FILE: Cmd4PriorityPollingQueue.js
================================================
'use strict';
// 3rd Party includes
const exec = require( "child_process" ).exec;
// These would already be initialized by index.js
let CMD4_ACC_TYPE_ENUM = require( "./lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC_TYPE_ENUM;
// Settings, Globals and Constants
let settings = require( "./cmd4Settings" );
const constants = require( "./cmd4Constants" );
// Pretty Colors
var chalk = require( "chalk" );
let trueTypeOf = require( "./utils/trueTypeOf" );
let lcFirst = require( "./utils/lcFirst" );
// For changing validValue Constants to Values and back again
var { transposeConstantToValidValue,
transposeValueToValidConstant,
transposeBoolToValue
} = require( "./utils/transposeCMD4Props" );
let HIGH_PRIORITY_SET = 0;
let HIGH_PRIORITY_GET = 1;
let LOW_PRIORITY_GET = 2;
class Cmd4PriorityPollingQueue
{
constructor( log, queueName, queueType = constants.DEFAULT_QUEUE_TYPE, queueRetryCount = constants.DEFAULT_WORM_QUEUE_RETRY_COUNT )
{
this.log = log;
// This works better for Unit testing
settings.cmd4Dbg = log.debugEnabled;
this.queueName = queueName;
this.queueType = queueType;
this.queueRetryCount = queueRetryCount;
this.queueStarted = false;
this.highPriorityQueue = [ ];
this.lowPriorityQueue = [ ];
this.lowPriorityQueueIndex = 0 ;
this.inProgressGets = 0;
this.inProgressSets = 0;
this.listOfRunningPolls = {};
// This is not a sanity timer.
// This controls when it is safe to do a "Get" of the Aircon
// after a failed condition. It does happen to fix the queue
// when something is wrong, but this is not the purpose of
// this timer.
this.pauseTimer = null;
this.lastGoodTransactionTime = Date.now( );
this.errorCountSinceLastGoodTransaction = 0;
// - Not a const so it can be manipulated during unit testing
this.pauseTimerTimeout = constants.DEFAULT_QUEUE_PAUSE_TIMEOUT;
// The WoRm queue needs error messages to be silenced as
// they are inevitable, but are handled through retries
// By default non WoRm queues are allowed to echo errors
if ( this.queueRetryCount == 0 || settings.debug )
this.echoE = true;
else
this.echoE = true;
this.changeQueueType( this, queueType );
}
echoRetryErrors( currentRetryCount )
{
// If debug then the default is true
if ( settings.cmd4Debug )
return true;
// Since this is the last retry, echo the error
if ( currentRetryCount == this.queueRetryCount )
return true;
return false;
}
// This function is called by homebridge to *PUT AN ENTRY INTO THE HIGHEST PRIORITY SET QUEUE*.
// We immediately return success if the device is accessible. Either way the Set is attempted
// above everything else except other SetValue requests.
prioritySetValue( accTypeEnumIndex, characteristicString, timeout, stateChangeResponseTime, value, homebridgeCallback )
{
// this is Accessory
//
//if ( settings.cmd4Dbg ) this.log.debug(`prioritySetValue, asked to set: ${ characteristicString } to ${ value }`);
// Save the value to cache. The set will come later
// this.cmd4Storage.setStoredValueForIndex( accTypeEnumIndex, value );
if ( this.errorValue != 0 )
{
if ( settings.cmd4Dbg ) this.log.debug(`prioritySetValue for ${ this.displayName }, homebridgeCallback returning error ${ this.errorValue } ${ this.errorString }`);
homebridgeCallback( this.errorValue );
} else
{
if ( settings.cmd4Dbg ) this.log.debug(`prioritySetValue for ${ this.displayName }, homebridgeCallback returning default success 0`);
homebridgeCallback( 0 );
}
let newEntry = { [ constants.IS_SET_lv ]: true, [ constants.ACCESSORY_lv ]: this, [ constants.ACC_TYPE_ENUM_INDEX_lv ]: accTypeEnumIndex, [ constants.CHARACTERISTIC_STRING_lv ]: characteristicString, [ constants.TIMEOUT_lv ]: timeout, [ constants.STATE_CHANGE_RESPONSE_TIME_lv ]: stateChangeResponseTime, [ constants.CALLBACK_lv ]: homebridgeCallback, [ constants.VALUE_lv ]: value };
// Determine where to put the entry in the queue
if ( this.queue.highPriorityQueue.length == 0 )
{
// No entries, then it goes on top
this.queue.highPriorityQueue.push( newEntry );
} else {
// Make sure that this is the latest "Set" of this entry
let index = this.queue.highPriorityQueue.findIndex( ( entry ) => entry.accessory.uuid == this.uuid && entry.isSet == true && entry.accTypeEnumIndex == accTypeEnumIndex );
if ( index == -1 )
{
// It doesn't exist in the queue, It needs to be placed after any "Sets".
// First Determine the first "Get"
let getIndex = this.queue.highPriorityQueue.findIndex( ( entry ) => entry.isSet == false );
if ( getIndex == -1 )
{
// No "Get" entrys, it goes at the end after everything.
this.queue.highPriorityQueue.push( newEntry );
} else
{
// Insert before the first "Get" entry
this.queue.highPriorityQueue.splice( getIndex, 0, newEntry );
}
} else
{
this.queue.highPriorityQueue[ index ] = newEntry;
}
}
this.queue.processQueueFunc( HIGH_PRIORITY_SET, this.queue );
}
// This function is called by homebridge to *PUT AN ENTRY INTO THE HIGHEST PRIORITY GET QUEUE*.
// We immediately return with success and the last known value if the device is accessible, otherwise
// the last failure error code.
// The Get is attempted no matter the devices availability. This is done after every Set
// request and at the bottom of the hightPrioritySetValue queue, but above any polling.
priorityGetValue( accTypeEnumIndex, characteristicString, timeout, homebridgeCallback )
{
// this is Accessory
// if ( settings.cmd4Dbg ) this.log.debug(`priorityGetValue for ${ this.displayName }, asked to Get: ${ characteristicString }`);
if ( this.errorValue != 0 )
{
// if ( settings.cmd4Dbg ) this.log.debug(`priorityGetValue for ${ this.displayName }, homebridgeCallback returning error ${ this.errorValue } ${ this.errorString}`);
homebridgeCallback( this.errorValue );
} else
{
// return the cached value
let storedValue = this.cmd4Storage.getStoredValueForIndex( accTypeEnumIndex );
// if ( settings.cmd4Dbg ) this.log.debug(`priorityGetValue for ${ this.displayName }, homebridgeCallback returning storedValue: ${ storedValue }`);
homebridgeCallback( 0, storedValue );
}
if ( this.queue.queueType != constants.QUEUETYPE_WORM2 )
{
// When the value is returned, it will update homebridge
this.queue.highPriorityQueue.push( { [ constants.IS_SET_lv ]: false, [ constants.QUEUE_GET_IS_UPDATE_lv ]: true, [ constants.ACCESSORY_lv ]: this, [ constants.ACC_TYPE_ENUM_INDEX_lv ]: accTypeEnumIndex, [ constants.CHARACTERISTIC_STRING_lv ]: characteristicString, [ constants.TIMEOUT_lv ]: timeout, [ constants.STATE_CHANGE_RESPONSE_TIME_lv ]: null, [ constants.VALUE_lv ]: null, [ constants.CALLBACK_lv ]: homebridgeCallback } );
this.queue.processQueueFunc( HIGH_PRIORITY_GET, this.queue );
}
}
// This function is called by polling to *PUT AN ENTRY INTO THE LOW PRIORITY POLLING QUEUE*.
addLowPriorityGetPolledQueueEntry( accessory, accTypeEnumIndex, characteristicString, interval, timeout )
{
// These are all gets from polling
accessory.queue.lowPriorityQueue.push( { [ constants.ACCESSORY_lv ]: accessory, [ constants.ACC_TYPE_ENUM_INDEX_lv ]: accTypeEnumIndex, [ constants.CHARACTERISTIC_STRING_lv ]: characteristicString, [ constants.INTERVAL_lv ]: interval, [ constants.TIMEOUT_lv ]: timeout } );
}
processHighPrioritySetQueue( entry )
{
if ( settings.cmd4Dbg ) this.log.debug( `Processing high priority queue "Set" entry: ${ entry.accTypeEnumIndex } length: ${ this.highPriorityQueue.length }` );
this.inProgressSets ++;
this.qSetValue( entry.accessory, entry.accTypeEnumIndex, entry.characteristicString, entry.timeout, entry.value, function ( error )
{
let queue = entry.accessory.queue;
// Save the error code - Pass or fail
entry.accessory.errorValue = error;
if ( error == 0 )
{
// Now that the set was successful, store the value
entry.accessory.cmd4Storage.setStoredValueForIndex( entry.accTypeEnumIndex, entry.value );
// Since the "Set" passed, do the stateChangeResponseTime
setTimeout( ( ) =>
{
// A set with no error means the queue is sane to do reading
queue.lastGoodTransactionTime = Date.now( );
queue.errorCountSinceLastGoodTransaction = 0;
// After the stateChangeResponseTime, do the related characteristic ( if any )
let relatedCurrentAccTypeEnumIndex = entry.accessory.getDevicesRelatedCurrentAccTypeEnumIndex( entry.accTypeEnumIndex );
if ( relatedCurrentAccTypeEnumIndex != null )
{
let relatedCurrentCharacteristicString = CMD4_ACC_TYPE_ENUM.properties[ relatedCurrentAccTypeEnumIndex ].type;
// Change the entry to a get and set queueGetIsUpdate to true
// Use unshift to make it next in line
entry.isSet = false;
entry.accTypeEnumIndex = relatedCurrentAccTypeEnumIndex;
entry.characteristicString = relatedCurrentCharacteristicString;
entry.queueGetIsUpdate = true;
queue.highPriorityQueue.unshift( entry );
}
// The "Set" is now complete after its stateChangeResponseTime.
queue.inProgressSets --;
setTimeout( ( ) => { queue.processQueueFunc( HIGH_PRIORITY_GET, queue ); }, 0 );
return;
}, entry.stateChangeResponseTime );
} else // setValue failed
{
// The "Set" is complete, even if it failed.
queue.inProgressSets --;
let currentRetryCount = queue.errorCountSinceLastGoodTransaction;
if ( currentRetryCount >= queue.queueRetryCount )
{
if ( queue.echoRetryErrors( currentRetryCount ) )
{
// Counting starts from zero, i.e queueRetries = 0, so add 1
queue.log.warn( `*${ currentRetryCount + 1 }* error(s) were encountered for "${ entry.accessory.displayName }" getValue. Last error found Getting: "${ entry.characteristicString}". Perhaps you should run in debug mode to find out what the problem might be.` );
}
// Convert the errorValue into an errorString
entry.accessory.errorString = new Error( constants.errorString( error ) );
// This does not work - Nothing happens to HomeKit !
// queue.log.warn( `START processHighPrioritySetQueue calling updateCharacteristic errorValue: ${ entry.accessory.errorValue} errorString: ${ entry.accessory.errorString }`);
// entry.accessory.service.getCharacteristic( CMD4_ACC_TYPE_ENUM.properties[ entry.accTypeEnumIndex ].characteristic ).updateValue( entry.accessory.errorString );
// queue.log.warn( `END processHighPrioritySetQueue calling updateCharacteristic errorValue: ${ entry.accessory.errorValue} errorString: ${ entry.accessory.errorString }`);
} else
{
// Increment the errorCount/currentRetryCount
queue.errorCountSinceLastGoodTransaction++;
// Set failed. We need to keep trying
queue.highPriorityQueue.push( entry );
}
entry.accessory.queue.pauseQueue( entry.accessory.queue );
}
// Note 1.
// Do not call the callback as it was done when the "Set" entry was
// created.
// Note 2.
// We cannot release the queue for further processing as the
// statechangeResponseTime has not completed. This must be
// done first or any next "Get" or "Set" would interfere
// with the device
});
}
processHighPriorityGetQueue( entry )
{
if ( settings.cmd4Dbg ) this.log.debug( `Processing high priority queue "Get" entry: ${ entry.accTypeEnumIndex } isUpdate: ${ entry.queueGetIsUpdate } length: ${ this.highPriorityQueue.length }` );
this.inProgressGets ++;
this.qGetValue( entry.accessory, entry.accTypeEnumIndex, entry.characteristicString, entry.timeout, function ( error, properValue )
{
let queue = entry.accessory.queue;
// Save the error code - Pass or fail
entry.accessory.errorValue = error;
// Nothing special was done for casing on errors, so omit it.
if ( error == 0 )
{
// Save the new returned value
entry.accessory.cmd4Storage.setStoredValueForIndex( entry.accTypeEnumIndex, properValue );
// hmmm if ( entry.queueGetIsUpdate == true )
entry.accessory.service.getCharacteristic( CMD4_ACC_TYPE_ENUM.properties[ entry.accTypeEnumIndex ].characteristic ).updateValue( properValue );
// A good anything, updates the lastGoodTransactionTime
queue.lastGoodTransactionTime = Date.now( );
queue.errorCountSinceLastGoodTransaction = 0;
} else // highPriority getValue failed
{
let currentRetryCount = queue.errorCountSinceLastGoodTransaction;
if ( currentRetryCount >= queue.queueRetryCount )
{
if ( queue.echoRetryErrors( currentRetryCount ) )
queue.log.warn( `*${ currentRetryCount + 1}* error(s) were encountered for "${ entry.accessory.displayName }" getValue. Last error found Getting: "${ entry.characteristicString}". Perhaps you should run in debug mode to find out what the problem might be.` );
// Convert the errorValue into an errorString
entry.accessory.errorString = new Error( constants.errorString( error ) );
// This does not work - Nothing happens to HomeKit !
// queue.log.warn( `START processHighPriorityGetQueue calling updateCharacteristic errorValue: ${ entry.accessory.errorValue} errorString: ${ entry.accessory.errorString }`);
// entry.accessory.service.getCharacteristic( CMD4_ACC_TYPE_ENUM.properties[ entry.accTypeEnumIndex ].characteristic ).updateValue( entry.accessory.errorString );
// queue.log.warn( `END processHighPriorityGetQueue calling updateCharacteristic errorValue: ${ entry.accessory.errorValue} errorString: ${ entry.accessory.errorString }`);
} else
{
// Increment the errorCount/currentRetryCount
queue.errorCountSinceLastGoodTransaction++;
// High Priority Get failed. We keep retrying until the mod of
// queueRetryCount reaches zero, which is WoRm only as it has more than 1
// default retry count.
queue.highPriorityQueue.push( entry );
}
entry.accessory.queue.pauseQueue( entry.accessory.queue );
}
queue.inProgressGets --;
setTimeout( ( ) => { queue.processQueueFunc( HIGH_PRIORITY_GET, queue ); }, 0 );
});
}
// This is called from polling
processEntryFromLowPriorityQueue( entry )
{
if ( settings.cmd4Dbg ) this.log.debug( `Processing low priority queue entry: ${ entry.accTypeEnumIndex }` );
let queue = entry.accessory.queue;
queue.inProgressGets ++;
// isLowPriority is set to true,
queue.qGetValue( entry.accessory, entry.accTypeEnumIndex, entry.characteristicString, entry.timeout, function ( error, properValue )
{
// For the next one
queue.inProgressGets --;
// Save the error code - Pass or fail
entry.accessory.errorValue = error;
// Nothing special was done for casing on errors, so omit it.
if ( error == 0 )
{
// Save the new value
entry.accessory.cmd4Storage.setStoredValueForIndex( entry.accTypeEnumIndex, properValue );
if ( settings.cmd4Dbg ) entry.accessory.log.debug( `processEntryFromLowPriorityQueue calling updateValue properValue: ${ properValue }`);
// Update the new value in homebridge
entry.accessory.service.getCharacteristic( CMD4_ACC_TYPE_ENUM.properties[ entry.accTypeEnumIndex ].characteristic ).updateValue( properValue );
// A good anything, updates the lastGoodTransactionTime
queue.lastGoodTransactionTime = Date.now( );
queue.errorCountSinceLastGoodTransaction = 0;
} else { // LowPriority getValue failed
queue.errorCountSinceLastGoodTransaction++;
// Convert the errorValue into an errorString
entry.accessory.errorString = new Error( constants.errorString( error ));
// This does not work - Nothing happens to HomeKit !
// Call updateValue with new Error so device will become unavailable
// queue.log.warn( `START processEntryFromLowPriorityQueue calling updateCharacteristic errorValue: ${ entry.accessory.errorValue } errorString: ${ entry.accessory.errorString }`);
// entry.accessory.service.getCharacteristic( CMD4_ACC_TYPE_ENUM.properties[ entry.accTypeEnumIndex ].characteristic ).updateValue( entry.accessory.errorString );
// queue.log.warn( `END processEntryFromLowPriorityQueue calling updateCharacteristic errorValue: ${ entry.accessory.errorValue } errorString: ${ entry.accessory.errorString }`);
queue.pauseQueue( entry.accessory.queue );
}
// Now that this one has been processed, schedule it again for next time
queue.scheduleLowPriorityEntry( entry )
});
}
// ***********************************************
//
// qGetValue: Method to call an external script
// that returns an accessories status
// for a given characteristic.
//
// The script will be passed:
// Get <Device Name> <accTypeEnumIndex>
//
// Where:
// - Device name is the name in your
// config.json file.
// - accTypeEnumIndex represents
// the characteristic to get as in index into
// the CMD4_ACC_TYPE_ENUM.
//
// ***********************************************
qGetValue( accessory, accTypeEnumIndex, characteristicString, timeout, callback )
{
let self = accessory;
let queue = accessory.queue;
let cmd = self.state_cmd_prefix + self.state_cmd + " Get '" + self.displayName + "' '" + characteristicString + "'" + self.state_cmd_suffix;
// My AdvAir friends want to allow single quotes in accessory names, which
// may have consequences with globbing for others.
if ( self.state_cmd.match( /AdvAir.sh/ ) )
{
cmd = self.state_cmd_prefix + self.state_cmd + ' Get "' + self.displayName + '" ' + "'" + characteristicString + "'" + self.state_cmd_suffix;
}
if ( settings.cmd4Dbg ) self.log.debug( `getValue: accTypeEnumIndex:( ${ accTypeEnumIndex } )-"${ characteristicString }" function for: ${ self.displayName } cmd: ${ cmd } timeout: ${ timeout }` );
let reply = "NxN";
// Execute command to Get a characteristics value for an accessory
// exec( cmd, { timeout: timeout }, function ( error, stdout, stderr )
//let child = spawn( cmd, { shell:true } );
let child = exec( cmd, { timeout: timeout }, function ( error, stdout, stderr )
{
if ( stderr )
if ( queue.echoE ) self.log.error( `getValue: ${ characteristicString } function for ${ self.displayName } streamed to stderr: ${ stderr }` );
// Handle errors when process closes
if ( error )
if ( queue.echoE ) self.log.error( chalk.red( `getValue ${ characteristicString } function failed for ${ self.displayName } cmd: ${ cmd } Failed. Generated Error: ${ error }` ) );
reply = stdout;
}).on('close', ( code ) =>
{
// Was the return code successful ?
if ( code != 0 )
{
// Commands that time out have "null" return codes. So get the real one.
if ( child.killed == true )
{
if ( queue.echoE ) self.log.error( chalk.red( `getValue ${ characteristicString } function timed out ${ timeout }ms for ${ self.displayName } cmd: ${ cmd } Failed` ) );
callback( constants.ERROR_TIMER_EXPIRED );
return;
}
if ( queue.echoE ) self.log.error( chalk.red( `getValue ${ characteristicString } function failed for ${ self.displayName } cmd: ${ cmd } Failed. Error: ${ code }. ${ constants.DBUSY }` ) );
callback( code );
return;
}
if ( reply == "NxN" )
{
if ( queue.echoE ) self.log.error( `getValue: nothing returned from stdout for ${ characteristicString } ${ self.displayName }. ${ constants.DBUSY }` );
callback( constants.ERROR_NO_DATA_REPLY );
return;
}
if ( reply == null )
{
if ( queue.echoE ) self.log.error( `getValue: null returned from stdout for ${ characteristicString } ${ self.displayName }. ${ constants.DBUSY }` );
// We can call our callback though ;-)
callback( constants.ERROR_NULL_REPLY );
return;
}
// Coerce to string for manipulation
reply += '';
// Remove trailing newline or carriage return, then
// Remove leading and trailing spaces, carriage returns ...
let trimmedReply = reply.replace(/\n|\r$/,"").trim( );
// Theoretically not needed as this is caught below, but I wanted
// to catch this before much string manipulation was done.
if ( trimmedReply.toUpperCase( ) == "NULL" )
{
if ( queue.echoE ) self.log.error( `getValue: "${ trimmedReply }" returned from stdout for ${ characteristicString } ${ self.displayName }. ${ constants.DBUSY }` );
callback( constants.ERROR_NULL_STRING_REPLY );
return;
}
// Handle beginning and ending matched single or double quotes. Previous version too heavy duty.
// - Remove matched double quotes at begining and end, then
// - Remove matched single quotes at beginning and end, then
// - remove leading and trailing spaces.
let unQuotedReply = trimmedReply.replace(/^"(.+)"$/,"$1").replace(/^'(.+)'$/,"$1").trim( );
if ( unQuotedReply == "" )
{
if ( queue.echoE ) self.log.error( `getValue: ${ characteristicString } function for: ${ self.displayName } returned an empty string "${ trimmedReply }". ${ constants.DBUSY }` );
callback( constants.ERROR_EMPTY_STRING_REPLY );
return;
}
// The above "null" checked could possibly have quotes around it.
// Now that the quotes are removed, I must check again. The
// things I must do for bad data ....
if ( unQuotedReply.toUpperCase( ) == "NULL" )
{
if ( queue.echoE ) self.log.error( `getValue: ${ characteristicString } function for ${ self.displayName } returned the string "${ trimmedReply }". ${ constants.DBUSY }` );
callback( constants.ERROR_2ND_NULL_STRING_REPLY );
return;
}
let words = unQuotedReply.split( " " ).length;
if ( words > 1 && CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].props.allowedWordCount == 1 )
{
self.log.warn( `getValue: Warning, Retrieving ${ characteristicString }, expected only one word value for: ${ self.displayName } of: ${ trimmedReply }` );
}
if ( settings.cmd4Dbg ) self.log.debug( `getValue: ${ characteristicString } function for: ${ self.displayName } returned: ${ unQuotedReply }` );
var transposed = transposeConstantToValidValue( CMD4_ACC_TYPE_ENUM.properties, accTypeEnumIndex, unQuotedReply )
if ( settings.cmd4Dbg && transposed != unQuotedReply ) self.log.debug( `getValue: ${ characteristicString } for: ${ self.displayName } transposed: ${ transposed }` );
// Return the appropriate type, by seeing what it is
// defined as in Homebridge,
let properValue = CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].stringConversionFunction( transposed );
if ( properValue == undefined )
{
self.log.warn( `${ self.displayName } ` + chalk.red( `Cannot convert value: ${ unQuotedReply } to ${ CMD4_ACC_TYPE_ENUM.properties[ accTypeEnumIndex ].props.format } for ${ characteristicString }` ) );
callback( constants.ERROR_NON_CONVERTABLE_REPLY );
return;
}
if ( settings.cmd4Dbg && properValue != transposed ) self.log.debug( `getValue: ${ characteristicString } for: ${ self.displayName } properValue: ${ properValue }` );
// Success !!!!
callback( 0, properValue );
// Store history using fakegato if set up
self.updateAccessoryAttribute( accTypeEnumIndex, properValue );
});
}
// ***********************************************
//
// qSetValue: Method to call an external script
// that sets an accessories status
// for a given characteristic.
//
//
// The script will be passed:
// Set < Device Name > < accTypeEnumIndex > < Value >
//
//
// Where:
// - Device name is the name in your
// config.json file.
// - accTypeEnumIndex represents
// the characteristic to get as in index into
// the CMD4_ACC_TYPE_ENUM.
// - Characteristic is the accTypeEnumIndex
// in HAP form.
// - Value is new characteristic value.
//
// Notes:
// ( 1 ) In the special TARGET set characteristics, getValue
// is called to update HomeKit.
// Example: Set My_Door < TargetDoorState > 1
// calls: Get My_Door < CurrentDoorState >
//
// - Where he value in <> is an one of CMD4_ACC_TYPE_ENUM
// ***********************************************
qSetValue( accessory, accTypeEnumIndex, characteristicString, timeout, value, queueCallback )
{
let self = accessory;
let queue = accessory.queue;
if ( self.hV.outputConstants == true )
{
value = transposeValueToValidConstant( CMD4_ACC_TYPE_ENUM.properties, accTypeEnumIndex, value );
} else
{
value = transposeBoolToValue( value );
}
let cmd = accessory.state_cmd_prefix + accessory.state_cmd + " Set '" + accessory.displayName + "' '" + characteristicString + "' '" + value + "'" + accessory.state_cmd_suffix;
// My AdvAir friends want to allow single quotes in accessory names, which
// may have consequences with globbing for others.
if ( accessory.state_cmd.match( /AdvAir.sh/ ) )
{
cmd = accessory.state_cmd_prefix + accessory.state_cmd + ' Set "' + accessory.displayName + '" ' + "'" + characteristicString + "' '" + value + "'" + accessory.state_cmd_suffix;
}
if ( accessory.hV.statusMsg == "TRUE" )
self.log.info( chalk.blue( `Setting ${ self.displayName } ${ characteristicString }` ) + ` ${ value }` );
if ( settings.cmd4Dbg ) self.log.debug( `setValue: accTypeEnumIndex:( ${ accTypeEnumIndex } )-"${ characteristicString }" function for: ${ self.displayName } ${ value } cmd: ${ cmd } timeout: ${ timeout }` );
// Execute command to Set a characteristic value for an accessory
let child = exec( cmd, { timeout: timeout }, function ( error, stdout, stderr )
{
if ( stderr )
if ( queue.echoE ) self.log.error( `setValue: ${ characteristicString } function for ${ self.displayName } streamed to stderr: ${ stderr }` );
if ( error )
if ( queue.echoE ) self.log.error( chalk.red( `setValue ${ characteristicString } function failed for ${ self.displayName } cmd: ${ cmd } Failed. Error: ${ error.message }` ) );
}).on( "close", ( code ) =>
{
if ( code != 0 )
{
if ( child.killed == true )
{
if ( queue.echoE ) self.log.error( chalk.red( `setValue ${ characteristicString } function failed for ${ self.displayName } cmd: ${ cmd } Failed. Error: ${ code } ${ constants.DBUSY }` ) );
queueCallback( constants.ERROR_TIMER_EXPIRED );
return;
}
queueCallback( code );
return;
}
queueCallback( code );
});
}
// The queue is self maintaining, except for lowPriorityEntries
// which if passed in, must be rescheduled as they go by their own
// intervals and thus must handle the return code.
processWormQueue( lastTransactionType, queue, lowPriorityEntry = null )
{
// "WoRm", No matter what, only one "Set" allowed
if ( queue.inProgressSets > 0 )
{
// if ( settings.cmd4Dbg ) queue.log.debug(`processWormQueue queue.inProgressSets > 0 : ${queue.inProgressSets}`);
// We are *NOT* processing the low prioirity queue entry
return false;
}
// It is not a good time to do a anything, so skip it
if ( queue.lastGoodTransactionTime == 0 )
{
// if ( settings.cmd4Dbg ) queue.log.debug(`processWormQueue queue.lastGoodTransactionTime == 0`);
// We are *NOT* processing the low prioirity queue entry
return false;
}
if ( queue.highPriorityQueue.length > 0 )
{
let nextEntry = queue.highPriorityQueue[ 0 ];
if ( nextEntry.isSet == true )
{
// If already in progress, when they finish they will restart the queue
// Otherwise continuing will purge the next item from the queue as it
// cannot be run with an entry already in progress.
if ( nextEntry.accessory.queue.inProgressSets > 0 ||
nextEntry.accessory.queue.inProgressGets > 0 )
{
// Return as queue is busy.
// Return false as we are *NOT* processing the low prioirity queue entry
// if ( settings.cmd4Dbg ) queue.log.debug(`processWormQueue queue.inProgressSets> 0 ${ nextEntry.accessory.queue.inProgressSets } ${ nextEntry.accessory.queue.inProgressGets }`);
return false;
}
queue.processHighPrioritySetQueue( queue.highPriorityQueue.shift( ) );
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
}
// This must be a "Get". Process them all.
let max = queue.highPriorityQueue.length;
while( queue.highPriorityQueue.length > 0 &&
nextEntry.isSet == false &&
max >= 1 )
{
queue.processHighPriorityGetQueue( queue.highPriorityQueue.shift( ) );
nextEntry = queue.highPriorityQueue[ 0 ];
max--;
}
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
} else if ( lastTransactionType == HIGH_PRIORITY_SET ||
lastTransactionType == HIGH_PRIORITY_GET )
{
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
}
// This is self evident, until their are other types of Prioritys
if ( lastTransactionType == LOW_PRIORITY_GET &&
lowPriorityEntry != null &&
queue.queueStarted == true )
{
queue.processEntryFromLowPriorityQueue( lowPriorityEntry );
// We are processing the low priority queue entry.
return true;
} else {
if ( lastTransactionType == LOW_PRIORITY_GET &&
queue.queueStarted == false )
{
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
} if ( queue.inProgressGets == 0 &&
queue.inProgressSets == 0 )
{
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
} else {
if ( settings.cmd4Dbg ) this.log.debug( `Unhandled lastTransactionType: ${ lastTransactionType } inProgressSets: ${ queue.inProgressSets } inProgressGets: ${ queue.inProgressGets } queueStarted: ${ queue.queueStarted } lowQueueLen: ${ queue.lowPriorityQueue.length } hiQueueLen: ${ queue.highPriorityQueue.length }` );
}
}
}
// The queue is self maintaining, except for lowPriorityEntries
// which if passed in, must be rescheduled as they go by their own
// intervals and thus must handle the return code.
processSequentialQueue( lastTransactionType, queue, lowPriorityEntry = null )
{
// Sequential, No matter what, only one transaction allowed
if ( queue.inProgressSets > 0 ||
queue.inProgressGets > 0 )
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
// It is not a good time to do a anything, so skip it
if ( queue.lastGoodTransactionTime == 0 )
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
if ( queue.highPriorityQueue.length > 0 )
{
let nextEntry = queue.highPriorityQueue[ 0 ];
if ( nextEntry.isSet == true )
{
queue.processHighPrioritySetQueue( queue.highPriorityQueue.shift( ) );
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
}
// Has to be a High Priority "Get" entry. Process just this one.
queue.processHighPriorityGetQueue( queue.highPriorityQueue.shift( ) );
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
} else if ( lastTransactionType == HIGH_PRIORITY_SET ||
lastTransactionType == HIGH_PRIORITY_GET )
{
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
}
// This is self evident, until their are other types of Prioritys
if ( lastTransactionType == LOW_PRIORITY_GET &&
lowPriorityEntry != null &&
queue.queueStarted == true )
{
queue.processEntryFromLowPriorityQueue( lowPriorityEntry );
// We are processing the low priority queue entry.
return true;
} else {
if ( lastTransactionType == LOW_PRIORITY_GET &&
queue.queueStarted == false )
{
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
} if ( queue.inProgressGets == 0 &&
queue.inProgressSets == 0 )
{
// Return false as we are *NOT* processing the low prioirity queue entry
return false;
} else {
if ( settings.cmd4Dbg ) this.log.debug( `Unhandled lastTransactionType: ${ lastTransactionType } inProgressSets: ${ queue.inProgressSets } inProgressGets: ${ queue.inProgressGets } queueStarted: ${ queue.queueStarted } lowQueueLen: ${ queue.lowPriorityQueue.length } hiQueueLen: ${ queue.highPriorityQueue.length }` );
}
}
}
// The standard queue is just free running, except if the queue has not
// been started yet.
processPassThruQueue( lastTransactionType, queue, lowPriorityEntry = null )
{
if ( lastTransactionType == LOW_PRIORITY_GET &&
lowPriorityEntry != null &&
queue.queueStarted == true )
{
queue.processEntryFromLowPriorityQueue( lowPriorityEntry );
}
if ( queue.highPriorityQueue.length > 0 )
{
let nextEntry = queue.highPriorityQueue[ 0 ];
if ( nextEntry.isSet == true )
{
queue.processHighPrioritySetQueue( queue.highPriorityQueue.shift( ) );
}
else
{
queue.processHighPriorityGetQueue( queue.highPriorityQueue.shift( ) );
}
}
}
scheduleLowPriorityEntry( entry )
{
let accessory = entry.accessory;
let queue = entry.accessory.queue;
if ( settings.cmd4Dbg ) accessory.log.debug( `Scheduling Poll of index: ${ entry.accTypeEnumIndex } characteristic: ${ entry.characteristicString } for: ${ accessory.displayName } timeout: ${ entry.timeout } interval: ${ entry.interval }` );
// Clear polling
if ( queue.listOfRunningPolls &&
queue.listOfRunningPolls[ accessory.displayName + entry.accTypeEnumIndex ] == undefined )
clearTimeout( queue.listOfRunningPolls[ accessory.displayName + entry.accTypeEnumIndex ] );
queue.listOfRunningPolls[ accessory.displayName + entry.accTypeEnumIndex ] = setTimeout( ( ) =>
{
// If the queue was busy/not available, schedule the entry at a later time
if ( queue.processQueueFunc( LOW_PRIORITY_GET, queue, entry ) == false )
{
if ( settings.cmd4Dbg ) accessory.log.debug( `processsQueue returned false` );
queue.scheduleLowPriorityEntry( entry );
}
}, entry.interval);
}
pauseQueue( queue )
{
if ( queue.queueType == constants.QUEUETYPE_STANDARD )
return;
queue.lastGoodTransactionTime = 0;
if ( queue.pauseTimer == null )
{
queue.pauseTimer = setTimeout( ( ) =>
{
// So we do not trip over this again immediately
queue.lastGoodTransactionTime = Date.now( );
queue.pauseTimer = null;
queue.processQueueFunc( HIGH_PRIORITY_GET, queue );
}, queue.pauseTimerTimeout );
}
}
printQueueStats( queue )
{
let line = `QUEUE "${ queue.queueName }" stats`;
this.log.info( line );
this.log.info( `${ "=".repeat( line.length ) }` );
this.log.info( "No longer applicable" );
}
dumpQueue( queue )
{
let line = `Low Priority Queue "${ queue.queueName }"`;
this.log.info( line );
this.log.info( `${ "=".repeat( line.length ) }` );
queue.lowPriorityQueue.forEach( ( entry, entryIndex ) =>
{
this.log.info( `${ entryIndex } ${ entry.accessory.displayName } characteristic: ${ entry.characteristicString } accTypeEnumIndex: ${ entry.accTypeEnumIndex } interval: ${ entry.interval } timeout: ${ entry.timeout }` );
} );
}
startQueue( queue, allDoneCallback )
{
queue.lowPriorityQueueIndex = 0 ;
let delay = 0;
let staggeredDelays = [ 3000, 6000, 9000, 12000 ];
let staggeredDelaysLength = staggeredDelays.length;
let staggeredDelayIndex = 0;
let lastAccessoryUUID = ""
let allDoneCount = 0;
if ( settings.cmd4Dbg ) this.log.debug( `enablePolling for the first time` );
// If there is nothing in the lowPriorityQueue, we are dome.
// Demo mode or Unit testing.
if ( queue.lowPriorityQueue.length == 0 )
{
allDoneCallback( allDoneCount );
setTimeout( ( ) => { queue.processQueueFunc( HIGH_PRIORITY_GET, queue ); }, 0 );
} else
{
queue.lowPriorityQueue.forEach( ( entry, entryIndex ) =>
{
allDoneCount ++;
setTimeout( ( ) =>
{
if ( entryIndex == 0 && settings.cmd4Dbg )
{
if ( queue.queueType == constants.QUEUETYPE_WORM ||
queue.queueType == constants.QUEUETYPE_WORM2
)
{
entry.accessory.log.debug( `Started staggered kick off of ${ queue.lowPriorityQueue.length } polled characteristics for queue: "${ entry.accessory.queue.queueName }"` );
} else
{
entry.accessory.log.debug( `Started staggered kick off of ${ queue.lowPriorityQueue.length } polled characteristics for "${ entry.accessory.displayName }"` );
}
}
if ( settings.cmd4Dbg ) entry.accessory.log.debug( `Kicking off polling for: ${ entry.accessory.displayName } ${ entry.characteristicString } interval:${ entry.interval }, staggered:${ staggeredDelays[ staggeredDelayIndex ] }` );
queue.scheduleLowPriorityEntry( entry );
if ( entryIndex == queue.lowPriorityQueue.length -1 )
{
if ( settings.cmd4Dbg )
{
if ( queue.queueType == constants.QUEUETYPE_WORM ||
queue.queueType == constants.QUEUETYPE_WORM2
)
{
entry.accessory.log.debug( `All characteristics are now being polled for queue: "${ queue.queueName }"` );
}
else
{
entry.accessory.log.debug( `All characteristics are now being polled for "${ entry.accessory.displayName }"` );
}
}
allDoneCallback( allDoneCount );
}
}, delay );
if ( staggeredDelayIndex++ >= staggeredDelaysLength )
staggeredDelayIndex = 0;
if ( lastAccessoryUUID != entry.accessory.uuid )
staggeredDelayIndex = 0;
lastAccessoryUUID = entry.accessory.uuid;
delay += staggeredDelays[ staggeredDelayIndex ];
});
}
queue.queueStarted = true;
}
changeQueueType( queue, queueType )
{
if ( queue.queueStarted )
throw new Error( `Cannot change queueType when queue is running` );
// The WoRm queue needs error messages to be silenced as
// they are inevitable, but are handled through retries
// By default non WoRm queus are allowed to echo errors
this.echoE = true;
// Default
this.processQueueFunc = this.processWormQueue;
switch ( queueType )
{
case constants.QUEUETYPE_SEQUENTIAL:
this.processQueueFunc = this.processSequentialQueue;
break;
case constants.QUEUETYPE_WORM:
case constants.QUEUETYPE_WORM2:
this.processQueueFunc = this.processWormQueue;
// When not in debug mode, do not echo errors for the WoRm queue
// as errors are handled through retries.
if ( ! settings.cmd4Dbg )
this.echoE = false;
break;
case constants.QUEUETYPE_STANDARD:
// only polled entries go straight through the queue
this.processQueueFunc = this.processPassThruQueue;
break;
case constants.QUEUETYPE_PASSTHRU:
// entries go straight through the queue
this.processQueueFunc = this.processPassThruQueue;
break;
default:
this.log.error( `Error: Invalid queue type: ${ queueType }` );
}
}
isCharacteristicPolled( accTypeEnumIndex, queue, accessory )
{
if ( queue.lowPriorityQueue.filter(
entry => entry.accessory.uuid == accessory.uuid &&
entry.accTypeEnumIndex == accTypeEnumIndex
).length == 0 )
{
return false;
}
return true;
}
}
var queueExists = function( queueName )
{
return settings.listOfCreatedPriorityQueues[ queueName ];
}
var addQueue = function( log, queueName, queueType = constants.DEFAULT_QUEUE_TYPE, queueRetryCount = constants.DEFAULT_WORM_QUEUE_RETRY_COUNT )
{
let queue = queueExists( queueName );
if ( queue != undefined )
return queue;
log.debug( `Creating new Priority Polled Queue "${ queueName }" with QueueType of: "${ queueType }" retryCount: ${queueRetryCount}` );
queue = new Cmd4PriorityPollingQueue( log, queueName, queueType, queueRetryCount );
settings.listOfCreatedPriorityQueues[ queueName ] = queue;
return queue;
}
var parseAddQueueTypes = function ( log, entrys )
{
if ( trueTypeOf( entrys ) != Array )
throw new Error( `${ constants.QUEUETYPES } is not an Array of { "Queue Name": "QueueType" }. found: ${ entrys }` );
entrys.forEach( ( entry, entryIndex ) =>
{
let queueName = null;
let queueType = constants.DEFAULT_QUEUE_TYPE;
let queueRetryCount = constants.DEFAULT_WORM_QUEUE_RETRY_COUNT;
for ( let key in entry )
{
let lcKey = lcFirst( key );
// warn now
if ( key.charAt( 0 ) === key.charAt( 0 ).toUpperCase( ) )
{
log.warn( `The config.json queueTypes key: ${ key } is Capitalized. All keys in the near future will ALWAYS start with a lower case character for homebridge-ui integration.\nTo remove this Warning, Please fix your config.json.` );
}
let value = entry[ key ];
switch( lcKey )
{
case constants.QUEUE:
if ( settings.listOfCreatedPriorityQueues[ entry.queue ] )
throw new Error( `QueueName: ${ entry.queue } was added twice` );
queueName = value;
break;
case constants.QUEUETYPE:
// Set the default Queue Retry Count based on QueueType
switch ( value )
{
case constants.QUEUETYPE_WORM:
queueRetryCount = constants.DEFAULT_WORM_QUEUE_RETRY_COUNT;
queueType = value;
break;
case constants.QUEUETYPE_WORM2:
queueRetryCount = constants.DEFAULT_WORM_QUEUE_RETRY_COUNT;
queueType = value;
break;
case constants.QUEUETYPE_SEQUENTIAL:
queueRetryCount = constants.DEFAULT_STANDARD_QUEUE_RETRY_COUNT;
queueType = value;
break;
case constants.QUEUETYPE_STANDARD:
queueRetryCount = constants.DEFAULT_STANDARD_QUEUE_RETRY_COUNT;
queueType = value;
break;
default:
throw new Error( `QueueType: ${ entry.queueType } is not valid at index: ${ entryIndex }. Expected: ${ constants.QUEUETYPE_WORM }, ${ constants.QUEUETYPE_WORM2 } or ${ constants.QUEUETYPE_SEQUENTIAL }` );
}
break;
case constants.QUEUE_RETRIES:
queueRetryCount = value;
break;
default:
throw new Error( `Unknown Queue option"${ key } not provided at index ${ entryIndex }` );
}
}
// At least a Queue name must be defined, the rest are defaulted
if ( queueName == null )
throw new Error( `"${ constants.QUEUE }" not provided at index ${ entryIndex }` );
if ( settings.cmd4Dbg ) log.debug( `calling addQueue: ${ queueName } type: ${ queueType } retryCount: ${ queueRetryCount }` );
addQueue( log, queueName, queueType, queueRetryCount );
} );
}
module.exports = { addQueue,
parseAddQueueTypes,
queueExists,
Cmd4PriorityPollingQueue
}
================================================
FILE: Extras/Cmd4Scripts/CheckYourScript.sh
================================================
#!/bin/bash --noprofile --norc
# Fun colour & cursor stuff
TCR=$(tput cr)
TCLR=$(tput clear)
TBLD=$(tput bold)
TNRM=$(tput sgr0)
TBLK=$(tput setaf 0)
TRED=$(tput setaf 1)
TGRN=$(tput setaf 2)
TYEL=$(tput setaf 3)
TBLU=$(tput setaf 4)
TMAG=$(tput setaf 5)
TCYN=$(tput setaf 6)
TWHT=$(tput setaf 7)
# OS type for flavours of different commands (like date on OSX)
case $(uname | tr '[:upper:]' '[:lower:]') in
solaris*)
date_cmd="date -u +%s.%N"
;;
darwin*)
# OSX does not have msec
date_cmd="date -u +%s"
;;
linux*)
date_cmd="date -u +%s.%N"
;;
bsd*)
date_cmd="date -u +%s.%N"
;;
msys*)
date_cmd="date -u +%s.%N"
;;
*)
echo "unknown: OSTYPE:$OSTYPE"
exit -1
;;
esac
printf "${TCLR}"
if [ "${1}" = '-h' ]; then
printf "${TBLU}Usage:${TNRM}"
printf "${TNRM} SHELL> cd \n"
printf "${TNRM} SHELL> bash --noprofile --norc \n"
printf "\n"
printf "${TBLU}Syntax:${TNRM}\n"
printf " ${TBLU}${0}${TNRM} 'full state command'\n"
printf "${TBLU}i.e.${TNRM}\n"
printf " '.homebridge/Cmd4Scripts/CheckYourScript.sh' 'bin/MyExec' 'Get' 'MyDevice' 'On'\n"
printf " or\n"
printf " '.homebridge/Cmd4Scripts/CheckYourScript.sh' 'bash bin/YourScript.sh' 'Get' 'MyDevice' 'On'\n"
printf " or\n"
printf " '.homebridge/Cmd4Scripts/CheckYourScript.sh' 'bin/YourScript.sh 'Get 'MyDevice' 'On'\n"
printf "\n"
printf "Note: Add the '' around the command to prevent globbing, which is not done by homebridge-cmd4\n"
exit 0
fi
# Processes are run from your home directory, so go there first.
cd "${HOME}"
printf "${TBLU}Changing to:${TNRM}'${HOME}' ${TBLU}where processes are run from${TNRM}\n"
# $HOME is expanded by the shell and not scripts, so do not rely on it.
unset HOME
printf "${TBLU}Enviroment in shell is limited to these variables:${TNRM}\n"
env
printf "\n"
printf "${TBLU}Command will be run from the directory: ${TNRM}${PWD}\n"
output=""
rc=0
if [ "$2" = 'Set' ] || [ "$3" = 'Set' ]; then
printf "${TBLU}(Set) Cmd4 would execute:${TNRM} $* "
printf "\n"
start_time="$( $date_cmd )"
output=$("$@")
rc="$?"
end_time="$( $date_cmd )"
# The elapsed time (in microseconds)
# We add 1 second as OSX does not have msec dates.
# One second will make no difference, but might help a newbie.
elapsed=( $(/usr/bin/bc <<<"($end_time-$start_time) * 1000 + 1000") )
printf "\n"
if [ $rc = 0 ]; then
printf "${TGRN}Command passed with returned code:${TNRM}'${rc}'\n"
if [ "${output}" != "" ]; then
printf "Output: '${output}' would be ignored\n"
fi
printf "$TBLU}The timeout value should be at least :${TNRM} ${elapsed} (microseconds)\n"
printf "$TBLU}Multiply by 5 for safety.${TNRM}\n"
else
printf "${TRED}Command given did not exit with a ${TNRM}0${TRED} return code and would fail in homebridge-cmd4 rc=${TNRM}'${rc}'\n"
printf "Understand that the error given is a result of running your command in a basic shell environment. Head the errors given\n"
fi
else
printf "${TBLU}(Get) Cmd4 would execute:${TNRM} $*"
printf "\n"
start_time="$( $date_cmd )"
output=$("$@")
rc="$?"
end_time="$( $date_cmd )"
# The elapsed time (in microseconds)
# We add 1 second as OSX does not have msec dates.
elapsed=( $(/usr/bin/bc <<<"($end_time-$start_time) * 1000 + 1000") )
printf "\n"
wordCount=0
if [ $rc = 0 ]; then
printf "${TGRN}Command passed with returned code:${TNRM}'${rc}'\n"
printf "${TBLU}Output: of command was:${TNRM}'${output}'\n"
wordCount=$(IFS=' '; set -f; set -- "${output}"; echo $#)
if [ "${wordCount}" != '1' ]; then
printf "${TYEL}Word count of output should only be 1, not ${TNRM}'${wordCount}'\n"
fi
printf "$TBLU}The timeout value should be at least :${TNRM} ${elapsed} (microseconds)\n"
printf "$TBLU}Multiply by 5 for safety.${TNRM}\n"
else
printf "${TRED}Command given did not exit with a ${TNRM}0${TRED} return code and would fail in homebridge-cmd4 rc=${TNRM}'${rc}'\n"
printf "Understand that the error given is a result of running your command in a basic shell environment. Head the errors given\n"
fi
fi
exit ${rc}
================================================
FILE: Extras/Cmd4Scripts/Examples/AirPurifier.js
================================================
#!/usr/bin/env node
// State.js
//
// Description:
// This script *CAN* be called by the HomeBridge plugin Cmd4 as defined in your config.json
// file. The purpose is to fake the existance of an accessory or to be modified by you
// such that acessories can Get/Set characteristics as defined in the HomeKit Accessory
// Specifications.
//
// Plugin Installation:
// - npm install [-g] Cmd4
// - mkdir $HOME/.homebridge/Cmd4Scripts
// - cp State.js $HOME/.homebridge/Cmd4Scripts/State.js
// - chmod u+x $HOME/.homebridge/Cmd4Scripts/State.js
//
// Parameters are:
// Get <Accessory name> <Characteristic>
// Set <Accessory name> <Characteristic> <value>
//
// Note 1: These paramaters match the those of the Cmd4 plugin.
// Note 2: All characteristics of the HomeKit Accessory Protocol (HAP)
// specifications are supported, except for camera streaming;
// Though I have found some like LockManagement are not within IOS.
// Side Note: The Eve app is nicer, try it.
// Note 3: The Hap spec is quoted throughout, but this is not the spec so
// this is not gauranteed to be correct.
// Note 4: In this latest version, State.js does not care which characteristic
// belongs with a specific accessssory for the purpose of creating
// custom accessories. IOS would not like this, but if you use
// the provided config.json file that defines the corrrect characteristics,
// this will not matter.
//
// How it works:
// A characteristic value is stored in the $HOME/.homebridge/Cmd4Scripts/Cmd4States in
// the file named "Status + <Accessory Name> _ <Characteristic>" and is returned or
// written to based on the <Get|Set> option.
//
// For example: node State.js Set My_Door MotionDetected 0
// will create the file .homebridge/Cmd4Scripts/Cmd4States/My_Door_MotionDetected
// with the contents of "0"
// Note: See how this is run from $HOME (Important!)
//
// Environmental Variables Used:
// $HOME - to create path to .homebridge
//
//
// Interesting fact. If you use characteristics as is, and define all possible accessories in your config.json
// file, they will all appear fully functional within HomeKit. In this way you
// can play with HomeKit and not have any physical devices at all. Cool Eh!
//
// *IMPORTANT* - I cannot audit commented constants. The lib/CMD4_DEVICE_ENUM.js is what is used
// for constant translations.
'use strict';
// FileSystem requirements.
var fs = require('fs');
var path = require('path');
var os = require('os');
// The files created within Cmd4StatesPath contain just a value of the accessories last state,
// so they are very small in size.
var Cmd4StatesPath = path.join(os.homedir(), ".homebridge/Cmd4Scripts/Cmd4States");
var length = process.argv.length;
var device = "";
var io = "";
var characteristic = "";
var option = "";
if ( length == 2 ) process.exit( 0 );
if ( length <= 2 ) {
process.stdout.write( `Usage: ${ process.argv[ 0 ] } <Get> <AccessoryName> <Characteristic>` );
process.stdout.write( ` ${ process.argv[ 0 ] } <Set> <AccessoryName> <Characteristic> <Value>` );
process.exit(-1);
}
if ( length >= 2 ) io = process.argv[ 2 ];
if ( length >= 3 ) device = process.argv[ 3 ];
if ( length >= 4 ) characteristic = process.argv[ 4 ];
if ( length >= 5 ) option = process.argv[ 5 ];
// Placing the states in a subdirectory makes things look cleaner.
// Some platforms require an exception handler
const mkdirSync = function( dirPath )
{
try {
fs.mkdirSync( dirPath )
} catch ( err ) {
if ( err.code !== 'EEXIST' )
{
process.stdout.write( `mkdir failed: ${ dirPath }`);
throw err;
} else {
// directory already exists - OK
}
}
}
mkdirSync( Cmd4StatesPath );
// Such a simple way to store state information that is small and fast!
// Put exception handling here too. Just in case!
function writeData( a, b,c )
{
var fn = Cmd4StatesPath + "/Status_" + a + "_" + b;
try {
fs.writeFileSync( fn,c );
} catch (err) {
if ( err.code !== 'EEXIST' )
{
process.stdout.write( `write data failed: ${ fn } data: ${ c }` );
throw err;
} else {
// file already exists - OK
}
}
}
// Read the state information. If there is none, just return what
// was expected.
// Put exception handling here too. Just in case!
function readData( a, b )
{
var fn = Cmd4StatesPath + "/Status_" + a + "_" + b;
c = "";
try {
c = String( fs.readFileSync( fn, 'utf8' ) );
} catch (err) {
if ( err.code === 'ENOENT' ) {
// This is OK. just return what was expected.
return c;
} else
{
if ( err.code !== 'EEXIST' )
{
process.stdout.write( `read data failed: ${ fn }` );
throw err;
} else {
// file already exists - OK
}
}
}
return c;
}
var c = "";
switch( io )
{
case "Get":
{
switch( characteristic )
{
case "Active": // 3
{
c = readData( device, characteristic );
if ( c == "" ) process.stdout.write( "1" ); else process.stdout.write( `"${ c }"` );
// UUID: 000000B0-0000-1000-8000-0026BB765291
// Type: public.hap.characteristic.active
// Permissions: Paired Write, Paired Read, Notify
// Format: uint8
// Minimum Value: 0
// Maximum Value: 1
// Step Value: 1
// Valid Values
// 0 - "INACTIVE"
// 1 - "ACTIVE"
break;
}
case "CurrentAirPurifierState": // 34
{
c = readData( device, characteristic );
if ( c == "" ) process.stdout.write( "2" ); else process.stdout.write( `"${ c }"` );
// UUID: 000000A9-0000-1000-8000-0026BB765291
// Type: public.hap.characteristic.air-purifier.state.current
// Permissions: Paired Read, Notify
// Format: uint8
// Minimum Value: 0
// Maximum Value: 2
// Step Value: 1
// Valid Values:
// 0 - "INACTIVE"
// 1 - "IDLE"
// 2 - "PURIFYING_AIR"
break;
}
case "LockPhysicalControls": // 86
{
c = readData( device, characteristic );
if ( c == "" ) process.stdout.write( "0" ); else process.stdout.write( `"${ c }"` );
// UUID: 000000A7-0000-1000-8000-0026BB765291
// Type: public.hap.characteristic.lock-physical-controls
// Permissions: Paired Write,Paired Read, Notify
// Format: uint8
// Minimum Value: 0
// Maximum Value: 1
// Step Value: 1
// Valid Values:
// 0 - "CONTROL_LOCK_DISABLED" - Control lock disabled
// 1 - "CONTROL_LOCK_ENABLED" - Control lock enabled
break;
}
case "Name": // 97
{
c = readData( device, characteristic );
if ( c == "" ) process.stdout.write( `"${ device }"` ); else process.stdout.write( `"${ c }"` );
// UUID: 00000023-0000-1000-8000-0026BB765291
// Type: public.hap.characteristic.name
// Permissions: Paired Read, Notify
// Format: string
// Maximum Length: 64
break;
}
case "RotationSpeed": // 139
{
c = readData( device, characteristic );
if ( c == "" ) process.stdout.write( "100" ); else process.stdout.write( `"${ c }"` );
// UUID: 00000029-0000-1000-8000-0026BB765291
// Type: public.hap.characteristic.rotation.speed
// Permissions: Paired Read, Paired Write, Notify
// Format: float
// Minimum Value: 0
// Maximum Value: 100
// Step Value: 1
// Unit: percentage
break;
}
case "SwingMode": // 180
{
c = readData( device, characteristic );
if ( c == "" ) process.stdout.write( "100" ); else process.stdout.write( `"${ c }"` );
// UUID: 000000B6-0000-1000-8000-0026BB765291
// Type: public.hap.characteristic.swing-mode
// Permissions: Paired Read, Notify, Paired Write
// Format: uint8
// Minimum Value: 0
// Maximum Value: 1
// Step Value: 1
// Valid Values:
// 0 - "SWING_DISABLED"
// 1 - "SWING_ENABLED"
break;
}
case "TargetAirPurifierState": // 181
{
c = readData( device, characteristic );
if ( c == "" ) process.stdout.write( "1" ); else process.stdout.write( `"${ c }"` );
// UUID: 000000A8-0000-1000-8000-0026BB765291
// Type: public.hap.characteristic.air-purifier.state.target
// Permissions: Paired Write,Paired Read, Notify
// Format: uint8
// Minimum Value: 0
// Maximum Value: 1
// Step Value: 1
// Valid Values:
// 0 - "MANUAL"
// 1 - "AUTO"
break;
}
default:
process.stderr.write( `Unknown Characteristic for: ${ io } Device: ${ device } Characteristic: ${ characteristic }` );
process.exit( -1 );
}
break;
} // End of Switch for "Get"
case "Set":
{
switch( characteristic )
{
case "Active": // 3
{
writeData( device, characteristic, option );
// Off
if ( option == 0 )
{
// InActive - 0
writeData( device, "CurrentAirPurifierState", 0 );
// Auto Mode - 1
writeData( device, "TargetAirPurifierState", 1 );
} else // On
{
// Purifying Air - 2
writeData( device, "CurrentAirPurifierState", 2 );
// Auto Mode - 1
writeData( device, "TargetAirPurifierState", 1 );
}
break;
}
case "CurrentAirPurifierState": // 34
{
writeData( device, characteristic, option );
// Not settable in Hap Spec, here for debugging.
break;
}
case "LockPhysicalControls": // 86
{
writeData( device, characteristic, option );
break;
}
case "Name": // 97
{
writeData( device, characteristic, option );
// Not settable in Hap Spec, here for debugging.
break;
}
case "RotationSpeed": // 139
{
writeData( device, characteristic, option );
break;
}
case "SwingMode": // 180
{
writeData( device, characteristic, option );
break;
}
case "TargetAirPurifierState": // 181
{
writeData( device, characteristic, option );
// Set to done
write
gitextract_pta7yrh3/
├── .eslintrc.json
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.md
│ │ ├── feature-request.md
│ │ └── support-request.md
│ └── pull_request_template.md
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── Cmd4Accessory.js
├── Cmd4Platform.js
├── Cmd4PriorityPollingQueue.js
├── Extras/
│ ├── Cmd4Scripts/
│ │ ├── CheckYourScript.sh
│ │ ├── Examples/
│ │ │ ├── AirPurifier.js
│ │ │ ├── AnyDevice
│ │ │ ├── DoorLock.sh
│ │ │ ├── ExampleJavaScript_template.js
│ │ │ ├── ExampleShellScript_template.sh
│ │ │ ├── PS4.sh
│ │ │ ├── PS5.sh
│ │ │ ├── SecuritySystem.js
│ │ │ ├── advanced_ping.sh
│ │ │ ├── basic_ping.sh
│ │ │ ├── middleWare.sh
│ │ │ └── wakeonlan.sh
│ │ └── State.js
│ ├── config.json
│ ├── config.min.json
│ └── jsmin.c
├── LICENSE
├── README.md
├── RUNNING_CHANGELOG.md
├── cmd4Constants.js
├── cmd4Settings.js
├── commitlint.config.js
├── docs/
│ ├── AdvancedTroubleShooting.md
│ ├── Developers.md
│ ├── autoGenerated/
│ │ └── CMD4_AccessoryDescriptions.html
│ └── index.html
├── index.js
├── lib/
│ ├── CMD4_ACC_TYPE_ENUM.js
│ ├── CMD4_CHAR_TYPE_ENUMS.js
│ └── CMD4_DEVICE_TYPE_ENUM.js
├── package.json
├── postinstall.js
├── test/
│ ├── CMD4_ACC_TYPE_ENUM.js
│ ├── CMD4_CHAR_TYPE_ENUMS.js
│ ├── CMD4_DEVICE_TYPE_ENUM.js
│ ├── Cmd4Accessory.js
│ ├── Cmd4AccessoryGetValue.js
│ ├── Cmd4AccessorySetValue.js
│ ├── Cmd4Mode.js
│ ├── Cmd4Platform.js
│ ├── Cmd4PlatformRestartTests.js
│ ├── Cmd4PriorityPollingQueue.js
│ ├── Cmd4Storage.js
│ ├── HV.js
│ ├── Logger.js
│ ├── VariableTimer.js
│ ├── allTests
│ ├── async-dump.js
│ ├── cmd4Constants.js
│ ├── configHasCharacteristicProps.js
│ ├── configTest.js
│ ├── echoScripts/
│ │ ├── echo_0
│ │ ├── echo_1
│ │ ├── echo_ACTIVE
│ │ ├── echo_DISABLED
│ │ ├── echo_ENABLED
│ │ ├── echo_INACTIVE
│ │ ├── echo_On
│ │ ├── echo_after5seconds
│ │ ├── echo_errorToStderr
│ │ ├── echo_false
│ │ ├── echo_nothing
│ │ ├── echo_null
│ │ ├── echo_nullAndErrorToStderr
│ │ ├── echo_quoted0
│ │ ├── echo_quoted1
│ │ ├── echo_quotedFALSE
│ │ ├── echo_quotedNULL
│ │ ├── echo_quotedNothing
│ │ ├── echo_quotedTRUE
│ │ ├── echo_too_much
│ │ ├── echo_true
│ │ ├── echo_true_withRcOf1
│ │ ├── justExitWithRCof0
│ │ ├── justExitWithRCof1
│ │ ├── runToTimeoutRcOf0
│ │ ├── runToTimeoutRcOf1
│ │ └── testGetSetValues.js
│ ├── extractKeyValue.js
│ ├── fakeGato.js
│ ├── getAccessoryNameFunctions.js
│ ├── getAccessoryUUID.js
│ ├── getSetAllValues.js
│ ├── indexOfEnum.js
│ ├── initPluginTest.js
│ ├── internalRelatedTargetTests.js
│ ├── isAccDirective.js
│ ├── isCmd4Directive.js
│ ├── isDevDirective.js
│ ├── isJSON.js
│ ├── isNumeric.js
│ ├── loadPluginTest.js
│ ├── mocha-setup
│ ├── pollingTest.js
│ ├── sanityTests
│ ├── systemTest.js
│ ├── testAdvAirGetSet.js
│ ├── testOurConfig.json.js
│ ├── transposeCMD4Props.js
│ ├── trueTypeOf.js
│ └── versionChecker.js
├── tools/
│ ├── Cmd4AccDocGenerator
│ ├── generateChangeLog
│ └── whereIsConstant
└── utils/
├── Cmd4Storage.js
├── HV.js
├── Logger.js
├── VariableTimer.js
├── createAccessorysInformationService.js
├── extractKeyValue.js
├── getAccessoryNameFunctions.js
├── getAccessoryUUID.js
├── indexOfEnum.js
├── indexOfEnumLintTest.js
├── isAccDirective.js
├── isCmd4Directive.js
├── isDevDirective.js
├── isJSON.js
├── isNumeric.js
├── lcFirst.js
├── transposeCMD4Props.js
├── trueTypeOf.js
├── ucFirst.js
└── versionChecker.js
SYMBOL INDEX (223 symbols across 46 files)
FILE: Cmd4Accessory.js
constant CMD4_ACC_TYPE_ENUM (line 41) | let CMD4_ACC_TYPE_ENUM = require( "./lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC_...
constant CMD4_DEVICE_TYPE_ENUM (line 42) | let CMD4_DEVICE_TYPE_ENUM = require( "./lib/CMD4_DEVICE_TYPE_ENUM" ).CMD...
function removeFromArray (line 53) | function removeFromArray( arr, val )
class Cmd4Accessory (line 80) | class Cmd4Accessory
method constructor (line 82) | constructor( log, config, api, STORED_DATA_ARRAY, parentInfo )
method identify (line 361) | identify( callback )
method getServices (line 366) | getServices( )
method addRequiredCharacteristicStoredValues (line 380) | addRequiredCharacteristicStoredValues ( )
method checkPollingConfigForUnsetCharacteristics (line 428) | checkPollingConfigForUnsetCharacteristics( pollingConfig )
method createServicesForStandaloneAccessoryAndItsChildren (line 534) | createServicesForStandaloneAccessoryAndItsChildren( accessory )
method setCachedValue (line 614) | setCachedValue( accTypeEnumIndex, characteristicString, value, callback )
method getCachedValue (line 657) | getCachedValue( accTypeEnumIndex, characteristicString, callback )
method configHasCharacteristicProps (line 680) | configHasCharacteristicProps( accTypeEnumIndex )
method checkCharacteristicNeedsFixing (line 724) | checkCharacteristicNeedsFixing( accessory, accTypeEnumIndex )
method addAllServiceCharacteristicsForAccessory (line 775) | addAllServiceCharacteristicsForAccessory( accessory )
method updateAccessoryAttribute (line 942) | updateAccessoryAttribute( accTypeEnumIndex, value )
method setupAccessoryFakeGatoService (line 1114) | setupAccessoryFakeGatoService( fakegatoConfig )
method validateStateCmd (line 1248) | validateStateCmd( state_cmd )
method parseKeyForCharacteristics (line 1258) | parseKeyForCharacteristics( key, value, parseConfigShouldUseCharacteri...
method processRequires (line 1307) | processRequires( requiresArray )
method processConstants (line 1332) | processConstants( constantsArgArray )
method processVariables (line 1368) | processVariables( variablesArgArray )
method accessoryTypeConfigToCmd4Accessories (line 1402) | accessoryTypeConfigToCmd4Accessories( config, parentInfo )
method processURL (line 1427) | processURL( url )
method replaceConstantsInString (line 1435) | replaceConstantsInString( orig )
method parseConfig (line 1447) | parseConfig( config, parseConfigShouldUseCharacteristicValues )
method lookupAccessoryHVForPollingCharacteristic (line 1786) | lookupAccessoryHVForPollingCharacteristic( accessory, accTypeEnumIndex )
method getDevicesRelatedTargetAccTypeEnumIndex (line 1818) | getDevicesRelatedTargetAccTypeEnumIndex( accCurrentEnumIndex )
method getDevicesRelatedCurrentAccTypeEnumIndex (line 1838) | getDevicesRelatedCurrentAccTypeEnumIndex( accTargetEnumIndex )
method determineCharacteristicsToPollForAccessory (line 1854) | determineCharacteristicsToPollForAccessory( accessory )
function checkAccessoryForDuplicateUUID (line 2050) | function checkAccessoryForDuplicateUUID( accessory, uuid )
FILE: Cmd4Platform.js
constant CMD4_CHAR_TYPE_ENUMS (line 26) | let CMD4_CHAR_TYPE_ENUMS = require( "./lib/CMD4_CHAR_TYPE_ENUMS" ).CMD4_...
constant CMD4_DEVICE_TYPE_ENUM (line 27) | let CMD4_DEVICE_TYPE_ENUM = require( "./lib/CMD4_DEVICE_TYPE_ENUM" ).CMD...
constant CMD4_ACC_TYPE_ENUM (line 28) | let CMD4_ACC_TYPE_ENUM = require( "./lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC_...
constant CMD4_FORMAT_TYPE_ENUM (line 30) | let CMD4_FORMAT_TYPE_ENUM = CMD4_CHAR_TYPE_ENUMS.CMD4_FORMAT_TYPE_ENUM;
constant CMD4_UNITS_TYPE_ENUM (line 31) | let CMD4_UNITS_TYPE_ENUM = CMD4_CHAR_TYPE_ENUMS.CMD4_UNIT_TYPE_ENUM;
constant CMD4_PERMS_TYPE_ENUM (line 32) | let CMD4_PERMS_TYPE_ENUM = CMD4_CHAR_TYPE_ENUMS.CMD4_PERMS_TYPE_ENUM;
class Cmd4Platform (line 42) | class Cmd4Platform
method constructor (line 44) | constructor( log, config, api )
method configureAccessory (line 149) | configureAccessory( platformAccessory )
method removeAccessory (line 158) | removeAccessory( platformAccessory )
method parseConfigForCmd4Directives (line 182) | parseConfigForCmd4Directives( config )
method processNewCharacteristicDefinitions (line 279) | processNewCharacteristicDefinitions( )
method discoverDevices (line 363) | discoverDevices( )
method createServicesForAccessoriesChildren (line 560) | createServicesForAccessoriesChildren( cmd4PlatformAccessory, fromExist...
method startPolling (line 692) | startPolling( queuedStartDelay = 40000 )
FILE: Cmd4PriorityPollingQueue.js
constant CMD4_ACC_TYPE_ENUM (line 8) | let CMD4_ACC_TYPE_ENUM = require( "./lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC_...
constant HIGH_PRIORITY_SET (line 28) | let HIGH_PRIORITY_SET = 0;
constant HIGH_PRIORITY_GET (line 29) | let HIGH_PRIORITY_GET = 1;
constant LOW_PRIORITY_GET (line 30) | let LOW_PRIORITY_GET = 2;
class Cmd4PriorityPollingQueue (line 32) | class Cmd4PriorityPollingQueue
method constructor (line 34) | constructor( log, queueName, queueType = constants.DEFAULT_QUEUE_TYPE,...
method echoRetryErrors (line 77) | echoRetryErrors( currentRetryCount )
method prioritySetValue (line 93) | prioritySetValue( accTypeEnumIndex, characteristicString, timeout, sta...
method priorityGetValue (line 154) | priorityGetValue( accTypeEnumIndex, characteristicString, timeout, hom...
method addLowPriorityGetPolledQueueEntry (line 183) | addLowPriorityGetPolledQueueEntry( accessory, accTypeEnumIndex, charac...
method processHighPrioritySetQueue (line 190) | processHighPrioritySetQueue( entry )
method processHighPriorityGetQueue (line 289) | processHighPriorityGetQueue( entry )
method processEntryFromLowPriorityQueue (line 354) | processEntryFromLowPriorityQueue( entry )
method qGetValue (line 424) | qGetValue( accessory, accTypeEnumIndex, characteristicString, timeout,...
method qSetValue (line 604) | qSetValue( accessory, accTypeEnumIndex, characteristicString, timeout,...
method processWormQueue (line 667) | processWormQueue( lastTransactionType, queue, lowPriorityEntry = null )
method processSequentialQueue (line 766) | processSequentialQueue( lastTransactionType, queue, lowPriorityEntry =...
method processPassThruQueue (line 836) | processPassThruQueue( lastTransactionType, queue, lowPriorityEntry = n...
method scheduleLowPriorityEntry (line 861) | scheduleLowPriorityEntry( entry )
method pauseQueue (line 887) | pauseQueue( queue )
method printQueueStats (line 908) | printQueueStats( queue )
method dumpQueue (line 915) | dumpQueue( queue )
method startQueue (line 926) | startQueue( queue, allDoneCallback )
method changeQueueType (line 1007) | changeQueueType( queue, queueType )
method isCharacteristicPolled (line 1045) | isCharacteristicPolled( accTypeEnumIndex, queue, accessory )
FILE: Extras/Cmd4Scripts/Examples/AirPurifier.js
function writeData (line 106) | function writeData( a, b,c )
function readData (line 126) | function readData( a, b )
FILE: Extras/Cmd4Scripts/Examples/SecuritySystem.js
function writeData (line 109) | function writeData( a, b,c )
function readData (line 129) | function readData( a, b )
FILE: Extras/Cmd4Scripts/State.js
function writeData (line 106) | function writeData( a, b,c )
function readData (line 126) | function readData( a, b )
FILE: Extras/jsmin.c
function getCharFromStdin (line 49) | int getCharFromStdin()
function outputChar (line 74) | void outputChar(int c)
function jsmin (line 119) | static void
function main (line 275) | extern int
FILE: index.js
function checkForUpdates (line 66) | function checkForUpdates( )
FILE: lib/CMD4_ACC_TYPE_ENUM.js
function stringToBool (line 8) | function stringToBool( str )
function stringToFloat (line 17) | function stringToFloat( str )
function stringToInt (line 27) | function stringToInt( str )
function stringToString (line 39) | function stringToString( str )
function stringPassThru (line 49) | function stringPassThru( str )
function stringToTLV8 (line 53) | function stringToTLV8( str )
class CustomCharacteristic (line 58) | class CustomCharacteristic
method constructor (line 60) | constructor( api, type, UUID, props )
FILE: test/CMD4_DEVICE_TYPE_ENUM.js
function testCharacteristicIndex (line 378) | function testCharacteristicIndex ( accTypeEnumIndex, rindex )
FILE: test/Cmd4Accessory.js
constant CMD4_ACC_TYPE_ENUM (line 12) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 13) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/Cmd4AccessoryGetValue.js
constant CMD4_ACC_TYPE_ENUM (line 16) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 17) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/Cmd4AccessorySetValue.js
constant CMD4_ACC_TYPE_ENUM (line 18) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 19) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, Servic...
FILE: test/Cmd4Mode.js
constant CMD4_ACC_TYPE_ENUM (line 17) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 18) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/Cmd4Platform.js
constant CMD4_CHAR_TYPE_ENUMS (line 18) | let CMD4_CHAR_TYPE_ENUMS = CHAR_DATA.init( _api.hap.Formats, _api.hap.Un...
constant CMD4_ACC_TYPE_ENUM (line 19) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 20) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/Cmd4PlatformRestartTests.js
constant TEST_BASE_DIR (line 16) | const TEST_BASE_DIR = path.join(__dirname, "tmp");
function rand (line 20) | function rand(prefix)
function randDir (line 24) | function randDir ()
function loadCachedPlatformAccessoriesFromDisk (line 30) | function loadCachedPlatformAccessoriesFromDisk( accessoryStorage, cached...
function restoreCachedPlatformAccessories (line 47) | function restoreCachedPlatformAccessories( cmd4Platform, cachedPlatformA...
function saveCachedPlatformAccessoriesOnDisk (line 57) | function saveCachedPlatformAccessoriesOnDisk( cachedPlatformAccessories,...
constant CMD4_ACC_TYPE_ENUM (line 77) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 78) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/Cmd4PriorityPollingQueue.js
constant HIGH_PRIORITY_SET (line 14) | let HIGH_PRIORITY_SET = 0;
constant HIGH_PRIORITY_GET (line 15) | let HIGH_PRIORITY_GET = 1;
constant CMD4_ACC_TYPE_ENUM (line 23) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 24) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
function dummyCallback (line 27) | function dummyCallback( )
FILE: test/Cmd4Storage.js
constant CMD4_ACC_TYPE_ENUM (line 10) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
FILE: test/Logger.js
constant CMD4_ACC_TYPE_ENUM (line 11) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 12) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/async-dump.js
method init (line 25) | init(asyncId, type, triggerAsyncId)
method destroy (line 29) | destroy( asyncId ) {
FILE: test/configHasCharacteristicProps.js
constant CMD4_ACC_TYPE_ENUM (line 16) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 17) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/configTest.js
constant CMD4_ACC_TYPE_ENUM (line 36) | let CMD4_ACC_TYPE_ENUM = cmd4.CMD4_ACC_TYPE_ENUM;
constant CMD4_DEVICE_TYPE_ENUM (line 37) | let CMD4_DEVICE_TYPE_ENUM = cmd4.CMD4_DEVICE_TYPE_ENUM;
function testAccessoryConfig (line 50) | function testAccessoryConfig ( accessoryConfig )
function testConstantKey (line 174) | function testConstantKey( key )
function processConstantsConfig (line 186) | function processConstantsConfig( config )
function testVariableKey (line 213) | function testVariableKey( key )
function processVariablesConfig (line 224) | function processVariablesConfig( config )
function processLinkedTypesConfig (line 257) | function processLinkedTypesConfig( config )
function testType (line 281) | function testType( type )
function testName (line 289) | function testName( name )
function testModel (line 296) | function testModel ( model )
function testStateChangeResponseTime (line 303) | function testStateChangeResponseTime ( stateChangeResponseTime )
function testInterval (line 310) | function testInterval( interval )
function testCmd4_Mode (line 317) | function testCmd4_Mode( cmd4Mode )
function testTimeout (line 325) | function testTimeout( timeout )
function testStateCmd (line 332) | function testStateCmd ( state_cmd )
function testCharacteristicString (line 339) | function testCharacteristicString ( characteristic )
function testCharacteristic (line 353) | function testCharacteristic ( characteristic, value )
function testPollingConfig (line 392) | function testPollingConfig( pollingConfig )
function testFakegatoConfig (line 461) | function testFakegatoConfig( fakegatoConfig )
FILE: test/echoScripts/testGetSetValues.js
function showHelp (line 5) | function showHelp()
function createDeviceFileName (line 25) | function createDeviceFileName( fn, DEVICE, CHARACTERISTIC )
function doGet (line 30) | function doGet( )
function doSet (line 52) | function doSet( )
constant ELEMENTS (line 88) | let ELEMENTS = process.argv.length;
constant DEVICE (line 92) | let DEVICE = process.argv[3];
constant CHARACTERISTIC (line 93) | let CHARACTERISTIC = process.argv[4];
FILE: test/fakeGato.js
constant CMD4_ACC_TYPE_ENUM (line 19) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 20) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/getSetAllValues.js
function removeStateFileThatShouldAlreadyBeRemoved (line 36) | function removeStateFileThatShouldAlreadyBeRemoved( characteristicString )
FILE: test/indexOfEnum.js
constant CMD4_ACC_TYPE_ENUM (line 13) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 14) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/internalRelatedTargetTests.js
constant CMD4_ACC_TYPE_ENUM (line 13) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 14) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/isAccDirective.js
constant CMD4_ACC_TYPE_ENUM (line 12) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
FILE: test/isCmd4Directive.js
constant CMD4_ACC_TYPE_ENUM (line 12) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
FILE: test/isDevDirective.js
constant CMD4_ACC_TYPE_ENUM (line 13) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 14) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/loadPluginTest.js
function getIndexOfValue (line 13) | function getIndexOfValue( obj, value )
FILE: test/pollingTest.js
constant CMD4_ACC_TYPE_ENUM (line 20) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 21) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/systemTest.js
constant HIGH_PRIORITY_SET (line 13) | let HIGH_PRIORITY_SET = 0;
constant CMD4_ACC_TYPE_ENUM (line 22) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 23) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/testAdvAirGetSet.js
constant HIGH_PRIORITY_GET (line 14) | let HIGH_PRIORITY_GET = 1;
constant CMD4_ACC_TYPE_ENUM (line 22) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 23) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: test/transposeCMD4Props.js
constant CMD4_ACC_TYPE_ENUM (line 17) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic, _api.ha...
constant CMD4_DEVICE_TYPE_ENUM (line 18) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
function testTransposeConstantTo (line 351) | function testTransposeConstantTo( CMD4_ENUM_properties_obj, accTypeEnumI...
function testTransposeValueTo (line 389) | function testTransposeValueTo( CMD4_ENUM_properties_obj, accTypeEnumInde...
FILE: utils/Cmd4Storage.js
constant CMD4_ACC_TYPE_ENUM (line 4) | let CMD4_ACC_TYPE_ENUM = require( "../lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC...
class Cmd4Storage (line 6) | class Cmd4Storage
method constructor (line 8) | constructor( log, cmd4Storage )
method upgradeDataArray (line 56) | upgradeDataArray( fromVersion, fromData)
method loadLatestData (line 80) | loadLatestData( data )
method getStoredValueForIndex (line 85) | getStoredValueForIndex( accTypeEnumIndex )
method getStoredValueForCharacteristic (line 94) | getStoredValueForCharacteristic( characteristicString )
method setStoredValueForIndex (line 100) | setStoredValueForIndex( accTypeEnumIndex, value )
method setStoredValueForCharacteristic (line 107) | setStoredValueForCharacteristic( characteristicString, value )
method testStoredValueForIndex (line 117) | testStoredValueForIndex( accTypeEnumIndex )
method testStoredValueForCharacteristic (line 124) | testStoredValueForCharacteristic( characteristicString )
FILE: utils/HV.js
constant CMD4_DEVICE_TYPE_ENUM (line 6) | let CMD4_DEVICE_TYPE_ENUM = require( "../lib/CMD4_DEVICE_TYPE_ENUM" ).CM...
class HV (line 8) | class HV
method constructor (line 10) | constructor ()
method update (line 23) | update( entity )
FILE: utils/Logger.js
function getLogPrefix (line 24) | function getLogPrefix( prefix )
class Logger (line 32) | class Logger
method constructor (line 34) | constructor( )
method setDebugEnabled (line 57) | setDebugEnabled( enabled = true )
method setOutputEnabled (line 62) | setOutputEnabled( enabled = true )
method setBufferEnabled (line 71) | setBufferEnabled( enabled = true )
method setTimestampEnabled (line 80) | setTimestampEnabled( enabled = true )
method forceColor (line 87) | forceColor( )
method info (line 91) | info( message, ...parameters )
method warn (line 95) | warn( message, ...parameters )
method error (line 99) | error( message, ...parameters )
method debug (line 103) | debug( message, ...parameters )
method log (line 108) | log( level, message, ...parameters )
method reset (line 164) | reset()
FILE: utils/VariableTimer.js
class VariableTimer (line 43) | class VariableTimer
method constructor (line 45) | constructor ()
method start (line 52) | start( cb , iv )
method execute (line 62) | execute( e )
method stop (line 69) | stop( )
method set_interval (line 74) | set_interval( iv )
FILE: utils/indexOfEnumLintTest.js
constant ACC_DATA (line 21) | let ACC_DATA = require('../lib/CMD4_ACC_TYPE_ENUM');
constant DEVICE_DATA (line 22) | let DEVICE_DATA = require('../lib/CMD4_DEVICE_TYPE_ENUM');
constant CMD4_ACC_TYPE_ENUM (line 24) | let CMD4_ACC_TYPE_ENUM = ACC_DATA.init( _api.hap.Characteristic );
constant CMD4_DEVICE_TYPE_ENUM (line 25) | let CMD4_DEVICE_TYPE_ENUM = DEVICE_DATA.init( CMD4_ACC_TYPE_ENUM, _api.h...
FILE: utils/isAccDirective.js
constant CMD4_ACC_TYPE_ENUM (line 3) | let CMD4_ACC_TYPE_ENUM = require( "../lib/CMD4_ACC_TYPE_ENUM" ).CMD4_ACC...
function isAccDirective (line 16) | function isAccDirective( type, allowUpper = false )
FILE: utils/isCmd4Directive.js
function isCmd4Directive (line 18) | function isCmd4Directive( directive, allowUpper = false )
FILE: utils/isDevDirective.js
constant CMD4_DEVICE_TYPE_ENUM (line 6) | let CMD4_DEVICE_TYPE_ENUM = require( "../lib/CMD4_DEVICE_TYPE_ENUM" ).CM...
function isDevDirective (line 19) | function isDevDirective( deviceName, allowUpper = false )
FILE: utils/isJSON.js
function isJSON (line 9) | function isJSON( m )
FILE: utils/trueTypeOf.js
function trueTypeOf (line 15) | function trueTypeOf( m )
FILE: utils/versionChecker.js
function splitVersion (line 15) | function splitVersion( version )
function getPackagedVersion (line 31) | function getPackagedVersion( )
function isVersionNewerThanPackagedVersion (line 37) | function isVersionNewerThanPackagedVersion( version )
function isUpgrade (line 63) | function isUpgrade( )
Condensed preview — 136 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,250K chars).
[
{
"path": ".eslintrc.json",
"chars": 1193,
"preview": "{\n \"env\": {\n \"browser\": false,\n \"commonjs\": false,\n \"node\": true,\n \"mocha\": true,\n "
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report.md",
"chars": 1699,
"preview": "---\nname: Bug Report\nabout: Raise a bug related report for cmd4.\ntitle: \"[Bug]\"\nlabels: bug\nassignees: ztalbot2000\n\n---\n"
},
{
"path": ".github/ISSUE_TEMPLATE/feature-request.md",
"chars": 992,
"preview": "---\nname: Feature Request\nabout: Suggest an idea or improvement for cmd4.\ntitle: \"[Feature Request]\"\nlabels: enhancement"
},
{
"path": ".github/ISSUE_TEMPLATE/support-request.md",
"chars": 1858,
"preview": "---\nname: Support Request\nabout: Stuck on one of the installation steps or having trouble with your script?\ntitle: \"[Sup"
},
{
"path": ".github/pull_request_template.md",
"chars": 948,
"preview": "---\nname: Pull Request\nabout: Resolve an issue or add an improvement to cmd4.\ntitle: \"[Pull Request]\"\nlabels: pull-reque"
},
{
"path": ".gitignore",
"chars": 296,
"preview": "# Mac\n.DS_Store\n\n# Text editors\n*.swp\n\n# Stupid commits of npm package\n*.tgz\n\n# My favorite temp backup\n*.[0-9]\n*.save*\n"
},
{
"path": ".npmignore",
"chars": 203,
"preview": ".eslintrc.json\n.gitignore\n.github\n.huskyrc\n.DS_Store\n*_save*\n*_bak*\n*.swp\ncommitlint.config.js\njsmin\nutils/indexOfEnumLi"
},
{
"path": "CHANGELOG.md",
"chars": 287,
"preview": "# Homebridges-cmd4 - CMD4 Plugin for Homebridge - Supports ~All Accessory Types and now all Characteristics too!!\n#### 8"
},
{
"path": "Cmd4Accessory.js",
"chars": 94623,
"preview": "'use strict';\n\nconst moment = require( \"moment\" );\n\n// Settings, Globals and Constants\nlet settings = require( \"./cmd4Se"
},
{
"path": "Cmd4Platform.js",
"chars": 32523,
"preview": "'use strict';\n\n// Cmd4 includes seperated out for Unit testing\nconst { getAccessoryName,\n getAccessoryDisplayName"
},
{
"path": "Cmd4PriorityPollingQueue.js",
"chars": 48029,
"preview": "'use strict';\n\n// 3rd Party includes\nconst exec = require( \"child_process\" ).exec;\n\n\n// These would already be initializ"
},
{
"path": "Extras/Cmd4Scripts/CheckYourScript.sh",
"chars": 4251,
"preview": "#!/bin/bash --noprofile --norc\n\n# Fun colour & cursor stuff\nTCR=$(tput cr)\nTCLR=$(tput clear)\nTBLD=$(tput bold)\nTNRM=$(t"
},
{
"path": "Extras/Cmd4Scripts/Examples/AirPurifier.js",
"chars": 11925,
"preview": "#!/usr/bin/env node\n// State.js\n//\n// Description:\n// This script *CAN* be called by the HomeBridge plugin Cmd4 as de"
},
{
"path": "Extras/Cmd4Scripts/Examples/AnyDevice",
"chars": 11013,
"preview": "#!/usr/bin/env node\n\n/*\n\n AnyDevice\n =========\n\n Description:\n ============\n This script is used to fake any C"
},
{
"path": "Extras/Cmd4Scripts/Examples/DoorLock.sh",
"chars": 1798,
"preview": "#!/bin/sh\n\n#\n# In this example we see a Raspberry Pi with a gpio pin that triggers\n# a lock mechanism. The lock is momen"
},
{
"path": "Extras/Cmd4Scripts/Examples/ExampleJavaScript_template.js",
"chars": 4447,
"preview": "#!/usr/bin/env node\n// ExampleScript_template.js\n//\n// Description:\n// This script is a goood starting place for you "
},
{
"path": "Extras/Cmd4Scripts/Examples/ExampleShellScript_template.sh",
"chars": 3545,
"preview": "#!/bin/bash\n\n# ExampleScript_template.sh\n#\n# Description:\n# This script is a goood starting place for you to create C"
},
{
"path": "Extras/Cmd4Scripts/Examples/PS4.sh",
"chars": 4323,
"preview": "#!/bin/bash\n\n\n#\n# This Cmd4 example demonstrates a script that is compatible with\n# cmdSwitch2's example of controlling "
},
{
"path": "Extras/Cmd4Scripts/Examples/PS5.sh",
"chars": 3464,
"preview": "#!/bin/bash\n\n#\n# This Cmd4 script uses playactor to turn on/off a PS5.\n#\n# You will need to install playactor and have t"
},
{
"path": "Extras/Cmd4Scripts/Examples/SecuritySystem.js",
"chars": 11009,
"preview": "#!/usr/bin/env node\n// SecuritySystem.js\n//\n// Description:\n// This script *CAN* be called by the HomeBridge plugin C"
},
{
"path": "Extras/Cmd4Scripts/Examples/advanced_ping.sh",
"chars": 3048,
"preview": "#!/bin/sh\n\n#\n# This Cmd4 example demonstrates a little more advanced way of using ping to test if an\n# accessory is on t"
},
{
"path": "Extras/Cmd4Scripts/Examples/basic_ping.sh",
"chars": 2676,
"preview": "#!/bin/sh\n\n#\n# This Cmd4 example demonstrates how you can test if an accessory is on the network using ping.\n#\n# Your Cm"
},
{
"path": "Extras/Cmd4Scripts/Examples/middleWare.sh",
"chars": 161,
"preview": "#!/bin/bash\nresult=$(node .homebridge/Cmd4Scripts/State.js $* 2>&1)\necho $( date ) >> /tmp/Cmd4.log\necho $* >> /tmp/Cmd4"
},
{
"path": "Extras/Cmd4Scripts/Examples/wakeonlan.sh",
"chars": 4472,
"preview": "#!/bin/bash\n\n#\n# This Cmd4 example demonstrates a script that can be used for a wakeonlan\n# scenario. It is a port of th"
},
{
"path": "Extras/Cmd4Scripts/State.js",
"chars": 116094,
"preview": "#!/usr/bin/env node\n// State.js\n//\n// Description:\n// This script *CAN* be called by the HomeBridge plugin Cmd4 as de"
},
{
"path": "Extras/config.json",
"chars": 37658,
"preview": "{\n \"bridge\":\n {\n \"name\": \"MAC Test Homebridge\",\n \"username\": \"CC:22:3D:E3:CE:30\",\n \"port\": 51826,\n "
},
{
"path": "Extras/config.min.json",
"chars": 114526,
"preview": "{\n \"bridge\":\n {\n \"name\": \"MAC Test Homebridge\",\n \"username\": \"CC:22:3D:E3:CE:30\",\n \"port\": 51826,\n "
},
{
"path": "Extras/jsmin.c",
"chars": 8274,
"preview": "/* jsmin.c\n\nCopyright (c) 2017 John Talbot (www.BeNiceGames.com)\n\n\n- Inspired by jsmin from Douglas Crockford (www.crock"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 11107,
"preview": "<span align=\"center\">\n \n[](https://www.npmjs.com/"
},
{
"path": "RUNNING_CHANGELOG.md",
"chars": 65786,
"preview": "# Homebridges-cmd4 - CMD4 Plugin for Homebridge - Supports ~All Accessory Types and now all Characteristics too!!\n<base "
},
{
"path": "cmd4Constants.js",
"chars": 7249,
"preview": "\"use strict\";\n\n// Naming convention\n// DEFAULT_ => Default values\n// _l => Lower Case\n// _lv => Lo"
},
{
"path": "cmd4Settings.js",
"chars": 708,
"preview": "\"use strict\";\n//\n// This is the name of the platform that users will use to register\n// the plugin in the Homebridge con"
},
{
"path": "commitlint.config.js",
"chars": 871,
"preview": "module.exports = {\n extends: ['@commitlint/config-conventional'] ,\n plugins: ['commitlint-plugin-function-rules'],\n "
},
{
"path": "docs/AdvancedTroubleShooting.md",
"chars": 7239,
"preview": "# Homebridges-cmd4 - Advanced Trouble Shooting.\n\n## Table of Contents\n* [**About Advanced Trouble Shooting**](#about-adv"
},
{
"path": "docs/Developers.md",
"chars": 21059,
"preview": "# Homebridges-cmd4 - Cmd4 Developers Guide.\n<base _target=\"_self\">\n\n\n## Table of Contents\n* [**About CMD4 Developers Gui"
},
{
"path": "docs/autoGenerated/CMD4_AccessoryDescriptions.html",
"chars": 369178,
"preview": "<!DOCTYPE HTML>\n<HTML LANG=\"en\">\n<HEAD>\n<META CHARSET=\"utf-8\">\n<META NAME=\"viewport\" CONTENT=\"width=device-width, initia"
},
{
"path": "docs/index.html",
"chars": 361,
"preview": "<!DOCTYPE html>\n<HTML>\n <HEAD>\n <META http-equiv=\"refresh\" content=\"0; url='https://ztalbot2000.github.io/homeb"
},
{
"path": "index.js",
"chars": 3034,
"preview": "\"use strict\";\n\n//\n// Homebridge\n// Flow / \\\n// "
},
{
"path": "lib/CMD4_ACC_TYPE_ENUM.js",
"chars": 274654,
"preview": "'use strict';\n\n// The sObject.defineProperty is to resolve a lint issue.\n// See utils/indexOfEnumLintTest.js for further"
},
{
"path": "lib/CMD4_CHAR_TYPE_ENUMS.js",
"chars": 3836,
"preview": "'use strict';\n\nvar CMD4_CHAR_TYPE_ENUMS =\n{\n CMD4_FORMAT_TYPE_ENUM:\n {\n BOOL: 0,\n INT"
},
{
"path": "lib/CMD4_DEVICE_TYPE_ENUM.js",
"chars": 129258,
"preview": "'use strict';\n\nconst constants = require( \"../cmd4Constants\" );\n\n\n// The sObject.defineProperty is to resolve a lint iss"
},
{
"path": "package.json",
"chars": 5268,
"preview": "{\n \"name\": \"homebridge-cmd4\",\n \"description\": \"Exec Plugin for Homebridge supporting all accessorys and characterist"
},
{
"path": "postinstall.js",
"chars": 1843,
"preview": "// Post install notes\n\n\n// Fun colour stuff\nconst chalk = require( \"chalk\" );\n\nconst myPkg = require( \"./package.json\" )"
},
{
"path": "test/CMD4_ACC_TYPE_ENUM.js",
"chars": 34309,
"preview": "'use strict';\n\nvar _api = new HomebridgeAPI(); // object we feed to Plugins\nvar Service = _api.hap.Service;\n\n\ndescribe( "
},
{
"path": "test/CMD4_CHAR_TYPE_ENUMS.js",
"chars": 4956,
"preview": "'use strict';\n\nvar _api = new HomebridgeAPI(); // object we feed to Plugins\n\nlet { indexOfEnum } = require( \"../utils/in"
},
{
"path": "test/CMD4_DEVICE_TYPE_ENUM.js",
"chars": 19012,
"preview": "'use strict';\n\nvar _api = new HomebridgeAPI(); // object we feed to Plugins\n\n\ndescribe( \"Testing require of CMD4_DEVICE_"
},
{
"path": "test/Cmd4Accessory.js",
"chars": 57420,
"preview": "\"use strict\";\n\n\n// Settings, Globals and Constants\nlet settings = require( \"../cmd4Settings\" );\nconst constants = requir"
},
{
"path": "test/Cmd4AccessoryGetValue.js",
"chars": 71798,
"preview": "\"use strict\";\n\n// ***************** TEST LOADING **********************\n\n\nlet { Cmd4Platform } = require( \"../Cmd4Platfo"
},
{
"path": "test/Cmd4AccessorySetValue.js",
"chars": 34809,
"preview": "\"use strict\";\n\n// ***************** TEST LOADING **********************\n\n\nlet { Cmd4Accessory } = require( \"../Cmd4Acces"
},
{
"path": "test/Cmd4Mode.js",
"chars": 4099,
"preview": "\"use strict\";\n\n// ***************** TEST LOADING **********************\n\n\nlet { Cmd4Platform } = require( \"../Cmd4Platfo"
},
{
"path": "test/Cmd4Platform.js",
"chars": 34294,
"preview": "#!node\n\n\n// Settings, Globals and Constants\nlet settings = require( \"../cmd4Settings\" );\nlet constants = require( \"../cm"
},
{
"path": "test/Cmd4PlatformRestartTests.js",
"chars": 20398,
"preview": "\"use strict\";\n\nconst settings = require( \"../cmd4Settings\" );\n\nvar __importDefault = (this && this.__importDefault) || f"
},
{
"path": "test/Cmd4PriorityPollingQueue.js",
"chars": 108880,
"preview": "#!node\n\n\n// Settings, Globals and Constants\nlet settings = require( \"../cmd4Settings\" );\nlet constants = require( \"../cm"
},
{
"path": "test/Cmd4Storage.js",
"chars": 14304,
"preview": "\"use strict\";\n\n\nlet Cmd4Storage = require( \"../utils/Cmd4Storage\" );\n\nvar _api = new HomebridgeAPI( ); // object we feed"
},
{
"path": "test/HV.js",
"chars": 5370,
"preview": "\"use strict\";\n\n\nlet HV = require( \"../utils/HV\" );\nlet constants = require( \"../cmd4Constants\" );\n\n\ndescribe(`A Hierarch"
},
{
"path": "test/Logger.js",
"chars": 8254,
"preview": "\"use strict\";\n\n// Settings, Globals and Constants\nlet settings = require( \"../cmd4Settings\" );\n// Let logger control log"
},
{
"path": "test/VariableTimer.js",
"chars": 2717,
"preview": "\"use strict\";\n\n\nlet VariableTimer = require( \"../utils/VariableTimer\" );\n\n\ndescribe('A Variable Timer Test', ( ) =>\n{\n "
},
{
"path": "test/allTests",
"chars": 881,
"preview": "test/Logger.js\ntest/async-dump.js\ntest/configHasCharacteristicProps.js\ntest/isJSON.js\ntest/indexOfEnum.js\ntest/isNumeric"
},
{
"path": "test/async-dump.js",
"chars": 1180,
"preview": "'use strict';\n\n// How To use:\n//\n// 1. In package.json change scipt \"test\", adding:\n// node_modules/.bin/mocha --r"
},
{
"path": "test/cmd4Constants.js",
"chars": 14366,
"preview": "\"use strict\";\n\nvar fs = require(\"fs\");\n\nconst constants = require( \"../cmd4Constants\" );\n\n\ndescribe( \"Testing cmd4Consta"
},
{
"path": "test/configHasCharacteristicProps.js",
"chars": 15510,
"preview": "\"use strict\";\n\n\n// ***************** TEST LOADING **********************\n\n\n\nlet { Cmd4Accessory } = require( \"../Cmd4Acc"
},
{
"path": "test/configTest.js",
"chars": 14620,
"preview": "'use strict';\n\nvar _api = new HomebridgeAPI( ); // object we feed to Plugins\n\nlet indexOfEnum = require( \"../utils/index"
},
{
"path": "test/echoScripts/echo_0",
"chars": 41,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo 0;\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_1",
"chars": 43,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \"1\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_ACTIVE",
"chars": 73,
"preview": "#!/bin/bash\necho \"blast\" > /tmp/blast;\n\nstdbuf -o0 echo ACTIVE;\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_DISABLED",
"chars": 50,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \"DISABLED\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_ENABLED",
"chars": 49,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \"Enabled\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_INACTIVE",
"chars": 48,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo INACTIVE;\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_On",
"chars": 42,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo On;\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_after5seconds",
"chars": 54,
"preview": "#!/bin/bash\n\nsleep 5;\nstdbuf -o0 echo false;\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_errorToStderr",
"chars": 77,
"preview": "#!/bin/bash\n\nstdbuf -e0 -i0 echo \"This message goes to stderr\" >&2;\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_false",
"chars": 45,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo false;\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_nothing",
"chars": 42,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \"\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_null",
"chars": 46,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \"null\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_nullAndErrorToStderr",
"chars": 97,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo null\nstdbuf -o0 e0 echo \"This message goes to stderr\" >&2;\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_quoted0",
"chars": 45,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \\\"0\\\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_quoted1",
"chars": 45,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \\\"1\\\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_quotedFALSE",
"chars": 49,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \\\"False\\\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_quotedNULL",
"chars": 48,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \\\"NULL\\\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_quotedNothing",
"chars": 47,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \\\" \\\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_quotedTRUE",
"chars": 48,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \\\"True\\\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_too_much",
"chars": 115,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo \"\";\nstdbuf -o0 echo \"second extra line\";\nstdbuf -o0 echo \"third extra line\";\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_true",
"chars": 44,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo true;\n\nexit 0;\n"
},
{
"path": "test/echoScripts/echo_true_withRcOf1",
"chars": 44,
"preview": "#!/bin/bash\n\nstdbuf -o0 echo true;\n\nexit 1;\n"
},
{
"path": "test/echoScripts/justExitWithRCof0",
"chars": 21,
"preview": "#!/bin/bash\n\nexit 0;\n"
},
{
"path": "test/echoScripts/justExitWithRCof1",
"chars": 21,
"preview": "#!/bin/bash\n\nexit 1;\n"
},
{
"path": "test/echoScripts/runToTimeoutRcOf0",
"chars": 36,
"preview": "#!/bin/bash\n\nsleep 1000000;\nexit 0;\n"
},
{
"path": "test/echoScripts/runToTimeoutRcOf1",
"chars": 36,
"preview": "#!/bin/bash\n\nsleep 1000000;\nexit 1;\n"
},
{
"path": "test/echoScripts/testGetSetValues.js",
"chars": 2175,
"preview": "#!/opt/homebrew/bin/node\n\nconst fs = require( \"fs\" );\n\nfunction showHelp()\n{\n console.log(`\n Syntax: Get Device <Cha"
},
{
"path": "test/extractKeyValue.js",
"chars": 1755,
"preview": "'use strict';\n\nvar CMD4_ACC_TYPE_ENUM = {\n AccessoryFlags: 0,\n properties: {}\n};\nCMD4_ACC_TYP"
},
{
"path": "test/fakeGato.js",
"chars": 28565,
"preview": "#!node\n\n\n// Settings, Globals and Constants\nlet settings = require( \"../cmd4Settings\" );\n\nlet Cmd4Accessory = require( \""
},
{
"path": "test/getAccessoryNameFunctions.js",
"chars": 6691,
"preview": "'use strict';\n\nconst { getAccessoryName, getAccessoryDisplayName } = require( \"../utils/getAccessoryNameFunctions\" );\n\nd"
},
{
"path": "test/getAccessoryUUID.js",
"chars": 1573,
"preview": "\"use strict\";\n\nvar _api = new HomebridgeAPI(); // object we feed to Plugins\nvar pluginModule = require( \"../index\" );\n\nv"
},
{
"path": "test/getSetAllValues.js",
"chars": 7044,
"preview": "'use strict';\n\nconst os = require( \"os\" );\nconst cmd4StateDir = os.homedir( ) + \"/.homebridge/Cmd4Scripts/Cmd4States/\"\n\n"
},
{
"path": "test/indexOfEnum.js",
"chars": 3367,
"preview": "'use strict';\n\nlet { indexOfEnum } = require( \"../utils/indexOfEnum\" );\nObject.defineProperty(exports, \"indexOfEnum\", { "
},
{
"path": "test/initPluginTest.js",
"chars": 7007,
"preview": "\"use strict\";\n\n\n\n// ***************** TEST LOADING **********************\n\n\nvar pluginModule = require( \"../index\" );\n\n\n"
},
{
"path": "test/internalRelatedTargetTests.js",
"chars": 9860,
"preview": "\"use strict\";\n\n// Settings, Globals and Constants\nlet settings = require( \"../cmd4Settings\" );\n\nvar Cmd4Platform = requi"
},
{
"path": "test/isAccDirective.js",
"chars": 2254,
"preview": "'use strict';\n\nlet { indexOfEnum } = require( \"../utils/indexOfEnum\" );\nObject.defineProperty(exports, \"indexOfEnum\", { "
},
{
"path": "test/isCmd4Directive.js",
"chars": 2099,
"preview": "'use strict';\n\nlet { indexOfEnum } = require( \"../utils/indexOfEnum\" );\nObject.defineProperty(exports, \"indexOfEnum\", { "
},
{
"path": "test/isDevDirective.js",
"chars": 2576,
"preview": "'use strict';\n\nlet { indexOfEnum } = require( \"../utils/indexOfEnum\" );\nObject.defineProperty(exports, \"indexOfEnum\", { "
},
{
"path": "test/isJSON.js",
"chars": 2286,
"preview": "'use strict';\n\nvar isJSON = require( \"../utils/isJSON.js\" );\n\ndescribe( \"Testing isJSON\", ( ) =>\n{\n it( \"isJSON should"
},
{
"path": "test/isNumeric.js",
"chars": 3262,
"preview": "'use strict';\n\nvar isNumeric = require( \"../utils/isNumeric.js\" );\n\ndescribe( \"Testing isNumeric\", ( ) =>\n{\n it( \"isNu"
},
{
"path": "test/loadPluginTest.js",
"chars": 3274,
"preview": "\"use strict\";\n\n// ***************** TEST LOADING **********************\n\n\nvar pluginModule = require( \"../index\" );\nvar "
},
{
"path": "test/mocha-setup",
"chars": 3254,
"preview": "// Define common functions and values for all unit tests.\n\nconst which = require('which');\nconst path = require( \"path\" "
},
{
"path": "test/pollingTest.js",
"chars": 13714,
"preview": "#!node\n\n\n// Settings, Globals and Constants\nlet settings = require( \"../cmd4Settings\" );\nlet constants = require( \"../cm"
},
{
"path": "test/sanityTests",
"chars": 532,
"preview": "test/Cmd4Storage.js\ntest/HV.js\ntest/cmd4Constants.js\ntest/CMD4_CHAR_TYPE_ENUMS.js\ntest/CMD4_ACC_TYPE_ENUM.js\ntest/CMD4_D"
},
{
"path": "test/systemTest.js",
"chars": 13727,
"preview": "#!node\n\n\n// Settings, Globals and Constants\nlet settings = require( \"../cmd4Settings\" );\nlet constants = require( \"../cm"
},
{
"path": "test/testAdvAirGetSet.js",
"chars": 15841,
"preview": "#!node\n\n\n// Settings, Globals and Constants\nlet settings = require( \"../cmd4Settings\" );\nlet constants = require( \"../cm"
},
{
"path": "test/testOurConfig.json.js",
"chars": 538,
"preview": "'use strict';\n\nvar fs = require('fs'),\nParser = require('jsonparse');\n\n\nvar json = fs.readFileSync(\"Extras/config.json\")"
},
{
"path": "test/transposeCMD4Props.js",
"chars": 20120,
"preview": "'use strict';\n\n// ***************** TEST LOADING **********************\n\nvar { transposeConstantToValidValue,\n tran"
},
{
"path": "test/trueTypeOf.js",
"chars": 4163,
"preview": "'use strict';\n\nvar trueTypeOf = require( \"../utils/trueTypeOf.js\" );\n\ndescribe( \"Testing trueTypeOf\", ( ) =>\n{\n it( \"t"
},
{
"path": "test/versionChecker.js",
"chars": 2200,
"preview": "'use strict';\n\nconst { isUpgrade, getLatestVersion, isVersionNewerThanPackagedVersion, getPackagedVersion } = require( \""
},
{
"path": "tools/Cmd4AccDocGenerator",
"chars": 48621,
"preview": "#!/usr/bin/env node\n\n// Note:\n// Must read https://docs.github.com/en/rest/reference/markdown#render-an-arbitrary-markdo"
},
{
"path": "tools/generateChangeLog",
"chars": 9180,
"preview": "#!/usr/bin/env node\n\n'use strict';\n\n// File System utilities\nlet fs = require( \"fs\" );\nlet readline = require( \"readline"
},
{
"path": "tools/whereIsConstant",
"chars": 3876,
"preview": "#!/usr/bin/env node\n\"use strict\";\n\n// File System utilities\nlet fs = require(\"fs\");\n\n// Command line parser\nconst { Comm"
},
{
"path": "utils/Cmd4Storage.js",
"chars": 4629,
"preview": "'use strict';\n\n// These would already be initialized by index.js\nlet CMD4_ACC_TYPE_ENUM = require( \"../lib/CMD4_ACC_TYPE"
},
{
"path": "utils/HV.js",
"chars": 2665,
"preview": "'use strict';\n\nconst constants = require( \"../cmd4Constants\" );\n\n// These would already be initialized by index.js\nlet C"
},
{
"path": "utils/Logger.js",
"chars": 4211,
"preview": "\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function ( mod ) {\n return (mod && mod.__esModu"
},
{
"path": "utils/VariableTimer.js",
"chars": 1970,
"preview": "'use strict';\n\n/* Orig\nvar variableTimer = {\n running: false,\n iv: 5000,\n timeout: false,\n cb : function(){}"
},
{
"path": "utils/createAccessorysInformationService.js",
"chars": 1732,
"preview": "'use strict';\n\nmodule.exports = function createAccessorysInformationService( accessory )\n{\n // Standalone accessories "
},
{
"path": "utils/extractKeyValue.js",
"chars": 353,
"preview": "'use strict';\n\n// Description:\n// Extracts a key from an object given a value.\n//\n// @param obj - The object to get t"
},
{
"path": "utils/getAccessoryNameFunctions.js",
"chars": 1045,
"preview": "'use strict';\n\n// Description:\n// Routines of which given an Accessory Config. Extract the Accessory Name based on\n//"
},
{
"path": "utils/getAccessoryUUID.js",
"chars": 943,
"preview": "'use strict';\n\n// Description:\n// Get or create a Accessories UUID based on what it is configured as.\n//\n// @param co"
},
{
"path": "utils/indexOfEnum.js",
"chars": 659,
"preview": "'use strict';\n\n\n// Description:\n// Create an Object prototype to for getting an index of an enumerated type.\n//\n\nmodu"
},
{
"path": "utils/indexOfEnumLintTest.js",
"chars": 1736,
"preview": "'use strict';\n\n// This is not a utility. This is a testcase, but not a Unit test either.\n// Lint had picked up an error "
},
{
"path": "utils/isAccDirective.js",
"chars": 1643,
"preview": "'use strict';\n\nlet CMD4_ACC_TYPE_ENUM = require( \"../lib/CMD4_ACC_TYPE_ENUM\" ).CMD4_ACC_TYPE_ENUM;\n\n\n// Description:\n// "
},
{
"path": "utils/isCmd4Directive.js",
"chars": 1534,
"preview": "'use strict';\n\nconst constants = require( \"../cmd4Constants\" );\nconst lcFirst = require( \"./lcFirst\" );\n\n\n// Description"
},
{
"path": "utils/isDevDirective.js",
"chars": 1714,
"preview": "'use strict';\n\nlet ucFirst = require( \"../utils/ucFirst\" );\n\n\nlet CMD4_DEVICE_TYPE_ENUM = require( \"../lib/CMD4_DEVICE_T"
},
{
"path": "utils/isJSON.js",
"chars": 843,
"preview": "'use strict';\n\n// Description:\n// Determine if parameter is a true JSON object, not an array, but {}\n//\n// @param m -"
},
{
"path": "utils/isNumeric.js",
"chars": 344,
"preview": "'use strict';\n\n// Description:\n// Determine if a given parameter is numeric.\n//\n// @param num - The number to determi"
},
{
"path": "utils/lcFirst.js",
"chars": 739,
"preview": "'use strict';\n\n// Description:\n// Convert the first character of a string to lower case.\n//\n// @param string - The st"
},
{
"path": "utils/transposeCMD4Props.js",
"chars": 4016,
"preview": "'use strict';\n\n// Description:\n// Routines to convert Cmd4 values to constants and back.\n//\n// @param CMD4_ACC_TYPE_E"
},
{
"path": "utils/trueTypeOf.js",
"chars": 1046,
"preview": "'use strict';\n\n// Description:\n// Determine the true type of an object, because typeOf is screwy\n// for null/undef"
},
{
"path": "utils/ucFirst.js",
"chars": 739,
"preview": "'use strict';\n\n// Description:\n// Convert the first character of a string to upper case.\n//\n// @param string - The st"
},
{
"path": "utils/versionChecker.js",
"chars": 2754,
"preview": "'use strict';\n\n// These routines are used to get this packages\n// version information.\n// It not only uses Promises but "
}
]
About this extraction
This page contains the full source code of the ztalbot2000/homebridge-cmd4 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 136 files (2.1 MB), approximately 546.4k tokens, and a symbol index with 223 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.