├── lib ├── res │ ├── output.jpg │ ├── snapshot.jpg │ ├── snapshot111.jpg │ └── snapshotnew.jpg ├── util │ ├── once.js │ ├── clone.js │ ├── hkdf.js │ ├── uuid.js │ ├── tlv.js │ ├── encryption.js │ ├── eventedhttp.js │ └── chacha20poly1305.js ├── Bridge.js ├── Advertiser.js ├── model │ ├── IdentifierCache.js │ └── AccessoryInfo.js ├── AccessoryLoader.js ├── gen │ ├── import.js │ └── HomeKitTypes-Bridge.js ├── camera │ └── RTPProxy.js ├── Service.js ├── Camera.js └── Characteristic.js ├── task ├── HAPInstaller_Rahul ├── LICENSE ├── motionEyeInstaller_Rahul ├── index.js ├── BridgedCore.js ├── duckdns.js ├── Core.js ├── Full_Installer_Rahul ├── CameraCore.js ├── accessories ├── HumiditySensor_MRz_accessory.js ├── TemperatureSensor_MRz_accessory.js ├── MotionSensorBR_accessory.js ├── DoorBell_accessory.backup.js ├── GarageDoorOpener_accessory.js ├── Fan_accessory.js ├── MotionSensor_MRz_accessory.js ├── Outlet_MRz_accessory.js ├── Lock_accessory.js ├── SecuritySystem_accessory.backup.js ├── types.js ├── Thermostat_accessory.js ├── Light_accessory.js └── RGBLight_MRz_accessory.js └── README.md /lib/res/output.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulmranjith/homekit/HEAD/lib/res/output.jpg -------------------------------------------------------------------------------- /lib/res/snapshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulmranjith/homekit/HEAD/lib/res/snapshot.jpg -------------------------------------------------------------------------------- /lib/res/snapshot111.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulmranjith/homekit/HEAD/lib/res/snapshot111.jpg -------------------------------------------------------------------------------- /lib/res/snapshotnew.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulmranjith/homekit/HEAD/lib/res/snapshotnew.jpg -------------------------------------------------------------------------------- /lib/util/once.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | once: once 4 | } 5 | 6 | function once(func) { 7 | var called = false; 8 | 9 | return function() { 10 | if (called) { 11 | throw new Error("This callback function has already been called by someone else; it can only be called one time."); 12 | } 13 | else { 14 | called = true; 15 | return func.apply(this, arguments); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /task: -------------------------------------------------------------------------------- 1 | cd /home/pi/rmrz/HAP-NodeJS/lib/res 2 | 3 | wget http://localhost:8765/picture/1/current/ -O output.jpg 4 | file=output.jpg 5 | minimumsize=5000 6 | actualsize=$(wc -c <"$file") 7 | if [ $actualsize -ge $minimumsize ]; then 8 | echo image download successful 9 | else 10 | wget http://localhost:8765/picture/1/current/ -O output.jpg 11 | fi 12 | ffmpeg -i /home/pi/rmrz/HAP-NodeJS/lib/res/output.jpg -vf scale=1280:720 /home/pi/rmrz/HAP-NodeJS/lib/res/snapshot.jpg -y 13 | -------------------------------------------------------------------------------- /HAPInstaller_Rahul: -------------------------------------------------------------------------------- 1 | cd /home/pi/rmrz 2 | sudo apt-get update 3 | sudo apt-get remove nodejs nodejs-legacy -y 4 | sudo apt-get install git-core libnss-mdns libavahi-compat-libdnssd-dev -y 5 | sudo wget http://node-arm.herokuapp.com/node_latest_armhf.deb 6 | sudo dpkg -i node_latest_armhf.deb 7 | sudo rm -rf node_latest_armhf.deb 8 | sudo npm install -g node-gyp 9 | sudo git clone https://github.com/KhaosT/HAP-NodeJS.git 10 | cd HAP-NodeJS/ 11 | sudo wget https://goo.gl/u1h0Vc -O task 12 | sudo chmod +x task 13 | sudo npm install 14 | -------------------------------------------------------------------------------- /lib/util/clone.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | module.exports = { 5 | clone: clone 6 | } 7 | 8 | 9 | /** 10 | * A simple clone function that also allows you to pass an "extend" object whose properties will be 11 | * added to the cloned copy of the original object passed. 12 | */ 13 | function clone(object, extend) { 14 | 15 | var cloned = {}; 16 | 17 | for (var key in object) { 18 | cloned[key] = object[key]; 19 | } 20 | 21 | for (var key in extend) { 22 | cloned[key] = extend[key]; 23 | } 24 | 25 | return cloned; 26 | }; 27 | -------------------------------------------------------------------------------- /lib/Bridge.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('./Accessory').Accessory; 2 | var inherits = require('util').inherits; 3 | 4 | module.exports = { 5 | Bridge: Bridge 6 | }; 7 | 8 | /** 9 | * Bridge is a special type of HomeKit Accessory that hosts other Accessories "behind" it. This way you 10 | * can simply publish() the Bridge (with a single HAPServer on a single port) and all bridged Accessories 11 | * will be hosted automatically, instead of needed to publish() every single Accessory as a separate server. 12 | */ 13 | 14 | function Bridge(displayName, serialNumber) { 15 | Accessory.call(this, displayName, serialNumber); 16 | this._isBridge = true; 17 | } 18 | 19 | inherits(Bridge, Accessory); 20 | -------------------------------------------------------------------------------- /lib/util/hkdf.js: -------------------------------------------------------------------------------- 1 | var crypto = require("crypto"); 2 | var bufferShim = require('buffer-shims'); 3 | 4 | module.exports = { 5 | HKDF: HKDF 6 | }; 7 | 8 | function HKDF(hashAlg, salt, ikm, info, size) { 9 | // create the hash alg to see if it exists and get its length 10 | var hash = crypto.createHash(hashAlg); 11 | var hashLength = hash.digest().length; 12 | 13 | // now we compute the PRK 14 | var hmac = crypto.createHmac(hashAlg, salt); 15 | hmac.update(ikm); 16 | var prk = hmac.digest(); 17 | 18 | var prev = bufferShim.alloc(0); 19 | var output; 20 | var buffers = []; 21 | var num_blocks = Math.ceil(size / hashLength); 22 | info = bufferShim.from(info); 23 | 24 | for (var i=0; i 0;) { 31 | if (leftLength >= 255) { 32 | tempBuffer = Buffer.concat([tempBuffer,bufferShim.from([type,0xFF]),data.slice(currentStart, currentStart + 255)]); 33 | leftLength -= 255; 34 | currentStart = currentStart + 255; 35 | } else { 36 | tempBuffer = Buffer.concat([tempBuffer,bufferShim.from([type,leftLength]),data.slice(currentStart, currentStart + leftLength)]); 37 | leftLength -= leftLength; 38 | } 39 | }; 40 | 41 | encodedTLVBuffer = tempBuffer; 42 | } 43 | 44 | // do we have more to encode? 45 | if (arguments.length > 2) { 46 | 47 | // chop off the first two arguments which we already processed, and process the rest recursively 48 | var remainingArguments = Array.prototype.slice.call(arguments, 2); 49 | var remainingTLVBuffer = encode.apply(this, remainingArguments); 50 | 51 | // append the remaining encoded arguments directly to the buffer 52 | encodedTLVBuffer = Buffer.concat([encodedTLVBuffer, remainingTLVBuffer]) 53 | } 54 | 55 | return encodedTLVBuffer; 56 | } 57 | 58 | function decode(data) { 59 | 60 | var objects = {}; 61 | 62 | var leftLength = data.length; 63 | var currentIndex = 0; 64 | 65 | for (; leftLength > 0;) { 66 | var type = data[currentIndex] 67 | var length = data[currentIndex+1] 68 | currentIndex += 2; 69 | leftLength -= 2; 70 | 71 | var newData = data.slice(currentIndex, currentIndex+length); 72 | 73 | if (objects[type]) { 74 | objects[type] = Buffer.concat([objects[type],newData]); 75 | } else { 76 | objects[type] = newData; 77 | } 78 | 79 | currentIndex += length; 80 | leftLength -= length; 81 | }; 82 | 83 | return objects; 84 | } 85 | -------------------------------------------------------------------------------- /accessories/MotionSensorBR_accessory.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | 6 | 7 | // here's a fake temperature sensor device that we'll expose to HomeKit 8 | var FAKE_MOTIONSENSOR = { 9 | isPresent: false, 10 | getState: function() { 11 | console.log("Getting the current state!"); 12 | return FAKE_MOTIONSENSOR.isPresent; 13 | }, 14 | randomState: function() { 15 | // randomize temperature to a value between 0 and 100 16 | FAKE_MOTIONSENSOR.isPresent = !FAKE_MOTIONSENSOR.isPresent; 17 | } 18 | } 19 | 20 | 21 | // Generate a consistent UUID for our Temperature Sensor Accessory that will remain the same 22 | // even when restarting our server. We use the `uuid.generate` helper function to create 23 | // a deterministic UUID based on an arbitrary "namespace" and the string "temperature-sensor". 24 | var sensorUUID = uuid.generate('hap-nodejs:accessories:motion-sensor'); 25 | 26 | // This is the Accessory that we'll return to HAP-NodeJS that represents our fake lock. 27 | var sensor = exports.accessory = new Accessory('BedRoom Motion Sensor', sensorUUID); 28 | 29 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 30 | sensor.username = "11:33:33:AE:53:3A"; 31 | sensor.pincode = "031-45-154"; 32 | 33 | // Add the actual TemperatureSensor Service. 34 | // We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` 35 | sensor 36 | .addService(Service.MotionSensor) 37 | .getCharacteristic(Characteristic.MotionDetected) 38 | .on('get', function(callback) { 39 | 40 | // return our current value 41 | callback(null, FAKE_MOTIONSENSOR.getState()); 42 | }); 43 | 44 | // randomize our temperature reading every 3 seconds 45 | 46 | 47 | 48 | // PIRSensor.watch(function(error, input) { 49 | // if (error) { 50 | // throw error; 51 | // } 52 | // console.log(input); 53 | // 54 | // }) 55 | 56 | 57 | 58 | var mqtt = require('mqtt'); 59 | var client = mqtt.connect('mqtt://localhost'); 60 | 61 | client.on('connect', function() { 62 | client.subscribe('MOTION'); 63 | 64 | client.on('message', function(topic, message) { 65 | if (message == 'ON') { 66 | 67 | //publishMqtt('LIGHTOFF', '^^ motion from PIR'); 68 | sensor 69 | .getService(Service.MotionSensor) 70 | .setCharacteristic(Characteristic.MotionDetected, true); 71 | console.log(message.toString()); 72 | } else { 73 | sensor 74 | .getService(Service.MotionSensor) 75 | .setCharacteristic(Characteristic.MotionDetected, false); 76 | console.log(message.toString()); 77 | 78 | } 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /accessories/DoorBell_accessory.backup.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | 6 | function publishMqtt(type, value) { 7 | console.log(value); 8 | var mqtt = require('mqtt'); 9 | var clients = mqtt.connect('mqtt://localhost'); 10 | clients.publish(type, value.toString()); 11 | } 12 | var Gpio = require('onoff').Gpio, 13 | PIRSensor = new Gpio(27, 'in', 'both'); 14 | 15 | start(); 16 | // here's a fake temperature sensor device that we'll expose to HomeKit 17 | var DoorBell = { 18 | Volume: 18, 19 | switchEvent: 1, 20 | getState: function() { 21 | console.log("Getting the current state!"); 22 | return DoorBell.Volume; 23 | }, 24 | setValue: function(value) { 25 | // randomize temperature to a value between 0 and 100 26 | DoorBell.Volume = value; 27 | } 28 | } 29 | 30 | 31 | // Generate a consistent UUID for our Temperature Sensor Accessory that will remain the same 32 | // even when restarting our server. We use the `uuid.generate` helper function to create 33 | // a deterministic UUID based on an arbitrary "namespace" and the string "temperature-sensor". 34 | var DoorBellUUID = uuid.generate('hap-nodejs:accessories:door-bell'); 35 | 36 | // This is the Accessory that we'll return to HAP-NodeJS that represents our fake lock. 37 | var doorbell = exports.accessory = new Accessory('Door Bell', DoorBellUUID); 38 | 39 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 40 | doorbell.username = "11:11:22:33:44:3A"; 41 | doorbell.pincode = "031-45-154"; 42 | 43 | // Add the actual TemperatureSensor Service. 44 | // We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` 45 | doorbell 46 | .addService(Service.Doorbell) 47 | .getCharacteristic(Characteristic.Volume) 48 | .on('set', function(value, callback) { 49 | DoorBell.setValue(value); 50 | callback(); 51 | }) 52 | .on('get', function(callback) { 53 | // return our current value 54 | callback(null, DoorBell.getState()); 55 | }); 56 | 57 | // randomize our temperature reading every 3 seconds 58 | 59 | 60 | 61 | // PIRSensor.watch(function(error, input) { 62 | // if (error) { 63 | // throw error; 64 | // } 65 | // console.log(input); 66 | // 67 | // }) 68 | 69 | var timer; 70 | 71 | var switchPosition = 1; 72 | 73 | function start() { 74 | timer = setInterval(function() { 75 | 76 | if (switchPosition == 1) { 77 | switchPosition = 0; 78 | } else { 79 | switchPosition = 1; 80 | } 81 | doorbell 82 | .getService(Service.Doorbell) 83 | .setCharacteristic(Characteristic.Volume, switchPosition); 84 | console.log("Volume"); 85 | }, 3000) 86 | } 87 | 88 | function stop() { 89 | clearTimeout(timer); 90 | }; 91 | -------------------------------------------------------------------------------- /accessories/GarageDoorOpener_accessory.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | 6 | 7 | var FAKE_GARAGE = { 8 | opened: false, 9 | open: function() { 10 | console.log("Opening the Garage!"); 11 | //add your code here which allows the garage to open 12 | FAKE_GARAGE.opened = true; 13 | }, 14 | close: function() { 15 | console.log("Closing the Garage!"); 16 | //add your code here which allows the garage to close 17 | FAKE_GARAGE.opened = false; 18 | }, 19 | identify: function() { 20 | //add your code here which allows the garage to be identified 21 | console.log("Identify the Garage"); 22 | }, 23 | status: function(){ 24 | //use this section to get sensor values. set the boolean FAKE_GARAGE.opened with a sensor value. 25 | console.log("Sensor queried!"); 26 | //FAKE_GARAGE.opened = true/false; 27 | } 28 | }; 29 | 30 | var garageUUID = uuid.generate('hap-nodejs:accessories:'+'GarageDoor'); 31 | var garage = exports.accessory = new Accessory('Garage Door', garageUUID); 32 | 33 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 34 | garage.username = "C1:5D:3F:EE:5E:FA"; //edit this if you use Core.js 35 | garage.pincode = "031-45-154"; 36 | 37 | garage 38 | .getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Liftmaster") 40 | .setCharacteristic(Characteristic.Model, "Rev-1") 41 | .setCharacteristic(Characteristic.SerialNumber, "TW000165"); 42 | 43 | garage.on('identify', function(paired, callback) { 44 | FAKE_GARAGE.identify(); 45 | callback(); 46 | }); 47 | 48 | garage 49 | .addService(Service.GarageDoorOpener, "Garage Door") 50 | .setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.CLOSED) // force initial state to CLOSED 51 | .getCharacteristic(Characteristic.TargetDoorState) 52 | .on('set', function(value, callback) { 53 | 54 | if (value == Characteristic.TargetDoorState.CLOSED) { 55 | FAKE_GARAGE.close(); 56 | callback(); 57 | garage 58 | .getService(Service.GarageDoorOpener) 59 | .setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.CLOSED); 60 | } 61 | else if (value == Characteristic.TargetDoorState.OPEN) { 62 | FAKE_GARAGE.open(); 63 | callback(); 64 | garage 65 | .getService(Service.GarageDoorOpener) 66 | .setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.OPEN); 67 | } 68 | }); 69 | 70 | 71 | garage 72 | .getService(Service.GarageDoorOpener) 73 | .getCharacteristic(Characteristic.CurrentDoorState) 74 | .on('get', function(callback) { 75 | 76 | var err = null; 77 | FAKE_GARAGE.status(); 78 | 79 | if (FAKE_GARAGE.opened) { 80 | console.log("Query: Is Garage Open? Yes."); 81 | callback(err, Characteristic.CurrentDoorState.OPEN); 82 | } 83 | else { 84 | console.log("Query: Is Garage Open? No."); 85 | callback(err, Characteristic.CurrentDoorState.CLOSED); 86 | } 87 | }); 88 | -------------------------------------------------------------------------------- /accessories/Fan_accessory.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | 6 | 7 | // here's a fake hardware device that we'll expose to HomeKit 8 | var FAKE_FAN = { 9 | powerOn: false, 10 | setPowerOn: function(on) { 11 | if(on){ 12 | //put your code here to turn on the fan 13 | FAKE_FAN.powerOn = on; 14 | } 15 | else{ 16 | //put your code here to turn off the fan 17 | FAKE_FAN.powerOn = on; 18 | } 19 | }, 20 | setSpeed: function(value) { 21 | console.log("Setting fan rSpeed to %s", value); 22 | //put your code here to set the fan to a specific value 23 | }, 24 | identify: function() { 25 | //put your code here to identify the fan 26 | console.log("Fan Identified!"); 27 | } 28 | } 29 | 30 | // This is the Accessory that we'll return to HAP-NodeJS that represents our fake fan. 31 | var fan = exports.accessory = new Accessory('Fan', uuid.generate('hap-nodejs:accessories:Fan')); 32 | 33 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 34 | fan.username = "1A:2B:3C:4D:5E:FF"; 35 | fan.pincode = "031-45-154"; 36 | 37 | // set some basic properties (these values are arbitrary and setting them is optional) 38 | fan 39 | .getService(Service.AccessoryInformation) 40 | .setCharacteristic(Characteristic.Manufacturer, "Sample Company") 41 | 42 | // listen for the "identify" event for this Accessory 43 | fan.on('identify', function(paired, callback) { 44 | FAKE_FAN.identify(); 45 | callback(); // success 46 | }); 47 | 48 | // Add the actual Fan Service and listen for change events from iOS. 49 | // We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` 50 | fan 51 | .addService(Service.Fan, "Fan") // services exposed to the user should have "names" like "Fake Light" for us 52 | .getCharacteristic(Characteristic.On) 53 | .on('set', function(value, callback) { 54 | FAKE_FAN.setPowerOn(value); 55 | callback(); // Our fake Fan is synchronous - this value has been successfully set 56 | }); 57 | 58 | // We want to intercept requests for our current power state so we can query the hardware itself instead of 59 | // allowing HAP-NodeJS to return the cached Characteristic.value. 60 | fan 61 | .getService(Service.Fan) 62 | .getCharacteristic(Characteristic.On) 63 | .on('get', function(callback) { 64 | 65 | // this event is emitted when you ask Siri directly whether your fan is on or not. you might query 66 | // the fan hardware itself to find this out, then call the callback. But if you take longer than a 67 | // few seconds to respond, Siri will give up. 68 | 69 | var err = null; // in case there were any problems 70 | 71 | if (FAKE_FAN.powerOn) { 72 | callback(err, true); 73 | } 74 | else { 75 | callback(err, false); 76 | } 77 | }); 78 | 79 | // also add an "optional" Characteristic for spped 80 | fan 81 | .getService(Service.Fan) 82 | .addCharacteristic(Characteristic.RotationSpeed) 83 | .on('get', function(callback) { 84 | callback(null, FAKE_FAN.rSpeed); 85 | }) 86 | .on('set', function(value, callback) { 87 | FAKE_FAN.setSpeed(value); 88 | callback(); 89 | }) 90 | -------------------------------------------------------------------------------- /accessories/MotionSensor_MRz_accessory.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | 6 | var cmd = require('node-cmd'); 7 | 8 | function publishMqtt(type, value) {//publish motion detected 9 | console.log(value); 10 | var mqtt = require('mqtt'); 11 | var clients = mqtt.connect('mqtt://localhost'); 12 | clients.publish(type, value.toString()); 13 | } 14 | 15 | var Gpio = require('onoff').Gpio, 16 | PIRSensor = new Gpio(27, 'in', 'both'); //PIN setup for PIR sensors 17 | 18 | start(); 19 | // here's a fake temperature sensor device that we'll expose to HomeKit 20 | var MRz_MOTIONSENSOR = { 21 | isPresent: false, 22 | getState: function() { 23 | console.log("Getting the current state!"); 24 | return MRz_MOTIONSENSOR.isPresent; 25 | }, 26 | randomState: function() { 27 | // randomize temperature to a value between 0 and 100 28 | MRz_MOTIONSENSOR.isPresent = !MRz_MOTIONSENSOR.isPresent; 29 | } 30 | } 31 | 32 | 33 | // Generate a consistent UUID for our Temperature Sensor Accessory that will remain the same 34 | // even when restarting our server. We use the `uuid.generate` helper function to create 35 | // a deterministic UUID based on an arbitrary "namespace" and the string "temperature-sensor". 36 | var sensorUUID = uuid.generate('hap-nodejs:accessories:motion-sensor'); 37 | 38 | // This is the Accessory that we'll return to HAP-NodeJS that represents our fake lock. 39 | var sensor = exports.accessory = new Accessory('Motion Sensor MRz', sensorUUID); 40 | 41 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 42 | sensor.username = "11:39:33:AE:53:3A"; 43 | sensor.pincode = "031-45-154"; 44 | 45 | // Add the actual TemperatureSensor Service. 46 | // We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` 47 | sensor 48 | .addService(Service.MotionSensor) 49 | .getCharacteristic(Characteristic.MotionDetected) 50 | .on('get', function(callback) { 51 | 52 | // return our current value 53 | callback(null, MRz_MOTIONSENSOR.getState()); 54 | }); 55 | 56 | 57 | var timer; 58 | 59 | var foundMotion = false; 60 | //start a timer with set interval of 1 sec to check every second whether any motion has been detected.if it prevails for 2 secs then alert is generated using the mqtt 61 | function start() { 62 | timer = setInterval(function() { 63 | var statePIR = PIRSensor.readSync(); 64 | if (statePIR == 1) { 65 | console.log(statePIR) // motion detected for 2 seconds will trigger alert 66 | if (foundMotion) { 67 | //motion detected for 1 sec 68 | console.log("motion detected...") 69 | cmd.run('sudo sh /home/pi/rmrz/HAP-NodeJS/task'); 70 | sensor 71 | .getService(Service.MotionSensor) 72 | .setCharacteristic(Characteristic.MotionDetected, true); 73 | publishMqtt('MOTION', '**Intrusion**'); // publish mqtt motion intrusion for LCD display 74 | foundMotion = false; 75 | } 76 | if (foundMotion == false) { 77 | foundMotion = true; 78 | } 79 | } else { 80 | sensor 81 | .getService(Service.MotionSensor) 82 | .setCharacteristic(Characteristic.MotionDetected, false); 83 | console.log("NO motion detected...") 84 | foundMotion = false; 85 | } 86 | }, 1000) 87 | } 88 | 89 | function stop() { 90 | clearTimeout(timer); 91 | }; 92 | -------------------------------------------------------------------------------- /accessories/Outlet_MRz_accessory.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | var err = null; // in case there were any problems 6 | 7 | 8 | function publishMqtt(type, value) { 9 | console.log(value); 10 | var mqtt = require('mqtt'); 11 | var clients = mqtt.connect('mqtt://localhost'); 12 | clients.publish(type, value.toString()); 13 | } 14 | // here's a fake hardware device that we'll expose to HomeKit 15 | var MRz_OUTLET = { 16 | setPowerOn: function(on) { 17 | console.log("Turning the outlet %s!...", on ? "on" : "off"); 18 | if (on) { 19 | MRz_OUTLET.powerOn = true; 20 | if (err) { 21 | return console.log(err); 22 | } 23 | publishMqtt("cmnd/sonoff/POWER", "ON");//publishing the sonoff to controll sonoff esp8266 outlet s20 24 | console.log("...outlet is now on."); 25 | } else { 26 | MRz_OUTLET.powerOn = false; 27 | if (err) { 28 | return console.log(err); 29 | } 30 | publishMqtt("cmnd/sonoff/POWER", "OFF");//publishing the sonoff to controll sonoff esp8266 outlet s20 31 | console.log("...outlet is now off."); 32 | } 33 | }, 34 | identify: function() { 35 | console.log("Identify the RMR Power Outlet."); 36 | } 37 | } 38 | 39 | // Generate a consistent UUID for our outlet Accessory that will remain the same even when 40 | // restarting our server. We use the `uuid.generate` helper function to create a deterministic 41 | // UUID based on an arbitrary "namespace" and the accessory name. 42 | var outletUUID = uuid.generate('hap-nodejs:accessories:Outlet'); 43 | 44 | // This is the Accessory that we'll return to HAP-NodeJS that represents our fake light. 45 | var outlet = exports.accessory = new Accessory('Outlet MRz', outletUUID); 46 | 47 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 48 | outlet.username = "1A:11:34:33:33:FF"; 49 | outlet.pincode = "031-45-154"; 50 | 51 | // set some basic properties (these values are arbitrary and setting them is optional) 52 | outlet 53 | .getService(Service.AccessoryInformation) 54 | .setCharacteristic(Characteristic.Manufacturer, "RMR") 55 | .setCharacteristic(Characteristic.Model, "RMR-123") 56 | .setCharacteristic(Characteristic.SerialNumber, "RAHULMR123"); 57 | 58 | // listen for the "identify" event for this Accessory 59 | outlet.on('identify', function(paired, callback) { 60 | MRz_OUTLET.identify(); 61 | callback(); // success 62 | }); 63 | 64 | // Add the actual outlet Service and listen for change events from iOS. 65 | // We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` 66 | outlet 67 | .addService(Service.Outlet, "Power Outlet") // services exposed to the user should have "names" like "Fake Light" for us 68 | .getCharacteristic(Characteristic.On) 69 | .on('set', function(value, callback) { 70 | MRz_OUTLET.setPowerOn(value); 71 | callback(); // Our fake Outlet is synchronous - this value has been successfully set 72 | }); 73 | 74 | // We want to intercept requests for our current power state so we can query the hardware itself instead of 75 | // allowing HAP-NodeJS to return the cached Characteristic.value. 76 | outlet 77 | .getService(Service.Outlet) 78 | .getCharacteristic(Characteristic.On) 79 | .on('get', function(callback) { 80 | 81 | // this event is emitted when you ask Siri directly whether your light is on or not. you might query 82 | // the light hardware itself to find this out, then call the callback. But if you take longer than a 83 | // few seconds to respond, Siri will give up. 84 | 85 | var err = null; // in case there were any problems 86 | 87 | if (MRz_OUTLET.powerOn) { 88 | console.log("Are we on? Yes."); 89 | callback(err, true); 90 | } else { 91 | console.log("Are we on? No."); 92 | callback(err, false); 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /accessories/Lock_accessory.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | 6 | // here's a fake hardware device that we'll expose to HomeKit 7 | var FAKE_LOCK = { 8 | locked: false, 9 | lock: function() { 10 | console.log("Locking the lock!"); 11 | FAKE_LOCK.locked = true; 12 | }, 13 | unlock: function() { 14 | console.log("Unlocking the lock!"); 15 | FAKE_LOCK.locked = false; 16 | }, 17 | identify: function() { 18 | console.log("Identify the lock!"); 19 | } 20 | } 21 | 22 | // Generate a consistent UUID for our Lock Accessory that will remain the same even when 23 | // restarting our server. We use the `uuid.generate` helper function to create a deterministic 24 | // UUID based on an arbitrary "namespace" and the word "lock". 25 | var lockUUID = uuid.generate('hap-nodejs:accessories:lock'); 26 | 27 | // This is the Accessory that we'll return to HAP-NodeJS that represents our fake lock. 28 | var lock = exports.accessory = new Accessory('Lock', lockUUID); 29 | 30 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 31 | lock.username = "C1:33:3A:EE:5E:FA"; 32 | lock.pincode = "031-45-154"; 33 | 34 | // set some basic properties (these values are arbitrary and setting them is optional) 35 | lock 36 | .getService(Service.AccessoryInformation) 37 | .setCharacteristic(Characteristic.Manufacturer, "Oltica") 38 | .setCharacteristic(Characteristic.Model, "Rev-1") 39 | .setCharacteristic(Characteristic.SerialNumber, "AS2NASF88EW"); 40 | 41 | // listen for the "identify" event for this Accessory 42 | lock.on('identify', function(paired, callback) { 43 | FAKE_LOCK.identify(); 44 | callback(); // success 45 | }); 46 | 47 | // Add the actual Door Lock Service and listen for change events from iOS. 48 | // We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` 49 | lock 50 | .addService(Service.LockMechanism, "Fake Lock") // services exposed to the user should have "names" like "Fake Light" for us 51 | .getCharacteristic(Characteristic.LockTargetState) 52 | .on('set', function(value, callback) { 53 | 54 | if (value == Characteristic.LockTargetState.UNSECURED) { 55 | FAKE_LOCK.unlock(); 56 | callback(); // Our fake Lock is synchronous - this value has been successfully set 57 | 58 | // now we want to set our lock's "actual state" to be unsecured so it shows as unlocked in iOS apps 59 | lock 60 | .getService(Service.LockMechanism) 61 | .setCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.UNSECURED); 62 | } else if (value == Characteristic.LockTargetState.SECURED) { 63 | FAKE_LOCK.lock(); 64 | callback(); // Our fake Lock is synchronous - this value has been successfully set 65 | 66 | // now we want to set our lock's "actual state" to be locked so it shows as open in iOS apps 67 | lock 68 | .getService(Service.LockMechanism) 69 | .setCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.SECURED); 70 | } 71 | }); 72 | 73 | // We want to intercept requests for our current state so we can query the hardware itself instead of 74 | // allowing HAP-NodeJS to return the cached Characteristic.value. 75 | lock 76 | .getService(Service.LockMechanism) 77 | .getCharacteristic(Characteristic.LockCurrentState) 78 | .on('get', function(callback) { 79 | 80 | // this event is emitted when you ask Siri directly whether your lock is locked or not. you might query 81 | // the lock hardware itself to find this out, then call the callback. But if you take longer than a 82 | // few seconds to respond, Siri will give up. 83 | 84 | var err = null; // in case there were any problems 85 | 86 | if (FAKE_LOCK.locked) { 87 | console.log("Are we locked? Yes."); 88 | callback(err, Characteristic.LockCurrentState.SECURED); 89 | } else { 90 | console.log("Are we locked? No."); 91 | callback(err, Characteristic.LockCurrentState.UNSECURED); 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /lib/model/IdentifierCache.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var storage = require('node-persist'); 3 | 4 | 'use strict'; 5 | 6 | module.exports = { 7 | IdentifierCache: IdentifierCache 8 | }; 9 | 10 | 11 | /** 12 | * IdentifierCache is a model class that manages a system of associating HAP "Accessory IDs" and "Instance IDs" 13 | * with other values that don't usually change. HomeKit Clients use Accessory/Instance IDs as a primary key of 14 | * sorts, so the IDs need to remain "stable". For instance, if you create a HomeKit "Scene" called "Leaving Home" 15 | * that sets your Alarm System's "Target Alarm State" Characteristic to "Arm Away", that Scene will store whatever 16 | * "Instance ID" it was given for the "Target Alarm State" Characteristic. If the ID changes later on this server, 17 | * the scene will stop working. 18 | */ 19 | 20 | function IdentifierCache(username) { 21 | this.username = username; 22 | this._cache = {}; // cache[key:string] = id:number 23 | this._usedCache = null; // for usage tracking and expiring old keys 24 | } 25 | 26 | IdentifierCache.prototype.startTrackingUsage = function() { 27 | this._usedCache = {}; 28 | } 29 | 30 | IdentifierCache.prototype.stopTrackingUsageAndExpireUnused = function() { 31 | // simply rotate in the new cache that was built during our normal getXYZ() calls. 32 | this._cache = this._usedCache; 33 | this._usedCache = null; 34 | } 35 | 36 | IdentifierCache.prototype.getCache = function(key) { 37 | var value = this._cache[key]; 38 | 39 | // track this cache item if needed 40 | if (this._usedCache && typeof value !== 'undefined') 41 | this._usedCache[key] = value; 42 | 43 | return value; 44 | } 45 | 46 | IdentifierCache.prototype.setCache = function(key, value) { 47 | this._cache[key] = value; 48 | 49 | // track this cache item if needed 50 | if (this._usedCache) 51 | this._usedCache[key] = value; 52 | 53 | return value; 54 | } 55 | 56 | IdentifierCache.prototype.getAID = function(accessoryUUID) { 57 | var key = accessoryUUID; 58 | 59 | // ensure that our "next AID" field is not expired 60 | this.getCache('|nextAID'); 61 | 62 | return this.getCache(key) || this.setCache(key, this.getNextAID()); 63 | } 64 | 65 | IdentifierCache.prototype.getIID = function(accessoryUUID, serviceUUID, serviceSubtype, characteristicUUID) { 66 | 67 | var key = accessoryUUID 68 | + '|' + serviceUUID 69 | + (serviceSubtype ? '|' + serviceSubtype : '') 70 | + (characteristicUUID ? '|' + characteristicUUID : ''); 71 | 72 | // ensure that our "next IID" field for this accessory is not expired 73 | this.getCache(accessoryUUID + '|nextIID'); 74 | 75 | return this.getCache(key) || this.setCache(key, this.getNextIID(accessoryUUID)); 76 | } 77 | 78 | IdentifierCache.prototype.getNextAID = function() { 79 | var key = '|nextAID'; 80 | var nextAID = this.getCache(key) || 2; // start at 2 because the root Accessory or Bridge must be 1 81 | this.setCache(key, nextAID + 1); // increment 82 | return nextAID; 83 | } 84 | 85 | IdentifierCache.prototype.getNextIID = function(accessoryUUID) { 86 | var key = accessoryUUID + '|nextIID'; 87 | var nextIID = this.getCache(key) || 2; // iid 1 is reserved for the Accessory Information service 88 | this.setCache(key, nextIID + 1); // increment 89 | return nextIID; 90 | } 91 | 92 | /** 93 | * Persisting to File System 94 | */ 95 | 96 | // Gets a key for storing this IdentifierCache in the filesystem, like "IdentifierCache.CC223DE3CEF3.json" 97 | IdentifierCache.persistKey = function(username) { 98 | return util.format("IdentifierCache.%s.json", username.replace(/:/g,"").toUpperCase()); 99 | } 100 | 101 | IdentifierCache.load = function(username) { 102 | var key = IdentifierCache.persistKey(username); 103 | var saved = storage.getItem(key); 104 | 105 | if (saved) { 106 | var info = new IdentifierCache(username); 107 | info._cache = saved.cache; 108 | return info; 109 | } 110 | else { 111 | return null; 112 | } 113 | } 114 | 115 | IdentifierCache.prototype.save = function() { 116 | var saved = { 117 | cache: this._cache 118 | }; 119 | 120 | var key = IdentifierCache.persistKey(this.username); 121 | 122 | storage.setItemSync(key, saved); 123 | storage.persistSync(); 124 | } 125 | -------------------------------------------------------------------------------- /accessories/SecuritySystem_accessory.backup.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | 6 | var Gpio = require('onoff').Gpio, 7 | PIRSensor = new Gpio(27, 'in'); 8 | // here's a fake temperature sensor device that we'll expose to HomeKit 9 | var RMR_SECURITYSYSTEM = { 10 | State: 0, 11 | getState: function() { 12 | console.log("Getting the current state!"); 13 | return RMR_SECURITYSYSTEM.State; 14 | }, 15 | randomState: function() { 16 | // randomize temperature to a value between 0 and 100 17 | if (RMR_SECURITYSYSTEM.State == 4) { 18 | RMR_SECURITYSYSTEM.State = 0; 19 | } else { 20 | RMR_SECURITYSYSTEM.State = RMR_SECURITYSYSTEM.State + 4; 21 | } 22 | }, 23 | 24 | setSensorState: function(value) { 25 | 26 | console.log("setting state") 27 | RMR_SECURITYSYSTEM.State = value; 28 | if (value == 1) { 29 | start(); 30 | } else { 31 | stop(); 32 | } 33 | 34 | } 35 | } 36 | 37 | 38 | // Generate a consistent UUID for our Temperature Sensor Accessory that will remain the same 39 | // even when restarting our server. We use the `uuid.generate` helper function to create 40 | // a deterministic UUID based on an arbitrary "namespace" and the string "temperature-sensor". 41 | var sensorUUID = uuid.generate('hap-nodejs:accessories:securitysystem'); 42 | 43 | // This is the Accessory that we'll return to HAP-NodeJS that represents our fake lock. 44 | var securitysensor = exports.accessory = new Accessory('Security System', sensorUUID); 45 | 46 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 47 | securitysensor.username = "11:5A:3A:B3:AE:FA"; 48 | securitysensor.pincode = "031-45-154"; 49 | 50 | // Add the actual TemperatureSensor Service. 51 | // We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` 52 | securitysensor 53 | .addService(Service.SecuritySystem) 54 | .getCharacteristic(Characteristic.SecuritySystemCurrentState) 55 | .on('get', function(callback) { 56 | console.log("current get " + RMR_SECURITYSYSTEM.getState()); 57 | // return our current value 58 | callback(null, RMR_SECURITYSYSTEM.getState()); 59 | }) 60 | .on('set', function(value, callback) { 61 | console.log("current set " + value) 62 | callback(); 63 | }); 64 | 65 | securitysensor 66 | .getService(Service.SecuritySystem) 67 | .getCharacteristic(Characteristic.SecuritySystemTargetState) 68 | .on('get', function(callback) { 69 | console.log("target get " + RMR_SECURITYSYSTEM.getState()); 70 | // return our current value 71 | callback(null, RMR_SECURITYSYSTEM.getState()); 72 | }) 73 | .on('set', function(value, callback) { 74 | console.log("target set " + value) 75 | RMR_SECURITYSYSTEM.setSensorState(value); 76 | callback(); 77 | }); 78 | 79 | 80 | var systemOn; 81 | var timer; 82 | 83 | function start() { 84 | 85 | timer = setInterval(function() { 86 | var statePIR = PIRSensor.readSync(); 87 | if (statePIR == 1) { 88 | RMR_SECURITYSYSTEM.State = 4 89 | //securitysensor.CaptureImage(); 90 | securitysensor 91 | .getService(Service.SecuritySystem) 92 | .setCharacteristic(Characteristic.SecuritySystemCurrentState, RMR_SECURITYSYSTEM.State); 93 | } else { 94 | RMR_SECURITYSYSTEM.State = 0; 95 | securitysensor 96 | .getService(Service.SecuritySystem) 97 | .setCharacteristic(Characteristic.SecuritySystemCurrentState, RMR_SECURITYSYSTEM.State); 98 | } 99 | }, 999) 100 | } 101 | 102 | function stop() { 103 | clearTimeout(timer); 104 | }; 105 | 106 | 107 | 108 | // 109 | // // randomize our temperature reading every 3 seconds 110 | // setInterval(function() { 111 | // 112 | // RMR_SECURITYSYSTEM.randomState(); 113 | // 114 | // // update the characteristic value so interested iOS devices can get notified 115 | // securitysensor 116 | // .getService(Service.SecuritySystem) 117 | // .setCharacteristic(Characteristic.SecuritySystemCurrentState, RMR_SECURITYSYSTEM.State); 118 | // 119 | // }, 60000); 120 | -------------------------------------------------------------------------------- /accessories/types.js: -------------------------------------------------------------------------------- 1 | var exports = module.exports = {}; 2 | 3 | //HomeKit Types UUID's 4 | 5 | var stPre = "000000"; 6 | var stPost = "-0000-1000-8000-0026BB765291"; 7 | 8 | 9 | //HomeKitTransportCategoryTypes 10 | exports.OTHER_TCTYPE = 1; 11 | exports.FAN_TCTYPE = 3; 12 | exports.GARAGE_DOOR_OPENER_TCTYPE = 4; 13 | exports.LIGHTBULB_TCTYPE = 5; 14 | exports.DOOR_LOCK_TCTYPE = 6; 15 | exports.OUTLET_TCTYPE = 7; 16 | exports.SWITCH_TCTYPE = 8; 17 | exports.THERMOSTAT_TCTYPE = 9; 18 | exports.SENSOR_TCTYPE = 10; 19 | exports.ALARM_SYSTEM_TCTYPE = 11; 20 | exports.DOOR_TCTYPE = 12; 21 | exports.WINDOW_TCTYPE = 13; 22 | exports.WINDOW_COVERING_TCTYPE = 14; 23 | exports.PROGRAMMABLE_SWITCH_TCTYPE = 15; 24 | 25 | //HomeKitServiceTypes 26 | 27 | exports.LIGHTBULB_STYPE = stPre + "43" + stPost; 28 | exports.SWITCH_STYPE = stPre + "49" + stPost; 29 | exports.THERMOSTAT_STYPE = stPre + "4A" + stPost; 30 | exports.GARAGE_DOOR_OPENER_STYPE = stPre + "41" + stPost; 31 | exports.ACCESSORY_INFORMATION_STYPE = stPre + "3E" + stPost; 32 | exports.FAN_STYPE = stPre + "40" + stPost; 33 | exports.OUTLET_STYPE = stPre + "47" + stPost; 34 | exports.LOCK_MECHANISM_STYPE = stPre + "45" + stPost; 35 | exports.LOCK_MANAGEMENT_STYPE = stPre + "44" + stPost; 36 | exports.ALARM_STYPE = stPre + "7E" + stPost; 37 | exports.WINDOW_COVERING_STYPE = stPre + "8C" + stPost; 38 | exports.OCCUPANCY_SENSOR_STYPE = stPre + "86" + stPost; 39 | exports.CONTACT_SENSOR_STYPE = stPre + "80" + stPost; 40 | exports.MOTION_SENSOR_STYPE = stPre + "85" + stPost; 41 | exports.HUMIDITY_SENSOR_STYPE = stPre + "82" + stPost; 42 | exports.TEMPERATURE_SENSOR_STYPE = stPre + "8A" + stPost; 43 | 44 | //HomeKitCharacteristicsTypes 45 | 46 | 47 | exports.ALARM_CURRENT_STATE_CTYPE = stPre + "66" + stPost; 48 | exports.ALARM_TARGET_STATE_CTYPE = stPre + "67" + stPost; 49 | exports.ADMIN_ONLY_ACCESS_CTYPE = stPre + "01" + stPost; 50 | exports.AUDIO_FEEDBACK_CTYPE = stPre + "05" + stPost; 51 | exports.BRIGHTNESS_CTYPE = stPre + "08" + stPost; 52 | exports.BATTERY_LEVEL_CTYPE = stPre + "68" + stPost; 53 | exports.COOLING_THRESHOLD_CTYPE = stPre + "0D" + stPost; 54 | exports.CONTACT_SENSOR_STATE_CTYPE = stPre + "6A" + stPost; 55 | exports.CURRENT_DOOR_STATE_CTYPE = stPre + "0E" + stPost; 56 | exports.CURRENT_LOCK_MECHANISM_STATE_CTYPE = stPre + "1D" + stPost; 57 | exports.CURRENT_RELATIVE_HUMIDITY_CTYPE = stPre + "10" + stPost; 58 | exports.CURRENT_TEMPERATURE_CTYPE = stPre + "11" + stPost; 59 | exports.HEATING_THRESHOLD_CTYPE = stPre + "12" + stPost; 60 | exports.HUE_CTYPE = stPre + "13" + stPost; 61 | exports.IDENTIFY_CTYPE = stPre + "14" + stPost; 62 | exports.LOCK_MANAGEMENT_AUTO_SECURE_TIMEOUT_CTYPE = stPre + "1A" + stPost; 63 | exports.LOCK_MANAGEMENT_CONTROL_POINT_CTYPE = stPre + "19" + stPost; 64 | exports.LOCK_MECHANISM_LAST_KNOWN_ACTION_CTYPE = stPre + "1C" + stPost; 65 | exports.LOGS_CTYPE = stPre + "1F" + stPost; 66 | exports.MANUFACTURER_CTYPE = stPre + "20" + stPost; 67 | exports.MODEL_CTYPE = stPre + "21" + stPost; 68 | exports.MOTION_DETECTED_CTYPE = stPre + "22" + stPost; 69 | exports.NAME_CTYPE = stPre + "23" + stPost; 70 | exports.OBSTRUCTION_DETECTED_CTYPE = stPre + "24" + stPost; 71 | exports.OUTLET_IN_USE_CTYPE = stPre + "26" + stPost; 72 | exports.OCCUPANCY_DETECTED_CTYPE = stPre + "71" + stPost; 73 | exports.POWER_STATE_CTYPE = stPre + "25" + stPost; 74 | exports.PROGRAMMABLE_SWITCH_SWITCH_EVENT_CTYPE = stPre + "73" + stPost; 75 | exports.PROGRAMMABLE_SWITCH_OUTPUT_STATE_CTYPE = stPre + "74" + stPost; 76 | exports.ROTATION_DIRECTION_CTYPE = stPre + "28" + stPost; 77 | exports.ROTATION_SPEED_CTYPE = stPre + "29" + stPost; 78 | exports.SATURATION_CTYPE = stPre + "2F" + stPost; 79 | exports.SERIAL_NUMBER_CTYPE = stPre + "30" + stPost; 80 | exports.STATUS_LOW_BATTERY_CTYPE = stPre + "79" + stPost; 81 | exports.STATUS_FAULT_CTYPE = stPre + "77" + stPost; 82 | exports.TARGET_DOORSTATE_CTYPE = stPre + "32" + stPost; 83 | exports.TARGET_LOCK_MECHANISM_STATE_CTYPE = stPre + "1E" + stPost; 84 | exports.TARGET_RELATIVE_HUMIDITY_CTYPE = stPre + "34" + stPost; 85 | exports.TARGET_TEMPERATURE_CTYPE = stPre + "35" + stPost; 86 | exports.TEMPERATURE_UNITS_CTYPE = stPre + "36" + stPost; 87 | exports.VERSION_CTYPE = stPre + "37" + stPost; 88 | exports.WINDOW_COVERING_TARGET_POSITION_CTYPE = stPre + "7C" + stPost; 89 | exports.WINDOW_COVERING_CURRENT_POSITION_CTYPE = stPre + "6D" + stPost; 90 | exports.WINDOW_COVERING_OPERATION_STATE_CTYPE = stPre + "72" + stPost; 91 | exports.CURRENTHEATINGCOOLING_CTYPE = stPre + "0F" + stPost; 92 | exports.TARGETHEATINGCOOLING_CTYPE = stPre + "33" + stPost; 93 | -------------------------------------------------------------------------------- /accessories/Thermostat_accessory.js: -------------------------------------------------------------------------------- 1 | // HomeKit types required 2 | var types = require("./types.js") 3 | var exports = module.exports = {}; 4 | 5 | var execute = function(accessory,characteristic,value){ console.log("executed accessory: " + accessory + ", and characteristic: " + characteristic + ", with value: " + value + "."); } 6 | 7 | exports.accessory = { 8 | displayName: "Thermostat 1", 9 | username: "CA:3E:BC:4D:5E:FF", 10 | pincode: "031-45-154", 11 | services: [{ 12 | sType: types.ACCESSORY_INFORMATION_STYPE, 13 | characteristics: [{ 14 | cType: types.NAME_CTYPE, 15 | onUpdate: null, 16 | perms: ["pr"], 17 | format: "string", 18 | initialValue: "Thermostat 1", 19 | supportEvents: false, 20 | supportBonjour: false, 21 | manfDescription: "Bla", 22 | designedMaxLength: 255 23 | },{ 24 | cType: types.MANUFACTURER_CTYPE, 25 | onUpdate: null, 26 | perms: ["pr"], 27 | format: "string", 28 | initialValue: "Oltica", 29 | supportEvents: false, 30 | supportBonjour: false, 31 | manfDescription: "Bla", 32 | designedMaxLength: 255 33 | },{ 34 | cType: types.MODEL_CTYPE, 35 | onUpdate: null, 36 | perms: ["pr"], 37 | format: "string", 38 | initialValue: "Rev-1", 39 | supportEvents: false, 40 | supportBonjour: false, 41 | manfDescription: "Bla", 42 | designedMaxLength: 255 43 | },{ 44 | cType: types.SERIAL_NUMBER_CTYPE, 45 | onUpdate: null, 46 | perms: ["pr"], 47 | format: "string", 48 | initialValue: "A1S2NASF88EW", 49 | supportEvents: false, 50 | supportBonjour: false, 51 | manfDescription: "Bla", 52 | designedMaxLength: 255 53 | },{ 54 | cType: types.IDENTIFY_CTYPE, 55 | onUpdate: null, 56 | perms: ["pw"], 57 | format: "bool", 58 | initialValue: false, 59 | supportEvents: false, 60 | supportBonjour: false, 61 | manfDescription: "Identify Accessory", 62 | designedMaxLength: 1 63 | }] 64 | },{ 65 | sType: types.THERMOSTAT_STYPE, 66 | characteristics: [{ 67 | cType: types.NAME_CTYPE, 68 | onUpdate: null, 69 | perms: ["pr"], 70 | format: "string", 71 | initialValue: "Thermostat Control", 72 | supportEvents: false, 73 | supportBonjour: false, 74 | manfDescription: "Bla", 75 | designedMaxLength: 255 76 | },{ 77 | cType: types.CURRENTHEATINGCOOLING_CTYPE, 78 | onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Current HC", value); }, 79 | perms: ["pr","ev"], 80 | format: "int", 81 | initialValue: 0, 82 | supportEvents: false, 83 | supportBonjour: false, 84 | manfDescription: "Current Mode", 85 | designedMaxLength: 1, 86 | designedMinValue: 0, 87 | designedMaxValue: 2, 88 | designedMinStep: 1, 89 | },{ 90 | cType: types.TARGETHEATINGCOOLING_CTYPE, 91 | onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Target HC", value); }, 92 | perms: ["pw","pr","ev"], 93 | format: "int", 94 | initialValue: 0, 95 | supportEvents: false, 96 | supportBonjour: false, 97 | manfDescription: "Target Mode", 98 | designedMinValue: 0, 99 | designedMaxValue: 3, 100 | designedMinStep: 1, 101 | },{ 102 | cType: types.CURRENT_TEMPERATURE_CTYPE, 103 | onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Current Temperature", value); }, 104 | perms: ["pr","ev"], 105 | format: "int", 106 | initialValue: 20, 107 | supportEvents: false, 108 | supportBonjour: false, 109 | manfDescription: "Current Temperature", 110 | unit: "celsius" 111 | },{ 112 | cType: types.TARGET_TEMPERATURE_CTYPE, 113 | onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Target Temperature", value); }, 114 | perms: ["pw","pr","ev"], 115 | format: "int", 116 | initialValue: 20, 117 | supportEvents: false, 118 | supportBonjour: false, 119 | manfDescription: "Target Temperature", 120 | designedMinValue: 16, 121 | designedMaxValue: 38, 122 | designedMinStep: 1, 123 | unit: "celsius" 124 | },{ 125 | cType: types.TEMPERATURE_UNITS_CTYPE, 126 | onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Unit", value); }, 127 | perms: ["pr","ev"], 128 | format: "int", 129 | initialValue: 0, 130 | supportEvents: false, 131 | supportBonjour: false, 132 | manfDescription: "Unit" 133 | }] 134 | }] 135 | } -------------------------------------------------------------------------------- /lib/model/AccessoryInfo.js: -------------------------------------------------------------------------------- 1 | var storage = require('node-persist'); 2 | var util = require('util'); 3 | var crypto = require('crypto'); 4 | var ed25519 = require('ed25519'); 5 | var bufferShim = require('buffer-shims'); 6 | 7 | 'use strict'; 8 | 9 | module.exports = { 10 | AccessoryInfo: AccessoryInfo 11 | }; 12 | 13 | 14 | /** 15 | * AccessoryInfo is a model class containing a subset of Accessory data relevant to the internal HAP server, 16 | * such as encryption keys and username. It is persisted to disk. 17 | */ 18 | 19 | function AccessoryInfo(username) { 20 | this.username = username; 21 | this.displayName = ""; 22 | this.category = ""; 23 | this.pincode = ""; 24 | this.signSk = bufferShim.alloc(0); 25 | this.signPk = bufferShim.alloc(0); 26 | this.pairedClients = {}; // pairedClients[clientUsername:string] = clientPublicKey:Buffer 27 | this.configVersion = 1; 28 | this.configHash = ""; 29 | 30 | this.relayEnabled = false; 31 | this.relayState = 2; 32 | this.relayAccessoryID = ""; 33 | this.relayAdminID = ""; 34 | this.relayPairedControllers = {}; 35 | this.accessoryBagURL = ""; 36 | } 37 | 38 | // Add a paired client to our memory. 'publicKey' should be an instance of Buffer. 39 | AccessoryInfo.prototype.addPairedClient = function(username, publicKey) { 40 | this.pairedClients[username] = publicKey; 41 | } 42 | 43 | // Add a paired client to our memory. 44 | AccessoryInfo.prototype.removePairedClient = function(username) { 45 | delete this.pairedClients[username]; 46 | 47 | if (Object.keys(this.pairedClients).length == 0) { 48 | this.relayEnabled = false; 49 | this.relayState = 2; 50 | this.relayAccessoryID = ""; 51 | this.relayAdminID = ""; 52 | this.relayPairedControllers = {}; 53 | this.accessoryBagURL = ""; 54 | } 55 | } 56 | 57 | // Gets the public key for a paired client as a Buffer, or falsey value if not paired. 58 | AccessoryInfo.prototype.getClientPublicKey = function(username) { 59 | return this.pairedClients[username]; 60 | } 61 | 62 | // Returns a boolean indicating whether this accessory has been paired with a client. 63 | AccessoryInfo.prototype.paired = function() { 64 | return Object.keys(this.pairedClients).length > 0; // if we have any paired clients, we're paired. 65 | } 66 | 67 | AccessoryInfo.prototype.updateRelayEnableState = function(state) { 68 | this.relayEnabled = state; 69 | } 70 | 71 | AccessoryInfo.prototype.updateRelayState = function(newState) { 72 | this.relayState = newState; 73 | } 74 | 75 | AccessoryInfo.prototype.addPairedRelayClient = function(username, accessToken) { 76 | this.relayPairedControllers[username] = accessToken; 77 | } 78 | 79 | AccessoryInfo.prototype.removePairedRelayClient = function(username) { 80 | delete this.relayPairedControllers[username]; 81 | } 82 | 83 | // Gets a key for storing this AccessoryInfo in the filesystem, like "AccessoryInfo.CC223DE3CEF3.json" 84 | AccessoryInfo.persistKey = function(username) { 85 | return util.format("AccessoryInfo.%s.json", username.replace(/:/g,"").toUpperCase()); 86 | } 87 | 88 | AccessoryInfo.create = function(username) { 89 | var accessoryInfo = new AccessoryInfo(username); 90 | 91 | // Create a new unique key pair for this accessory. 92 | var seed = crypto.randomBytes(32); 93 | var keyPair = ed25519.MakeKeypair(seed); 94 | 95 | accessoryInfo.signSk = keyPair.privateKey; 96 | accessoryInfo.signPk = keyPair.publicKey; 97 | 98 | return accessoryInfo; 99 | } 100 | 101 | AccessoryInfo.load = function(username) { 102 | var key = AccessoryInfo.persistKey(username); 103 | var saved = storage.getItem(key); 104 | 105 | if (saved) { 106 | var info = new AccessoryInfo(username); 107 | info.displayName = saved.displayName || ""; 108 | info.category = saved.category || ""; 109 | info.pincode = saved.pincode || ""; 110 | info.signSk = bufferShim.from(saved.signSk || '', 'hex'); 111 | info.signPk = bufferShim.from(saved.signPk || '', 'hex'); 112 | 113 | info.pairedClients = {}; 114 | for (var username in saved.pairedClients || {}) { 115 | var publicKey = saved.pairedClients[username]; 116 | info.pairedClients[username] = bufferShim.from(publicKey, 'hex'); 117 | } 118 | 119 | info.configVersion = saved.configVersion || 1; 120 | info.configHash = saved.configHash || ""; 121 | 122 | info.relayEnabled = saved.relayEnabled || false; 123 | info.relayState = saved.relayState || 2; 124 | info.relayAccessoryID = saved.relayAccessoryID || ""; 125 | info.relayAdminID = saved.relayAdminID || ""; 126 | info.relayPairedControllers = saved.relayPairedControllers || {}; 127 | info.accessoryBagURL = saved.accessoryBagURL || ""; 128 | 129 | return info; 130 | } 131 | else { 132 | return null; 133 | } 134 | } 135 | 136 | AccessoryInfo.prototype.save = function() { 137 | var saved = { 138 | displayName: this.displayName, 139 | category: this.category, 140 | pincode: this.pincode, 141 | signSk: this.signSk.toString('hex'), 142 | signPk: this.signPk.toString('hex'), 143 | pairedClients: {}, 144 | configVersion: this.configVersion, 145 | configHash: this.configHash, 146 | relayEnabled: this.relayEnabled, 147 | relayState: this.relayState, 148 | relayAccessoryID: this.relayAccessoryID, 149 | relayAdminID: this.relayAdminID, 150 | relayPairedControllers: this.relayPairedControllers, 151 | accessoryBagURL: this.accessoryBagURL 152 | }; 153 | 154 | for (var username in this.pairedClients) { 155 | var publicKey = this.pairedClients[username]; 156 | saved.pairedClients[username] = publicKey.toString('hex'); 157 | } 158 | 159 | var key = AccessoryInfo.persistKey(this.username); 160 | 161 | storage.setItemSync(key, saved); 162 | storage.persistSync(); 163 | } 164 | -------------------------------------------------------------------------------- /accessories/Light_accessory.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | 6 | var LightController = { 7 | name: "Simple Light", //name of accessory 8 | pincode: "031-45-154", 9 | username: "FA:3C:ED:5A:1A:1A", // MAC like address used by HomeKit to differentiate accessories. 10 | manufacturer: "HAP-NodeJS", //manufacturer (optional) 11 | model: "v1.0", //model (optional) 12 | serialNumber: "A12S345KGB", //serial number (optional) 13 | 14 | power: false, //curent power status 15 | brightness: 100, //current brightness 16 | hue: 0, //current hue 17 | saturation: 0, //current saturation 18 | 19 | outputLogs: false, //output logs 20 | 21 | setPower: function(status) { //set power of accessory 22 | if(this.outputLogs) console.log("Turning the '%s' %s", this.name, status ? "on" : "off"); 23 | this.power = status; 24 | }, 25 | 26 | getPower: function() { //get power of accessory 27 | if(this.outputLogs) console.log("'%s' is %s.", this.name, this.power ? "on" : "off"); 28 | return this.power ? true : false; 29 | }, 30 | 31 | setBrightness: function(brightness) { //set brightness 32 | if(this.outputLogs) console.log("Setting '%s' brightness to %s", this.name, brightness); 33 | this.brightness = brightness; 34 | }, 35 | 36 | getBrightness: function() { //get brightness 37 | if(this.outputLogs) console.log("'%s' brightness is %s", this.name, this.brightness); 38 | return this.brightness; 39 | }, 40 | 41 | setSaturation: function(saturation) { //set brightness 42 | if(this.outputLogs) console.log("Setting '%s' saturation to %s", this.name, saturation); 43 | this.saturation = saturation; 44 | }, 45 | 46 | getSaturation: function() { //get brightness 47 | if(this.outputLogs) console.log("'%s' saturation is %s", this.name, this.saturation); 48 | return this.saturation; 49 | }, 50 | 51 | setHue: function(hue) { //set brightness 52 | if(this.outputLogs) console.log("Setting '%s' hue to %s", this.name, hue); 53 | this.hue = hue; 54 | }, 55 | 56 | getHue: function() { //get hue 57 | if(this.outputLogs) console.log("'%s' hue is %s", this.name, this.hue); 58 | return this.hue; 59 | }, 60 | 61 | identify: function() { //identify the accessory 62 | if(this.outputLogs) console.log("Identify the '%s'", this.name); 63 | } 64 | } 65 | 66 | // Generate a consistent UUID for our light Accessory that will remain the same even when 67 | // restarting our server. We use the `uuid.generate` helper function to create a deterministic 68 | // UUID based on an arbitrary "namespace" and the word "light". 69 | var lightUUID = uuid.generate('hap-nodejs:accessories:light' + LightController.name); 70 | 71 | // This is the Accessory that we'll return to HAP-NodeJS that represents our light. 72 | var lightAccessory = exports.accessory = new Accessory(LightController.name, lightUUID); 73 | 74 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 75 | lightAccessory.username = LightController.username; 76 | lightAccessory.pincode = LightController.pincode; 77 | 78 | // set some basic properties (these values are arbitrary and setting them is optional) 79 | lightAccessory 80 | .getService(Service.AccessoryInformation) 81 | .setCharacteristic(Characteristic.Manufacturer, LightController.manufacturer) 82 | .setCharacteristic(Characteristic.Model, LightController.model) 83 | .setCharacteristic(Characteristic.SerialNumber, LightController.serialNumber); 84 | 85 | // listen for the "identify" event for this Accessory 86 | lightAccessory.on('identify', function(paired, callback) { 87 | LightController.identify(); 88 | callback(); 89 | }); 90 | 91 | // Add the actual Lightbulb Service and listen for change events from iOS. 92 | // We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` 93 | lightAccessory 94 | .addService(Service.Lightbulb, LightController.name) // services exposed to the user should have "names" like "Light" for this case 95 | .getCharacteristic(Characteristic.On) 96 | .on('set', function(value, callback) { 97 | LightController.setPower(value); 98 | 99 | // Our light is synchronous - this value has been successfully set 100 | // Invoke the callback when you finished processing the request 101 | // If it's going to take more than 1s to finish the request, try to invoke the callback 102 | // after getting the request instead of after finishing it. This avoids blocking other 103 | // requests from HomeKit. 104 | callback(); 105 | }) 106 | // We want to intercept requests for our current power state so we can query the hardware itself instead of 107 | // allowing HAP-NodeJS to return the cached Characteristic.value. 108 | .on('get', function(callback) { 109 | callback(null, LightController.getPower()); 110 | }); 111 | 112 | // To inform HomeKit about changes occurred outside of HomeKit (like user physically turn on the light) 113 | // Please use Characteristic.updateValue 114 | // 115 | // lightAccessory 116 | // .getService(Service.Lightbulb) 117 | // .getCharacteristic(Characteristic.On) 118 | // .updateValue(true); 119 | 120 | // also add an "optional" Characteristic for Brightness 121 | lightAccessory 122 | .getService(Service.Lightbulb) 123 | .addCharacteristic(Characteristic.Brightness) 124 | .on('set', function(value, callback) { 125 | LightController.setBrightness(value); 126 | callback(); 127 | }) 128 | .on('get', function(callback) { 129 | callback(null, LightController.getBrightness()); 130 | }); 131 | 132 | // also add an "optional" Characteristic for Saturation 133 | lightAccessory 134 | .getService(Service.Lightbulb) 135 | .addCharacteristic(Characteristic.Saturation) 136 | .on('set', function(value, callback) { 137 | LightController.setSaturation(value); 138 | callback(); 139 | }) 140 | .on('get', function(callback) { 141 | callback(null, LightController.getSaturation()); 142 | }); 143 | 144 | // also add an "optional" Characteristic for Hue 145 | lightAccessory 146 | .getService(Service.Lightbulb) 147 | .addCharacteristic(Characteristic.Hue) 148 | .on('set', function(value, callback) { 149 | LightController.setHue(value); 150 | callback(); 151 | }) 152 | .on('get', function(callback) { 153 | callback(null, LightController.getHue()); 154 | }); 155 | -------------------------------------------------------------------------------- /lib/AccessoryLoader.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var Accessory = require('./Accessory').Accessory; 4 | var Service = require('./Service').Service; 5 | var Characteristic = require('./Characteristic').Characteristic; 6 | var uuid = require('./util/uuid'); 7 | var storage = require('node-persist'); 8 | 9 | module.exports = { 10 | loadDirectory: loadDirectory, 11 | parseAccessoryJSON: parseAccessoryJSON, 12 | parseServiceJSON: parseServiceJSON, 13 | parseCharacteristicJSON: parseCharacteristicJSON 14 | }; 15 | 16 | /** 17 | * Loads all accessories from the given folder. Handles object-literal-style accessories, "accessory factories", 18 | * and new-API style modules. 19 | */ 20 | 21 | function loadDirectory(dir) { 22 | 23 | // exported accessory objects loaded from this dir 24 | var accessories = []; 25 | 26 | fs.readdirSync(dir).forEach(function(file) { 27 | 28 | // "Accessories" are modules that export a single accessory. 29 | if (file.split('_').pop() === "accessory.js") { 30 | console.log("Parsing accessory: " + file); 31 | 32 | var loadedAccessory = require(path.join(dir, file)).accessory; 33 | accessories.push(loadedAccessory); 34 | } 35 | // "Accessory Factories" are modules that export an array of accessories. 36 | else if (file.split('_').pop() === "accfactory.js") { 37 | console.log("Parsing accessory factory: " + file); 38 | 39 | // should return an array of objects { accessory: accessory-json } 40 | var loadedAccessories = require(path.join(dir, file)); 41 | accessories = accessories.concat(loadedAccessories); 42 | } 43 | }); 44 | 45 | // now we need to coerce all accessory objects into instances of Accessory (some or all of them may 46 | // be object-literal JSON-style accessories) 47 | return accessories.map(function(accessory) { 48 | return (accessory instanceof Accessory) ? accessory : parseAccessoryJSON(accessory); 49 | }) 50 | } 51 | 52 | /** 53 | * Accepts object-literal JSON structures from previous versions of HAP-NodeJS and parses them into 54 | * newer-style structures of Accessory/Service/Characteristic objects. 55 | */ 56 | 57 | function parseAccessoryJSON(json) { 58 | 59 | // parse services first so we can extract the accessory name 60 | var services = []; 61 | 62 | json.services.forEach(function(serviceJSON) { 63 | var service = parseServiceJSON(serviceJSON); 64 | services.push(service); 65 | }); 66 | 67 | var displayName = json.displayName; 68 | 69 | services.forEach(function(service) { 70 | if (service.UUID == '0000003E-0000-1000-8000-0026BB765291') // Service.AccessoryInformation.UUID 71 | service.characteristics.forEach(function(characteristic) { 72 | if (characteristic.UUID == '00000023-0000-1000-8000-0026BB765291') // Characteristic.Name.UUID 73 | displayName = characteristic.value; 74 | }); 75 | }); 76 | 77 | var accessory = new Accessory(displayName, uuid.generate(displayName)); 78 | 79 | // create custom properties for "username" and "pincode" for Core.js to find later (if using Core.js) 80 | accessory.username = json.username; 81 | accessory.pincode = json.pincode; 82 | 83 | // clear out the default services 84 | accessory.services.length = 0; 85 | 86 | // add services 87 | services.forEach(function(service) { 88 | accessory.addService(service); 89 | }); 90 | 91 | return accessory; 92 | } 93 | 94 | function parseServiceJSON(json) { 95 | var serviceUUID = json.sType; 96 | 97 | // build characteristics first so we can extract the Name (if present) 98 | var characteristics = []; 99 | 100 | json.characteristics.forEach(function(characteristicJSON) { 101 | var characteristic = parseCharacteristicJSON(characteristicJSON); 102 | characteristics.push(characteristic); 103 | }) 104 | 105 | var displayName = null; 106 | 107 | // extract the "Name" characteristic to use for 'type' discrimination if necessary 108 | characteristics.forEach(function(characteristic) { 109 | if (characteristic.UUID == '00000023-0000-1000-8000-0026BB765291') // Characteristic.Name.UUID 110 | displayName = characteristic.value; 111 | }); 112 | 113 | // Use UUID for "displayName" if necessary, as the JSON structures don't have a value for this 114 | var service = new Service(displayName || serviceUUID, serviceUUID, displayName); 115 | 116 | characteristics.forEach(function(characteristic) { 117 | if (characteristic.UUID != '00000023-0000-1000-8000-0026BB765291') // Characteristic.Name.UUID, already present in all Services 118 | service.addCharacteristic(characteristic); 119 | }) 120 | 121 | return service; 122 | } 123 | 124 | function parseCharacteristicJSON(json) { 125 | var characteristicUUID = json.cType; 126 | 127 | var characteristic = new Characteristic(json.manfDescription || characteristicUUID, characteristicUUID); 128 | 129 | // copy simple properties 130 | characteristic.value = json.initialValue; 131 | characteristic.setProps({ 132 | format: json.format, // example: "int" 133 | minValue: json.designedMinValue, 134 | maxValue: json.designedMaxValue, 135 | minStep: json.designedMinStep, 136 | unit: json.unit, 137 | perms: json.perms // example: ["pw","pr","ev"] 138 | }); 139 | 140 | // monkey-patch this characteristic to add the legacy method `updateValue` which used to exist, 141 | // and that accessory modules had access to via the `onRegister` function. This was the old mechanism 142 | // for communicating state changes about accessories that happened "outside" HomeKit. 143 | characteristic.updateValue = function(value, peer) { 144 | characteristic.setValue(value); 145 | } 146 | 147 | // monkey-patch legacy "locals" property which used to exist. 148 | characteristic.locals = json.locals; 149 | 150 | var updateFunc = json.onUpdate; // optional function(value) 151 | var readFunc = json.onRead; // optional function(callback(value)) 152 | var registerFunc = json.onRegister; // optional function 153 | 154 | if (updateFunc) { 155 | characteristic.on('set', function(value, callback) { 156 | updateFunc(value); 157 | callback(); 158 | }); 159 | } 160 | 161 | if (readFunc) { 162 | characteristic.on('get', function(callback) { 163 | readFunc(function(value) { 164 | callback(null, value); // old onRead callbacks don't use Error as first param 165 | }); 166 | }); 167 | } 168 | 169 | if (registerFunc) { 170 | registerFunc(characteristic); 171 | } 172 | 173 | return characteristic; 174 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # homekit with ESP8266 integration using MQTT - MRz 2 | 3 | Homekit HAP-NodeJS - Integration of HAP-NodeJS with WS2812(NeoPixel) ,DHT11 Temperature/Humidity Sensor,SONOFF S20(ESP8266 based outlet)and PIR sensor. 4 | 5 | 6 | This project uses existing HAP-NodeJS from [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) on Raspberry Pi.Changes are done for the following accessories : 7 | 8 | - Motion Sensor 9 | - Temperature Sensor 10 | - Humidity Sensor 11 | - Light (WS2812 Sensor) 12 | - Camera 13 | - Outlet Sensor 14 | 15 | 16 | These accessories are added as different file name with MRz added to the file/accessory. 17 | 18 | **RPi Setup :** 19 | ------------ 20 | MQTT Server using Mosca. 21 | forever installed for executing scripts in start up. 22 | MotionEye for RPi Camera . 23 | duckDNS used to access RPi remotely through port forwading. 24 | 25 | 26 | **Accessory Information :** 27 | --------------------- 28 | - Motion Sensor: [PIR ](https://www.aliexpress.com/item/Free-Shipping-HC-SR501-Adjust-Infrared-IR-Pyroelectric-Infrared-PIR-module-Motion-Sensor-Detector-Module-We/1564561530.html?spm=2114.01010208.3.1.fw8lUJ&ws_ab_test=searchweb0_0,searchweb201602_6_10065_10068_433_434_10136_10137_10138_10060_10062_10141_10056_126_10055_10054_10059_201_10531_10099_10530_10103_10102_10096_10052_10144_10053_10050_10107_10142_10051_10106_10143_10526_10529_10084_10083_10080_10082_10081_10110_10111_10112_10113_10114_10078_10079_10073_10070_10122_10123_10124,searchweb201603_7,afswitch_1,ppcSwitch_5,single_sort_0_default&btsid=22f1d41e-f8c1-4ebc-905d-674cf9cd61df&algo_expid=5bdb3aea-6f98-4a18-b021-21d7399bc9fc-0&algo_pvid=5bdb3aea-6f98-4a18-b021-21d7399bc9fc "PIR") 29 | 30 | PIR sensor is tied to PIN 27 . The interrupt is captured using the npm library pigpio. When motion is detected for a duration of 2 secs the image is captured from the motioneye and is stored . This image is sent to the iOS notifiction if it is subscribed . 31 | 32 | - Temperature/Humidity Sensor :[DHT 11](https://www.aliexpress.com/item/New-DHT11-Temperature-and-Relative-Humidity-Sensor-Module-for-arduino/1873305905.html?spm=2114.01010208.3.1.UYYlF0&ws_ab_test=searchweb0_0,searchweb201602_6_10065_10068_433_434_10136_10137_10138_10060_10062_10141_10056_126_10055_10054_10059_201_10531_10099_10530_10103_10102_10096_10052_10144_10053_10050_10107_10142_10051_10106_10143_10526_10529_10084_10083_10080_10082_10081_10110_10111_10112_10113_10114_10078_10079_10073_10070_10122_10123_10124,searchweb201603_7,afswitch_1,ppcSwitch_5,single_sort_0_default&btsid=81de70fd-4b96-438e-a3f3-905a1678a619&algo_expid=4c343c5c-f9ec-410c-b281-fcc752c34535-0&algo_pvid=4c343c5c-f9ec-410c-b281-fcc752c34535) 33 | 34 | DHT11 is used to read using the ESP8266 and is transmitted using the MQTT . The message is published and the MQTT server running in the RPi reads and trigger/sets the temp and humidity sensors properties . This is done at every 1 hr interval. 35 | 36 | - Light - WS2812 RGB NeoPixel : 37 | [WS2812 - NexPixel](https://www.aliexpress.com/item/1pcs-RGB-LED-Ring-24Bit-WS2812-5050-RGB-LED-Integrated-Drivers/32787336145.html?spm=2114.01010208.3.63.upTssX&ws_ab_test=searchweb0_0,searchweb201602_6_10065_10068_433_434_10136_10137_10138_10060_10062_10141_10056_126_10055_10054_10059_201_10531_10099_10530_10103_10102_10096_10052_10144_10053_10050_10107_10142_10051_10106_10143_10526_10529_10084_10083_10080_10082_10081_10110_10111_10112_10113_10114_10078_10079_10073_10070_10122_10123_10124,searchweb201603_7,afswitch_1,ppcSwitch_5,single_sort_0_default&btsid=f754c8b2-0913-4684-847c-9fd93dafff57&algo_expid=db077eb5-dd01-4f75-8dd9-69b0b711c656-7&algo_pvid=db077eb5-dd01-4f75-8dd9-69b0b711c656) 38 | 39 | ESP8266 is connected to WS2812 RGB .The HSV details are transmitted from the RPi through MQTT to ESP8266. The HSV from the iOS Home app is changed when color/brightness/hue/saturation is changed.Neopixel library is used in ESP8266 for controlling the WS2812.PubClient for the MQTT server/client. The same ESP8266 is connected with the WS2812 and the DHT11 and comminucates to the HAP through MQTT protocals. 40 | 41 | - Camera : 42 | [RaspiCameraz](https://www.aliexpress.com/item/Free-Shipping-Raspberry-Pi-CSI-Camera-Module-5MP-Webcam-Video-1080p-720p/32414048534.html?spm=2114.40010208.4.9.j5VPEm) 43 | 44 | Raspi Cam used to capture the image once motion detection happens.The camera is also configured for the motionEye for live streaming.The PIR sensor once detects any motion captures an image using task (thanks to legotheboss for the task to capture the image ). The notification once selected, the app is opened and the live streaming starts. 45 | 46 | - Outlet : 47 | The Outlet uses [S20 Sonoff](http://sonoff.itead.cc/en/products/residential/s20-socket) 48 | 49 | Sonoff S20 outlet an ESP8266 integrated outlet .The stock firmware is replaced with [Tasmota](https://github.com/arendst/Sonoff-Tasmota). This is a customized one which has an MQTT server integrated.Also this emulates the Belkin hub and can be controller by Alexa as well .This can be configured in multiple ways and the button can perform multiple actions. 50 | 51 | **HomeKit Installation** 52 | The homekit installer downloads the HAP-NodeJS from the repository ,installs motion eye ,downloads the updated accesories, camera,accessory.js and updated HAPServer.js(fixed the notification unregistering problem),other npm modules 53 | ``` 54 | cd /home/pi/Desktop 55 | sudo wget https://goo.gl/HZuiRI -O MRzInstaller 56 | sudo sh MRzInstaller 57 | ``` 58 | 59 | This will take care of installing all the modules as required. 60 | 61 | **ESP8266 Integration** 62 | 63 | -For ESP8266 integration using the MQTT please do check [ESP8266 MQTT MRz ](https://github.com/rahulmranjith/MQTT_esp8266_NeoPixel/tree/master) 64 | 65 | **Additional Changes :** 66 | ---------------- 67 | * **Configure the RPi for remote access:** 68 | 69 | `sudo nano /etc/dhcpcd.conf` 70 | 71 | Add the following code to the last. This will set the IP of the pi as 192.168.1.99 72 | 73 | ``` 74 | interface eth0 75 | static ip_address=192.168.1.199/24 76 | static routers=192.168.1.1 77 | static domain_name_servers=192.168.1.1 78 | interface wlan0 79 | static ip_address=192.168.1.99/24 80 | static routers=192.168.1.1 81 | static domain_name_servers=192.168.1.1 82 | 83 | ``` 84 | 85 | * **Set the RPi camera for the motion eye** 86 | 87 | `sudo nano /etc/modules` 88 | 89 | Add `bcm2835-v4l2` to the end and reboot. 90 | 91 | * **Camera red led off** 92 | 93 | `sudo nano /boot/config.txt` 94 | 95 | Add `disable_camera_led=1` to the last line 96 | 97 | * **Configure forever for startup script** 98 | 99 | `sudo nano /etc/rc.local` 100 | 101 | Add 102 | ``` 103 | sudo forever start /home/pi/rmrz/HAP-NodeJS/CameraCore.js 104 | sudo forever start /home/pi/rmrz/duckdns.js 105 | ``` 106 | before `exit 0` 107 | -------------------------------------------------------------------------------- /lib/gen/import.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var plist = require('simple-plist'); 4 | var Characteristic = require('../Characteristic').Characteristic; 5 | 6 | /** 7 | * This module is intended to be run from the command line. It is a script that extracts Apple's Service 8 | * and Characteristic UUIDs and structures from Apple's own HomeKit Accessory Simulator app. 9 | */ 10 | 11 | // assumed location of the plist we need (might want to make this a command-line argument at some point) 12 | var plistPath = '/Applications/HomeKit Accessory Simulator.app/Contents/Frameworks/HAPAccessoryKit.framework/Versions/A/Resources/default.metadata.plist'; 13 | var metadata = plist.readFileSync(plistPath); 14 | 15 | // begin writing the output file 16 | var outputPath = path.join(__dirname, 'HomeKitTypes.js'); 17 | var output = fs.createWriteStream(outputPath); 18 | 19 | output.write("// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY\n"); 20 | output.write("\n"); 21 | output.write("var inherits = require('util').inherits;\n"); 22 | output.write("var Characteristic = require('../Characteristic').Characteristic;\n"); 23 | output.write("var Service = require('../Service').Service;\n"); 24 | output.write("\n"); 25 | 26 | /** 27 | * Characteristics 28 | */ 29 | 30 | // index Characteristics for quick access while building Services 31 | var characteristics = {}; // characteristics[UUID] = classyName 32 | 33 | for (var index in metadata.Characteristics) { 34 | var characteristic = metadata.Characteristics[index]; 35 | var classyName = characteristic.Name.replace(/[\s\-]/g, ""); // "Target Door State" -> "TargetDoorState" 36 | classyName = classyName.replace(/[.]/g, "_"); // "PM2.5" -> "PM2_5" 37 | 38 | // index classyName for when we want to declare these in Services below 39 | characteristics[characteristic.UUID] = classyName; 40 | 41 | output.write("/**\n * Characteristic \"" + characteristic.Name + "\"\n */\n\n"); 42 | output.write("Characteristic." + classyName + " = function() {\n"); 43 | output.write(" Characteristic.call(this, '" + characteristic.Name + "', '" + characteristic.UUID + "');\n"); 44 | 45 | // apply Characteristic properties 46 | output.write(" this.setProps({\n"); 47 | output.write(" format: Characteristic.Formats." + getCharacteristicFormatsKey(characteristic.Format)); 48 | 49 | // special unit type? 50 | if (characteristic.Unit) 51 | output.write(",\n unit: Characteristic.Units." + getCharacteristicUnitsKey(characteristic.Unit)); 52 | 53 | // apply any basic constraints if present 54 | if (characteristic.Constraints && typeof characteristic.Constraints.MaximumValue !== 'undefined') 55 | output.write(",\n maxValue: " + characteristic.Constraints.MaximumValue); 56 | 57 | if (characteristic.Constraints && typeof characteristic.Constraints.MinimumValue !== 'undefined') 58 | output.write(",\n minValue: " + characteristic.Constraints.MinimumValue); 59 | 60 | if (characteristic.Constraints && typeof characteristic.Constraints.StepValue !== 'undefined') 61 | output.write(",\n minStep: " + characteristic.Constraints.StepValue); 62 | 63 | output.write(",\n perms: ["); 64 | var sep = "" 65 | for (var i in characteristic.Properties) { 66 | var perms = getCharacteristicPermsKey(characteristic.Properties[i]); 67 | if (perms) { 68 | output.write(sep + "Characteristic.Perms." + getCharacteristicPermsKey(characteristic.Properties[i])); 69 | sep = ", " 70 | } 71 | } 72 | output.write("]"); 73 | 74 | output.write("\n });\n"); 75 | 76 | // set default value 77 | output.write(" this.value = this.getDefaultValue();\n"); 78 | 79 | output.write("};\n\n"); 80 | output.write("inherits(Characteristic." + classyName + ", Characteristic);\n\n"); 81 | output.write("Characteristic." + classyName + ".UUID = '" + characteristic.UUID + "';\n\n"); 82 | 83 | if (characteristic.Constraints && characteristic.Constraints.ValidValues) { 84 | // this characteristic can only have one of a defined set of values (like an enum). Define the values 85 | // as static members of our subclass. 86 | output.write("// The value property of " + classyName + " must be one of the following:\n"); 87 | 88 | for (var value in characteristic.Constraints.ValidValues) { 89 | var name = characteristic.Constraints.ValidValues[value]; 90 | 91 | var constName = name.toUpperCase().replace(/[^\w]+/g, '_'); 92 | if ((/^[1-9]/).test(constName)) constName = "_" + constName; // variables can't start with a number 93 | output.write("Characteristic." + classyName + "." + constName + " = " + value + ";\n"); 94 | } 95 | 96 | output.write("\n"); 97 | } 98 | } 99 | 100 | 101 | /** 102 | * Services 103 | */ 104 | 105 | for (var index in metadata.Services) { 106 | var service = metadata.Services[index]; 107 | var classyName = service.Name.replace(/[\s\-]/g, ""); // "Smoke Sensor" -> "SmokeSensor" 108 | 109 | output.write("/**\n * Service \"" + service.Name + "\"\n */\n\n"); 110 | output.write("Service." + classyName + " = function(displayName, subtype) {\n"); 111 | // call superclass constructor 112 | output.write(" Service.call(this, displayName, '" + service.UUID + "', subtype);\n"); 113 | 114 | // add Characteristics for this Service 115 | if (service.RequiredCharacteristics) { 116 | output.write("\n // Required Characteristics\n"); 117 | 118 | for (var index in service.RequiredCharacteristics) { 119 | var characteristicUUID = service.RequiredCharacteristics[index]; 120 | 121 | // look up the classyName from the hash we built above 122 | var characteristicClassyName = characteristics[characteristicUUID]; 123 | 124 | output.write(" this.addCharacteristic(Characteristic." + characteristicClassyName + ");\n"); 125 | } 126 | } 127 | 128 | // add "Optional" Characteristics for this Service 129 | if (service.OptionalCharacteristics) { 130 | output.write("\n // Optional Characteristics\n"); 131 | 132 | for (var index in service.OptionalCharacteristics) { 133 | var characteristicUUID = service.OptionalCharacteristics[index]; 134 | 135 | // look up the classyName from the hash we built above 136 | var characteristicClassyName = characteristics[characteristicUUID]; 137 | 138 | output.write(" this.addOptionalCharacteristic(Characteristic." + characteristicClassyName + ");\n"); 139 | } 140 | } 141 | 142 | output.write("};\n\n"); 143 | output.write("inherits(Service." + classyName + ", Service);\n\n"); 144 | output.write("Service." + classyName + ".UUID = '" + service.UUID + "';\n\n"); 145 | } 146 | 147 | output.write("var HomeKitTypesBridge = require('./HomeKitTypes-Bridge');\n\n"); 148 | 149 | /** 150 | * Done! 151 | */ 152 | 153 | output.end(); 154 | 155 | /** 156 | * Useful functions 157 | */ 158 | 159 | function getCharacteristicFormatsKey(format) { 160 | // coerce 'int32' to 'int' 161 | if (format == 'int32') format = 'int'; 162 | 163 | // look up the key in our known-formats dict 164 | for (var key in Characteristic.Formats) 165 | if (Characteristic.Formats[key] == format) 166 | return key; 167 | 168 | throw new Error("Unknown characteristic format '" + format + "'"); 169 | } 170 | 171 | function getCharacteristicUnitsKey(units) { 172 | // look up the key in our known-units dict 173 | for (var key in Characteristic.Units) 174 | if (Characteristic.Units[key] == units) 175 | return key; 176 | 177 | throw new Error("Unknown characteristic units '" + units + "'"); 178 | } 179 | 180 | function getCharacteristicPermsKey(perm) { 181 | switch (perm) { 182 | case "read": return "READ"; 183 | case "write": return "WRITE"; 184 | case "cnotify": return "NOTIFY"; 185 | case "uncnotify": return undefined; 186 | default: throw new Error("Unknown characteristic permission '" + perm + "'"); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /lib/camera/RTPProxy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const dgram = require('dgram'); 4 | const EventEmitter = require('events').EventEmitter; 5 | 6 | class RTPProxy extends EventEmitter { 7 | constructor(options) { 8 | super() 9 | 10 | let self = this; 11 | self.type = options.isIPV6 ? 'udp6' : 'udp4' 12 | 13 | self.options = options; 14 | self.startingPort = 10000; 15 | 16 | self.outgoingAddress = options.outgoingAddress; 17 | self.outgoingPort = options.outgoingPort; 18 | self.incomingPayloadType = 0; 19 | self.outgoingSSRC = options.outgoingSSRC; 20 | self.disabled = options.disabled; 21 | self.incomingSSRC = null; 22 | self.outgoingPayloadType = null; 23 | } 24 | 25 | setup() { 26 | let self = this; 27 | return self.createSocketPair(self.type) 28 | .then(function(sockets) { 29 | self.incomingRTPSocket = sockets[0]; 30 | self.incomingRTCPSocket = sockets[1]; 31 | 32 | return self.createSocket(self.type); 33 | }).then(function(socket) { 34 | self.outgoingSocket = socket; 35 | self.onBound(); 36 | }); 37 | } 38 | 39 | destroy() { 40 | let self = this; 41 | if (self.incomingRTPSocket) { 42 | self.incomingRTPSocket.close(); 43 | } 44 | 45 | if (self.incomingRTCPSocket) { 46 | self.incomingRTCPSocket.close(); 47 | } 48 | 49 | if (self.outgoingSocket) { 50 | self.outgoingSocket.close(); 51 | } 52 | } 53 | 54 | incomingRTPPort() { 55 | let self = this; 56 | return self.incomingRTPSocket.address().port; 57 | } 58 | 59 | incomingRTCPPort() { 60 | let self = this; 61 | return self.incomingRTCPSocket.address().port; 62 | } 63 | 64 | outgoingLocalPort() { 65 | let self = this; 66 | return self.outgoingSocket.address().port; 67 | } 68 | 69 | setServerAddress(address) { 70 | let self = this; 71 | self.serverAddress = address; 72 | } 73 | 74 | setServerRTPPort(port) { 75 | let self = this; 76 | self.serverRTPPort = port; 77 | } 78 | 79 | setServerRTCPPort(port) { 80 | let self = this; 81 | self.serverRTCPPort = port; 82 | } 83 | 84 | setIncomingPayloadType(pt) { 85 | let self = this; 86 | self.incomingPayloadType = pt; 87 | } 88 | 89 | setOutgoingPayloadType(pt) { 90 | let self = this; 91 | self.outgoingPayloadType = pt; 92 | } 93 | 94 | sendOut(msg) { 95 | let self = this; 96 | // Just drop it if we're not setup yet, I guess. 97 | if(!self.outgoingAddress || !self.outgoingPort) 98 | return; 99 | 100 | self.outgoingSocket.send(msg, self.outgoingPort, self.outgoingAddress); 101 | } 102 | 103 | sendBack(msg) { 104 | let self = this; 105 | // Just drop it if we're not setup yet, I guess. 106 | if(!self.serverAddress || !self.serverRTCPPort) 107 | return; 108 | 109 | self.outgoingSocket.send(msg, self.serverRTCPPort, self.serverAddress); 110 | } 111 | 112 | onBound() { 113 | let self = this; 114 | if(self.disabled) 115 | return; 116 | 117 | self.incomingRTPSocket.on('message', function(msg, rinfo) { 118 | self.rtpMessage(msg); 119 | }); 120 | 121 | self.incomingRTCPSocket.on('message', function(msg, rinfo) { 122 | self.rtcpMessage(msg); 123 | }); 124 | 125 | self.outgoingSocket.on('message', function(msg, rinfo) { 126 | self.rtcpReply(msg); 127 | }); 128 | } 129 | 130 | rtpMessage(msg) { 131 | let self = this; 132 | 133 | if(msg.length < 12) { 134 | // Not a proper RTP packet. Just forward it. 135 | self.sendOut(msg); 136 | return; 137 | } 138 | 139 | let mpt = msg.readUInt8(1); 140 | let pt = mpt & 0x7F; 141 | if(pt == self.incomingPayloadType) { 142 | mpt = (mpt & 0x80) | self.outgoingPayloadType; 143 | msg.writeUInt8(mpt, 1); 144 | } 145 | 146 | if(self.incomingSSRC === null) 147 | self.incomingSSRC = msg.readUInt32BE(4); 148 | 149 | msg.writeUInt32BE(self.outgoingSSRC, 8); 150 | self.sendOut(msg); 151 | } 152 | 153 | processRTCPMessage(msg, transform) { 154 | let self = this; 155 | let rtcpPackets = []; 156 | let offset = 0; 157 | while((offset + 4) <= msg.length) { 158 | let pt = msg.readUInt8(offset + 1); 159 | let len = msg.readUInt16BE(offset + 2) * 4; 160 | if((offset + 4 + len) > msg.length) 161 | break; 162 | let packet = msg.slice(offset, offset + 4 + len); 163 | 164 | packet = transform(pt, packet); 165 | 166 | if(packet) 167 | rtcpPackets.push(packet); 168 | 169 | offset += 4 + len; 170 | } 171 | 172 | if(rtcpPackets.length > 0) 173 | return Buffer.concat(rtcpPackets); 174 | 175 | return null; 176 | } 177 | 178 | rtcpMessage(msg) { 179 | let self = this; 180 | 181 | let processed = self.processRTCPMessage(msg, function(pt, packet) { 182 | if(pt != 200 || packet.length < 8) 183 | return packet; 184 | 185 | if(self.incomingSSRC === null) 186 | self.incomingSSRC = packet.readUInt32BE(4); 187 | packet.writeUInt32BE(self.outgoingSSRC, 4); 188 | return packet; 189 | }); 190 | 191 | if(processed) 192 | self.sendOut(processed); 193 | } 194 | 195 | rtcpReply(msg) { 196 | let self = this; 197 | 198 | let processed = self.processRTCPMessage(msg, function(pt, packet) { 199 | if(pt != 201 || packet.length < 12) 200 | return packet; 201 | 202 | // Assume source 1 is the one we want to edit. 203 | packet.writeUInt32BE(self.incomingSSRC, 8); 204 | return packet; 205 | }); 206 | 207 | 208 | if(processed) 209 | self.sendOut(processed); 210 | } 211 | 212 | createSocket(type) { 213 | let self = this; 214 | return new Promise(function(resolve, reject) { 215 | let retry = function() { 216 | let socket = dgram.createSocket(type); 217 | 218 | let bindErrorHandler = function() { 219 | if(self.startingPort == 65535) 220 | self.startingPort = 10000; 221 | else 222 | ++self.startingPort; 223 | 224 | socket.close(); 225 | retry(); 226 | }; 227 | 228 | socket.once('error', bindErrorHandler); 229 | 230 | socket.on('listening', function() { 231 | resolve(socket); 232 | }); 233 | 234 | socket.bind(self.startingPort); 235 | }; 236 | 237 | retry(); 238 | }); 239 | } 240 | 241 | createSocketPair(type) { 242 | let self = this; 243 | return new Promise(function(resolve, reject) { 244 | let retry = function() { 245 | let socket1 = dgram.createSocket(type); 246 | let socket2 = dgram.createSocket(type); 247 | let state = {socket1: 0, socket2: 0}; 248 | 249 | let recheck = function() { 250 | if(state.socket1 == 0 || state.socket2 == 0) 251 | return; 252 | 253 | if(state.socket1 == 2 && state.socket2 == 2) { 254 | resolve([socket1, socket2]); 255 | return; 256 | } 257 | 258 | if(self.startingPort == 65534) 259 | self.startingPort = 10000; 260 | else 261 | ++self.startingPort; 262 | 263 | socket1.close(); 264 | socket2.close(); 265 | 266 | retry(self.startingPort); 267 | } 268 | 269 | socket1.once('error', function() { 270 | state.socket1 = 1; 271 | recheck(); 272 | }); 273 | 274 | socket2.once('error', function() { 275 | state.socket2 = 1; 276 | recheck(); 277 | }); 278 | 279 | socket1.once('listening', function() { 280 | state.socket1 = 2; 281 | recheck(); 282 | }); 283 | 284 | socket2.once('listening', function() { 285 | state.socket2 = 2; 286 | recheck(); 287 | }); 288 | 289 | socket1.bind(self.startingPort); 290 | socket2.bind(self.startingPort + 1); 291 | } 292 | 293 | retry(); 294 | }); 295 | } 296 | } 297 | 298 | module.exports = RTPProxy; 299 | -------------------------------------------------------------------------------- /accessories/RGBLight_MRz_accessory.js: -------------------------------------------------------------------------------- 1 | var Accessory = require('../').Accessory; 2 | var Service = require('../').Service; 3 | var Characteristic = require('../').Characteristic; 4 | var uuid = require('../').uuid; 5 | 6 | //mqtt to publish the value hue.saturation.brightness to the ESP8266 server. 7 | 8 | function publishMqtt(type, value) { 9 | console.log(value); 10 | var mqtt = require('mqtt'); 11 | var clients = mqtt.connect('mqtt://localhost'); 12 | clients.publish(type, value.toString()); 13 | } 14 | 15 | //connect a normal LED to GPIO 17 to view the light onoff, brightness change using the PWM cycles. 16 | var Gpio = require('pigpio').Gpio; 17 | led = new Gpio(17, { 18 | mode: Gpio.OUTPUT 19 | }); 20 | 21 | dutyCycle = 0; 22 | 23 | var LightController = { 24 | name: " RGB MRz Light", //name of accessory 25 | pincode: "031-45-154", 26 | username: "11:99:33:12:33:1A", // MAC like address used by HomeKit to differentiate accessories. 27 | manufacturer: "JAI Systems", //manufacturer (optional) 28 | model: "v1.0", //model (optional) 29 | serialNumber: "A12345K99", //serial number (optional) 30 | 31 | power: false, //curent power status 32 | brightness: 100, //current brightness 33 | hue: 0, //current hue 34 | saturation: 0, //current saturation 35 | 36 | outputLogs: true, //output logs 37 | 38 | setPower: function(status) { //set power of accessory 39 | if (this.outputLogs) console.log("Turning the '%s' %s", this.name, status ? "on" : "off"); 40 | if (status == 1 || status == true) { 41 | publishMqtt('LIGHTON', 'LIGHTON'); 42 | publishMqtt('brightness', 'brightness' + this.brightness);//publish brightness message to esp8266 43 | led.pwmWrite(255); 44 | //pwm with 255 gives ull 45 | 46 | } else { 47 | publishMqtt('LIGHTOFF', 'LIGHTOFF');//publish lightoff message to esp8266 48 | led.pwmWrite(0); 49 | //make light off 50 | } 51 | this.power = status; 52 | }, 53 | 54 | getPower: function() { //get power of accessory 55 | if (this.outputLogs) console.log("'%s' is %s.", this.name, this.power ? "on" : "off"); 56 | return this.power ? true : false; 57 | }, 58 | 59 | setBrightness: function(brightness) { //set brightness 60 | publishMqtt('brightness', 'brightness' + brightness);//publish brig to ESP8266 61 | if (this.outputLogs) console.log("Setting '%s' brightness to %s", this.name, brightness); 62 | this.brightness = brightness; 63 | var cycle = (brightness * 255) / 100; 64 | cycle = Math.round(cycle); 65 | 66 | led.pwmWrite(cycle);//GPIO Led brigh value. 67 | 68 | console.log(cycle); 69 | }, 70 | 71 | getBrightness: function() { //get brightness 72 | if (this.outputLogs) console.log("'%s' brightness is %s", this.name, this.brightness); 73 | return this.brightness; 74 | }, 75 | 76 | setSaturation: function(saturation) { //set brightness 77 | publishMqtt('saturation', 'saturation' + saturation);//publish saturation to ESP8266 78 | if (this.outputLogs) console.log("Setting '%s' saturation to %s", this.name, saturation); 79 | this.saturation = saturation; 80 | }, 81 | 82 | getSaturation: function() { //get brightness 83 | if (this.outputLogs) console.log("'%s' saturation is %s", this.name, this.saturation); 84 | return this.saturation; 85 | }, 86 | 87 | setHue: function(hue) { //set brightness 88 | publishMqtt('hue', 'hue' + hue); 89 | if (this.outputLogs) console.log("Setting '%s' hue to %s", this.name, hue); 90 | this.hue = hue; 91 | }, 92 | 93 | getHue: function() { //get hue 94 | if (this.outputLogs) console.log("'%s' hue is %s", this.name, this.hue); 95 | return this.hue; 96 | }, 97 | 98 | identify: function() { //identify the accessory 99 | if (this.outputLogs) console.log("Identify the '%s'", this.name); 100 | } 101 | } 102 | 103 | // Generate a consistent UUID for our light Accessory that will remain the same even when 104 | // restarting our server. We use the `uuid.generate` helper function to create a deterministic 105 | // UUID based on an arbitrary "namespace" and the word "light". 106 | var lightUUID = uuid.generate('hap-nodejs:accessories:light' + LightController.name); 107 | 108 | // This is the Accessory that we'll return to HAP-NodeJS that represents our light. 109 | var lightAccessory = exports.accessory = new Accessory(LightController.name, lightUUID); 110 | 111 | // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) 112 | lightAccessory.username = LightController.username; 113 | lightAccessory.pincode = LightController.pincode; 114 | 115 | // set some basic properties (these values are arbitrary and setting them is optional) 116 | lightAccessory 117 | .getService(Service.AccessoryInformation) 118 | .setCharacteristic(Characteristic.Manufacturer, LightController.manufacturer) 119 | .setCharacteristic(Characteristic.Model, LightController.model) 120 | .setCharacteristic(Characteristic.SerialNumber, LightController.serialNumber); 121 | 122 | // listen for the "identify" event for this Accessory 123 | lightAccessory.on('identify', function(paired, callback) { 124 | LightController.identify(); 125 | callback(); 126 | }); 127 | 128 | // Add the actual Lightbulb Service and listen for change events from iOS. 129 | // We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` 130 | lightAccessory 131 | .addService(Service.Lightbulb, LightController.name) // services exposed to the user should have "names" like "Light" for this case 132 | .getCharacteristic(Characteristic.On) 133 | .on('set', function(value, callback) { 134 | LightController.setPower(value); 135 | 136 | // Our light is synchronous - this value has been successfully set 137 | // Invoke the callback when you finished processing the request 138 | // If it's going to take more than 1s to finish the request, try to invoke the callback 139 | // after getting the request instead of after finishing it. This avoids blocking other 140 | // requests from HomeKit. 141 | callback(); 142 | }) 143 | // We want to intercept requests for our current power state so we can query the hardware itself instead of 144 | // allowing HAP-NodeJS to return the cached Characteristic.value. 145 | .on('get', function(callback) { 146 | callback(null, LightController.getPower()); 147 | }); 148 | 149 | // To inform HomeKit about changes occurred outside of HomeKit (like user physically turn on the light) 150 | // Please use Characteristic.updateValue 151 | // 152 | // lightAccessory 153 | // .getService(Service.Lightbulb) 154 | // .getCharacteristic(Characteristic.On) 155 | // .updateValue(true); 156 | 157 | // also add an "optional" Characteristic for Brightness 158 | lightAccessory 159 | .getService(Service.Lightbulb) 160 | .addCharacteristic(Characteristic.Brightness) 161 | .on('set', function(value, callback) { 162 | LightController.setBrightness(value); 163 | callback(); 164 | }) 165 | .on('get', function(callback) { 166 | callback(null, LightController.getBrightness()); 167 | }); 168 | 169 | // also add an "optional" Characteristic for Saturation 170 | lightAccessory 171 | .getService(Service.Lightbulb) 172 | .addCharacteristic(Characteristic.Saturation) 173 | .on('set', function(value, callback) { 174 | LightController.setSaturation(value); 175 | callback(); 176 | }) 177 | .on('get', function(callback) { 178 | callback(null, LightController.getSaturation()); 179 | }); 180 | 181 | // also add an "optional" Characteristic for Hue 182 | lightAccessory 183 | .getService(Service.Lightbulb) 184 | .addCharacteristic(Characteristic.Hue) 185 | .on('set', function(value, callback) { 186 | LightController.setHue(value); 187 | callback(); 188 | }) 189 | .on('get', function(callback) { 190 | callback(null, LightController.getHue()); 191 | }); 192 | -------------------------------------------------------------------------------- /lib/util/encryption.js: -------------------------------------------------------------------------------- 1 | var crypto = require("crypto"); 2 | var chacha20poly1305 = require("./chacha20poly1305"); 3 | var curve25519 = require("curve25519-n"); 4 | var assert = require('assert'); 5 | var bufferShim = require('buffer-shims'); 6 | 7 | module.exports = { 8 | generateCurve25519SecretKey: generateCurve25519SecretKey, 9 | generateCurve25519PublicKeyFromSecretKey: generateCurve25519PublicKeyFromSecretKey, 10 | generateCurve25519SharedSecKey: generateCurve25519SharedSecKey, 11 | layerEncrypt: layerEncrypt, 12 | layerDecrypt: layerDecrypt, 13 | verifyAndDecrypt: verifyAndDecrypt, 14 | encryptAndSeal: encryptAndSeal 15 | }; 16 | 17 | function fromHex(h) { 18 | h.replace(/([^0-9a-f])/g, ''); 19 | var out = [], len = h.length, w = ''; 20 | for (var i = 0; i < len; i += 2) { 21 | w = h[i]; 22 | if (((i+1) >= len) || typeof h[i+1] === 'undefined') { 23 | w += '0'; 24 | } else { 25 | w += h[i+1]; 26 | } 27 | out.push(parseInt(w, 16)); 28 | } 29 | return out; 30 | } 31 | 32 | function generateCurve25519SecretKey() { 33 | var secretKey = bufferShim.alloc(32); 34 | curve25519.makeSecretKey(secretKey); 35 | return secretKey; 36 | } 37 | 38 | function generateCurve25519PublicKeyFromSecretKey(secKey) { 39 | var publicKey = curve25519.derivePublicKey(secKey); 40 | return publicKey; 41 | } 42 | 43 | function generateCurve25519SharedSecKey(secKey, pubKey) { 44 | var sharedSec = curve25519.deriveSharedSecret(secKey, pubKey); 45 | return sharedSec; 46 | } 47 | 48 | //Security Layer Enc/Dec 49 | 50 | function layerEncrypt(data, count, key) { 51 | var result = bufferShim.alloc(0); 52 | var total = data.length; 53 | for (var offset = 0; offset < total; ) { 54 | var length = Math.min(total - offset, 0x400); 55 | var leLength = bufferShim.alloc(2); 56 | leLength.writeUInt16LE(length,0); 57 | 58 | var nonce = bufferShim.alloc(8); 59 | writeUInt64LE(count.value++, nonce, 0); 60 | 61 | var result_Buffer = bufferShim.alloc(length); 62 | var result_mac = bufferShim.alloc(16); 63 | encryptAndSeal(key, nonce, data.slice(offset, offset + length), 64 | leLength,result_Buffer, result_mac); 65 | 66 | offset += length; 67 | 68 | result = Buffer.concat([result,leLength,result_Buffer,result_mac]); 69 | } 70 | return result; 71 | } 72 | 73 | function layerDecrypt(packet, count, key, extraInfo) { 74 | // Handle Extra Info 75 | if (extraInfo.leftoverData != undefined) { 76 | packet = Buffer.concat([extraInfo.leftoverData, packet]); 77 | } 78 | 79 | var result = bufferShim.alloc(0); 80 | var total = packet.length; 81 | 82 | for (var offset = 0; offset < total;) { 83 | var realDataLength = packet.slice(offset,offset+2).readUInt16LE(0); 84 | 85 | var availableDataLength = total - offset - 2 - 16; 86 | if (realDataLength > availableDataLength) { 87 | // Fragmented packet 88 | extraInfo.leftoverData = packet.slice(offset); 89 | break; 90 | } else { 91 | extraInfo.leftoverData = undefined; 92 | } 93 | 94 | var nonce = bufferShim.alloc(8); 95 | writeUInt64LE(count.value++, nonce, 0); 96 | 97 | var result_Buffer = bufferShim.alloc(realDataLength); 98 | 99 | if (verifyAndDecrypt(key, nonce, packet.slice(offset + 2, offset + 2 + realDataLength), 100 | packet.slice(offset + 2 + realDataLength, offset + 2 + realDataLength + 16), 101 | packet.slice(offset,offset+2),result_Buffer)) { 102 | result = Buffer.concat([result,result_Buffer]); 103 | offset += (18 + realDataLength); 104 | } else { 105 | console.log("Layer Decrypt fail!"); 106 | console.log("Packet: %s", packet.toString('hex')); 107 | return 0; 108 | } 109 | }; 110 | 111 | return result; 112 | } 113 | 114 | //General Enc/Dec 115 | function verifyAndDecrypt(key,nonce,ciphertext,mac,addData,plaintext) { 116 | var ctx = new chacha20poly1305.Chacha20Ctx(); 117 | chacha20poly1305.chacha20_keysetup(ctx, key); 118 | chacha20poly1305.chacha20_ivsetup(ctx, nonce); 119 | var poly1305key = bufferShim.alloc(64); 120 | var zeros = bufferShim.alloc(64); 121 | chacha20poly1305.chacha20_update(ctx,poly1305key,zeros,zeros.length); 122 | 123 | var poly1305_contxt = new chacha20poly1305.Poly1305Ctx(); 124 | chacha20poly1305.poly1305_init(poly1305_contxt, poly1305key); 125 | 126 | var addDataLength = 0; 127 | if (addData != undefined) { 128 | addDataLength = addData.length; 129 | chacha20poly1305.poly1305_update(poly1305_contxt, addData, addData.length); 130 | if ((addData.length % 16) != 0) { 131 | chacha20poly1305.poly1305_update(poly1305_contxt, bufferShim.alloc(16-(addData.length%16)), 16-(addData.length%16)); 132 | } 133 | } 134 | 135 | chacha20poly1305.poly1305_update(poly1305_contxt, ciphertext, ciphertext.length); 136 | if ((ciphertext.length % 16) != 0) { 137 | chacha20poly1305.poly1305_update(poly1305_contxt, bufferShim.alloc(16-(ciphertext.length%16)), 16-(ciphertext.length%16)); 138 | } 139 | 140 | var leAddDataLen = bufferShim.alloc(8); 141 | writeUInt64LE(addDataLength, leAddDataLen, 0); 142 | chacha20poly1305.poly1305_update(poly1305_contxt, leAddDataLen, 8); 143 | 144 | var leTextDataLen = bufferShim.alloc(8); 145 | writeUInt64LE(ciphertext.length, leTextDataLen, 0); 146 | chacha20poly1305.poly1305_update(poly1305_contxt, leTextDataLen, 8); 147 | 148 | var poly_out = []; 149 | chacha20poly1305.poly1305_finish(poly1305_contxt, poly_out); 150 | 151 | if (chacha20poly1305.poly1305_verify(mac, poly_out) != 1) { 152 | console.log("Verify Fail"); 153 | return false; 154 | } else { 155 | var written = chacha20poly1305.chacha20_update(ctx,plaintext,ciphertext,ciphertext.length); 156 | chacha20poly1305.chacha20_final(ctx,plaintext.slice(written, ciphertext.length)); 157 | return true; 158 | } 159 | } 160 | 161 | function encryptAndSeal(key,nonce,plaintext,addData,ciphertext,mac) { 162 | var ctx = new chacha20poly1305.Chacha20Ctx(); 163 | chacha20poly1305.chacha20_keysetup(ctx, key); 164 | chacha20poly1305.chacha20_ivsetup(ctx, nonce); 165 | var poly1305key = bufferShim.alloc(64); 166 | var zeros = bufferShim.alloc(64); 167 | chacha20poly1305.chacha20_update(ctx,poly1305key,zeros,zeros.length); 168 | 169 | var written = chacha20poly1305.chacha20_update(ctx,ciphertext,plaintext,plaintext.length); 170 | chacha20poly1305.chacha20_final(ctx,ciphertext.slice(written,plaintext.length)); 171 | 172 | var poly1305_contxt = new chacha20poly1305.Poly1305Ctx(); 173 | chacha20poly1305.poly1305_init(poly1305_contxt, poly1305key); 174 | 175 | var addDataLength = 0; 176 | if (addData != undefined) { 177 | addDataLength = addData.length; 178 | chacha20poly1305.poly1305_update(poly1305_contxt, addData, addData.length); 179 | if ((addData.length % 16) != 0) { 180 | chacha20poly1305.poly1305_update(poly1305_contxt, bufferShim.alloc(16-(addData.length%16)), 16-(addData.length%16)); 181 | } 182 | } 183 | 184 | chacha20poly1305.poly1305_update(poly1305_contxt, ciphertext, ciphertext.length); 185 | if ((ciphertext.length % 16) != 0) { 186 | chacha20poly1305.poly1305_update(poly1305_contxt, bufferShim.alloc(16-(ciphertext.length%16)), 16-(ciphertext.length%16)); 187 | } 188 | 189 | var leAddDataLen = bufferShim.alloc(8); 190 | writeUInt64LE(addDataLength, leAddDataLen, 0); 191 | chacha20poly1305.poly1305_update(poly1305_contxt, leAddDataLen, 8); 192 | 193 | var leTextDataLen = bufferShim.alloc(8); 194 | writeUInt64LE(ciphertext.length, leTextDataLen, 0); 195 | chacha20poly1305.poly1305_update(poly1305_contxt, leTextDataLen, 8); 196 | 197 | chacha20poly1305.poly1305_finish(poly1305_contxt, mac); 198 | } 199 | 200 | var MAX_UINT32 = 0x00000000FFFFFFFF 201 | var MAX_INT53 = 0x001FFFFFFFFFFFFF 202 | 203 | function onesComplement(number) { 204 | number = ~number 205 | if (number < 0) { 206 | number = (number & 0x7FFFFFFF) + 0x80000000 207 | } 208 | return number 209 | } 210 | 211 | function uintHighLow(number) { 212 | assert(number > -1 && number <= MAX_INT53, "number out of range") 213 | assert(Math.floor(number) === number, "number must be an integer") 214 | var high = 0 215 | var signbit = number & 0xFFFFFFFF 216 | var low = signbit < 0 ? (number & 0x7FFFFFFF) + 0x80000000 : signbit 217 | if (number > MAX_UINT32) { 218 | high = (number - low) / (MAX_UINT32 + 1) 219 | } 220 | return [high, low] 221 | } 222 | 223 | function intHighLow(number) { 224 | if (number > -1) { 225 | return uintHighLow(number) 226 | } 227 | var hl = uintHighLow(-number) 228 | var high = onesComplement(hl[0]) 229 | var low = onesComplement(hl[1]) 230 | if (low === MAX_UINT32) { 231 | high += 1 232 | low = 0 233 | } 234 | else { 235 | low += 1 236 | } 237 | return [high, low] 238 | } 239 | 240 | function writeUInt64BE(number, buffer, offset) { 241 | offset = offset || 0 242 | var hl = uintHighLow(number) 243 | buffer.writeUInt32BE(hl[0], offset) 244 | buffer.writeUInt32BE(hl[1], offset + 4) 245 | } 246 | 247 | function writeUInt64LE (number, buffer, offset) { 248 | offset = offset || 0 249 | var hl = uintHighLow(number) 250 | buffer.writeUInt32LE(hl[1], offset) 251 | buffer.writeUInt32LE(hl[0], offset + 4) 252 | } 253 | -------------------------------------------------------------------------------- /lib/Service.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits; 2 | var clone = require('./util/clone').clone; 3 | var EventEmitter = require('events').EventEmitter; 4 | var Characteristic = require('./Characteristic').Characteristic; 5 | 6 | 'use strict'; 7 | 8 | module.exports = { 9 | Service: Service 10 | } 11 | 12 | /** 13 | * Service represents a set of grouped values necessary to provide a logical function. For instance, a 14 | * "Door Lock Mechanism" service might contain two values, one for the "desired lock state" and one for the 15 | * "current lock state". A particular Service is distinguished from others by its "type", which is a UUID. 16 | * HomeKit provides a set of known Service UUIDs defined in HomeKitTypes.js along with a corresponding 17 | * concrete subclass that you can instantiate directly to setup the necessary values. These natively-supported 18 | * Services are expected to contain a particular set of Characteristics. 19 | * 20 | * Unlike Characteristics, where you cannot have two Characteristics with the same UUID in the same Service, 21 | * you can actually have multiple Services with the same UUID in a single Accessory. For instance, imagine 22 | * a Garage Door Opener with both a "security light" and a "backlight" for the display. Each light could be 23 | * a "Lightbulb" Service with the same UUID. To account for this situation, we define an extra "subtype" 24 | * property on Service, that can be a string or other string-convertible object that uniquely identifies the 25 | * Service among its peers in an Accessory. For instance, you might have `service1.subtype = 'security_light'` 26 | * for one and `service2.subtype = 'backlight'` for the other. 27 | * 28 | * You can also define custom Services by providing your own UUID for the type that you generate yourself. 29 | * Custom Services can contain an arbitrary set of Characteristics, but Siri will likely not be able to 30 | * work with these. 31 | * 32 | * @event 'characteristic-change' => function({characteristic, oldValue, newValue, context}) { } 33 | * Emitted after a change in the value of one of our Characteristics has occurred. 34 | */ 35 | 36 | function Service(displayName, UUID, subtype) { 37 | 38 | if (!UUID) throw new Error("Services must be created with a valid UUID."); 39 | 40 | this.displayName = displayName; 41 | this.UUID = UUID; 42 | this.subtype = subtype; 43 | this.iid = null; // assigned later by our containing Accessory 44 | this.characteristics = []; 45 | this.optionalCharacteristics = []; 46 | 47 | // every service has an optional Characteristic.Name property - we'll set it to our displayName 48 | // if one was given 49 | // if you don't provide a display name, some HomeKit apps may choose to hide the device. 50 | if (displayName) { 51 | // create the characteristic if necessary 52 | var nameCharacteristic = 53 | this.getCharacteristic(Characteristic.Name) || 54 | this.addCharacteristic(Characteristic.Name); 55 | 56 | nameCharacteristic.setValue(displayName); 57 | } 58 | } 59 | 60 | inherits(Service, EventEmitter); 61 | 62 | Service.prototype.addCharacteristic = function(characteristic) { 63 | // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance 64 | // of Characteristic. Coerce if necessary. 65 | if (typeof characteristic === 'function') { 66 | characteristic = new (Function.prototype.bind.apply(characteristic, arguments)); 67 | } 68 | // check for UUID conflict 69 | for (var index in this.characteristics) { 70 | var existing = this.characteristics[index]; 71 | if (existing.UUID === characteristic.UUID) 72 | throw new Error("Cannot add a Characteristic with the same UUID as another Characteristic in this Service: " + existing.UUID); 73 | } 74 | 75 | // listen for changes in characteristics and bubble them up 76 | characteristic.on('change', function(change) { 77 | // make a new object with the relevant characteristic added, and bubble it up 78 | this.emit('characteristic-change', clone(change, {characteristic:characteristic})); 79 | }.bind(this)); 80 | 81 | this.characteristics.push(characteristic); 82 | 83 | this.emit('service-configurationChange', clone({service:this})); 84 | 85 | return characteristic; 86 | } 87 | 88 | Service.prototype.removeCharacteristic = function(characteristic) { 89 | var targetCharacteristicIndex; 90 | 91 | for (var index in this.characteristics) { 92 | var existingCharacteristic = this.characteristics[index]; 93 | 94 | if (existingCharacteristic === characteristic) { 95 | targetCharacteristicIndex = index; 96 | break; 97 | } 98 | } 99 | 100 | if (targetCharacteristicIndex) { 101 | this.characteristics.splice(targetCharacteristicIndex, 1); 102 | characteristic.removeAllListeners(); 103 | 104 | this.emit('service-configurationChange', clone({service:this})); 105 | } 106 | } 107 | 108 | Service.prototype.getCharacteristic = function(name) { 109 | // returns a characteristic object from the service 110 | // If Service.prototype.getCharacteristic(Characteristic.Type) does not find the characteristic, 111 | // but the type is in optionalCharacteristics, it adds the characteristic.type to the service and returns it. 112 | var index, characteristic; 113 | for (index in this.characteristics) { 114 | characteristic = this.characteristics[index]; 115 | if (typeof name === 'string' && characteristic.displayName === name) { 116 | return characteristic; 117 | } 118 | else if (typeof name === 'function' && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) { 119 | return characteristic; 120 | } 121 | } 122 | if (typeof name === 'function') { 123 | for (index in this.optionalCharacteristics) { 124 | characteristic = this.optionalCharacteristics[index]; 125 | if ((characteristic instanceof name) || (name.UUID === characteristic.UUID)) { 126 | return this.addCharacteristic(name); 127 | } 128 | } 129 | } 130 | }; 131 | 132 | Service.prototype.testCharacteristic = function(name) { 133 | // checks for the existence of a characteristic object in the service 134 | var index, characteristic; 135 | for (index in this.characteristics) { 136 | characteristic = this.characteristics[index]; 137 | if (typeof name === 'string' && characteristic.displayName === name) { 138 | return true; 139 | } 140 | else if (typeof name === 'function' && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) { 141 | return true; 142 | } 143 | } 144 | return false; 145 | } 146 | 147 | Service.prototype.setCharacteristic = function(name, value) { 148 | this.getCharacteristic(name).setValue(value); 149 | return this; // for chaining 150 | } 151 | 152 | // A function to only updating the remote value, but not firiring the 'set' event. 153 | Service.prototype.updateCharacteristic = function(name, value){ 154 | this.getCharacteristic(name).updateValue(value); 155 | return this; 156 | } 157 | 158 | Service.prototype.addOptionalCharacteristic = function(characteristic) { 159 | // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance 160 | // of Characteristic. Coerce if necessary. 161 | if (typeof characteristic === 'function') 162 | characteristic = new characteristic(); 163 | 164 | this.optionalCharacteristics.push(characteristic); 165 | } 166 | 167 | Service.prototype.getCharacteristicByIID = function(iid) { 168 | for (var index in this.characteristics) { 169 | var characteristic = this.characteristics[index]; 170 | if (characteristic.iid === iid) 171 | return characteristic; 172 | } 173 | } 174 | 175 | Service.prototype._assignIDs = function(identifierCache, accessoryName) { 176 | 177 | // the Accessory Information service must have a (reserved by IdentifierCache) ID of 1 178 | if (this.UUID === '0000003E-0000-1000-8000-0026BB765291') { 179 | this.iid = 1; 180 | } 181 | else { 182 | // assign our own ID based on our UUID 183 | this.iid = identifierCache.getIID(accessoryName, this.UUID, this.subtype); 184 | } 185 | 186 | // assign IIDs to our Characteristics 187 | for (var index in this.characteristics) { 188 | var characteristic = this.characteristics[index]; 189 | characteristic._assignID(identifierCache, accessoryName, this.UUID, this.subtype); 190 | } 191 | } 192 | 193 | /** 194 | * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. 195 | */ 196 | Service.prototype.toHAP = function(opt) { 197 | 198 | var characteristicsHAP = []; 199 | 200 | for (var index in this.characteristics) { 201 | var characteristic = this.characteristics[index]; 202 | characteristicsHAP.push(characteristic.toHAP(opt)); 203 | } 204 | 205 | var hap = { 206 | iid: this.iid, 207 | type: this.UUID, 208 | characteristics: characteristicsHAP 209 | }; 210 | 211 | if (this.isPrimaryService !== undefined) { 212 | hap['primary'] = this.isPrimaryService; 213 | } 214 | 215 | return hap; 216 | } 217 | 218 | Service.prototype._setupCharacteristic = function(characteristic) { 219 | // listen for changes in characteristics and bubble them up 220 | characteristic.on('change', function(change) { 221 | // make a new object with the relevant characteristic added, and bubble it up 222 | this.emit('characteristic-change', clone(change, {characteristic:characteristic})); 223 | }.bind(this)); 224 | } 225 | 226 | Service.prototype._sideloadCharacteristics = function(targetCharacteristics) { 227 | for (var index in targetCharacteristics) { 228 | var target = targetCharacteristics[index]; 229 | this._setupCharacteristic(target); 230 | } 231 | 232 | this.characteristics = targetCharacteristics.slice(); 233 | } 234 | -------------------------------------------------------------------------------- /lib/Camera.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var debug = require('debug')('Camera'); 4 | var inherits = require('util').inherits; 5 | var EventEmitter = require('events').EventEmitter; 6 | var clone = require('./util/clone').clone; 7 | var uuid = require('./util/uuid'); 8 | var Service = require('./Service').Service; 9 | var Characteristic = require('./Characteristic').Characteristic; 10 | var StreamController = require('./StreamController').StreamController; 11 | var HomeKitTypes = require('./gen/HomeKitTypes'); 12 | 13 | //capture 14 | var cmd = require('node-cmd'); 15 | const spawn = require('child_process').spawn; 16 | //var shell = require('shelljs'); 17 | var fs = require('fs'); 18 | 19 | 20 | var ip = require('ip'); 21 | 22 | //capture 23 | 24 | module.exports = { 25 | Camera: Camera 26 | }; 27 | 28 | function Camera() { 29 | this.services = []; 30 | this.streamControllers = []; 31 | 32 | this.pendingSessions = {}; 33 | this.ongoingSessions = {}; 34 | 35 | let options = { 36 | proxy: false, // Requires RTP/RTCP MUX Proxy 37 | disable_audio_proxy: false, // If proxy = true, you can opt out audio proxy via this 38 | srtp: true, // Supports SRTP AES_CM_128_HMAC_SHA1_80 encryption 39 | video: { 40 | resolutions: [ 41 | [1920, 1080, 30], // Width, Height, framerate 42 | [320, 240, 15], // Apple Watch requires this configuration 43 | [1280, 960, 30], 44 | [1280, 720, 30], 45 | [1024, 768, 30], 46 | [640, 480, 30], 47 | [640, 360, 30], 48 | [480, 360, 30], 49 | [480, 270, 30], 50 | [320, 240, 30], 51 | [320, 180, 30] 52 | ], 53 | codec: { 54 | profiles: [0, 1, 2], // Enum, please refer StreamController.VideoCodecParamProfileIDTypes 55 | levels: [0, 1, 2] // Enum, please refer StreamController.VideoCodecParamLevelTypes 56 | } 57 | }, 58 | audio: { 59 | comfort_noise: false, 60 | codecs: [{ 61 | type: "OPUS", // Audio Codec 62 | samplerate: 24 // 8, 16, 24 KHz 63 | }, 64 | { 65 | type: "AAC-eld", 66 | samplerate: 16 67 | } 68 | ] 69 | } 70 | } 71 | 72 | this.createCameraControlService(); 73 | this._createStreamControllers(2, options); 74 | } 75 | 76 | Camera.prototype.handleSnapshotRequest = function(request, callback) { 77 | // Image request: {width: number, height: number} 78 | // Please override this and invoke callback(error, image buffer) when the snapshot is ready 79 | 80 | 81 | //cmd.run('sudo sh /home/pi/node_modules/hap-nodejs/task'); 82 | var snapshot = fs.readFileSync(__dirname + '/res/snapshot.jpg'); 83 | callback(undefined, snapshot); 84 | // var raspistill = `raspistill -w ${request.width} -h ${request.height} -t 5 -o ` + __dirname + `/res/snapshotnew.jpg`; 85 | // 86 | // shell.exec(raspistill, function(code, stdout, stderr) { 87 | // var snapshot = undefined; 88 | // console.log("directory name " + __dirname); 89 | // if (code === 0) { 90 | // snapshot = fs.readFileSync(__dirname + '/res/snapshotnew.jpg'); 91 | // } 92 | // callback(stderr, snapshot); 93 | // }); 94 | //var snapshot = fs.readFileSync(__dirname + '/res/snapshot.jpg'); 95 | //callback(undefined, snapshot); 96 | } 97 | 98 | Camera.prototype.handleCloseConnection = function(connectionID) { 99 | this.streamControllers.forEach(function(controller) { 100 | controller.handleCloseConnection(connectionID); 101 | }); 102 | } 103 | 104 | Camera.prototype.prepareStream = function(request, callback) { 105 | // Invoked when iOS device requires stream 106 | 107 | var sessionInfo = {}; 108 | 109 | let sessionID = request["sessionID"]; 110 | let targetAddress = request["targetAddress"]; 111 | 112 | sessionInfo["address"] = targetAddress; 113 | 114 | var response = {}; 115 | 116 | let videoInfo = request["video"]; 117 | if (videoInfo) { 118 | let targetPort = videoInfo["port"]; 119 | let srtp_key = videoInfo["srtp_key"]; 120 | let srtp_salt = videoInfo["srtp_salt"]; 121 | 122 | let videoResp = { 123 | port: targetPort, 124 | ssrc: 1, 125 | srtp_key: srtp_key, 126 | srtp_salt: srtp_salt 127 | }; 128 | 129 | response["video"] = videoResp; 130 | 131 | sessionInfo["video_port"] = targetPort; 132 | sessionInfo["video_srtp"] = Buffer.concat([srtp_key, srtp_salt]); 133 | sessionInfo["video_ssrc"] = 1; 134 | } 135 | 136 | let audioInfo = request["audio"]; 137 | if (audioInfo) { 138 | let targetPort = audioInfo["port"]; 139 | let srtp_key = audioInfo["srtp_key"]; 140 | let srtp_salt = audioInfo["srtp_salt"]; 141 | 142 | let audioResp = { 143 | port: targetPort, 144 | ssrc: 1, 145 | srtp_key: srtp_key, 146 | srtp_salt: srtp_salt 147 | }; 148 | 149 | response["audio"] = audioResp; 150 | 151 | sessionInfo["audio_port"] = targetPort; 152 | sessionInfo["audio_srtp"] = Buffer.concat([srtp_key, srtp_salt]); 153 | sessionInfo["audio_ssrc"] = 1; 154 | } 155 | 156 | let currentAddress = ip.address(); 157 | var addressResp = { 158 | address: currentAddress 159 | }; 160 | 161 | if (ip.isV4Format(currentAddress)) { 162 | addressResp["type"] = "v4"; 163 | } else { 164 | addressResp["type"] = "v6"; 165 | } 166 | 167 | response["address"] = addressResp; 168 | this.pendingSessions[uuid.unparse(sessionID)] = sessionInfo; 169 | 170 | callback(response); 171 | } 172 | 173 | Camera.prototype.handleStreamRequest = function(request) { 174 | // Invoked when iOS device asks stream to start/stop/reconfigure 175 | var sessionID = request["sessionID"]; 176 | var requestType = request["type"]; 177 | if (sessionID) { 178 | let sessionIdentifier = uuid.unparse(sessionID); 179 | 180 | if (requestType == "start") { 181 | var sessionInfo = this.pendingSessions[sessionIdentifier]; 182 | if (sessionInfo) { 183 | var width = 640; 184 | var height = 480; 185 | var fps = 15; 186 | var bitrate = 200; 187 | 188 | let videoInfo = request["video"]; 189 | if (videoInfo) { 190 | width = videoInfo["width"]; 191 | height = videoInfo["height"]; 192 | 193 | let expectedFPS = videoInfo["fps"]; 194 | if (expectedFPS < fps) { 195 | fps = expectedFPS; 196 | } 197 | 198 | bitrate = videoInfo["max_bit_rate"]; 199 | } 200 | 201 | let targetAddress = sessionInfo["address"]; 202 | let targetVideoPort = sessionInfo["video_port"]; 203 | let videoKey = sessionInfo["video_srtp"]; 204 | 205 | 206 | //let ffmpegCommand = '-f video4linux2 -i /dev/video0 -s ' + width + ':' + height + ' -threads auto -vcodec h264 -an -pix_fmt yuv420p -f rawvideo -tune zerolatency -vf scale=w=' + width + ':h=' + height + ' -b:v ' + bitrate + 'k -bufsize ' + 2 * bitrate + 'k -payload_type 99 -ssrc 1 -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params ' + videoKey.toString('base64') + ' srtp://' + targetAddress + ':' + targetVideoPort + '?rtcpport=' + targetVideoPort + '&localrtcpport=' + targetVideoPort + '&pkt_size=1378'; 207 | let ffmpegCommand = '-re -i http://localhost:8081 -threads 0 -vcodec h264 -an -pix_fmt yuv420p -r ' + fps + ' -f rawvideo -tune zerolatency -vf scale=' + width + ':' + height + ' -b:v ' + bitrate + 'k -bufsize ' + bitrate + 'k -payload_type 99 -ssrc 1 -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params ' + videoKey.toString('base64') + ' srtp://' + targetAddress + ':' + targetVideoPort + '?rtcpport=' + targetVideoPort + '&localrtcpport=' + targetVideoPort + '&pkt_size=1378'; 208 | 209 | console.log(targetAddress + targetVideoPort + videoKey); 210 | //original let ffmpegCommand = '-re -f avfoundation -r 29.970000 -i 0:0 -threads 0 -vcodec libx264 -an -pix_fmt yuv420p -r '+ fps +' -f rawvideo -tune zerolatency -vf scale='+ width +':'+ height +' -b:v '+ bitrate +'k -bufsize '+ bitrate +'k -payload_type 99 -ssrc 1 -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params '+videoKey.toString('base64')+' srtp://'+targetAddress+':'+targetVideoPort+'?rtcpport='+targetVideoPort+'&localrtcpport='+targetVideoPort+'&pkt_size=1378'; 211 | 212 | //gituhub issue compiled let ffmpegCommand = '-re -i rtsp://127.0.0.1:8554/pi_encode.h264 -threads 0 -vcodec h264_omx -an -pix_fmt yuv420p -r ' + fps + ' -f rawvideo -tune zerolatency -vf scale=' + width + ':' + height + ' -b:v ' + bitrate + 'k -bufsize ' + bitrate + 'k -payload_type 99 -ssrc 1 -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params ' + videoKey.toString('base64') + ' srtp://' + targetAddress + ':' + targetVideoPort + '?rtcpport=' + targetVideoPort + '&localrtcpport=' + targetVideoPort + '&pkt_size=1378'; 213 | 214 | let ffmpeg = spawn('ffmpeg', ffmpegCommand.split(' '), { 215 | env: process.env 216 | }); 217 | this.ongoingSessions[sessionIdentifier] = ffmpeg; 218 | } 219 | 220 | delete this.pendingSessions[sessionIdentifier]; 221 | } else if (requestType == "stop") { 222 | var ffmpegProcess = this.ongoingSessions[sessionIdentifier]; 223 | if (ffmpegProcess) { 224 | ffmpegProcess.kill('SIGKILL'); 225 | } 226 | 227 | delete this.ongoingSessions[sessionIdentifier]; 228 | } 229 | } 230 | } 231 | 232 | Camera.prototype.createCameraControlService = function() { 233 | var controlService = new Service.CameraControl(); 234 | 235 | // Developer can add control characteristics like rotation, night vision at here. 236 | 237 | this.services.push(controlService); 238 | } 239 | 240 | // Private 241 | 242 | Camera.prototype._createStreamControllers = function(maxStreams, options) { 243 | let self = this; 244 | 245 | for (var i = 0; i < maxStreams; i++) { 246 | var streamController = new StreamController(i, options, self); 247 | 248 | self.services.push(streamController.service); 249 | self.streamControllers.push(streamController); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /lib/Characteristic.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits; 2 | var EventEmitter = require('events').EventEmitter; 3 | var once = require('./util/once').once; 4 | 5 | 'use strict'; 6 | 7 | module.exports = { 8 | Characteristic: Characteristic 9 | } 10 | 11 | 12 | /** 13 | * Characteristic represents a particular typed variable that can be assigned to a Service. For instance, a 14 | * "Hue" Characteristic might store a 'float' value of type 'arcdegrees'. You could add the Hue Characteristic 15 | * to a Service in order to store that value. A particular Characteristic is distinguished from others by its 16 | * UUID. HomeKit provides a set of known Characteristic UUIDs defined in HomeKitTypes.js along with a 17 | * corresponding concrete subclass. 18 | * 19 | * You can also define custom Characteristics by providing your own UUID. Custom Characteristics can be added 20 | * to any native or custom Services, but Siri will likely not be able to work with these. 21 | * 22 | * Note that you can get the "value" of a Characteristic by accessing the "value" property directly, but this 23 | * is really a "cached value". If you want to fetch the latest value, which may involve doing some work, then 24 | * call getValue(). 25 | * 26 | * @event 'get' => function(callback(err, newValue), context) { } 27 | * Emitted when someone calls getValue() on this Characteristic and desires the latest non-cached 28 | * value. If there are any listeners to this event, one of them MUST call the callback in order 29 | * for the value to ever be delivered. The `context` object is whatever was passed in by the initiator 30 | * of this event (for instance whomever called `getValue`). 31 | * 32 | * @event 'set' => function(newValue, callback(err), context) { } 33 | * Emitted when someone calls setValue() on this Characteristic with a desired new value. If there 34 | * are any listeners to this event, one of them MUST call the callback in order for this.value to 35 | * actually be set. The `context` object is whatever was passed in by the initiator of this change 36 | * (for instance, whomever called `setValue`). 37 | * 38 | * @event 'change' => function({ oldValue, newValue, context }) { } 39 | * Emitted after a change in our value has occurred. The new value will also be immediately accessible 40 | * in this.value. The event object contains the new value as well as the context object originally 41 | * passed in by the initiator of this change (if known). 42 | */ 43 | 44 | function Characteristic(displayName, UUID, props) { 45 | this.displayName = displayName; 46 | this.UUID = UUID; 47 | this.iid = null; // assigned by our containing Service 48 | this.value = null; 49 | this.props = props || { 50 | format: null, 51 | unit: null, 52 | minValue: null, 53 | maxValue: null, 54 | minStep: null, 55 | perms: [] 56 | }; 57 | } 58 | 59 | inherits(Characteristic, EventEmitter); 60 | 61 | // Known HomeKit formats 62 | Characteristic.Formats = { 63 | BOOL: 'bool', 64 | INT: 'int', 65 | FLOAT: 'float', 66 | STRING: 'string', 67 | ARRAY: 'array', // unconfirmed 68 | DICTIONARY: 'dictionary', // unconfirmed 69 | UINT8: 'uint8', 70 | UINT16: 'uint16', 71 | UINT32: 'uint32', 72 | UINT64: 'uint64', 73 | DATA: 'data', // unconfirmed 74 | TLV8: 'tlv8' 75 | } 76 | 77 | // Known HomeKit unit types 78 | Characteristic.Units = { 79 | // HomeKit only defines Celsius, for Fahrenheit, it requires iOS app to do the conversion. 80 | CELSIUS: 'celsius', 81 | PERCENTAGE: 'percentage', 82 | ARC_DEGREE: 'arcdegrees', 83 | LUX: 'lux', 84 | SECONDS: 'seconds' 85 | } 86 | 87 | // Known HomeKit permission types 88 | Characteristic.Perms = { 89 | READ: 'pr', 90 | WRITE: 'pw', 91 | NOTIFY: 'ev', 92 | HIDDEN: 'hd' 93 | } 94 | 95 | /** 96 | * Copies the given properties to our props member variable, 97 | * and returns 'this' for chaining. 98 | * 99 | * @param 'props' { 100 | * format: , 101 | * unit: , 102 | * minValue: , 103 | * maxValue: , 104 | * minStep: , 105 | * perms: array of [Characteristic.Perms] like [Characteristic.Perms.READ, Characteristic.Perms.WRITE] 106 | * } 107 | */ 108 | Characteristic.prototype.setProps = function(props) { 109 | for (var key in (props || {})) 110 | if (Object.prototype.hasOwnProperty.call(props, key)) 111 | this.props[key] = props[key]; 112 | return this; 113 | } 114 | 115 | Characteristic.prototype.getValue = function(callback, context, connectionID) { 116 | 117 | if (this.listeners('get').length > 0) { 118 | 119 | // allow a listener to handle the fetching of this value, and wait for completion 120 | this.emit('get', once(function(err, newValue) { 121 | 122 | if (err) { 123 | // pass the error along to our callback 124 | if (callback) callback(err); 125 | } 126 | else { 127 | if (newValue === undefined || newValue === null) 128 | newValue = this.getDefaultValue(); 129 | 130 | // getting the value was a success; we can pass it along and also update our cached value 131 | var oldValue = this.value; 132 | this.value = newValue; 133 | if (callback) callback(null, newValue); 134 | 135 | // emit a change event if necessary 136 | if (oldValue !== newValue) 137 | this.emit('change', { oldValue:oldValue, newValue:newValue, context:context }); 138 | } 139 | 140 | }.bind(this)), context, connectionID); 141 | } 142 | else { 143 | 144 | // no one is listening to the 'get' event, so just return the cached value 145 | if (callback) 146 | callback(null, this.value); 147 | } 148 | } 149 | 150 | Characteristic.prototype.setValue = function(newValue, callback, context, connectionID) { 151 | 152 | if (this.listeners('set').length > 0) { 153 | 154 | // allow a listener to handle the setting of this value, and wait for completion 155 | this.emit('set', newValue, once(function(err) { 156 | 157 | if (err) { 158 | // pass the error along to our callback 159 | if (callback) callback(err); 160 | } 161 | else { 162 | if (newValue === undefined || newValue === null) 163 | newValue = this.getDefaultValue(); 164 | // setting the value was a success; so we can cache it now 165 | var oldValue = this.value; 166 | this.value = newValue; 167 | if (callback) callback(); 168 | 169 | if (oldValue !== newValue) 170 | this.emit('change', { oldValue:oldValue, newValue:newValue, context:context }); 171 | } 172 | 173 | }.bind(this)), context, connectionID); 174 | 175 | } 176 | else { 177 | if (newValue === undefined || newValue === null) 178 | newValue = this.getDefaultValue(); 179 | // no one is listening to the 'set' event, so just assign the value blindly 180 | var oldValue = this.value; 181 | this.value = newValue; 182 | if (callback) callback(); 183 | 184 | if (oldValue !== newValue) 185 | this.emit('change', { oldValue:oldValue, newValue:newValue, context:context }); 186 | } 187 | 188 | return this; // for chaining 189 | } 190 | 191 | Characteristic.prototype.updateValue = function(newValue, callback, context) { 192 | 193 | if (newValue === undefined || newValue === null) 194 | newValue = this.getDefaultValue(); 195 | // no one is listening to the 'set' event, so just assign the value blindly 196 | var oldValue = this.value; 197 | this.value = newValue; 198 | if (callback) callback(); 199 | 200 | if (oldValue !== newValue) 201 | this.emit('change', { oldValue:oldValue, newValue:newValue, context:context }); 202 | return this; // for chaining 203 | } 204 | 205 | Characteristic.prototype.getDefaultValue = function() { 206 | switch (this.props.format) { 207 | case Characteristic.Formats.BOOL: return false; 208 | case Characteristic.Formats.STRING: return ""; 209 | case Characteristic.Formats.ARRAY: return []; // who knows! 210 | case Characteristic.Formats.DICTIONARY: return {}; // who knows! 211 | case Characteristic.Formats.DATA: return ""; // who knows! 212 | case Characteristic.Formats.TLV8: return ""; // who knows! 213 | default: return this.props.minValue || 0; 214 | } 215 | } 216 | 217 | Characteristic.prototype._assignID = function(identifierCache, accessoryName, serviceUUID, serviceSubtype) { 218 | 219 | // generate our IID based on our UUID 220 | this.iid = identifierCache.getIID(accessoryName, serviceUUID, serviceSubtype, this.UUID); 221 | } 222 | 223 | /** 224 | * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. 225 | */ 226 | Characteristic.prototype.toHAP = function(opt) { 227 | 228 | // ensure our value fits within our constraints if present 229 | var value = this.value; 230 | if (this.props.minValue != null && value < this.props.minValue) value = this.props.minValue; 231 | if (this.props.maxValue != null && value > this.props.maxValue) value = this.props.maxValue; 232 | if (this.props.format != null) { 233 | if (this.props.format === Characteristic.Formats.INT) 234 | value = parseInt(value); 235 | else if (this.props.format === Characteristic.Formats.UINT8) 236 | value = parseInt(value); 237 | else if (this.props.format === Characteristic.Formats.UINT16) 238 | value = parseInt(value); 239 | else if (this.props.format === Characteristic.Formats.UINT32) 240 | value = parseInt(value); 241 | else if (this.props.format === Characteristic.Formats.UINT64) 242 | value = parseInt(value); 243 | else if (this.props.format === Characteristic.Formats.FLOAT) { 244 | value = parseFloat(value); 245 | if (this.props.minStep != null) { 246 | var pow = Math.pow(10, decimalPlaces(this.props.minStep)); 247 | value = Math.round(value * pow) / pow; 248 | } 249 | } 250 | } 251 | 252 | var hap = { 253 | iid: this.iid, 254 | type: this.UUID, 255 | perms: this.props.perms, 256 | format: this.props.format, 257 | value: value, 258 | description: this.displayName 259 | 260 | // These properties used to be sent but do not seem to be used: 261 | // 262 | // events: false, 263 | // bonjour: false 264 | }; 265 | 266 | if (this.props.validValues != null && this.props.validValues.length > 0) { 267 | hap['valid-values'] = this.props.validValues; 268 | } 269 | 270 | if (this.props.validValueRanges != null && this.props.validValueRanges.length > 0 && !(this.props.validValueRanges.length & 1)) { 271 | hap['valid-values-range'] = this.props.validValueRanges; 272 | } 273 | 274 | // extra properties 275 | if (this.props.unit != null) hap.unit = this.props.unit; 276 | if (this.props.maxValue != null) hap.maxValue = this.props.maxValue; 277 | if (this.props.minValue != null) hap.minValue = this.props.minValue; 278 | if (this.props.minStep != null) hap.minStep = this.props.minStep; 279 | 280 | // add maxLen if string length is > 64 bytes and trim to max 256 bytes 281 | if (this.props.format === Characteristic.Formats.STRING) { 282 | var str = new Buffer(value, 'utf8'), 283 | len = str.byteLength; 284 | if (len > 256) { // 256 bytes is the max allowed length 285 | hap.value = str.toString('utf8', 0, 256); 286 | hap.maxLen = 256; 287 | } else if (len > 64) { // values below can be ommited 288 | hap.maxLen = len; 289 | } 290 | } 291 | 292 | // if we're not readable, omit the "value" property - otherwise iOS will complain about non-compliance 293 | if (this.props.perms.indexOf(Characteristic.Perms.READ) == -1) 294 | delete hap.value; 295 | 296 | // delete the "value" property anyway if we were asked to 297 | if (opt && opt.omitValues) 298 | delete hap.value; 299 | 300 | return hap; 301 | } 302 | 303 | // Mike Samuel 304 | // http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number 305 | function decimalPlaces(num) { 306 | var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); 307 | if (!match) { return 0; } 308 | return Math.max( 309 | 0, 310 | // Number of digits right of decimal point. 311 | (match[1] ? match[1].length : 0) 312 | // Adjust for scientific notation. 313 | - (match[2] ? +match[2] : 0)); 314 | } 315 | -------------------------------------------------------------------------------- /lib/util/eventedhttp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var debug = require('debug')('EventedHTTPServer'); 4 | var EventEmitter = require('events').EventEmitter; 5 | var inherits = require('util').inherits; 6 | var net = require('net'); 7 | var http = require('http'); 8 | var uuid = require("./uuid"); 9 | var bufferShim = require('buffer-shims'); 10 | 11 | module.exports = { 12 | EventedHTTPServer: EventedHTTPServer 13 | } 14 | 15 | 16 | /** 17 | * EventedHTTPServer provides an HTTP-like server that supports HAP "extensions" for security and events. 18 | * 19 | * Implementation 20 | * -------------- 21 | * In order to implement the "custom HTTP" server required by the HAP protocol (see HAPServer.js) without completely 22 | * reinventing the wheel, we create both a generic TCP socket server as well as a standard Node HTTP server. 23 | * The TCP socket server acts as a proxy, allowing users of this class to transform data (for encryption) as necessary 24 | * and passing through bytes directly to the HTTP server for processing. This way we get Node to do all 25 | * the "heavy lifting" of HTTP like parsing headers and formatting responses. 26 | * 27 | * Events are sent by simply waiting for current HTTP traffic to subside and then sending a custom response packet 28 | * directly down the wire via the socket. 29 | * 30 | * Each connection to the main TCP server gets its own internal HTTP server, so we can track ongoing requests/responses 31 | * for safe event insertion. 32 | * 33 | * @event 'listening' => function() { } 34 | * Emitted when the server is fully set up and ready to receive connections. 35 | * 36 | * @event 'request' => function(request, response, session, events) { } 37 | * Just like the 'http' module, request is http.IncomingMessage and response is http.ServerResponse. 38 | * The 'session' param is an arbitrary object that you can use to store data associated with this connection; 39 | * it will not be used by this class. The 'events' param is an object where the keys are the names of 40 | * events that this connection has signed up for. It is initially empty and listeners are expected to manage it. 41 | * 42 | * @event 'decrypt' => function(data, {decrypted.data}, session) { } 43 | * Fired when we receive data from the client device. You may detemine whether the data is encrypted, and if 44 | * so, you can decrypt the data and store it into a new 'data' property of the 'decrypted' argument. If data is not 45 | * encrypted, you can simply leave 'data' as null and the original data will be passed through as-is. 46 | * 47 | * @event 'encrypt' => function(data, {encrypted.data}, session) { } 48 | * Fired when we wish to send data to the client device. If necessary, set the 'data' property of the 49 | * 'encrypted' argument to be the encrypted data and it will be sent instead. 50 | */ 51 | 52 | function EventedHTTPServer() { 53 | this._tcpServer = net.createServer(); 54 | this._connections = []; // track all open connections (for sending events) 55 | } 56 | 57 | inherits(EventedHTTPServer, EventEmitter); 58 | 59 | EventedHTTPServer.prototype.listen = function(targetPort) { 60 | this._tcpServer.listen(targetPort); 61 | 62 | this._tcpServer.on('listening', function() { 63 | var port = this._tcpServer.address().port; 64 | debug("Server listening on port %s", port); 65 | this.emit('listening', port); 66 | }.bind(this)); 67 | 68 | this._tcpServer.on('connection', this._onConnection.bind(this)); 69 | } 70 | 71 | EventedHTTPServer.prototype.stop = function() { 72 | this._tcpServer.close(); 73 | this._connections = []; 74 | } 75 | 76 | EventedHTTPServer.prototype.sendEvent = function(event, data, contentType, exclude) { 77 | for (var index in this._connections) { 78 | var connection = this._connections[index]; 79 | connection.sendEvent(event, data, contentType, exclude); 80 | } 81 | } 82 | 83 | // Called by net.Server when a new client connects. We will set up a new EventedHTTPServerConnection to manage the 84 | // lifetime of this connection. 85 | EventedHTTPServer.prototype._onConnection = function(socket) { 86 | var connection = new EventedHTTPServerConnection(socket); 87 | 88 | // pass on session events to our listeners directly 89 | connection.on('request', function(request, response, session, events) { this.emit('request', request, response, session, events); }.bind(this)); 90 | connection.on('encrypt', function(data, encrypted, session) { this.emit('encrypt', data, encrypted, session); }.bind(this)); 91 | connection.on('decrypt', function(data, decrypted, session) { this.emit('decrypt', data, decrypted, session); }.bind(this)); 92 | connection.on('close', function() { this._handleConnectionClose(connection); }.bind(this)); 93 | this._connections.push(connection); 94 | } 95 | 96 | EventedHTTPServer.prototype._handleConnectionClose = function(connection) { 97 | this.emit('session-close', connection.sessionID); 98 | 99 | // remove it from our array of connections for events 100 | this._connections.splice(this._connections.indexOf(connection), 1); 101 | } 102 | 103 | 104 | /** 105 | * Manages a single iOS-initiated HTTP connection during its lifetime. 106 | * 107 | * @event 'request' => function(request, response) { } 108 | * @event 'decrypt' => function(data, {decrypted.data}, session) { } 109 | * @event 'encrypt' => function(data, {encrypted.data}, session) { } 110 | * @event 'close' => function() { } 111 | */ 112 | 113 | function EventedHTTPServerConnection(clientSocket) { 114 | this.sessionID = uuid.generate(clientSocket.remoteAddress + ':' + clientSocket.remotePort); 115 | 116 | this._remoteAddress = clientSocket.remoteAddress; // cache because it becomes undefined in 'onClientSocketClose' 117 | this._pendingClientSocketData = bufferShim.alloc(0); // data received from client before HTTP proxy is fully setup 118 | this._fullySetup = false; // true when we are finished establishing connections 119 | this._writingResponse = false; // true while we are composing an HTTP response (so events can wait) 120 | this._pendingEventData = bufferShim.alloc(0); // event data waiting to be sent until after an in-progress HTTP response is being written 121 | 122 | // clientSocket is the socket connected to the actual iOS device 123 | this._clientSocket = clientSocket; 124 | this._clientSocket.on('data', this._onClientSocketData.bind(this)); 125 | this._clientSocket.on('close', this._onClientSocketClose.bind(this)); 126 | this._clientSocket.on('error', this._onClientSocketError.bind(this)); // we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely. 127 | 128 | // serverSocket is our connection to our own internal httpServer 129 | this._serverSocket = null; // created after httpServer 'listening' event 130 | 131 | // create our internal HTTP server for this connection that we will proxy data to and from 132 | this._httpServer = http.createServer(); 133 | this._httpServer.timeout = 0; // clients expect to hold connections open as long as they want 134 | this._httpServer.on('listening', this._onHttpServerListening.bind(this)); 135 | this._httpServer.on('request', this._onHttpServerRequest.bind(this)); 136 | this._httpServer.on('error', this._onHttpServerError.bind(this)); 137 | this._httpServer.listen(0); 138 | 139 | // an arbitrary dict that users of this class can store values in to associate with this particular connection 140 | this._session = { 141 | sessionID: this.sessionID 142 | }; 143 | 144 | // a collection of event names subscribed to by this connection 145 | this._events = {}; // this._events[eventName] = true (value is arbitrary, but must be truthy) 146 | 147 | debug("[%s] New connection from client", this._remoteAddress); 148 | } 149 | 150 | inherits(EventedHTTPServerConnection, EventEmitter); 151 | 152 | EventedHTTPServerConnection.prototype.sendEvent = function(event, data, contentType, excludeEvents) { 153 | 154 | // has this connection subscribed to the given event? if not, nothing to do! 155 | if (!this._events[event]) { 156 | return; 157 | } 158 | 159 | // does this connection's 'events' object match the excludeEvents object? if so, don't send the event. 160 | if (excludeEvents === this._events) { 161 | debug("[%s] Muting event '%s' notification for this connection since it originated here.", this._remoteAddress, event); 162 | return; 163 | } 164 | 165 | debug("[%s] Sending HTTP event '%s' with data: %s", this._remoteAddress, event, data.toString('utf8')); 166 | 167 | // ensure data is a Buffer 168 | data = bufferShim.from(data); 169 | 170 | // format this payload as an HTTP response 171 | var linebreak = bufferShim.from("0D0A","hex"); 172 | data = Buffer.concat([ 173 | bufferShim.from('EVENT/1.0 200 OK'), linebreak, 174 | bufferShim.from('Content-Type: ' + contentType), linebreak, 175 | bufferShim.from('Content-Length: ' + data.length), linebreak, 176 | linebreak, 177 | data 178 | ]); 179 | 180 | // give listeners an opportunity to encrypt this data before sending it to the client 181 | var encrypted = { data: null }; 182 | this.emit('encrypt', data, encrypted, this._session); 183 | if (encrypted.data) data = encrypted.data; 184 | 185 | // if we're in the middle of writing an HTTP response already, put this event in the queue for when 186 | // we're done. otherwise send it immediately. 187 | if (this._writingResponse) 188 | this._pendingEventData = Buffer.concat([this._pendingEventData, data]); 189 | else 190 | this._clientSocket.write(data); 191 | } 192 | 193 | EventedHTTPServerConnection.prototype._sendPendingEvents = function() { 194 | 195 | // an existing HTTP response was finished, so let's flush our pending event buffer if necessary! 196 | if (this._pendingEventData.length > 0) { 197 | debug("[%s] Writing pending HTTP event data", this._remoteAddress); 198 | this._clientSocket.write(this._pendingEventData); 199 | } 200 | 201 | // clear the buffer 202 | this._pendingEventData = bufferShim.alloc(0); 203 | } 204 | 205 | // Called only once right after constructor finishes 206 | EventedHTTPServerConnection.prototype._onHttpServerListening = function() { 207 | this._httpPort = this._httpServer.address().port; 208 | debug("[%s] HTTP server listening on port %s", this._remoteAddress, this._httpPort); 209 | 210 | // closes before this are due to retrying listening, which don't need to be handled 211 | this._httpServer.on('close', this._onHttpServerClose.bind(this)); 212 | 213 | // now we can establish a connection to this running HTTP server for proxying data 214 | this._serverSocket = net.createConnection(this._httpPort); 215 | this._serverSocket.on('connect', this._onServerSocketConnect.bind(this)); 216 | this._serverSocket.on('data', this._onServerSocketData.bind(this)); 217 | this._serverSocket.on('close', this._onServerSocketClose.bind(this)); 218 | this._serverSocket.on('error', this._onServerSocketError.bind(this)); // we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely. 219 | } 220 | 221 | // Called only once right after onHttpServerListening 222 | EventedHTTPServerConnection.prototype._onServerSocketConnect = function() { 223 | 224 | // we are now fully set up: 225 | // - clientSocket is connected to the iOS device 226 | // - serverSocket is connected to the httpServer 227 | // - ready to proxy data! 228 | this._fullySetup = true; 229 | 230 | // start by flushing any pending buffered data received from the client while we were setting up 231 | if (this._pendingClientSocketData.length > 0) { 232 | this._serverSocket.write(this._pendingClientSocketData); 233 | this._pendingClientSocketData = null; 234 | } 235 | } 236 | 237 | // Received data from client (iOS) 238 | EventedHTTPServerConnection.prototype._onClientSocketData = function(data) { 239 | 240 | // give listeners an opportunity to decrypt this data before processing it as HTTP 241 | var decrypted = { data: null }; 242 | this.emit('decrypt', data, decrypted, this._session); 243 | if (decrypted.data) data = decrypted.data; 244 | 245 | if (this._fullySetup) { 246 | // proxy it along to the HTTP server 247 | this._serverSocket.write(data); 248 | } 249 | else { 250 | // we're not setup yet, so add this data to our buffer 251 | this._pendingClientSocketData = Buffer.concat([this._pendingClientSocketData, data]); 252 | } 253 | } 254 | 255 | // Received data from HTTP Server 256 | EventedHTTPServerConnection.prototype._onServerSocketData = function(data) { 257 | 258 | // give listeners an opportunity to encrypt this data before sending it to the client 259 | var encrypted = { data: null }; 260 | this.emit('encrypt', data, encrypted, this._session); 261 | if (encrypted.data) data = encrypted.data; 262 | 263 | // proxy it along to the client (iOS) 264 | this._clientSocket.write(data); 265 | } 266 | 267 | // Our internal HTTP Server has been closed (happens after we call this._httpServer.close() below) 268 | EventedHTTPServerConnection.prototype._onServerSocketClose = function() { 269 | debug("[%s] HTTP connection was closed", this._remoteAddress); 270 | 271 | // make sure the iOS side is closed as well 272 | this._clientSocket.destroy(); 273 | 274 | // we only support a single long-lived connection to our internal HTTP server. Since it's closed, 275 | // we'll need to shut it down entirely. 276 | this._httpServer.close(); 277 | } 278 | 279 | // Our internal HTTP Server has been closed (happens after we call this._httpServer.close() below) 280 | EventedHTTPServerConnection.prototype._onServerSocketError = function(err) { 281 | debug("[%s] HTTP connection error: ", this._remoteAddress, err.message); 282 | 283 | // _onServerSocketClose will be called next 284 | } 285 | 286 | EventedHTTPServerConnection.prototype._onHttpServerRequest = function(request, response) { 287 | debug("[%s] HTTP request: %s", this._remoteAddress, request.url); 288 | 289 | this._writingResponse = true; 290 | 291 | // sign up to know when the response is ended, so we can safely send EVENT responses 292 | response.on('finish', function() { 293 | 294 | debug("[%s] HTTP Response is finished", this._remoteAddress); 295 | this._writingResponse = false; 296 | this._sendPendingEvents(); 297 | 298 | }.bind(this)); 299 | 300 | // pass it along to listeners 301 | this.emit('request', request, response, this._session, this._events); 302 | } 303 | 304 | EventedHTTPServerConnection.prototype._onHttpServerClose = function() { 305 | debug("[%s] HTTP server was closed", this._remoteAddress); 306 | 307 | // notify listeners that we are completely closed 308 | this.emit('close'); 309 | } 310 | 311 | EventedHTTPServerConnection.prototype._onHttpServerError = function(err) { 312 | debug("[%s] HTTP server error: %s", this._remoteAddress, err.message); 313 | 314 | if (err.code === 'EADDRINUSE') { 315 | this._httpServer.close(); 316 | this._httpServer.listen(0); 317 | } 318 | } 319 | 320 | EventedHTTPServerConnection.prototype._onClientSocketClose = function() { 321 | debug("[%s] Client connection closed", this._remoteAddress); 322 | 323 | // shutdown the other side 324 | this._serverSocket.destroy(); 325 | } 326 | 327 | EventedHTTPServerConnection.prototype._onClientSocketError = function(err) { 328 | debug("[%s] Client connection error: %s", this._remoteAddress, err.message); 329 | 330 | // _onClientSocketClose will be called next 331 | } 332 | -------------------------------------------------------------------------------- /lib/util/chacha20poly1305.js: -------------------------------------------------------------------------------- 1 | /* chacha20 - 256 bits */ 2 | 3 | // Written in 2014 by Devi Mandiri. Public domain. 4 | // 5 | // Implementation derived from chacha-ref.c version 20080118 6 | // See for details: http://cr.yp.to/chacha/chacha-20080128.pdf 7 | 8 | var bufferShim = require('buffer-shims'); 9 | 10 | module.exports = { 11 | Chacha20Ctx: Chacha20Ctx, 12 | chacha20_keysetup: chacha20_keysetup, 13 | chacha20_ivsetup: chacha20_ivsetup, 14 | chacha20_keystream: chacha20_keystream, 15 | chacha20_update: chacha20_update, 16 | chacha20_final: chacha20_final, 17 | 18 | Poly1305Ctx: Poly1305Ctx, 19 | poly1305_init: poly1305_init, 20 | poly1305_update: poly1305_update, 21 | poly1305_finish: poly1305_finish, 22 | poly1305_auth: poly1305_auth, 23 | poly1305_verify: poly1305_verify 24 | }; 25 | 26 | var Chacha20KeySize = 32; 27 | var Chacha20NonceSize = 8; 28 | 29 | function Chacha20Ctx() { 30 | this.input = new Array(16); 31 | this.leftover = 0; 32 | this.buffer = bufferShim.alloc(64); 33 | }; 34 | 35 | function load32(x, i) { 36 | return x[i] | (x[i+1]<<8) | (x[i+2]<<16) | (x[i+3]<<24); 37 | } 38 | 39 | function store32(x, i, u) { 40 | x[i] = u & 0xff; u >>>= 8; 41 | x[i+1] = u & 0xff; u >>>= 8; 42 | x[i+2] = u & 0xff; u >>>= 8; 43 | x[i+3] = u & 0xff; 44 | } 45 | 46 | function plus(v, w) { 47 | return (v + w) >>> 0; 48 | } 49 | 50 | function rotl32(v, c) { 51 | return ((v << c) >>> 0) | (v >>> (32 - c)); 52 | } 53 | 54 | function quarterRound(x, a, b, c, d) { 55 | x[a] = plus(x[a], x[b]); x[d] = rotl32(x[d] ^ x[a], 16); 56 | x[c] = plus(x[c], x[d]); x[b] = rotl32(x[b] ^ x[c], 12); 57 | x[a] = plus(x[a], x[b]); x[d] = rotl32(x[d] ^ x[a], 8); 58 | x[c] = plus(x[c], x[d]); x[b] = rotl32(x[b] ^ x[c], 7); 59 | } 60 | 61 | function chacha20_keysetup(ctx, key) { 62 | ctx.input[0] = 1634760805; 63 | ctx.input[1] = 857760878; 64 | ctx.input[2] = 2036477234; 65 | ctx.input[3] = 1797285236; 66 | for (var i = 0; i < 8; i++) { 67 | ctx.input[i+4] = load32(key, i*4); 68 | } 69 | } 70 | 71 | function chacha20_ivsetup(ctx, iv) { 72 | ctx.input[12] = 0; 73 | ctx.input[13] = 0; 74 | ctx.input[14] = load32(iv, 0); 75 | ctx.input[15] = load32(iv, 4); 76 | } 77 | 78 | function chacha20_encrypt(ctx, dst, src, len) { 79 | var x = new Array(16); 80 | var buf = new Array(64); 81 | var i = 0, dpos = 0, spos = 0; 82 | 83 | while (len > 0 ) { 84 | for (i = 16; i--;) x[i] = ctx.input[i]; 85 | for (i = 20; i > 0; i -= 2) { 86 | quarterRound(x, 0, 4, 8,12); 87 | quarterRound(x, 1, 5, 9,13); 88 | quarterRound(x, 2, 6,10,14); 89 | quarterRound(x, 3, 7,11,15); 90 | quarterRound(x, 0, 5,10,15); 91 | quarterRound(x, 1, 6,11,12); 92 | quarterRound(x, 2, 7, 8,13); 93 | quarterRound(x, 3, 4, 9,14); 94 | } 95 | for (i = 16; i--;) x[i] += ctx.input[i]; 96 | for (i = 16; i--;) store32(buf, 4*i, x[i]); 97 | 98 | ctx.input[12] = plus(ctx.input[12], 1); 99 | if (!ctx.input[12]) { 100 | ctx.input[13] = plus(ctx.input[13], 1); 101 | } 102 | if (len <= 64) { 103 | for (i = len; i--;) { 104 | dst[i+dpos] = src[i+spos] ^ buf[i]; 105 | } 106 | return; 107 | } 108 | for (i = 64; i--;) { 109 | dst[i+dpos] = src[i+spos] ^ buf[i]; 110 | } 111 | len -= 64; 112 | spos += 64; 113 | dpos += 64; 114 | } 115 | } 116 | 117 | function chacha20_decrypt(ctx, dst, src, len) { 118 | chacha20_encrypt(ctx, dst, src, len); 119 | } 120 | 121 | function chacha20_update(ctx, dst, src, inlen) { 122 | var bytes = 0; 123 | var out_start = 0; 124 | var out_inc = 0; 125 | 126 | if ((ctx.leftover + inlen) >= 64) { 127 | 128 | if (ctx.leftover != 0) { 129 | bytes = 64 - ctx.leftover; 130 | 131 | if (src.length > 0) { 132 | src.copy(ctx.buffer,ctx.leftover,0,bytes); 133 | src = src.slice(bytes,src.length); 134 | } 135 | 136 | chacha20_encrypt(ctx,dst,ctx.buffer,64); 137 | inlen -= bytes; 138 | dst = dst.slice(64,dst.length); 139 | out_inc += 64; 140 | ctx.leftover = 0; 141 | } 142 | 143 | bytes = (inlen & (~63)); 144 | if (bytes > 0) { 145 | chacha20_encrypt(ctx, dst, src, bytes); 146 | inlen -= bytes; 147 | src = src.slice(bytes,src.length); 148 | dst = dst.slice(bytes,dst.length); 149 | out_inc += bytes; 150 | } 151 | 152 | } 153 | 154 | if (inlen > 0) { 155 | if (src.length > 0) { 156 | src.copy(ctx.buffer,ctx.leftover,0,src.length); 157 | } else { 158 | var zeros = bufferShim.alloc(inlen); 159 | zeros.copy(ctx.buffer,ctx.leftover,0,inlen); 160 | } 161 | ctx.leftover += inlen; 162 | } 163 | 164 | return out_inc - out_start; 165 | } 166 | 167 | function chacha20_final(ctx, dst) { 168 | if (ctx.leftover != 0) { 169 | chacha20_encrypt(ctx, dst, ctx.buffer, 64); 170 | } 171 | 172 | return ctx.leftover; 173 | } 174 | 175 | function chacha20_keystream(ctx, dst, len) { 176 | for (var i = 0; i < len; ++i) dst[i] = 0; 177 | chacha20_encrypt(ctx, dst, dst, len); 178 | } 179 | 180 | /* poly1305 */ 181 | 182 | // Written in 2014 by Devi Mandiri. Public domain. 183 | // 184 | // Implementation derived from poly1305-donna-16.h 185 | // See for details: https://github.com/floodyberry/poly1305-donna 186 | 187 | var Poly1305KeySize = 32; 188 | var Poly1305TagSize = 16; 189 | 190 | function Poly1305Ctx() { 191 | this.buffer = new Array(Poly1305TagSize); 192 | this.leftover = 0; 193 | this.r = new Array(10); 194 | this.h = new Array(10); 195 | this.pad = new Array(8); 196 | this.finished = 0; 197 | }; 198 | 199 | function U8TO16(p, pos) { 200 | return ((p[pos] & 0xff) & 0xffff) | (((p[pos+1] & 0xff) & 0xffff) << 8); 201 | } 202 | 203 | function U16TO8(p, pos, v) { 204 | p[pos] = (v ) & 0xff; 205 | p[pos+1] = (v >>> 8) & 0xff; 206 | } 207 | 208 | function poly1305_init(ctx, key) { 209 | var t = [], i = 0; 210 | 211 | for (i = 8; i--;) t[i] = U8TO16(key, i*2); 212 | 213 | ctx.r[0] = t[0] & 0x1fff; 214 | ctx.r[1] = ((t[0] >>> 13) | (t[1] << 3)) & 0x1fff; 215 | ctx.r[2] = ((t[1] >>> 10) | (t[2] << 6)) & 0x1f03; 216 | ctx.r[3] = ((t[2] >>> 7) | (t[3] << 9)) & 0x1fff; 217 | ctx.r[4] = ((t[3] >>> 4) | (t[4] << 12)) & 0x00ff; 218 | ctx.r[5] = (t[4] >>> 1) & 0x1ffe; 219 | ctx.r[6] = ((t[4] >>> 14) | (t[5] << 2)) & 0x1fff; 220 | ctx.r[7] = ((t[5] >>> 11) | (t[6] << 5)) & 0x1f81; 221 | ctx.r[8] = ((t[6] >>> 8) | (t[7] << 8)) & 0x1fff; 222 | ctx.r[9] = (t[7] >>> 5) & 0x007f; 223 | 224 | for (i = 8; i--;) { 225 | ctx.h[i] = 0; 226 | ctx.pad[i] = U8TO16(key, 16+(2*i)); 227 | } 228 | ctx.h[8] = 0; 229 | ctx.h[9] = 0; 230 | ctx.leftover = 0; 231 | ctx.finished = 0; 232 | } 233 | 234 | function poly1305_blocks(ctx, m, mpos, bytes) { 235 | var hibit = ctx.finished ? 0 : (1 << 11); 236 | var t = [], d = [], c = 0, i = 0, j = 0; 237 | 238 | while (bytes >= Poly1305TagSize) { 239 | for (i = 8; i--;) t[i] = U8TO16(m, i*2+mpos); 240 | 241 | ctx.h[0] += t[0] & 0x1fff; 242 | ctx.h[1] += ((t[0] >>> 13) | (t[1] << 3)) & 0x1fff; 243 | ctx.h[2] += ((t[1] >>> 10) | (t[2] << 6)) & 0x1fff; 244 | ctx.h[3] += ((t[2] >>> 7) | (t[3] << 9)) & 0x1fff; 245 | ctx.h[4] += ((t[3] >>> 4) | (t[4] << 12)) & 0x1fff; 246 | ctx.h[5] += (t[4] >>> 1) & 0x1fff; 247 | ctx.h[6] += ((t[4] >>> 14) | (t[5] << 2)) & 0x1fff; 248 | ctx.h[7] += ((t[5] >>> 11) | (t[6] << 5)) & 0x1fff; 249 | ctx.h[8] += ((t[6] >>> 8) | (t[7] << 8)) & 0x1fff; 250 | ctx.h[9] += (t[7] >>> 5) | hibit; 251 | 252 | for (i = 0, c = 0; i < 10; i++) { 253 | d[i] = c; 254 | for (j = 0; j < 10; j++) { 255 | d[i] += (ctx.h[j] & 0xffffffff) * ((j <= i) ? ctx.r[i-j] : (5 * ctx.r[i+10-j])); 256 | if (j === 4) { 257 | c = (d[i] >>> 13); 258 | d[i] &= 0x1fff; 259 | } 260 | } 261 | c += (d[i] >>> 13); 262 | d[i] &= 0x1fff; 263 | } 264 | c = ((c << 2) + c); 265 | c += d[0]; 266 | d[0] = ((c & 0xffff) & 0x1fff); 267 | c = (c >>> 13); 268 | d[1] += c; 269 | 270 | for (i = 10; i--;) ctx.h[i] = d[i] & 0xffff; 271 | 272 | mpos += Poly1305TagSize; 273 | bytes -= Poly1305TagSize; 274 | } 275 | } 276 | 277 | function poly1305_update(ctx, m, bytes) { 278 | var want = 0, i = 0, mpos = 0; 279 | 280 | if (ctx.leftover) { 281 | want = (Poly1305TagSize - ctx.leftover); 282 | if (want > bytes) 283 | want = bytes; 284 | for (i = want; i--;) { 285 | ctx.buffer[ctx.leftover+i] = m[i+mpos]; 286 | } 287 | bytes -= want; 288 | mpos += want; 289 | ctx.leftover += want; 290 | if (ctx.leftover < Poly1305TagSize) 291 | return; 292 | poly1305_blocks(ctx, ctx.buffer, 0, Poly1305TagSize); 293 | ctx.leftover = 0; 294 | } 295 | 296 | if (bytes >= Poly1305TagSize) { 297 | want = (bytes & ~(Poly1305TagSize - 1)); 298 | poly1305_blocks(ctx, m, mpos, want); 299 | mpos += want; 300 | bytes -= want; 301 | } 302 | 303 | if (bytes) { 304 | for (i = bytes; i--;) { 305 | ctx.buffer[ctx.leftover+i] = m[i+mpos]; 306 | } 307 | ctx.leftover += bytes; 308 | } 309 | } 310 | 311 | function poly1305_finish(ctx, mac) { 312 | var g = [], c = 0, mask = 0, f = 0, i = 0; 313 | 314 | if (ctx.leftover) { 315 | i = ctx.leftover; 316 | ctx.buffer[i++] = 1; 317 | for (; i < Poly1305TagSize; i++) { 318 | ctx.buffer[i] = 0; 319 | } 320 | ctx.finished = 1; 321 | poly1305_blocks(ctx, ctx.buffer, 0, Poly1305TagSize); 322 | } 323 | 324 | c = ctx.h[1] >>> 13; 325 | ctx.h[1] &= 0x1fff; 326 | for (i = 2; i < 10; i++) { 327 | ctx.h[i] += c; 328 | c = ctx.h[i] >>> 13; 329 | ctx.h[i] &= 0x1fff; 330 | } 331 | ctx.h[0] += (c * 5); 332 | c = ctx.h[0] >>> 13; 333 | ctx.h[0] &= 0x1fff; 334 | ctx.h[1] += c; 335 | c = ctx.h[1] >>> 13; 336 | ctx.h[1] &= 0x1fff; 337 | ctx.h[2] += c; 338 | 339 | g[0] = ctx.h[0] + 5; 340 | c = g[0] >>> 13; 341 | g[0] &= 0x1fff; 342 | for (i = 1; i < 10; i++) { 343 | g[i] = ctx.h[i] + c; 344 | c = g[i] >>> 13; 345 | g[i] &= 0x1fff; 346 | } 347 | g[9] -= (1 << 13); 348 | g[9] &= 0xffff; 349 | 350 | mask = (g[9] >>> 15) - 1; 351 | for (i = 10; i--;) g[i] &= mask; 352 | mask = ~mask; 353 | for (i = 10; i--;) { 354 | ctx.h[i] = (ctx.h[i] & mask) | g[i]; 355 | } 356 | 357 | ctx.h[0] = ((ctx.h[0] ) | (ctx.h[1] << 13)) & 0xffff; 358 | ctx.h[1] = ((ctx.h[1] >> 3) | (ctx.h[2] << 10)) & 0xffff; 359 | ctx.h[2] = ((ctx.h[2] >> 6) | (ctx.h[3] << 7)) & 0xffff; 360 | ctx.h[3] = ((ctx.h[3] >> 9) | (ctx.h[4] << 4)) & 0xffff; 361 | ctx.h[4] = ((ctx.h[4] >> 12) | (ctx.h[5] << 1) | (ctx.h[6] << 14)) & 0xffff; 362 | ctx.h[5] = ((ctx.h[6] >> 2) | (ctx.h[7] << 11)) & 0xffff; 363 | ctx.h[6] = ((ctx.h[7] >> 5) | (ctx.h[8] << 8)) & 0xffff; 364 | ctx.h[7] = ((ctx.h[8] >> 8) | (ctx.h[9] << 5)) & 0xffff; 365 | 366 | f = (ctx.h[0] & 0xffffffff) + ctx.pad[0]; 367 | ctx.h[0] = f & 0xffff; 368 | for (i = 1; i < 8; i++) { 369 | f = (ctx.h[i] & 0xffffffff) + ctx.pad[i] + (f >>> 16); 370 | ctx.h[i] = f & 0xffff; 371 | } 372 | 373 | for (i = 8; i--;) { 374 | U16TO8(mac, i*2, ctx.h[i]); 375 | ctx.pad[i] = 0; 376 | } 377 | for (i = 10; i--;) { 378 | ctx.h[i] = 0; 379 | ctx.r[i] = 0; 380 | } 381 | } 382 | 383 | function poly1305_auth(mac, m, bytes, key) { 384 | var ctx = new Poly1305Ctx(); 385 | poly1305_init(ctx, key); 386 | poly1305_update(ctx, m, bytes); 387 | poly1305_finish(ctx, mac); 388 | } 389 | 390 | function poly1305_verify(mac1, mac2) { 391 | var dif = 0; 392 | for (var i = 0; i < 16; i++) { 393 | dif |= (mac1[i] ^ mac2[i]); 394 | } 395 | dif = (dif - 1) >>> 31; 396 | return (dif & 1); 397 | } 398 | 399 | /* chacha20poly1305 AEAD */ 400 | 401 | // Written in 2014 by Devi Mandiri. Public domain. 402 | 403 | // Caveat: 404 | // http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#page-9 405 | // specified P_MAX and A_MAX are 2^64 and C_MAX is 2^64+16. 406 | // While according to http://www.ecma-international.org/ecma-262/5.1/#sec-15.4 407 | // I think max input length = 2^32-1 = 4294967295 = ~3.9Gb due to the ToUint32 abstract operation. 408 | // Whatever ;) 409 | 410 | function AeadCtx(key) { 411 | this.key = key; 412 | }; 413 | 414 | function aead_init(c20ctx, key, nonce) { 415 | chacha20_keysetup(c20ctx, key); 416 | chacha20_ivsetup(c20ctx, nonce); 417 | 418 | var subkey = []; 419 | chacha20_keystream(c20ctx, subkey, 64); 420 | 421 | return subkey.slice(0, 32); 422 | } 423 | 424 | function store64(dst, pos, num) { 425 | var hi = 0, lo = num >>> 0; 426 | if ((+(Math.abs(num))) >= 1) { 427 | if (num > 0) { 428 | hi = ((Math.min((+(Math.floor(num/4294967296))), 4294967295))|0) >>> 0; 429 | } else { 430 | hi = (~~((+(Math.ceil((num - +(((~~(num)))>>>0))/4294967296))))) >>> 0; 431 | } 432 | } 433 | dst[pos] = lo & 0xff; lo >>>= 8; 434 | dst[pos+1] = lo & 0xff; lo >>>= 8; 435 | dst[pos+2] = lo & 0xff; lo >>>= 8; 436 | dst[pos+3] = lo & 0xff; 437 | dst[pos+4] = hi & 0xff; hi >>>= 8; 438 | dst[pos+5] = hi & 0xff; hi >>>= 8; 439 | dst[pos+6] = hi & 0xff; hi >>>= 8; 440 | dst[pos+7] = hi & 0xff; 441 | } 442 | 443 | function aead_mac(key, ciphertext, data) { 444 | var clen = ciphertext.length; 445 | var dlen = data.length; 446 | var m = new Array(clen + dlen + 16); 447 | var i = dlen; 448 | 449 | for (; i--;) m[i] = data[i]; 450 | store64(m, dlen, dlen); 451 | 452 | for (i = clen; i--;) m[dlen+8+i] = ciphertext[i]; 453 | store64(m, clen+dlen+8, clen); 454 | 455 | var mac = []; 456 | poly1305_auth(mac, m, m.length, key); 457 | 458 | return mac; 459 | } 460 | 461 | function aead_encrypt(ctx, nonce, input, ad) { 462 | var c = new Chacha20Ctx(); 463 | var key = aead_init(c, ctx.key, nonce); 464 | 465 | var ciphertext = []; 466 | chacha20_encrypt(c, ciphertext, input, input.length); 467 | 468 | var mac = aead_mac(key, ciphertext, ad); 469 | 470 | var out = []; 471 | out = out.concat(ciphertext, mac); 472 | 473 | return out; 474 | } 475 | 476 | function aead_decrypt(ctx, nonce, ciphertext, ad) { 477 | var c = new Chacha20Ctx(); 478 | var key = aead_init(c, ctx.key, nonce); 479 | var clen = ciphertext.length - Poly1305TagSize; 480 | var digest = ciphertext.slice(clen); 481 | var mac = aead_mac(key, ciphertext.slice(0, clen), ad); 482 | 483 | if (poly1305_verify(digest, mac) !== 1) return false; 484 | 485 | var out = []; 486 | chacha20_decrypt(c, out, ciphertext, clen); 487 | return out; 488 | } 489 | -------------------------------------------------------------------------------- /lib/gen/HomeKitTypes-Bridge.js: -------------------------------------------------------------------------------- 1 | // Removed from new HAS 2 | 3 | var inherits = require('util').inherits; 4 | var Characteristic = require('../Characteristic').Characteristic; 5 | var Service = require('../Service').Service; 6 | 7 | /** 8 | * Characteristic "Accessory Identifier" 9 | */ 10 | 11 | Characteristic.AccessoryIdentifier = function() { 12 | Characteristic.call(this, 'Accessory Identifier', '00000057-0000-1000-8000-0026BB765291'); 13 | this.setProps({ 14 | format: Characteristic.Formats.STRING, 15 | perms: [Characteristic.Perms.READ] 16 | }); 17 | this.value = this.getDefaultValue(); 18 | }; 19 | 20 | inherits(Characteristic.AccessoryIdentifier, Characteristic); 21 | 22 | Characteristic.AccessoryIdentifier.UUID = '00000057-0000-1000-8000-0026BB765291'; 23 | 24 | /** 25 | * Characteristic "Category" 26 | */ 27 | 28 | Characteristic.Category = function() { 29 | Characteristic.call(this, 'Category', '000000A3-0000-1000-8000-0026BB765291'); 30 | this.setProps({ 31 | format: Characteristic.Formats.UINT16, 32 | maxValue: 16, 33 | minValue: 1, 34 | minStep: 1, 35 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 36 | }); 37 | this.value = this.getDefaultValue(); 38 | }; 39 | 40 | inherits(Characteristic.Category, Characteristic); 41 | 42 | Characteristic.Category.UUID = '000000A3-0000-1000-8000-0026BB765291'; 43 | 44 | /** 45 | * Characteristic "Configure Bridged Accessory" 46 | */ 47 | 48 | Characteristic.ConfigureBridgedAccessory = function() { 49 | Characteristic.call(this, 'Configure Bridged Accessory', '000000A0-0000-1000-8000-0026BB765291'); 50 | this.setProps({ 51 | format: Characteristic.Formats.TLV8, 52 | perms: [Characteristic.Perms.WRITE] 53 | }); 54 | this.value = this.getDefaultValue(); 55 | }; 56 | 57 | inherits(Characteristic.ConfigureBridgedAccessory, Characteristic); 58 | 59 | Characteristic.ConfigureBridgedAccessory.UUID = '000000A0-0000-1000-8000-0026BB765291'; 60 | 61 | /** 62 | * Characteristic "Configure Bridged Accessory Status" 63 | */ 64 | 65 | Characteristic.ConfigureBridgedAccessoryStatus = function() { 66 | Characteristic.call(this, 'Configure Bridged Accessory Status', '0000009D-0000-1000-8000-0026BB765291'); 67 | this.setProps({ 68 | format: Characteristic.Formats.TLV8, 69 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 70 | }); 71 | this.value = this.getDefaultValue(); 72 | }; 73 | 74 | inherits(Characteristic.ConfigureBridgedAccessoryStatus, Characteristic); 75 | 76 | Characteristic.ConfigureBridgedAccessoryStatus.UUID = '0000009D-0000-1000-8000-0026BB765291'; 77 | 78 | /** 79 | * Characteristic "Current Time" 80 | */ 81 | 82 | Characteristic.CurrentTime = function() { 83 | Characteristic.call(this, 'Current Time', '0000009B-0000-1000-8000-0026BB765291'); 84 | this.setProps({ 85 | format: Characteristic.Formats.STRING, 86 | perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] 87 | }); 88 | this.value = this.getDefaultValue(); 89 | }; 90 | 91 | inherits(Characteristic.CurrentTime, Characteristic); 92 | 93 | Characteristic.CurrentTime.UUID = '0000009B-0000-1000-8000-0026BB765291'; 94 | 95 | /** 96 | * Characteristic "Day of the Week" 97 | */ 98 | 99 | Characteristic.DayoftheWeek = function() { 100 | Characteristic.call(this, 'Day of the Week', '00000098-0000-1000-8000-0026BB765291'); 101 | this.setProps({ 102 | format: Characteristic.Formats.UINT8, 103 | maxValue: 7, 104 | minValue: 1, 105 | minStep: 1, 106 | perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] 107 | }); 108 | this.value = this.getDefaultValue(); 109 | }; 110 | 111 | inherits(Characteristic.DayoftheWeek, Characteristic); 112 | 113 | Characteristic.DayoftheWeek.UUID = '00000098-0000-1000-8000-0026BB765291'; 114 | 115 | /** 116 | * Characteristic "Discover Bridged Accessories" 117 | */ 118 | 119 | Characteristic.DiscoverBridgedAccessories = function() { 120 | Characteristic.call(this, 'Discover Bridged Accessories', '0000009E-0000-1000-8000-0026BB765291'); 121 | this.setProps({ 122 | format: Characteristic.Formats.UINT8, 123 | perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] 124 | }); 125 | this.value = this.getDefaultValue(); 126 | }; 127 | 128 | inherits(Characteristic.DiscoverBridgedAccessories, Characteristic); 129 | 130 | Characteristic.DiscoverBridgedAccessories.UUID = '0000009E-0000-1000-8000-0026BB765291'; 131 | 132 | // The value property of DiscoverBridgedAccessories must be one of the following: 133 | Characteristic.DiscoverBridgedAccessories.START_DISCOVERY = 0; 134 | Characteristic.DiscoverBridgedAccessories.STOP_DISCOVERY = 1; 135 | 136 | /** 137 | * Characteristic "Discovered Bridged Accessories" 138 | */ 139 | 140 | Characteristic.DiscoveredBridgedAccessories = function() { 141 | Characteristic.call(this, 'Discovered Bridged Accessories', '0000009F-0000-1000-8000-0026BB765291'); 142 | this.setProps({ 143 | format: Characteristic.Formats.UINT16, 144 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 145 | }); 146 | this.value = this.getDefaultValue(); 147 | }; 148 | 149 | inherits(Characteristic.DiscoveredBridgedAccessories, Characteristic); 150 | 151 | Characteristic.DiscoveredBridgedAccessories.UUID = '0000009F-0000-1000-8000-0026BB765291'; 152 | 153 | /** 154 | * Characteristic "Link Quality" 155 | */ 156 | 157 | Characteristic.LinkQuality = function() { 158 | Characteristic.call(this, 'Link Quality', '0000009C-0000-1000-8000-0026BB765291'); 159 | this.setProps({ 160 | format: Characteristic.Formats.UINT8, 161 | maxValue: 4, 162 | minValue: 1, 163 | minStep: 1, 164 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 165 | }); 166 | this.value = this.getDefaultValue(); 167 | }; 168 | 169 | inherits(Characteristic.LinkQuality, Characteristic); 170 | 171 | Characteristic.LinkQuality.UUID = '0000009C-0000-1000-8000-0026BB765291'; 172 | 173 | /** 174 | * Characteristic "Reachable" 175 | */ 176 | 177 | Characteristic.Reachable = function() { 178 | Characteristic.call(this, 'Reachable', '00000063-0000-1000-8000-0026BB765291'); 179 | this.setProps({ 180 | format: Characteristic.Formats.BOOL, 181 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 182 | }); 183 | this.value = this.getDefaultValue(); 184 | }; 185 | 186 | inherits(Characteristic.Reachable, Characteristic); 187 | 188 | Characteristic.Reachable.UUID = '00000063-0000-1000-8000-0026BB765291'; 189 | 190 | /** 191 | * Characteristic "Relay Control Point" 192 | */ 193 | 194 | Characteristic.RelayControlPoint = function() { 195 | Characteristic.call(this, 'Relay Control Point', '0000005E-0000-1000-8000-0026BB765291'); 196 | this.setProps({ 197 | format: Characteristic.Formats.TLV8, 198 | perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] 199 | }); 200 | this.value = this.getDefaultValue(); 201 | }; 202 | 203 | inherits(Characteristic.RelayControlPoint, Characteristic); 204 | 205 | Characteristic.RelayControlPoint.UUID = '0000005E-0000-1000-8000-0026BB765291'; 206 | 207 | /** 208 | * Characteristic "Relay Enabled" 209 | */ 210 | 211 | Characteristic.RelayEnabled = function() { 212 | Characteristic.call(this, 'Relay Enabled', '0000005B-0000-1000-8000-0026BB765291'); 213 | this.setProps({ 214 | format: Characteristic.Formats.BOOL, 215 | perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] 216 | }); 217 | this.value = this.getDefaultValue(); 218 | }; 219 | 220 | inherits(Characteristic.RelayEnabled, Characteristic); 221 | 222 | Characteristic.RelayEnabled.UUID = '0000005B-0000-1000-8000-0026BB765291'; 223 | 224 | /** 225 | * Characteristic "Relay State" 226 | */ 227 | 228 | Characteristic.RelayState = function() { 229 | Characteristic.call(this, 'Relay State', '0000005C-0000-1000-8000-0026BB765291'); 230 | this.setProps({ 231 | format: Characteristic.Formats.UINT8, 232 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 233 | }); 234 | this.value = this.getDefaultValue(); 235 | }; 236 | 237 | inherits(Characteristic.RelayState, Characteristic); 238 | 239 | Characteristic.RelayState.UUID = '0000005C-0000-1000-8000-0026BB765291'; 240 | 241 | 242 | /** 243 | * Characteristic "Time Update" 244 | */ 245 | 246 | Characteristic.TimeUpdate = function() { 247 | Characteristic.call(this, 'Time Update', '0000009A-0000-1000-8000-0026BB765291'); 248 | this.setProps({ 249 | format: Characteristic.Formats.BOOL, 250 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 251 | }); 252 | this.value = this.getDefaultValue(); 253 | }; 254 | 255 | inherits(Characteristic.TimeUpdate, Characteristic); 256 | 257 | Characteristic.TimeUpdate.UUID = '0000009A-0000-1000-8000-0026BB765291'; 258 | 259 | /** 260 | * Characteristic "Tunnel Connection Timeout " 261 | */ 262 | 263 | Characteristic.TunnelConnectionTimeout = function() { 264 | Characteristic.call(this, 'Tunnel Connection Timeout ', '00000061-0000-1000-8000-0026BB765291'); 265 | this.setProps({ 266 | format: Characteristic.Formats.UINT32, 267 | perms: [Characteristic.Perms.WRITE, Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 268 | }); 269 | this.value = this.getDefaultValue(); 270 | }; 271 | 272 | inherits(Characteristic.TunnelConnectionTimeout, Characteristic); 273 | 274 | Characteristic.TunnelConnectionTimeout.UUID = '00000061-0000-1000-8000-0026BB765291'; 275 | 276 | /** 277 | * Characteristic "Tunneled Accessory Advertising" 278 | */ 279 | 280 | Characteristic.TunneledAccessoryAdvertising = function() { 281 | Characteristic.call(this, 'Tunneled Accessory Advertising', '00000060-0000-1000-8000-0026BB765291'); 282 | this.setProps({ 283 | format: Characteristic.Formats.BOOL, 284 | perms: [Characteristic.Perms.WRITE, Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 285 | }); 286 | this.value = this.getDefaultValue(); 287 | }; 288 | 289 | inherits(Characteristic.TunneledAccessoryAdvertising, Characteristic); 290 | 291 | Characteristic.TunneledAccessoryAdvertising.UUID = '00000060-0000-1000-8000-0026BB765291'; 292 | 293 | /** 294 | * Characteristic "Tunneled Accessory Connected" 295 | */ 296 | 297 | Characteristic.TunneledAccessoryConnected = function() { 298 | Characteristic.call(this, 'Tunneled Accessory Connected', '00000059-0000-1000-8000-0026BB765291'); 299 | this.setProps({ 300 | format: Characteristic.Formats.BOOL, 301 | perms: [Characteristic.Perms.WRITE, Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 302 | }); 303 | this.value = this.getDefaultValue(); 304 | }; 305 | 306 | inherits(Characteristic.TunneledAccessoryConnected, Characteristic); 307 | 308 | Characteristic.TunneledAccessoryConnected.UUID = '00000059-0000-1000-8000-0026BB765291'; 309 | 310 | /** 311 | * Characteristic "Tunneled Accessory State Number" 312 | */ 313 | 314 | Characteristic.TunneledAccessoryStateNumber = function() { 315 | Characteristic.call(this, 'Tunneled Accessory State Number', '00000058-0000-1000-8000-0026BB765291'); 316 | this.setProps({ 317 | format: Characteristic.Formats.FLOAT, 318 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 319 | }); 320 | this.value = this.getDefaultValue(); 321 | }; 322 | 323 | inherits(Characteristic.TunneledAccessoryStateNumber, Characteristic); 324 | 325 | Characteristic.TunneledAccessoryStateNumber.UUID = '00000058-0000-1000-8000-0026BB765291'; 326 | 327 | /** 328 | * Characteristic "Version" 329 | */ 330 | 331 | Characteristic.Version = function() { 332 | Characteristic.call(this, 'Version', '00000037-0000-1000-8000-0026BB765291'); 333 | this.setProps({ 334 | format: Characteristic.Formats.STRING, 335 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 336 | }); 337 | this.value = this.getDefaultValue(); 338 | }; 339 | 340 | inherits(Characteristic.Version, Characteristic); 341 | 342 | Characteristic.Version.UUID = '00000037-0000-1000-8000-0026BB765291'; 343 | 344 | /** 345 | * Service "Bridge Configuration" 346 | */ 347 | 348 | Service.BridgeConfiguration = function(displayName, subtype) { 349 | Service.call(this, displayName, '000000A1-0000-1000-8000-0026BB765291', subtype); 350 | 351 | // Required Characteristics 352 | this.addCharacteristic(Characteristic.ConfigureBridgedAccessoryStatus); 353 | this.addCharacteristic(Characteristic.DiscoverBridgedAccessories); 354 | this.addCharacteristic(Characteristic.DiscoveredBridgedAccessories); 355 | this.addCharacteristic(Characteristic.ConfigureBridgedAccessory); 356 | 357 | // Optional Characteristics 358 | this.addOptionalCharacteristic(Characteristic.Name); 359 | }; 360 | 361 | inherits(Service.BridgeConfiguration, Service); 362 | 363 | Service.BridgeConfiguration.UUID = '000000A1-0000-1000-8000-0026BB765291'; 364 | 365 | /** 366 | * Service "Bridging State" 367 | */ 368 | 369 | Service.BridgingState = function(displayName, subtype) { 370 | Service.call(this, displayName, '00000062-0000-1000-8000-0026BB765291', subtype); 371 | 372 | // Required Characteristics 373 | this.addCharacteristic(Characteristic.Reachable); 374 | this.addCharacteristic(Characteristic.LinkQuality); 375 | this.addCharacteristic(Characteristic.AccessoryIdentifier); 376 | this.addCharacteristic(Characteristic.Category); 377 | 378 | // Optional Characteristics 379 | this.addOptionalCharacteristic(Characteristic.Name); 380 | }; 381 | 382 | inherits(Service.BridgingState, Service); 383 | 384 | Service.BridgingState.UUID = '00000062-0000-1000-8000-0026BB765291'; 385 | 386 | /** 387 | * Service "Pairing" 388 | */ 389 | 390 | Service.Pairing = function(displayName, subtype) { 391 | Service.call(this, displayName, '00000055-0000-1000-8000-0026BB765291', subtype); 392 | 393 | // Required Characteristics 394 | this.addCharacteristic(Characteristic.PairSetup); 395 | this.addCharacteristic(Characteristic.PairVerify); 396 | this.addCharacteristic(Characteristic.PairingFeatures); 397 | this.addCharacteristic(Characteristic.PairingPairings); 398 | 399 | // Optional Characteristics 400 | }; 401 | 402 | inherits(Service.Pairing, Service); 403 | 404 | Service.Pairing.UUID = '00000055-0000-1000-8000-0026BB765291'; 405 | 406 | /** 407 | * Service "Protocol Information" 408 | */ 409 | 410 | Service.ProtocolInformation = function(displayName, subtype) { 411 | Service.call(this, displayName, '000000A2-0000-1000-8000-0026BB765291', subtype); 412 | 413 | // Required Characteristics 414 | this.addCharacteristic(Characteristic.Version); 415 | 416 | // Optional Characteristics 417 | }; 418 | 419 | inherits(Service.ProtocolInformation, Service); 420 | 421 | Service.ProtocolInformation.UUID = '000000A2-0000-1000-8000-0026BB765291'; 422 | 423 | /** 424 | * Service "Relay" 425 | */ 426 | 427 | Service.Relay = function(displayName, subtype) { 428 | Service.call(this, displayName, '0000005A-0000-1000-8000-0026BB765291', subtype); 429 | 430 | // Required Characteristics 431 | this.addCharacteristic(Characteristic.RelayEnabled); 432 | this.addCharacteristic(Characteristic.RelayState); 433 | this.addCharacteristic(Characteristic.RelayControlPoint); 434 | 435 | // Optional Characteristics 436 | }; 437 | 438 | inherits(Service.Relay, Service); 439 | 440 | Service.Relay.UUID = '0000005A-0000-1000-8000-0026BB765291'; 441 | 442 | /** 443 | * Service "Time Information" 444 | */ 445 | 446 | Service.TimeInformation = function(displayName, subtype) { 447 | Service.call(this, displayName, '00000099-0000-1000-8000-0026BB765291', subtype); 448 | 449 | // Required Characteristics 450 | this.addCharacteristic(Characteristic.CurrentTime); 451 | this.addCharacteristic(Characteristic.DayoftheWeek); 452 | this.addCharacteristic(Characteristic.TimeUpdate); 453 | 454 | // Optional Characteristics 455 | this.addOptionalCharacteristic(Characteristic.Name); 456 | }; 457 | 458 | inherits(Service.TimeInformation, Service); 459 | 460 | Service.TimeInformation.UUID = '00000099-0000-1000-8000-0026BB765291'; 461 | 462 | /** 463 | * Service "Tunneled BTLE Accessory Service" 464 | */ 465 | 466 | Service.TunneledBTLEAccessoryService = function(displayName, subtype) { 467 | Service.call(this, displayName, '00000056-0000-1000-8000-0026BB765291', subtype); 468 | 469 | // Required Characteristics 470 | this.addCharacteristic(Characteristic.Name); 471 | this.addCharacteristic(Characteristic.AccessoryIdentifier); 472 | this.addCharacteristic(Characteristic.TunneledAccessoryStateNumber); 473 | this.addCharacteristic(Characteristic.TunneledAccessoryConnected); 474 | this.addCharacteristic(Characteristic.TunneledAccessoryAdvertising); 475 | this.addCharacteristic(Characteristic.TunnelConnectionTimeout); 476 | 477 | // Optional Characteristics 478 | }; 479 | 480 | inherits(Service.TunneledBTLEAccessoryService, Service); 481 | 482 | Service.TunneledBTLEAccessoryService.UUID = '00000056-0000-1000-8000-0026BB765291'; --------------------------------------------------------------------------------