簡體   English   中英

將Netatmo氣象站連接到Amazon Echo(Alexa)

[英]Linking Netatmo Weather Station to Amazon Echo (Alexa)

[以下回答問題的完整教程。 歡迎反饋!]

我正在嘗試創建一個AWS Lambda函數,用於Amazon Alexa技能從我的Netatmo weatherstation獲取天氣信息。 基本上,我需要通過http請求連接到Netatmo雲。

這是我的代碼片段,http請求是為臨時訪問令牌完成的,請求沒問題,但結果正文是正文:{“error”:“invalid_request”}。 這可能是什么問題?

var clientId = "";
var clientSecret = "";
var userId="a@google.ro"; 
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();
}

我跑了! 這是一個快速演練:

  1. 獲取Amazon AWS的免費帳戶。 只要您的技能不能持續運行(您將通過AWS服務器上使用的運行時間和資源(每月700個免費小時)收費,您應該保持良好狀態並且保持免費。 該技能一次需要1-3秒才能運行。

  2. 在Amazon Web Services(AWS)中設置新的lambda函數。 每次調用技能時都會執行此功能。

這是技能代碼:

/**
*   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. 訪問netatmo的開發者網站( https://dev.netatmo.com/ )並創建一個新的應用程序。 這將是您在Netatmo端傳感器數據的接口。 應用程序將具有唯一的ID(即:5653769769f7411515036a0b)和客戶端密鑰(即:T4nHevTcRbs053TZsoLZiH1AFKLZGb83Fmw9q)。 (不,這些數字不代表有效的客戶ID和秘密,它們僅用於演示目的)

  2. 在上面的代碼中填寫所需的憑據(netatmo帳戶用戶和通行證,客戶ID和秘密)。

  3. 轉到Amazon Apps and Services( https://developer.amazon.com/edw/home.html )。 在菜單中,選擇Alexa,然后選擇Alexa Skills Kit(點擊開始使用)

  4. 現在您需要創建一個新技能。 給你的技能一個名字和調用。 該名稱將用於調用(或啟動)應用程序。 在Endpoint字段中,您需要提供之前創建的lambda函數的ARN id。 這個號碼可以在右上角顯示lambda功能的網頁上找到。 它應該是這樣的:arn:aws:lambda:us-east-1:255569121831:function:[你的函數名]。 完成此步驟后,左側會出現綠色復選標記以指示進度(進度菜單)。

  5. 下一階段涉及建立交互模型。 它負責將話語映射到意圖和插槽。 首先是意圖架構。 這是我的; 復制粘貼此代碼(並根據需要進行修改):

      { "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": [] } ] } 

接下來,自定義插槽類型。 單擊“添加插槽類型”。 給插槽命名

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

(用換行符替換逗號)

接下來,樣本說明:

    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. 測試,描述和發布信息可以留空,除非您計划將您的技能發送到亞馬遜,以便可以公開發布。 我把我的空白留了下來 :)

  2. 快好了。 你只需要啟用新技能。 轉到http://alexa.amazon.com/並在左側菜單中選擇技能。 找到您的技能並單擊啟用。

  3. 那個令人敬畏的時刻。 說“Alexa,打開[你的技能名稱]。” 默認情況下,室內和室外溫度應從netatmo雲中提取並由Alexa大聲讀出。 你也可以說“Alexa,打開[你的技能名稱]並獲得卧室的溫度。” 正如您可能已經注意到的那樣,“獲取[位置]中的溫度”部分對應於您之前填寫的樣本。

  4. 健康長壽·繁榮昌盛

好吧,抱歉這篇長篇文章。 我希望這個小教程/演練有一天會對某人有所幫助。 :)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM