google-home / smart-home-nodejs

A sample of the Smart Home device control APIs in Actions on Google
Apache License 2.0
892 stars 291 forks source link

update variable thermosatMode #505

Closed aandroide closed 4 years ago

aandroide commented 4 years ago

Hello,following the example thermostat here:https://developers.google.com/assistant/smarthome/traits/temperaturesetting#sample-query-request-and-response how can I update this variable with the value set in the google home app? otherwise every time I open the app I always see the value of the example of the link posted and not the temperature I set

Fleker commented 4 years ago

If you change the temperature in the Google Home app, you should receive an EXECUTE request.

aandroide commented 4 years ago

perfect thank you. one last question: when I start the app created for the first time, I have seen that the temperature is set only if I say at least once verbally set the thermostat on heating and set the temperature to tot degrees. I was hoping that this function was created automatically even just by clicking the heat mode from the google home app, and set the app to be able to adjust the degrees but it seems to me that it is not possible at the moment or am I wrong? I hope I explained myself.

Fleker commented 4 years ago

The app may not update automatically, you may need to back out and reopen to get a fresh update of the state.

aandroide commented 4 years ago

actually I tried several times but the icon remains transparent even if I click on the heat icon. it only works if I say the command set the temperature to tot degrees in that case it also creates the file in firebase automatically but manually this doesn't work only the first time that you use the device.

Fleker commented 4 years ago

When the device is first created does it have any states in its associated Firebase document?

aandroide commented 4 years ago

Annotazione 2020-04-07 000900

as soon as the app is opened, the thermostat icon is transparent. once the heat mode is clicked, the thermostat status in the firebase is created immediately and automatically. the item that is not created is the setpoint, that to create it I have to ask google vocally to set the temperature and then the data will be created in the firebase and the app comes to life. My suggestion is to give the possibility to create the set point parameter since the user selects the thermostat mode without asking it vocally

aandroide commented 4 years ago

I also wanted to report a bug: the thermostat scale starts from 10 degrees and reaches 32 degrees. if you accidentally set a temperature of 5 degrees, google sets it but the app restarts continuously as soon as the user tries to open it, to unlock the app you need to set a value from 10 to 32 vocally.

Fleker commented 4 years ago

I'm a bit confused by your screenshot. Are you running this sample for your thermostat? That's not the expected format for states.

aandroide commented 4 years ago

I'm a bit confused by your screenshot. Are you running this sample for your thermostat? That's not the expected format for states. yes, the photo shows the status values ​​saved in the firebase

and this is a part of the js file: { id: '4', type: 'action.devices.types.THERMOSTAT', traits: [ 'action.devices.traits.TemperatureSetting' ], name: { defaultNames: ['Smart Thermostat'], name: 'Thermostat ', nicknames: ['Living Room Thermostat'], }, deviceInfo: { manufacturer: 'Sirius Cybernetics Corporation', model: 't-1000', hwVersion: '3.2', swVersion: '11.4', }, willReportState: true, attributes: { availableThermostatModes: 'off,on,heat,cool,heatcool', queryOnlyTemperatureSetting: false, thermostatTemperatureUnit: 'C', commandOnlyTemperatureSetting: true }, },

I had to set commandOnlyTemperatureSetting: true otherwise the voice commands would not accept me and google replied that the device does not yet support this functionality. you confirm that it's okay to leave it on true?

Fleker commented 4 years ago

You could leave it on true, although your state data may not be returning correctly in a QUERY request.

aandroide commented 4 years ago

sorry, is there a codelabs thermostat to create the app that can be controlled by the assistant? I would like to integrate it to my esp8266 + dht22 using Firebase

Fleker commented 4 years ago

If you follow the codelab and make sure to update the endpoint logic correctly it should provide a good start.

aandroide commented 4 years ago

hi, I created this nodejs script but when I say set hot air, google's answer is: sorry but the device does not support this mode. However, I discovered that if I manually add the thermostMode: heat entry in the firebase, and then I ask google to put cold air, it does so and if I ask to set the temperature at 20 degrees it does so by creating the thermostatTemperatureSetpoint entry in the firebase. So I think the problem is how to get the thermostMode file to be created in firebase automatically once you ask google.

