ha-nest-protect icon indicating copy to clipboard operation
ha-nest-protect copied to clipboard

Add switch entity for Home/Away control

Open schmittx opened this issue 2 years ago • 5 comments

Description

Another feature lacking from Nest SDM is the ability to toggle home/away for the structure.

The structure.xxx data bucket already includes the away boolean parameter, so I believe it should be an easy addition (no protobufs required?).

Device (optional)

Structure

Additional information

No response

schmittx avatar Sep 22 '22 23:09 schmittx

+1 for this.

sidplayos2 avatar Oct 21 '22 04:10 sidplayos2

It took a while, but I will have a look again. Can you try to execute this via https://home.nest.com/ and capture the POST request (JSON). I don't have a thermostat, so I am not able to add this.

Is this not supported in the Nest integration in core?

iMicknl avatar Dec 16 '23 23:12 iMicknl

Hi Mick,

I did speak to Allen Porter about this a while ago, and it was because this HomeAway Sensor/Switch is not directly in the SDM API, same reason the Protects are not in the Nest Core.

I did find this switch does exist in the full blown HomeKit/Homebridge nest API (https://github.com/chrisjshull/homebridge-nest) which I understand the Protect integration is based on. If you scroll down to the Feature Options of that page you will see:

"HomeAway.AsOccupancySensorAndSwitch" - create Home/Away indicator as an OccupancySensor and a Switch

I found the Home/Away code is in the file lib/nest-homeaway-accessory.js Happy to trace the POST anyway, my thinking was if the logic below is similar to what we want to achieve?

In particular the getHome() and setHome() functions look relevant.

/**
 * Created by Adrian Cable on 7/26/19.
 */

'use strict';

const { NestDeviceAccessory, Service, Characteristic } = require('./nest-device-accessory')();

const nestDeviceDescriptor = {
    deviceType: 'home_away_sensor',
    deviceGroup: 'home_away_sensors',
    deviceDesc: 'Nest Home/Away Sensor'
};

class NestHomeAwayAccessory extends NestDeviceAccessory {
    constructor(conn, log, device, structure, platform) {
        super(conn, log, device, structure, platform);

        if (this.platform.optionSet('HomeAway.AsOccupancySensor', this.device.serial_number, this.device.device_id) || this.platform.optionSet('HomeAway.AsOccupancySensorAndSwitch', this.device.serial_number, this.device.device_id)) {
            const homeService = this.addService(Service.OccupancySensor, this.homeKitSanitize(this.device.name), 'home_occupied_sensor.' + this.device.device_id);
            this.bindCharacteristic(homeService, Characteristic.OccupancyDetected, this.device.name, this.getHome);
        }

        if (!this.platform.optionSet('HomeAway.AsOccupancySensor', this.device.serial_number, this.device.device_id)) {
            const homeService = this.addService(Service.Switch, this.homeKitSanitize(this.device.name), 'home_occupied.' + this.device.device_id);
            this.bindCharacteristic(homeService, Characteristic.On, this.device.name, this.getHome, this.setHome);
        }

        this.updateData();
    }

    getHome() {
        if (!this.structure.new_structure_id) {
            this.conn.verbose('Warning: getting Home/Away status using REST API - may lead to issues.');
        }
        return !this.device.away;
    }

    setHome(home, callback) {
        if (this.structure.new_structure_id) {
            // Set using protobuf API
            let cmd = {
                traitLabel: 'structure_mode',
                command: {
                    type_url: 'type.nestlabs.com/nest.trait.occupancy.StructureModeTrait.StructureModeChangeRequest',
                    value: {
                        structureMode: home ? 'STRUCTURE_MODE_HOME' : 'STRUCTURE_MODE_AWAY',
                        reason: 'STRUCTURE_MODE_REASON_EXPLICIT_INTENT',
                        userId: {
                            resourceId: this.structure.user_id
                        }
                    }
                }
            };

            this.conn.protobufSendCommand([ cmd ], 'STRUCTURE_' + this.structure.new_structure_id).asCallback(callback);
        } else {
            // Set using REST API
            this.conn.verbose('Warning: setting Home/Away status using REST API - may lead to issues.');
            let val = !home ? 'away' : 'home';
            this.setPropertyAsync('structure', 'away', val).asCallback(callback);
        }
    }
}

module.exports = function() {
    Object.keys(nestDeviceDescriptor).forEach(key => NestHomeAwayAccessory[key] = NestHomeAwayAccessory.prototype[key] = nestDeviceDescriptor[key]);
    return NestHomeAwayAccessory;
};

sidplayos2 avatar Dec 17 '23 22:12 sidplayos2

They are using Protobuf (a different protocol) for this, which we didn't implement for the Nest Protect integration (yet).

iMicknl avatar Dec 18 '23 20:12 iMicknl

Hi Mick,

So I've traced the POST request down in Chrome Dev Tools and it seems they are not using application/json for Content-Type but instead application/x-protobuf in the actual request headers:

Request URL: https://grpc-web.production.nest.com/nestlabs.gateway.v1.ResourceApi/SendCommand Request Method: POST Status Code: 200 OK Remote Address: 34.98.67.105:443 Referrer Policy: origin Access-Control-Allow-Origin: * Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 Content-Transfer-Encoding: base64 Content-Type: application/x-protobuf.google.rpc.streambody <--------- Response Content-Type Date: Wed, 27 Dec 2023 23:46:48 GMT Server: nginx/1.11.13 Via: 1.1 google :authority: grpc-web.production.nest.com :method: POST :path: /nestlabs.gateway.v1.ResourceApi/SendCommand :scheme: https Accept: / Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Authorization: Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Content-Length: 219 Content-Type: application/x-protobuf <--------- Request Content-Type Origin: https://home.nest.com Referer: https://home.nest.com/ Request-Id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Sec-Ch-Ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120" Sec-Ch-Ua-Mobile: ?0 Sec-Ch-Ua-Platform: "Windows" Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-site User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 X-Accept-Content-Transfer-Encoding: base64 X-Accept-Response-Streaming: true X-Nl-Webapp-Version: NlAppSDKVersion/9.5.3 NlSchemaVersion/2.1.20-336-g3111f722b

And the request payload looks to be the serialized class object:

payload

sidplayos2 avatar Dec 28 '23 00:12 sidplayos2