4

[Tutoriel complet dans la réponse à la question ci-dessous. Commentaires bienvenus!]Lier la station météo Netatmo à Amazon Echo (Alexa)

J'essaie de créer une fonction AWS Lambda à utiliser pour une compétence Amazon Alexa pour récupérer des informations météorologiques à partir de ma station météo Netatmo. Fondamentalement, je dois me connecter au nuage Netatmo via une requête http.

Voici un extrait de mon code, la requête http est faite pour le jeton d'accès temporaire, la requête est ok mais le corps du résultat est body: {"error": "invalid_request"}. Quel pourrait être le problème ici?

var clientId = ""; 
var clientSecret = ""; 
var userId="[email protected]"; 
var pass=""; 

function getNetatmoData(callback, cardTitle){ 
    var sessionAttributes = {}; 

    var formUserPass = { client_id: clientId, 
    client_secret: clientSecret, 
    username: userId, 
    password: pass, 
    scope: 'read_station', 
    grant_type: 'password' }; 

    shouldEndSession = false; 
    cardTitle = "Welcome"; 
    speechOutput =""; 
    repromptText =""; 

    var options = { 
     host: 'api.netatmo.net', 
     path: '/oauth2/token', 
     method: 'POST', 
     headers: { 
      'Content-Type': 'application/x-www-form-urlencoded', 
      'client_id': clientId, 
      'client_secret': clientSecret, 
      'username': userId, 
      'password': pass, 
      'scope': 'read_station', 
      'grant_type': 'password' 
     } 
    }; 
    var req = http.request(options, function(res) { 
      res.setEncoding('utf8'); 
      res.on('data', function (chunk) { 
       console.log("body: " + chunk); 

      }); 

      res.on('error', function (chunk) { 
       console.log('Error: '+chunk); 
      }); 

      res.on('end', function() { 

       speechOutput = "Request successfuly processed." 
       console.log(speechOutput); 
       repromptText = "" 
       callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession)); 
      }); 

     }); 

     req.on('error', function(e){console.log('error: '+e)}); 

     req.end(); 
} 
+0

Pouvez-vous tester l'appel API à partir d'un autre système (par exemple, directement à partir de votre ordinateur, plutôt que via Lambda)? Envoyer le même 'body', voir si cela fonctionne. –

+0

Je suis en mesure de confirmer que les informations d'identification sont correctes en utilisant http://http-post.com/ et en remplissant les paramètres nécessaires. La requête génère le jeton désiré ... –

+0

peut-il être que l'encodage est faux? Il pourrait clarifier pourquoi la réponse n'a pas d'informations supplémentaires - parce que la demande d'origine ne peut même pas être analysée! –

Répondre

8

Je l'ai eu en cours d'exécution! Voici une procédure pas à pas rapide:

  1. obtenir un compte gratuit pour Amazon AWS. Tant que vos compétences ne sont pas constamment en cours d'exécution (vous serez facturé par le temps d'exécution et les ressources utilisées sur les serveurs AWS avec quelque 700 heures gratuites chaque mois), vous devriez être bon et rester libre. La compétence nécessite 1-3 secondes pour s'exécuter à la fois.

  2. Configurez une nouvelle fonction lambda dans Amazon Web Services (AWS). Cette fonction sera exécutée chaque fois que la compétence est invoquée.

est ici le code de la compétence:

/** 
* Author: Mihai GALOS 
* Timestamp: 17:17:00, November 1st 2015 
*/ 

var http = require('https'); 
var https = require('https'); 
var querystring = require('querystring'); 

var clientId = ''; // create an application at https://dev.netatmo.com/ and fill in the generated clientId here 
var clientSecret = ''; // fill in the client secret for the application 
var userId= '' // your registration email address 
var pass = '' // your account password 


// Route the incoming request based on type (LaunchRequest, IntentRequest, 
// etc.) The JSON body of the request is provided in the event parameter. 
exports.handler = function (event, context) { 
    try { 
     console.log("event.session.application.applicationId=" + event.session.application.applicationId); 

     /** 
     * Uncomment this if statement and populate with your skill's application ID to 
     * prevent someone else from configuring a skill that sends requests to this function. 
     */ 
     /* 
     if (event.session.application.applicationId !== "amzn1.echo-sdk-ams.app.[unique-value-here]") { 
      context.fail("Invalid Application ID"); 
     } 
     */ 

     if (event.session.new) { 
      onSessionStarted({requestId: event.request.requestId}, event.session); 
     } 

     if (event.request.type === "LaunchRequest") { 
      onLaunch(event.request, 
        event.session, 
        function callback(sessionAttributes, speechletResponse) { 
         context.succeed(buildResponse(sessionAttributes, speechletResponse)); 
        }); 
     } else if (event.request.type === "IntentRequest") { 
      onIntent(event.request, 
        event.session, 
        function callback(sessionAttributes, speechletResponse) { 
         context.succeed(buildResponse(sessionAttributes, speechletResponse)); 
        }); 
     } else if (event.request.type === "SessionEndedRequest") { 
      onSessionEnded(event.request, event.session); 
      context.succeed(); 
     } 
    } catch (e) { 
     context.fail("Exception: " + e); 
    } 
}; 


