timcharper / homebridge-vivint

Integrates Vivint security system with Apple Home
Other
34 stars 34 forks source link

Garage Door Opener #3

Closed daymondm closed 5 years ago

daymondm commented 5 years ago

Hello Tim,

First off thank you for creating this api for vivint to use with homebridge. I'm new to GitHub as well as javascript programming but I think I've successfully added garage door opener functionality to your api. I've attached the adjusted code to device_set.js. I was hoping this would be useful to you in furthering your development of this project! Full disclosure... I really have very little idea of what I'm doing.

  class GarageDoor extends Device {
    constructor(accessory) {
      super(accessory)
      this.service = accessory.getServiceByUUIDAndSubType(Service.GarageDoorOpener)
      this.service
        .getCharacteristic(Characteristic.CurrentDoorState)
        .on('get', (next) => next(null, this.doorCurrentValue()));

      this.service
        .getCharacteristic(Characteristic.TargetDoorState)
        .on('get', (next) => next(null, this.doorCurrentValue()))
        .on('set', this.setDoorTargetStateCharacteristic.bind(this))
    }

    setDoorTargetStateCharacteristic(targetState, next) {
      //log("setDoorTargetStateCharacteristic: " + targetState + "this.data.s: " + this.data.s)
      // 0 -> open, 1->close

      // 2 = close, 4 = open
      if (targetState)
        vivintApi.putDevice('door', this.id, {s: 2, _id: this.id})
          .then(
            (success) => next(),
            (failure) => {
              log("failure " + failure)
              next(failure)
            })
      else
        vivintApi.putDevice('door', this.id, {s: 4, _id: this.id})
          .then(
            (success) => next(),
            (failure) => {
              log("failure " + failure)
              next(failure)
            })
    }

    doorCurrentValue() {
      //log("doorCurrentValue() this.data.s: " + this.data.s)

      if (this.data.s === 1)
        return Characteristic.CurrentDoorState.CLOSED
      else if (this.data.s === 2)
        return Characteristic.CurrentDoorState.CLOSING
      else if (this.data.s === 4)
        return Characteristic.CurrentDoorState.OPENING
      else if (this.data.s === 5)
        return Characteristic.CurrentDoorState.OPEN
      else
        return Characteristic.CurrentDoorState.STOPPED     
    }

    notify() {
      if (this.service) {
        this.service.getCharacteristic(Characteristic.CurrentDoorState)
          .updateValue(this.doorCurrentValue())
      }
    }
  }

  extend(GarageDoor, {
    appliesTo: (data) => { return data.t == "garage_door_device" },
    addServices: (accessory) => {
      accessory.addService(new Service.GarageDoorOpener(accessory.context.name))
    }
  })

and to the device class list -

let Devices = [ContactSensor, MotionSensor, Lock, Thermostat, GarageDoor]

daymondm commented 5 years ago

Also working on adding the panel and the ability to arm/disarm the system. It seems to work (mostly).

let Devices = [ContactSensor, MotionSensor, Lock, Thermostat, GarageDoor, Panel]

  class Panel extends Device {
    constructor(accessory) {
      super(accessory)
      this.service = accessory.getServiceByUUIDAndSubType(Service.SecuritySystem)

      this.targetPanelState = Characteristic.SecuritySystemTargetState.DISARMED

      this.service
        .getCharacteristic(Characteristic.SecuritySystemCurrentState)
        .on('get', (next) => next(null, this.targetPanelState))

      this.service
        .getCharacteristic(Characteristic.SecuritySystemTargetState)
        .on('get', (next) => next(null, this.ssCurrentValue()))
        .on('set', this.setssTargetState.bind(this))
    }

    setssTargetState(targetState, next) {
      //log("setssTargetState: " + targetState)
      // 3: off
      // 0: stay
      // 1: away
      // 2: night??? (Stay)

      // vivint: 3 stay, 0 disarm, 4 away

      if (targetState === 3) {
        vivintApi.putDevicePanel(0)
         this.targetPanelState = Characteristic.SecuritySystemTargetState.DISARMED
      } else if (targetState === 1) {
        vivintApi.putDevicePanel(4)
        this.targetPanelState = Characteristic.SecuritySystemTargetState.AWAY_ARM
      } else {
        vivintApi.putDevicePanel(3)
        this.targetPanelState = Characteristic.SecuritySystemTargetState.STAY_ARM
      }
    }

    ssCurrentValue() {
      if (this.data.newstate === 0) return Characteristic.SecuritySystemCurrentState.DISARMED
      else if (this.data.newstate === 3) return Characteristic.SecuritySystemCurrentState.STAY_ARM
      else if (this.data.newstate === 4) return Characteristic.SecuritySystemCurrentState.AWAY_ARM
      else return Characteristic.SecuritySystemCurrentState.DISARMED
    }

    notify() {
      if (this.data.stateupdate) {
        this.service.setCharacteristic(Characteristic.SecuritySystemCurrentState, this.ssCurrentValue())
        //this.targetPanelState = this.ssCurrentValue()
        //log("notify() received for panel, setting current state to: " + this.ssCurrentValue())
      }
    }

  }

  extend(Panel, {
    appliesTo: (data) => { return data.t == "primary_touch_link_device" },
    addServices: (accessory) => {
      accessory.addService(new Service.SecuritySystem(accessory.context.name))
    }
  })

had to modify how the pub nub messages are handled because the api doesn't include an _id of any devices when it announces changes in the arm state

    constructor() {
      // deviceData = systemInfo.system.par[0].d
      this.lastSnapshotTime = 0
      this.devices = []
      this.devicesById = {}
      this.panelId = 0
    }
    handleMessage(msg) {
     if (msg.message.da.plctx.validation) {
       if (msg.message.da.plctx.validation.args) {
         if (msg.message.da.plctx.validation.args.name[0] == "security_state") {
           //console.log("possible change in arming state detected!: " + msg.message.da.plctx.validation.args.value[0])
           if (this.panelId) {
             //log("trying to update current state")

             //log("1/Update to state: " + msg.message.da.plctx.validation.args.value[0] + " panelId: " + this.panelId)
             //log("2/Update to state: " + msg.message.da.plctx.validation.args.value)
             //log("3/Update to state: " + msg.message.da.plctx.validation.args.value[0])

             msg.message.da.d = []
             msg.message.da.d[0] = {
               _id: this.panelId,
               s: msg.message.da.plctx.validation.args.value[0],
               stateupdate: true,
               newstate: msg.message.da.plctx.validation.args.value[0]
             }
             //msg.message.da.d[0]._id = this.panelId
             //msg.message.da.d[0].s = msg.message.da.plctx.validation.args.value[0]
           }
         }
       }
     }

      if ((msg.message.da) && (msg.message.da.d)) {
        if (msg.message.da.plctx.ts < this.lastSnapshotTime) {
          log("Ignoring stale update", msg.message.da.plctx.ts, "<", this.lastSnapshotTime)
          return;
        }
        msg.message.da.d.forEach((patch) => {
          if (this.devicesById[patch._id]) {
            this.devicesById[patch._id].handlePatch(patch)
          }
        })
      }

    }

As it is now, it allows Siri to arm and disarm the panel, but when you change the state from the Home app it tends to hang on "updating".