Nest Thermostat accesstoken

Bill, I wrote a plugin for the Nest thermostat meanwhile the official integration arrives :wink: and the accessToken needs to be refreshed every hour. I am testing it with 3 of my thermostats and the way I am handling the generation of the token is to refresh it when loading the plugin and storing it in a variable, I also use the HasSubscribers() function. It seems to work well, but since I have 3 thermostats, it refreshes the token 3 times. Is there a way or workaround to refresh the token only once?

I’m not sure why it’s refreshing it 3 times.

How many plugin instances are you running?

You only need 1. Sounds like you may be running 3. If so, don’t. A plugin can have as many device objects as it needs. Have the same plugin manage all 3 thermostats.

Yes, I have 3 instances because I do not know how to handle the onSynchronizeDevices() which uses the ID and DisplayName which I guess should be different for every instance?

function onSynchronizeDevices() {
var GNThermostat = new Device();
GNThermostat.Id = “GoogleNestThermostat”;
GNThermostat.DisplayName = “Google Nest Thermostat”;
GNThermostat.Capabilities = [“Switch”, “TemperatureMeasurement”, “ThermostatMode”, “ThermostatOperatingState”, “HeatingSetpoint”];
GNThermostat.Attributes = [];
plugin.Devices[GNThermostat.Id] = GNThermostat;
}

Yes. You will need a unique Id for each. The Nest API should provide a unique identifier for each thermostat. You should be able to read that & assign it to these device objects.

I copied the 3 thermostats inside the same device source but the problem now is where to store the 3 Nest device_ids ?

Use the Id field on the Device object. That’s what it’s there for. That can be whatever you want it to be.

Get rid of that Id you made up “GoogleNestThermostat” & store the actual “device_id” in that field.

I am trying this but it does not find the devices…
Could you please have a look to the code whenever you have some time?, and point me the changes that need to be done?
Thanks a lot!, and sorry for my lack of knowledge.
GoogleNestThermostat.plugin (3.6 KB)

Bill, by the way, if you have an example I can learn from, it would be great ! Many thanks.

Try this:
GoogleNestThermostat_v2.plugin (3.6 KB)

plugin.Name = "GoogleNestThermostat";
plugin.OnChangeRequest = onChangeRequest;
plugin.OnConnect = onConnect;
plugin.OnDisconnect = onDisconnect;
plugin.OnPoll = onPoll;
plugin.OnSynchronizeDevices = onSynchronizeDevices;
plugin.PollingInterval = 5000;
plugin.DefaultSettings = { "projectID": "", "clientID": "", "clientSecret": "", "refreshToken": "" };

var projectID;
var clientID;
var clientSecret;
var refreshToken;
var http = new HTTPClient();

function onChangeRequest(device, attribute, value) {
    switch (attribute) {
        case "Switch":
            device.Switch = value;
            break;
        default:
            break;
    }
}

function onConnect() {
    projectID = plugin.Settings["projectID"];
    clientID = plugin.Settings["clientID"];
    clientSecret = plugin.Settings["clientSecret"];
    refreshToken = plugin.Settings["refreshToken"];
    accessToken = null;
}

function onDisconnect() {
    console.log("disconnected");
}

function onPoll() {
    for (var deviceID in plugin.Devices) {
        var GNThermostat = plugin.Devices[deviceID];
        if (GNThermostat != null) {
            if (GNThermostat.HasSubscribers()) {
                if (accessToken == null) {
                    accessToken = HttpRefreshToken();
                }
            }

            var getInfo = HttpGet("devices/" + deviceID);
            if (!getInfo.data.error) {
                var displayName = getInfo.data.parentRelations[0].displayName
                var ambientTemperatureCelsius = Math.round(getInfo.data.traits["sdm.devices.traits.Temperature"].ambientTemperatureCelsius * 10) / 10;
                var ambientHumidityPercent = getInfo.data.traits["sdm.devices.traits.Humidity"].ambientHumidityPercent;
                var mode = getInfo.data.traits["sdm.devices.traits.ThermostatMode"].mode;
                var Setpoint = Math.round(getInfo.data.traits["sdm.devices.traits.ThermostatTemperatureSetpoint"].heatCelsius * 10) / 10;

                console.log("displayName = " + displayName + " , ambientTemperatureCelsius = " + ambientTemperatureCelsius + " , ambientHumidityPercent = " + ambientHumidityPercent + " , mode = " + mode + " , Setpoint = " + Setpoint);
            }

            GNThermostat.Temperature = ambientTemperatureCelsius;
            GNThermostat.ThermostatMode = (mode == "HEAT") ? "Heat" : "Off";
            GNThermostat.ThermostatOperatingState = ((ambientTemperatureCelsius - Setpoint) > 0) ? "Idle" : "Heating";
            GNThermostat.HeatingSetpoint = Setpoint;
        }
    }
}