/**
 * Copyright 2018 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

const functions = require('firebase-functions');
const {smarthome} = require('actions-on-google');
const {google} = require('googleapis');
const util = require('util');
const admin = require('firebase-admin');
// Initialize Firebase
admin.initializeApp();
const firebaseRef = admin.database().ref('/');
// Initialize Homegraph
const auth = new google.auth.GoogleAuth({
  scopes: ['https://www.googleapis.com/auth/homegraph']
});
const homegraph = google.homegraph({
  version: 'v1',
  auth: auth
});

exports.fakeauth = functions.https.onRequest((request, response) => {
  const responseurl = util.format('%s?code=%s&state=%s',
    decodeURIComponent(request.query.redirect_uri), 'xxxxxx',
    request.query.state);
  console.log(responseurl);
  return response.redirect(responseurl);
});

exports.faketoken = functions.https.onRequest((request, response) => {
  const grantType = request.query.grant_type
    ? request.query.grant_type : request.body.grant_type;
  const secondsInDay = 86400; // 60 * 60 * 24
  const HTTP_STATUS_OK = 200;
  console.log(`Grant type ${grantType}`);

  let obj;
  if (grantType === 'authorization_code') {
    obj = {
      token_type: 'bearer',
      access_token: '123access',
      refresh_token: '123refresh',
      expires_in: secondsInDay,
    };
  } else if (grantType === 'refresh_token') {
    obj = {
      token_type: 'bearer',
      access_token: '123access',
      expires_in: secondsInDay,
    };
  }
  response.status(HTTP_STATUS_OK)
    .json(obj);
});

const app = smarthome({
  debug: true,
});

app.onSync((body) => {
  return {
    requestId: body.requestId,
    payload: {
      agentUserId: '123',
      devices: [{
        id: '4',
        type: 'action.devices.types.THERMOSTAT',
        traits: [
    'action.devices.traits.TemperatureSetting',

      ],
        name: {
          defaultNames: ['Temp control'],
          name: 'Temperature Controller',
          nicknames: ['Termostato'],
        },
        deviceInfo: {
          manufacturer: 'Acme Co',
          model: 'acme-thermosat',
          hwVersion: '1.0',
          swVersion: '1.0.1',
        },
        willReportState: true,
        attributes: {
    availableThermostatModes: 'off,on,heat,cool,heatcool',
    queryOnlyTemperatureSetting: false,
    thermostatTemperatureUnit: 'C',
    commandOnlyTemperatureSetting: false
        },
      }],
    },
  };
});

const queryFirebase = async (deviceId) => {
  const snapshot = await firebaseRef.child(deviceId).once('value');
  const snapshotVal = snapshot.val();
  return {

      thermostatMode: snapshotVal.TemperatureSetting.thermostatMode,
      thermostatTemperatureSetpoint: snapshotVal.TemperatureSetting.thermostatTemperatureSetpoint,
      thermostatTemperatureAmbient: snapshotVal.TemperatureSetting.thermostatTemperatureAmbient,
      thermostatHumidityAmbient: snapshotVal.TemperatureSetting.thermostatHumidityAmbient,
      thermostatTemperatureSetpointLow: snapshotVal.TemperatureSetting.thermostatTemperatureSetpointLow,
      thermostatTemperatureSetpointHigh: snapshotVal.TemperatureSetting.thermostatTemperatureSetpointHigh,
    };
}
const queryDevice = async (deviceId) => {
  const data = await queryFirebase(deviceId);
  return {
      thermostatMode: data.thermostatMode,
      thermostatTemperatureSetpoint: data.thermostatTemperatureSetpoint,
      thermostatTemperatureAmbient: data.thermostatTemperatureAmbient,
      thermostatHumidityAmbient: data.thermostatHumidityAmbient,
      thermostatTemperatureSetpointLow: data.thermostatTemperatureSetpointLow,
      thermostatTemperatureSetpointHigh: data.thermostatTemperatureSetpointHigh,
  };
}

app.onQuery(async (body) => {
  const {requestId} = body;
  const payload = {
    devices: {},
  };
  const queryPromises = [];
  const intent = body.inputs[0];
  for (const device of intent.payload.devices) {
    const deviceId = device.id;
    queryPromises.push(queryDevice(deviceId)
      .then((data) => {
        // Add response to device payload
        payload.devices[deviceId] = data;
      }
    ));
  }
  // Wait for all promises to resolve
  await Promise.all(queryPromises)
  return {
    requestId: requestId,
    payload: payload,
  };
});

const updateDevice = async (execution,deviceId) => {
  const {params,command} = execution;
  let state, ref;
  switch (command) {
    case 'action.devices.commands.ThermostatTemperatureSetpoint':
      state = {thermostatTemperatureSetpoint: params.thermostatTemperatureSetpoint};
      ref = firebaseRef.child(deviceId).child('TemperatureSetting');
      break;
    case 'action.devices.commands.ThermostatSetMode':
      state = {thermostatMode: params.thermostatMode};
      ref = firebaseRef.child(deviceId).child('TemperatureSetting');
      break;
    case 'action.devices.commands.ThermostatTemperatureSetRange':
      state = {thermostatTemperatureSetpointLow: params.thermostatTemperatureSetpointLow,thermostatTemperatureSetpointHigh: params.thermostatTemperatureSetpointHigh};
      ref = firebaseRef.child(deviceId).child('TemperatureSetting');
      break;
  }

  return ref.update(state)
    .then(() => state);
};

app.onExecute(async (body) => {
  const {requestId} = body;
  // Execution results are grouped by status
  const result = {
    ids: [],
    status: 'SUCCESS',
    states: {
      online: true,
    },
  };

  const executePromises = [];
  const intent = body.inputs[0];
  for (const command of intent.payload.commands) {
    for (const device of command.devices) {
      for (const execution of command.execution) {
        executePromises.push(
          updateDevice(execution,device.id)
            .then((data) => {
              result.ids.push(device.id);
              Object.assign(result.states, data);
            })
            .catch(() => console.error(`Unable to update ${device.id}`))
        );
      }
    }
  }

  await Promise.all(executePromises)
  return {
    requestId: requestId,
    payload: {
      commands: [result],
    },
  };
});

exports.smarthome = functions.https.onRequest(app);

exports.requestsync = functions.https.onRequest(async (request, response) => {
  response.set('Access-Control-Allow-Origin', '*');
  console.info('Request SYNC for user 123');
  try {
    const res = await homegraph.devices.requestSync({
      requestBody: {
        agentUserId: '123'
      }
    });
    console.info('Request sync response:', res.status, res.data);
    response.json(res.data);
  } catch (err) {
    console.error(err);
    response.status(500).send(`Error requesting sync: ${err}`)
  }
});

/**
 * Send a REPORT STATE call to the homegraph when data for any device id
 * has been changed.
 */