function onSessionStarted(sessionStartedRequest, session) { 
    console.log("onSessionStarted requestId=" + sessionStartedRequest.requestId + 
      ", sessionId=" + session.sessionId); 
} 


function onLaunch(launchRequest, session, callback) { 
    console.log("onLaunch requestId=" + launchRequest.requestId + 
      ", sessionId=" + session.sessionId); 

    // Dispatch to your skill's launch. 

    getData(callback); 

} 


function onIntent(intentRequest, session, callback) { 
    console.log("onIntent requestId=" + intentRequest.requestId + 
      ", sessionId=" + session.sessionId); 

    var intent = intentRequest.intent, 
     intentName = intentRequest.intent.name; 
    var intentSlots ; 

    console.log("intentRequest: "+ intentRequest); 
    if (typeof intentRequest.intent.slots !== 'undefined') { 
     intentSlots = intentRequest.intent.slots; 
    } 


    getData(callback,intentName, intentSlots); 


} 


function onSessionEnded(sessionEndedRequest, session) { 
    console.log("onSessionEnded requestId=" + sessionEndedRequest.requestId + 
      ", sessionId=" + session.sessionId); 
    // Add cleanup logic here 
} 

// --------------- Functions that control the skill's behavior ----------------------- 

function doCall(payload, options, onResponse, 
      callback, intentName, intentSlots){ 
    var response = '' 
    var req = https.request(options, function(res) { 
      res.setEncoding('utf8'); 

      console.log("statusCode: ", res.statusCode); 
      console.log("headers: ", res.headers); 


      res.on('data', function (chunk) { 
       console.log("body: " + chunk); 
       response += chunk; 
      }); 

      res.on('error', function (chunk) { 
       console.log('Error: '+chunk); 
      }); 

      res.on('end', function() { 
       var parsedResponse= JSON.parse(response); 
       if (typeof onResponse !== 'undefined') { 
        onResponse(parsedResponse, callback, intentName, intentSlots); 
       } 
      }); 

     }); 

     req.on('error', function(e){console.log('error: '+e)}); 
     req.write(payload); 

     req.end(); 

} 

function getData(callback, intentName, intentSlots){ 



     console.log("sending request to netatmo...") 

     var payload = querystring.stringify({ 
      'grant_type' : 'password', 
      'client_id'  : clientId, 
      'client_secret' : clientSecret, 
      'username'  : userId, 
      'password'  : pass, 
      'scope'   : 'read_station' 
     }); 

     var options = { 
      host: 'api.netatmo.net', 
      path: '/oauth2/token', 
      method: 'POST', 
      headers: { 
       'Content-Type': 'application/x-www-form-urlencoded', 
       'Content-Length': Buffer.byteLength(payload) 
      } 

     }; 

     //console.log('making request with data: ',options); 

     // get token and set callbackmethod to get measure 
     doCall(payload, options, onReceivedTokenResponse, callback, intentName, intentSlots); 
} 

function onReceivedTokenResponse(parsedResponse, callback, intentName, intentSlots){ 

     var payload = querystring.stringify({ 
      'access_token' : parsedResponse.access_token 
     }); 

     var options = { 
      host: 'api.netatmo.net', 
      path: '/api/devicelist', 
      method: 'POST', 
      headers: { 
       'Content-Type': 'application/x-www-form-urlencoded', 
       'Content-Length': Buffer.byteLength(payload) 
      } 

     }; 

    doCall(payload, options, getMeasure, callback, intentName, intentSlots); 

} 

