grover / homebridge-dacp

Remotely control Apple TV and iTunes via HomeKit.
MIT License
153 stars 15 forks source link

Detecting off status #65

Open eliottrobson opened 5 years ago

eliottrobson commented 5 years ago

I was reviewing the DACP protocol trying to find a way to detect when the Apple TV was turned off (so I could add play, pause and off lights) however the closest I got was detecting when the app is closed (or turned off). It works really well, is this something you would be happy for me to PR?

If not, and for anyone interested in setting this up for automation (I have only tested this on the alternate-playpause-switch) there's a few simple changes you need to make.

The first is to change the switch type to Outlet which enables on/off (playing/paused) and in use (on when playing / paused, off when app is closed).

https://github.com/grover/homebridge-dacp/blob/8a8f472f72eab24eb9b0c7ce0e9c599a7be723f3/src/DacpAccessory.js#L95-L108

This needs to change to:

  getPlayerControlsService(homebridge) {
    let service = Service.PlayerControlsService;
    let characteristic = Characteristic.PlayPause;
    let characteristic2 = null;

    if (this.config.features['alternate-playpause-switch'] === true) {
      service = Service.Outlet;
      characteristic = Characteristic.On;
      characteristic2 = Characteristic.OutletInUse;
    }

    this._playerControlsService = new PlayerControlsService(homebridge, this.log, this.name, this._dacpClient, service, characteristic, characteristic2);
    this._playStatusUpdateListeners.push(this._playerControlsService);

    return this._playerControlsService.getService();
  }

And then the controls service needs to change too:

https://github.com/grover/homebridge-dacp/blob/8a8f472f72eab24eb9b0c7ce0e9c599a7be723f3/src/PlayerControlsService.js#L3

The constructor needs to support the new parameter

constructor(homebridge, log, name, dacp, serviceCtor, characteristicCtor, characteristic2Ctor) {
    this.log = log;
    this.name = name;
    this._dacp = dacp;
    this._serviceCtor = serviceCtor;
    this._characteristicCtor = characteristicCtor;
    this._characteristic2Ctor = characteristic2Ctor;

    this._service = this.createService();
  }

The service needs to use the new characteristic

createService() {
    const svc = new this._serviceCtor(this.name);

    svc.getCharacteristic(this._characteristicCtor)
      .on('get', this._getPlayState.bind(this))
      .on('set', this._setPlayState.bind(this));

    if (this._characteristic2Ctor) {
      svc.getCharacteristic(this._characteristic2Ctor)
        .on('get', this._getOnState.bind(this))
        .on('set', this._setOnState.bind(this));
    }

    return svc;
  }

The update needs to track the new characteristic and determine the on state

update(response) {
    this._isPlaying = response.caps === 4;
    this._isOn = response.caps !== 0;

    this._service.getCharacteristic(this._characteristicCtor)
      .updateValue(this._isPlaying);

    if (this._characteristic2Ctor) {
      this._service.getCharacteristic(this._characteristic2Ctor)
        .updateValue(this._isOn);
    }
  }

And finally we need to add some state monitors for logging

_getOnState(callback) {
    this.log(`Returning current on state: ${this._isOn ? 'on' : 'off'}`);
    callback(undefined, this._isOn);
  }

  async _setOnState(isOn, callback) {
    this.log(`Setting current on state: ${isOn ? 'on' : 'off'}`);
    this._isOn = isOn;
  }

Once that is done, you should be able to listen to the power state going off to determine the Apple TV is off (or close enough at least).