function onSynchronizeDevices() {
    var GNThermostat = new Device();
    GNThermostat.Id = "GoogleNestThermostat";
    GNThermostat.DisplayName = "Google Nest Thermostat";
    GNThermostat.Capabilities = ["Switch", "TemperatureMeasurement", "ThermostatMode", "ThermostatOperatingState", "HeatingSetpoint"];
    GNThermostat.Attributes = [];
    plugin.Devices[GNThermostat.Id] = GNThermostat;
}

function HttpGet(attribute) {
    var httpHeaders = {
        headers: {
            'Authorization': "Bearer " + accessToken
        }
    };
    return http.get("https://smartdevicemanagement.googleapis.com/v1/enterprises/" + projectID + "/" + attribute, httpHeaders);
}

function HttpRefreshToken() {
    //console.log("getting accessToken = " + accessToken);
    var TokenResult = http.post("https://www.googleapis.com/oauth2/v4/token?client_id=" + clientID + "&client_secret=" + clientSecret + "&refresh_token=" + refreshToken + "&grant_type=refresh_token");
    if (!TokenResult.data.error) {
        return TokenResult.data.access_token;
    } else {
        return null;
    }
}
1 Like

Bill, this works like a charm, thanks a lot!, and also many thanks for your super fast reply.
One more thing, I am using the ThermostatTile and ThermostatDetails templates, are there any templates which will work better with the Nest thermostats?
Many many thanks again!

Good to hear. Yes, ThermostatTile & ThermostatDetails are the best templates to use for this device.

Ok perfect. f you want meanwhile we wait for the official integration of the Nest thermostat I can post the plugin for others to use it… My version will not be complete thought.

Yes. Please post it.

Ok.
Sorry for another question, I would like to change the temperature and I need to make the following executeCommand:
https://smartdevicemanagement.googleapis.com/v1/enterprises/{{project-id}}/devices/{{device-id}}:executeCommand
with the following body:
data-raw ‘{
“command” : “sdm.devices.commands.ThermostatTemperatureSetpoint.SetHeat”,
“params” : {
“heatCelsius” : 22.0
}
}’
As for the headers they are:
‘Authorization: Bearer XXXX’
‘Content-Type: text/plain’

It works in Postman but I do not know how to write it in HR, this is what I came up:

function HttpPost(deviceID, capability_attribute, attribute, value) {
var httpHeaders = {
headers: {
‘Authorization’: "Bearer " + accessToken,
‘Content-Type’: “text/plain”
}
};
var body = “{command: dm.devices.commands.” + capability_attribute + “, params: {” + attribute + " : " + value + “}}”;

return http.post('https://smartdevicemanagement.googleapis.com/v1/enterprises/' + projectID + '/devices/' + deviceID + ':executeCommand', body, httpHeaders);

}

Sorry for my lack of knowledge but I do not know how to write the call.
Could you please give me a hint whenever you have some time.
Many thanks again!

This is anoher version??

function HttpPost(deviceID, capability_attribute, attribute, value) {
var httpHeaders = {
headers: {
‘Authorization’: "Bearer " + accessToken
}
};
var body = { ‘command’: “sdm.devices.commands.” + capability_attribute, params: { attribute: value } };

return http.post('https://smartdevicemanagement.googleapis.com/v1/enterprises/' + projectID + '/devices/' + deviceID + '/executeCommand', body, httpHeaders);

}

An example of the body is:
{ “command” : “sdm.devices.commands.ThermostatTemperatureSetpoint.SetHeat”, “params” : { “heatCelsius” : 22.0 } };

When embedding code in the post, can you please use the 3 back-ticks “```” at the start & end of of your code snippets?

This stuff you are sharing is hard to read.

Sorry, I pasted the code in plain text and it look like that… I am adding the plugin itself so as you could have a better look.
I tried several changes in the syntax but I always get an error…?
In addition I added the “Eco” mode in the “SupportedThermostatModes” but it does not show up in the Deatils combo?, only Heat and Off are shown.
GoogleNestThermostat.plugin (5.4 KB)

There’s a bug in SupportedThermostatModes handling for Plugins. This will be fixed in the 3.20.0.0 release. For the time being, clear the ItemsSource property on that ComboBox & set the Items directly.

This code isn’t doing what you think it is. Object keys are constants in JavaScript. “attribute” is a constant value.

var body = { 'command': "sdm.devices.commands." + capability_attribute, params: { attribute: value } };

You need to create separate functions for your attributes. You should be doing something like this instead. Hardcode the heatCelsius value into your function.

var body = { 'command': "sdm.devices.commands." + capability_attribute, params: { heatCelsius: value } };