homebridge-cmd4
Version:
Exec Plugin for Homebridge supporting all accessorys and characteristics
397 lines (323 loc) • 11.9 kB
JavaScript
// 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.
;
// 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
writeData(device,"CurrentAirPurifierState", option);
break;
}
default:
process.stderr.write( `Unknown Characteristic for: ${ io } Device:${ device } Characteristic:${ characteristic }` );
process.exit( -1 );
}
break;
} // End of Switch Device for "Set"
default:
process.stderr.write( `Unknown IO ${ io }` );
process.exit( -1 );
}
//process.stdout.write( `Say What Device: ${ device } Characteristic: ${ characteristic } Option: ${ option }` );
process.exit( 0 );