function getMeasure(parsedResponse, callback, intentName, intentSlots){ 


     var data = { 
       tempOut   : parsedResponse.body.modules[0].dashboard_data.Temperature, 
       humOut   : parsedResponse.body.modules[0].dashboard_data.Humidity, 
       rfStrengthOut : parsedResponse.body.modules[0].rf_status, 
       batteryOut  : parsedResponse.body.modules[0].battery_vp, 

       tempIn  : parsedResponse.body.devices[0].dashboard_data.Temperature, 
       humIn  : parsedResponse.body.devices[0].dashboard_data.Humidity, 
       co2   : parsedResponse.body.devices[0].dashboard_data.CO2, 
       press  : parsedResponse.body.devices[0].dashboard_data.Pressure, 

       tempBedroom   : parsedResponse.body.modules[2].dashboard_data.Temperature, 
       humBedroom   : parsedResponse.body.modules[2].dashboard_data.Temperature, 
       co2Bedroom   : parsedResponse.body.modules[2].dashboard_data.CO2, 
       rfStrengthBedroom : parsedResponse.body.modules[2].rf_status, 
       batteryBedroom  : parsedResponse.body.modules[2].battery_vp, 

       rainGauge   : parsedResponse.body.modules[1].dashboard_data, 
       rainGaugeBattery : parsedResponse.body.modules[1].battery_vp 
       }; 

    var repromptText = null; 
    var sessionAttributes = {}; 
    var shouldEndSession = true; 
    var speechOutput ; 

    if("AskTemperature" === intentName) { 

     console.log("Intent: AskTemperature, Slot:"+intentSlots.Location.value); 

     if("bedroom" ===intentSlots.Location.value){ 
      speechOutput = "There are "+data.tempBedroom+" degrees in the bedroom."; 

     } 
     else if ("defaultall" === intentSlots.Location.value){ 
      speechOutput = "There are "+data.tempIn+" degrees inside and "+data.tempOut+" outside."; 
     } 

     if(data.rainGauge.Rain > 0) speechOutput += "It is raining."; 
    } else if ("AskRain" === intentName){ 
     speechOutput = "It is currently "; 
     if(data.rainGauge.Rain > 0) speechOutput += "raining."; 
     else speechOutput += "not raining. "; 

     speechOutput += "Last hour it has rained "+data.rainGauge.sum_rain_1+" millimeters, "+data.rainGauge.sum_rain_1+" in total today."; 
    } else { // AskTemperature 
     speechOutput = "Ok. There are "+data.tempIn+" degrees inside and "+data.tempOut+" outside."; 

     if(data.rainGauge.Rain > 0) speechOutput += "It is raining."; 
    } 

     callback(sessionAttributes, 
      buildSpeechletResponse("", speechOutput, repromptText, shouldEndSession)); 

} 

// --------------- Helpers that build all of the responses ----------------------- 

function buildSpeechletResponse(title, output, repromptText, shouldEndSession) { 
    return { 
     outputSpeech: { 
      type: "PlainText", 
      text: output 
     }, 
     card: { 
      type: "Simple", 
      title: "SessionSpeechlet - " + title, 
      content: "SessionSpeechlet - " + output 
     }, 
     reprompt: { 
      outputSpeech: { 
       type: "PlainText", 
       text: repromptText 
      } 
     }, 
     shouldEndSession: shouldEndSession 
    }; 
} 

