mirror of
https://github.com/mtan93/homebridge.git
synced 2026-04-14 14:23:10 +01:00
Merge pull request #210 from SphtKr/yamaha-issue-153
YamahaAVR: Fix for multiple devices, refactor, and custom Service+Characteristic
This commit is contained in:
@@ -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
|
|
||||||
}]
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user