exports.reportstate = functions.database.ref('{deviceId}').onWrite(async (change, context) => {
  console.info('Firebase write event triggered this cloud function');
  const snapshot = change.after.val();

  const requestBody = {
    requestId: 'ff36a3cc', /* Any unique ID */
    agentUserId: '123', /* Hardcoded user ID */
    payload: {
      devices: {
        states: {
          /* Report the current state of our thermostat */
          [context.params.deviceId]: {
      thermostatMode: snapshotVal.TemperatureSetting.thermostatMode,
      thermostatTemperatureSetpoint: snapshotVal.TemperatureSetting.thermostatTemperatureSetpoint,
      thermostatTemperatureAmbient: snapshotVal.TemperatureSetting.thermostatTemperatureAmbient,
      thermostatHumidityAmbient: snapshotVal.TemperatureSetting.thermostatHumidityAmbient,
      thermostatTemperatureSetpointLow: snapshotVal.TemperatureSetting.thermostatTemperatureSetpointLow,
      thermostatTemperatureSetpointHigh: snapshotVal.TemperatureSetting.thermostatTemperatureSetpointHigh,

          },
        },
      },
    },
  };

  const res = await homegraph.devices.reportStateAndNotification({
    requestBody
  });
  console.info('Report state response:', res.status, res.data);
});
Fleker commented 4 years ago