function buildResponse(sessionAttributes, speechletResponse) { 
    return { 
     version: "1.0", 
     sessionAttributes: sessionAttributes, 
     response: speechletResponse 
    }; 
} 
  1. Aller sur le site développeur de Netatmo (https://dev.netatmo.com/) et créer une nouvelle application. Ce sera votre interface avec les données du capteur du côté Netatmo. L'application aura un identifiant unique (c'est-à-dire: 5653769769f7411515036a0b) et un secret client (c'est-à-dire: T4nHevTcRbs053TZsoLZiH1AFKLZGb83Fmw9q). (Non, ces numéros ne représentent pas un identifiant client valide et secret, ils sont uniquement à des fins de démonstration)

  2. Remplissez les informations d'identification requises (utilisateur et passe de compte netatmo, ID client et secret) dans le code ci-dessus.

  3. Accédez à Amazon Apps and Services (https://developer.amazon.com/edw/home.html). Dans le menu, sélectionnez Alexa, puis Alexa Kit de compétences (cliquez sur Démarrer)

  4. Vous devez maintenant créer une nouvelle compétence. Donnez à votre adresse un nom et une invocation. Le nom sera utilisé pour invoquer (ou démarrer) l'application. Dans le champ Endpoint, vous devez indiquer l'identifiant ARN de votre fonction lambda créée plus tôt. Ce numéro peut être trouvé sur la page Web affichant votre fonction lambda, dans le coin supérieur droit. Cela devrait être quelque chose comme: arn: aws: lambda: us-east-1: 255569121831: function: [votre nom de fonction]. Une fois cette étape terminée, une coche verte apparaît à gauche pour indiquer la progression (menu de progression).

  5. La phase suivante implique la configuration du modèle d'interaction. Il est responsable de la cartographie des énoncés aux intentions et aux emplacements. Tout d'abord, le schéma d'intention. Voici la mienne; copier-coller ce code (et modifier si nécessaire):

    { 
    "intents": 
        [ 
         { 
          "intent": "AskTemperature", 
          "slots": [ 
            { 
            "name": "Location", 
            "type": "LIST_OF_LOCATIONS" 
            } 
          ] 
         }, 
    
         { 
          "intent": "AskCarbonDioxide", 
          "slots": [ 
            { 
            "name": "Location", 
            "type": "LIST_OF_LOCATIONS" 
            } 
          ] 
         }, 
         { 
          "intent": "AskHumidity", 
          "slots": [ 
            { 
            "name": "Location", 
            "type": "LIST_OF_LOCATIONS" 
            } 
          ] 
         }, 
    
         { 
          "intent": "AskRain", 
          "slots": [] 
         }, 
    
         { 
          "intent": "AskSound", 
          "slots": [] 
         }, 
         { 
          "intent": "AskWind", 
          "slots": [] 
         }, 
    
         { 
          "intent": "AskPressure", 
          "slots": [] 
         } 
    
    
        ] 
    } 
    

Ensuite, les types de logement personnalisés. Cliquez sur Ajouter un type de logement.Donnez la fente le nom

LIST_OF_LOCATIONS and newline-separated : DefaultAll, Inside, Outside, Living, Bedroom, Kitchen, Bathroom, Alpha, Beta 

(remplacer des virgules avec des sauts de ligne)

Suivant, échantillon utterences:

AskTemperature what's the temperature {Location} 
    AskTemperature what's the temperature in {Location} 
    AskTemperature what's the temperature in the {Location} 
    AskTemperature get the temperature {Location} 
    AskTemperature get the temperature in {Location} 
    AskTemperature get the temperature in the {Location} 

    AskCarbonDioxide what's the comfort level {Location} 
    AskCarbonDioxide what's the comfort level in {Location} 
    AskCarbonDioxide what's the comfort level in the {Location} 

    AskCarbonDioxide get the comfort level {Location} 
    AskCarbonDioxide get the comfort level in {Location} 
    AskCarbonDioxide get the comfort level in the {Location} 


    AskHumidity what's the humidity {Location} 
    AskHumidity what's the humidity in {Location} 
    AskHumidity what's the humidity in the {Location} 
    AskHumidity get the humidity {Location} 
    AskHumidity get the humidity from {Location} 
    AskHumidity get the humidity in {Location} 
    AskHumidity get the humidity in the {Location} 
    AskHumidity get humidity 


    AskRain is it raining 
    AskRain did it rain 
    AskRain did it rain today 
    AskRain get rain millimeter count 
    AskRain get rain 

    AskSound get sound level 
    AskSound tell me how loud it is 

    AskWind is it windy 
    AskWind get wind 
    AskWind get wind measures 
    AskWind get direction 
    AskWind get speed 

    AskPressure get pressure 
    AskPressure what's the pressure 
  1. Le test, Description et édition les informations peuvent être laissées en blanc, sauf si vous envisagez d'envoyer vos compétences à amazon afin qu'il puisse être rendu public. J'ai laissé le mien vide. :)

  2. Presque là. Vous avez juste besoin d'activer la nouvelle compétence. Passez au http://alexa.amazon.com/ et dans le menu de gauche, sélectionnez Compétences. Trouvez votre compétence et cliquez sur Activer.

  3. Ce moment génial. Dites "Alexa, ouvrez [votre nom de compétence]." Par défaut, la température intérieure et extérieure doit être extraite du nuage netatmo et lue à voix haute par Alexa. vous pouvez également dire "Alexa, ouvrez [votre nom de compétence] et obtenez la température dans la chambre à coucher". Comme vous l'avez peut-être déjà remarqué, la partie "obtenir la température dans [Lieu]" correspond aux exemples d'échantillons que vous avez remplis précédemment.

  4. à long vivre et prospérer

Eh bien, désolé pour le long courrier. J'espère que ce petit didacticiel/didacticiel sera un jour utile à quelqu'un. :)

+1

Merci pour ce tutoriel. J'ai créé une version allemande sur github: https://github.com/peerdavid/netatmo-skill –

+0

Salut David. Füge mal bitte einen Lien dans Github zu der Ursprungsquelle hinzu. Danke und schöne Grüße, Mihai. –

+0

Salut Mihai, je suis dans GitHub unter Merci à Link Link StackOverflow. Sollte ich noch etwas hinzufügen bzw. Merci à Umbenennen? –