当前位置: 首页 > 知识库问答 >
问题:

将Netatmo气象站链接到Amazon Echo(Alexa)

邢新
2023-03-14

【完整教程在下面回答的问题里。欢迎反馈!]

我正在尝试创建一个AWS Lambda函数,用于Amazon Alexa技能,从我的Netatmo气象站获取天气信息。基本上,我需要通过http请求连接到Netatmo云。

这是我的代码片段,http请求是为临时访问令牌完成的,请求是可以的,但结果正文是正文:{“错误”:“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个答案

侯英达
2023-03-14

我让它运行了!下面是一个快速演练:

>

  • 获取亚马逊 AWS 的免费帐户。只要您的技能不是持续运行的(您将按 AWS 服务器上使用的运行时间和资源计费,每月大约有 700 个免费小时),您就应该很优秀,并且它将保持免费。该技能一次需要1-3秒才能运行。

    在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
        };
    }
    

    转到netatmo的开发者网站(https://dev.netatmo.com/)并创建新的应用程序。这将是您与Netatmo侧传感器数据的接口。应用程序将具有唯一id(即:5653769769f7411515036a0b)和客户端机密(即:T4nHevTcRbs053TZsoLZiH1AFKLZGb83Fmw9q)。(不,这些数字不代表有效的客户端id和机密,它们仅用于演示目的)

    在上面的代码中填写所需的凭据(netatmo帐户用户和通行证、客户端ID和机密)。

    转到亚马逊应用程序和服务(https://developer.amazon.com/edw/home.html). 在菜单中,依次选择Alexa和Alexa Skills Kit(单击Get start)

    现在您需要创建一个新技能。为您的技能命名并调用。该名称将用于调用(或启动)应用程序。在Endpoint字段中,您需要输入之前创建的lambda函数的ARN id。这个数字可以在右上角显示lambda函数的网页上找到。它应该是这样的:arn: aws: lambda: us-east-1:255569121831: function:[您的函数名称]。完成此步骤后,左侧将出现一个绿色复选标记来指示进度(进度菜单)。

    下一阶段涉及建立交互模型。它负责将语句映射到意图和槽。首先是意图模式。这是我的;复制粘贴此代码(并根据需要进行修改):

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

    接下来,自定义插槽类型。单击html" target="_blank">添加插槽类型。为插槽命名

        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
    

    测试、描述和发布信息可以留空,除非你打算将你的技能发送到亚马逊,这样它就可以公开了。我的是空白的。:)

    快到了。你只需要启用新技能。去http://alexa.amazon.com/,在左边的菜单中,选择技能。找到您的技能,然后单击启用。

    那令人敬畏的时刻。说“阿列克谢,打开[你的技能名称”。默认情况下,室内和室外温度应该从netatmo云中获取,并由阿列克谢大声朗读。你也可以说“阿列克谢,打开[你的技能名称]并获取卧室的温度。”正如你可能已经注意到的,“获取[位置的温度”部分对应于你之前填写的样本。

    长寿致富

    很抱歉写了这么长的帖子。我希望这个小教程/演练有一天会对某人有所帮助。:)

  •  类似资料:
    • Like this? Please buy me a beer ... homebridge-netatmo This is a plugin for homebridge. It's a working implementation for several netatmo devices: netatmo weather station netatmo thermostat netatmo we

    • 问题内容: 我在main.storyboard上创建了一个UI元素,需要隐藏该元素,直到游戏结束,并且一旦玩家点击屏幕将其关闭即可。Main.storyboard链接到GameViewController,因此我所有的IBOutlet和IBActions都在其中,而我所有的游戏代码都在GameScene中。我如何将视图控制器链接到场景,以便弹出图像和按钮仅在游戏结束时出现。非常感谢您的帮助,我已经

    • 问题内容: 我在运行64位Ubuntu 12.04的桌面上下载并构建了gcc 4.8.1。我像文档建议一样使用命令从源代码中构建它 它似乎通过了所有测试,我将所有内容都安装到了带有后缀-4.8的主目录中,以区别于版本4.6.3的系统gcc。 不幸的是,当我使用g -4.8编译c 程序时,它链接到系统libc和libstdc ,而不是从gcc-4.8.1编译的较新的程序。我下载并构建了gcc 4.8

    • 我正在尝试使用Swing发出文件传输通知。这个想法是,当我的应用程序通过网络提供文件时,用户会收到一个,询问他或她是否想接受所述文件报价,如果他们的答案是是,我想打开一个,以便他们可以浏览到他们想要保存文件的位置。 我遇到的问题是两者单独工作都很好,但是当我设置它们以便打开我的应用程序死锁时。 有人知道这里出了什么问题吗?我尝试了调试,但没有发现任何奇怪的行为来表明它为什么会死锁。 编辑:下面的示

    • 我创建了一个即时应用。我把它上传到我的谷歌控制台,我发现了这个错误。 www.kochchy。cz网站尚未通过数字资产链接协议链接到您的应用程序。将应用程序站点与数字资产链接。 两个apks,即时和可安装使用相同的id:com.kochchy.instantapptest.app(每个定义在自己的模块清单) 我的基本模块清单如下所示: ------编辑------ 我做了新项目从谷歌即时应用程序示

    • 我目前正在使用twilio和laravel进行自动面试。我想实现的是类似于此场景的东西: 用户在网络上输入电话号码 Twilio将发送带有链接的短信(链接将在一个小时或更长时间内过期) 单击链接将向用户发起出站呼叫 自动面试将开始。 这在Twilio可行吗?这是我第一次使用它。 谢谢你