Fix for multiple devices, refactor, and custom Service+Characteristic

This commit is contained in:
S'pht'Kr
2015-09-28 06:12:25 +02:00
parent 6b0d701570
commit 2a66eac058

View File

@@ -1,5 +1,10 @@
var types = require("HAP-NodeJS/accessories/types.js"); var types = require("HAP-NodeJS/accessories/types.js");
var inherits = require('util').inherits;
var debug = require('debug')('YamahaAVR');
var Service = require("HAP-NodeJS").Service;
var Characteristic = require("HAP-NodeJS").Characteristic;
var Yamaha = require('yamaha-nodejs'); var Yamaha = require('yamaha-nodejs');
var Q = require('q');
var mdns = require('mdns'); var mdns = require('mdns');
//workaround for raspberry pi //workaround for raspberry pi
var sequence = [ var sequence = [
@@ -12,10 +17,53 @@ function YamahaAVRPlatform(log, config){
this.log = log; this.log = log;
this.config = config; this.config = config;
this.playVolume = config["play_volume"]; this.playVolume = config["play_volume"];
this.minVolume = config["min_volume"] || -50.0;
this.maxVolume = config["max_volume"] || -20.0;
this.gapVolume = this.maxVolume - this.minVolume;
this.setMainInputTo = config["setMainInputTo"]; this.setMainInputTo = config["setMainInputTo"];
this.expectedDevices = config["expected_devices"] || 100;
this.discoveryTimeout = config["discovery_timeout"] || 30;
this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence}); this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence});
} }
// Custom Characteristics and service...
YamahaAVRPlatform.AudioVolume = function() {
Characteristic.call(this, 'Audio Volume', '00001001-0000-1000-8000-135D67EC4377');
this.format = 'uint8';
this.unit = 'percentage';
this.maximumValue = 100;
this.minimumValue = 0;
this.stepValue = 1;
this.readable = true;
this.writable = true;
this.supportsEventNotification = true;
this.value = this.getDefaultValue();
};
inherits(YamahaAVRPlatform.AudioVolume, Characteristic);
YamahaAVRPlatform.Muting = function() {
Characteristic.call(this, 'Muting', '00001002-0000-1000-8000-135D67EC4377');
this.format = 'bool';
this.readable = true;
this.writable = true;
this.supportsEventNotification = true;
this.value = this.getDefaultValue();
};
inherits(YamahaAVRPlatform.Muting, Characteristic);
YamahaAVRPlatform.AudioDeviceService = function(displayName, subtype) {
Service.call(this, displayName, '00000001-0000-1000-8000-135D67EC4377', subtype);
// Required Characteristics
this.addCharacteristic(YamahaAVRPlatform.AudioVolume);
// Optional Characteristics
this.addOptionalCharacteristic(YamahaAVRPlatform.Muting);
};
inherits(YamahaAVRPlatform.AudioDeviceService, Service);
YamahaAVRPlatform.prototype = { YamahaAVRPlatform.prototype = {
accessories: function(callback) { accessories: function(callback) {
this.log("Getting Yamaha AVR devices."); this.log("Getting Yamaha AVR devices.");
@@ -24,7 +72,9 @@ YamahaAVRPlatform.prototype = {
var browser = this.browser; var browser = this.browser;
browser.stop(); browser.stop();
browser.removeAllListeners('serviceUp'); // cleanup listeners browser.removeAllListeners('serviceUp'); // cleanup listeners
var accessories = [];
var timer, timeElapsed = 0, checkCyclePeriod = 5000;
browser.on('serviceUp', function(service){ browser.on('serviceUp', function(service){
var name = service.name; var name = service.name;
//console.log('Found HTTP service "' + name + '"'); //console.log('Found HTTP service "' + name + '"');
@@ -36,12 +86,36 @@ YamahaAVRPlatform.prototype = {
var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0];
that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\"");
var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig); var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig);
callback([accessory]); accessories.push(accessory);
if(accessories.length >= this.expectedDevices)
timeoutFunction(); // We're done, call the timeout function now.
//callback([accessory]);
}, function(err){ }, function(err){
return; return;
}) });
}); });
browser.start(); browser.start();
// The callback can only be called once...so we'll have to find as many as we can
// in a fixed time and then call them in.
var timeoutFunction = function(){
if(accessories.length >= that.expectedDevices){
clearTimeout(timer);
} else {
timeElapsed += checkCyclePeriod;
if(timeElapsed > that.discoveryTimeout * 1000){
that.log("Waited " + that.discoveryTimeout + " seconds, stopping discovery.");
} else {
timer = setTimeout(timeoutFunction, checkCyclePeriod);
return;
}
}
browser.stop();
browser.removeAllListeners('serviceUp');
that.log("Discovery finished, found " + accessories.length + " Yamaha AVR devices.");
callback(accessories);
};
timer = setTimeout(timeoutFunction, checkCyclePeriod);
} }
}; };
@@ -56,6 +130,9 @@ function YamahaAVRAccessory(log, config, mdnsService, yamaha, sysConfig) {
this.serviceName = mdnsService.name + " Speakers"; this.serviceName = mdnsService.name + " Speakers";
this.setMainInputTo = config["setMainInputTo"]; this.setMainInputTo = config["setMainInputTo"];
this.playVolume = this.config["play_volume"]; this.playVolume = this.config["play_volume"];
this.minVolume = config["min_volume"] || -50.0;
this.maxVolume = config["max_volume"] || -20.0;
this.gapVolume = this.maxVolume - this.minVolume;
} }
YamahaAVRAccessory.prototype = { YamahaAVRAccessory.prototype = {
@@ -66,104 +143,74 @@ YamahaAVRAccessory.prototype = {
if (playing) { if (playing) {
yamaha.powerOn().then(function(){ return yamaha.powerOn().then(function(){
if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10); if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10);
else return { then: function(f, r){ f(); } }; else return Q();
}).then(function(){ }).then(function(){
if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo); if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo);
else return { then: function(f, r){ f(); } }; else return Q();
}).then(function(){ }).then(function(){
if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver( if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver(
'<YAMAHA_AV cmd="PUT"><AirPlay><Play_Control><Playback>Play</Playback></Play_Control></AirPlay></YAMAHA_AV>' '<YAMAHA_AV cmd="PUT"><AirPlay><Play_Control><Playback>Play</Playback></Play_Control></AirPlay></YAMAHA_AV>'
); );
else return { then: function(f, r){ f(); } }; else return Q();
//else return Promise.fulfilled(undefined);
}); });
} }
else { else {
yamaha.powerOff(); return yamaha.powerOff();
} }
}, },
getServices: function() { getServices: function() {
var that = this; var that = this;
return [{ var informationService = new Service.AccessoryInformation();
sType: types.ACCESSORY_INFORMATION_STYPE, var yamaha = this.yamaha;
characteristics: [{
cType: types.NAME_CTYPE, informationService
onUpdate: null, .setCharacteristic(Characteristic.Name, this.name)
perms: ["pr"], .setCharacteristic(Characteristic.Manufacturer, "Yamaha")
format: "string", .setCharacteristic(Characteristic.Model, this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0])
initialValue: this.name, .setCharacteristic(Characteristic.SerialNumber, this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]);
supportEvents: false,
supportBonjour: false, var switchService = new Service.Switch("Power State");
manfDescription: "Name of the accessory", switchService.getCharacteristic(Characteristic.On)
designedMaxLength: 255 .on('get', function(callback, context){
},{ yamaha.isOn().then(function(result){
cType: types.MANUFACTURER_CTYPE, callback(false, result);
onUpdate: null, }.bind(this));
perms: ["pr"], }.bind(this))
format: "string", .on('set', function(powerOn, callback){
initialValue: "Yamaha", this.setPlaying(powerOn).then(function(){
supportEvents: false, callback(false, powerOn);
supportBonjour: false, }, function(error){
manfDescription: "Manufacturer", callback(error, !powerOn); //TODO: Actually determine and send real new status.
designedMaxLength: 255 });
},{ }.bind(this));
cType: types.MODEL_CTYPE,
onUpdate: null, var audioDeviceService = new YamahaAVRPlatform.AudioDeviceService("Audio Functions");
perms: ["pr"], audioDeviceService.getCharacteristic(YamahaAVRPlatform.AudioVolume)
format: "string", .on('get', function(callback, context){
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0], yamaha.getBasicInfo().done(function(basicInfo){
supportEvents: false, var v = basicInfo.getVolume()/10.0;
supportBonjour: false, var p = 100 * ((v - that.minVolume) / that.gapVolume);
manfDescription: "Model", p = p < 0 ? 0 : p > 100 ? 100 : Math.round(p);
designedMaxLength: 255 debug("Got volume percent of " + p + "%");
},{ callback(false, p);
cType: types.SERIAL_NUMBER_CTYPE, });
onUpdate: null, })
perms: ["pr"], .on('set', function(p, callback){
format: "string", var v = ((p / 100) * that.gapVolume) + that.minVolume;
initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0], v = Math.round(v*10.0);
supportEvents: false, debug("Setting volume to " + v);
supportBonjour: false, yamaha.setVolumeTo(v).then(function(){
manfDescription: "SN", callback(false, p);
designedMaxLength: 255 });
},{ })
cType: types.IDENTIFY_CTYPE, .getValue(null, null); // force an asynchronous get
onUpdate: null,
perms: ["pw"],
format: "bool", return [informationService, switchService, audioDeviceService];
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.serviceName,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPlaying(value); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the playback state of the Yamaha AV Receiver",
designedMaxLength: 1
}]
}];
} }
}; };