It seems like you should create your device in your onSync handler, since then the data will always be available.

app.onSync(async (body) => {
  // Create device
  await firebaseRef.child('123').set({
    thermostatMode: 'off'
    // TODO Other initial states
  })
  return {
    ...
  }
})
aandroide commented 4 years ago

I tried as suggested now the command is created when the app is opened but if I put heat mode after a few seconds it turns off again why?

const queryFirebase = async (deviceId) => {
  const snapshot = await firebaseRef.child(deviceId).once('value');
  const snapshotVal = snapshot.val();
if (deviceId == '4') {
    // Create device
  firebaseRef.child(deviceId).child('TemperatureSetting').set({thermostatMode: 'off'})

    return {

      thermostatMode: snapshotVal.TemperatureSetting.thermostatMode,
      thermostatTemperatureSetpoint: snapshotVal.TemperatureSetting.thermostatTemperatureSetpoint,
      thermostatTemperatureAmbient: snapshotVal.TemperatureSetting.thermostatTemperatureAmbient,
      thermostatHumidityAmbient: snapshotVal.TemperatureSetting.thermostatHumidityAmbient,
      thermostatTemperatureSetpointLow: snapshotVal.TemperatureSetting.thermostatTemperatureSetpointLow,
      thermostatTemperatureSetpointHigh: snapshotVal.TemperatureSetting.thermostatTemperatureSetpointHigh
    };
  }
Fleker commented 4 years ago

The device state resets because you're resetting it. You should put the behavior in onSync so that it only is set the first time. Now you'll be changing the value continually because onQuery and queryFirebase are called frequently.

aandroide commented 4 years ago

thank you the problem has been solved by following this indication.

aandroide commented 4 years ago

I'd like to recycle an old tablet with Android 4.4 to hang on the wall as a thermostat display, but since the google home app is not compatible, I was thinking of creating a web app always with firebase. to integrate a screen with an adjustable thermostat and send the data to the firebase, what do you recommend?

aandroide commented 4 years ago

https://codepen.io/simoberny/pen/wrGoZZ

I thought of such a thing. is there a way to be able to view devices both from the google home app and from an html page?

or do I have to reprogram everything by changing the language in javascript?

Fleker commented 4 years ago

As the device state should be reflected on both your web app and Google's Home Graph (through your webhook), it should be possible to view the device in both locations with the same state.

However, this only applies to devices your webhook provides like the thermostat.

aandroide commented 4 years ago

Would you be kind enough to explain how to do it please?

Fleker commented 4 years ago

I'm not sure what you're trying to accomplish necessarily. You already have the Google Home supported.

aandroide commented 4 years ago

my intention would be to have the possibility to control the thermostat from an html page so that you can resurrect a tablet with android4.4 and be able to control the thermostat from there.

aandroide commented 4 years ago

Poiché lo stato del dispositivo deve riflettersi sia sull'app Web sia sul grafico principale di Google (tramite il webhook), dovrebbe essere possibile visualizzare il dispositivo in entrambe le posizioni con lo stesso stato.

Tuttavia, questo vale solo per i dispositivi forniti dal webhook come il termostato.

or better can you confirm if the same graphic header of the thermostat that we display in the google app, can be split in a mirror on an html page with the same graphics and css to be clear?

I would like to do the same thing as in the example of the washing machine on codelabs to check both from the app but also from the web.

Fleker commented 4 years ago

For accessing your Firebase from the web, I'd look at this getting started guide.

The Google Home app UI is not written in HTML and CSS, so you wouldn't be able to directly copy any controller UI from it.

aandroide commented 4 years ago

For accessing your Firebase from the web, I'd look at this getting started guide.

The Google Home app UI is not written in HTML and CSS, so you wouldn't be able to directly copy any controller UI from it.

hello, that was exactly what i wanted to know. So I create an html file by integrating the firebase script and then I will make it read and write the data in my database in such a way as to have everything synchronized. Thanks so much for the information.