絖綛 N@i.jp  昨日:00046624
 今日:00001090
 総計:00047714
keywords
管理者専用
  Post   Add link   Control Panel 































新しいトピック
最新:11/14 16:41


新しいコメント
最新:07/28 16:47






管理人へMAIL

プライバシーポリシー

IRKitスマートホームスキルの改良(5)

Lambdaに輝度調整と状態レポートに応答する処理を追加します


4. 行った操作の保存と状態レポートの応答処理を追加

 Lambdaに輝度調整の処理と、行った操作をDynamoDBに保存して状態レポートを返す処理を追加します。Lambdaはこんな感じになりました。昨年作ったものと結構変わっていますし、随分と長くなってしまいました。相変らず手抜きな部分も多いですけど・・・(^^;)

'use strict';
var AWS = require('aws-sdk');   // AWS SDKを使用
AWS.config.update({region: "us-west-2"});

// IRKitデバイス情報
const IRKit_DEVICE = process.env.My_IRKit_NAME;
const clientkey = process.env.My_ClientKey;
const deviceid = process.env.My_DeviceID;
const IRKit_Light_ID = IRKit_DEVICE + '-light-' + deviceid; // 照明デバイスの識別子

// エラー情報
const sendError = Error("Send error");          // 赤外線信号の送信が失敗
const internalError = Error("Internal error");  // 内部エラー
// エラーメッセージ
const InvDeviceMsg = "そのようなデバイスは登録されていません。";
const InvValueMsg = "指定可能な範囲外の値のため、設定できません。";
const UnSupportMsg = "その操作には対応していません。";

// リモコン信号
const LightIR = {
  on: { "format":"raw",
        "freq":38,
        "data":[18031,8755,1190,1037,1190,3228,1190,1037,1190,1037,1190,1037,
                1190,1037,1190,1037,1190,3228,1190,3228,1190,1037,1190,3228,
                1190,3228,1190,1037,1190,3228,1190,3228,1190,1037,1190,3228,
                1190,3228,1190,3228,1190,3228,1190,3228,1190,1037,1190,1037,
                1190,1037,1190,1037,1190,1037,1190,1037,1190,1037,1190,1037,
                1190,3228,1190,3228,1190,3228,1190,65535,0,18031,18031,4400,
                1190,65535,0,65535,0,60108,17421,4400,1190]
  },
  off: {"format":"raw",
        "freq":38,
        "data":[18031,8755,1190,1037,1190,3228,1190,1037,1190,1037,1190,1037,
                1190,1037,1190,1037,1190,3228,1190,3228,1190,1037,1190,3228,
                1190,3228,1190,1037,1190,3228,1190,3228,1190,1037,1190,1037,
                1190,3228,1190,3228,1190,3228,1190,3228,1190,1037,1190,1037,
                1190,1037,1190,3228,1190,1037,1190,1037,1190,1037,1190,1037,
                1190,3228,1190,3228,1190,3228,1190,65535,0,18031,18031,4400,
                1190,65535,0,65535,0,60108,17421,4400,1190]
  },
  dim: {"format":"raw",
        "freq":38,
        "data":[18031,8755,1190,1037,1190,3228,1190,1037,1190,1037,1190,1037,
                1190,1037,1190,1037,1190,3228,1190,3228,1190,1037,1190,3228,
                1190,3228,1190,1037,1190,3228,1190,3228,1190,1037,1190,1037,
                1190,3228,1190,3228,1190,1037,1190,3228,1190,1037,1190,1037,
                1190,1037,1190,3228,1190,1037,1150,1037,1150,3228,1190,1037,
                1150,3228,1190,3228,1190,3228,1190,65535,0,18031,18031,4400,
                1190,65535,0,65535,0,60108,17421,4400,1190]
  }
};

// リモコン信号の送信
function sendIR(ir, callback) {
    var https = require("https");
    var qs = require("querystring");
    var postData = qs.stringify({
        clientkey: clientkey,
        deviceid: deviceid,
        message: JSON.stringify(ir)
    });
    var req = https.request({
        host: "api.getirkit.com",
        path: "/1/messages",
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
            "Content-Length": postData.length
        }
    }, function (res) {
        var result = res.statusCode;
        if (result == 200) {
            console.log("res.statusCode=" + result);    // ログ出力
            callback(null, "success");                  // 送信成功
        }
        else {
            console.log(postData);                      // ログ出力
            console.log("res.statusCode=" + result);    // ログ出力
            callback(sendError);                        // 送信エラーを通知
        }
    });
    req.write(postData);
    req.end();
}

exports.handler = function (request, context, callback) {
    var header = request.directive.header;
    var req_name = header.name;

    switch (header.namespace) {
      case 'Alexa.Discovery':
        if (req_name === 'Discover') {
            log("DEGUG: ", "Discover request", JSON.stringify(request));
            handleDiscovery(request, context, callback);
        }
        break;

      case 'Alexa.PowerController':
        if (req_name === 'TurnOn' || req_name === 'TurnOff') {
            log("DEBUG: ", "TurnOn or TurnOff Request", JSON.stringify(request));
            handlePowerControl(request, context, callback);
        }
        break;

      case 'Alexa.BrightnessController':
        if (req_name === 'AdjustBrightness' || req_name === 'SetBrightness') {
            log("DEBUG: ", "Brightness Request", JSON.stringify(request));
            handleBrightnessControl(request, context, callback);
        }
        break;

      case 'Alexa':
        if (req_name === 'ReportState') {
            log("DEBUG: ", "Report State", JSON.stringify(request));
            handleState(request, callback);
        }
        break;

      default: {
        // サポートしていない操作
        log("DEBUG: ", "Unsupported request", JSON.stringify(request));
        callback(null, generateNotSupportResponse(request));
      }
    }

    // 照明デバイスの検出
    function handleDiscovery(request, context, callback) {
        var payload = {
            "endpoints":
            [
                {
                    "endpointId": IRKit_Light_ID,
                    "manufacturerName": "IRKit",
                    "friendlyName": "リビングの照明",  // このデバイス名で検出されます
                    "description": "リビングの照明をIRKitで操作できます。",
                    "displayCategories": ["LIGHT"],           // 照明器具
                    "cookie": {},
                    "capabilities":
                    [
                        {
                            "type": "AlexaInterface",
                            "interface": "Alexa",
                            "version": "3"
                        },
                        {
                            "type": "AlexaInterface",
                            "interface": "Alexa.PowerController",
                            "version": "3",
                            "properties": {
                                "supported": [{
                                    "name": "powerState"      // 電源のオン・オフ
                                }],
                                "retrievable": true
                            }
                        },
                        {
                            "type": "AlexaInterface",
                            "interface": "Alexa.BrightnessController",
                            "version": "3",
                            "properties": {
                                "supported": [{
                                    "name": "brightness"    // 輝度
                                }],
                                "retrievable": true
                            }
                        },
                        {
                            "type": "AlexaInterface",
                            "interface": "Alexa.EndpointHealth",
                            "version": "3",
                            "properties":{
                                "supported":[{
                                    "name":"connectivity"   // 動作状態
                                }],
                                "retrievable": true
                            }
                        }
                    ]
                },
            ]
        };
        var header = request.directive.header;
        header.name = "Discover.Response";
        log("DEBUG: ", "handleDiscovery", JSON.stringify({ header: header, payload: payload }));
        callback(null, { event: { header: header, payload: payload } });
    }

    // 照明の電源操作
    function handlePowerControl(request, context, callback) {
        var requestMethod = request.directive.header.name;
        var endpoint = request.directive.endpoint;
        var deviceId = endpoint.endpointId;         // 電源操作対象のデバイス識別子
        var powerState, brightness;
        var ir;
        var response;

        if (requestMethod === "TurnOn") {
            log("DEBUG: ", "handlePowerControl: ", "Light on");
            powerState = "ON";
            brightness = 100;
            ir = LightIR.on;
        }
        else if (requestMethod === "TurnOff") {
            log("DEBUG: ", "handlePowerControl: ", "Light off");
            powerState = "OFF";
            brightness = 0;
            ir = LightIR.off;
        }
        if (deviceId != IRKit_Light_ID) {
            // 無効なデバイス
            response = generateInvDevResponse(request);
            log("DEBUG: ", "Invalid deviceId:", JSON.stringify(response));
            callback(null, response);
        }
        response = generatePowerResponse(request, powerState);
        sendIR(ir, function (error) {
            if (error) {
                callback(error);
            }
            // DynamoDBへ書き込む
            writeDynamoItem(powerState, brightness, function () {
                //log("DEBUG: ", "handlePowerControl", JSON.stringify(response));
                callback(null, response);
            });
        });
    }

    // 照明の輝度操作
    function handleBrightnessControl(request, context, callback) {
        var requestMethod = request.directive.header.name;
        var endpoint = request.directive.endpoint;
        var payload  = request.directive.payload;
        var deviceId = endpoint.endpointId;             // 輝度操作対象のデバイス識別子
        var powerState = "ON";
        var brightnessResult;
        var ir = LightIR.dim;
        var response;

        readDynamoItem(function (data) {
            if (data) {
                brightnessResult = data.Item.brightness;
            }
            else {
                // DynamoDBからの読み出しが失敗
                callback(internalError);
            }
            if (requestMethod === "SetBrightness") {
                log("DEBUG: ", "handleBrightnessControl: ", "Set Brightness");
                let val = parseInt(payload.brightness, 10);
                if (val > 100 || val < 0) {
                    callback(null,generateInvValueResponse(request));
                }
                brightnessResult = val;
            }
            else if (requestMethod === "AdjustBrightness") {
                log("DEBUG: ", "handleBrightnessControl: ", "Adjust Brightness");
                let val = parseInt(payload.brightnessDelta, 10);
                if (val > 100 || val < -100) {
                    callback(null,generateInvValueResponse(request));
                }
                brightnessResult += val;
            }
            if (brightnessResult >= 100) {
                brightnessResult = 100;
                ir = LightIR.on;
            }
            else if (brightnessResult <= 0) {
                powerState = "OFF";
                brightnessResult = 0;
                ir = LightIR.off;
            }
            if (deviceId == IRKit_Light_ID) {
                response = generateBrightnessResponse(request, brightnessResult);
            }
            else {
                // 無効なデバイス
                response = generateInvDevResponse(request);
                log("DEBUG: ", "Invalid deviceId", JSON.stringify(response));
                callback(null, response);
            }
            sendIR(ir, function (error) {
                if (error) {
                    callback(error);
                }
                // DynamoDBに書き込む
                writeDynamoItem(powerState, brightnessResult, function () {
                    //log("DEBUG: ", "handleBrightnessControl", JSON.stringify(response));
                    callback(null, response);
                });
            });
        });
    }

    // 照明の状態(DynamoDBに保存されている値で代替)レスポンスを返却する
    function handleState(request, callback) {
        readDynamoItem(function (data) {
            if (data) {
                let res = generateStateResponse(request,
                                                data.Item.powerState,
                                                data.Item.brightness);
                //log("DEBUG: ", "handleState", JSON.stringify(res));
                callback(null, res);
            }
            else {
                // DynamoDBからの読み出しが失敗
                callback(internalError);
            }
        });
    }
};

// 存在していないエンドポイント(デバイス)へのディレクティブ
function generateInvDevResponse(request) {
    return generateErrorResponse(request, "NO_SUCH_ENDPOINT", InvDeviceMsg);
}

// 無効な値を設定しようとするディレクティブ
function generateInvValueResponse(request) {
    return generateErrorResponse(request, "INVALID_VALUE", InvValueMsg);
}

// サポートしていないディレクティブ
function generateNotSupportResponse(request) {
    return generateErrorResponse(request, "INVALID_DIRECTIVE", UnSupportMsg);
}

// エラー応答(JSON)を生成する
function generateErrorResponse(request, error_type, message) {
    var responseHeader = request.directive.header;
    var endpoint = request.directive.endpoint;
    var deviceId = endpoint.endpointId;         // デバイス識別子
    var requestToken = endpoint.scope.token;

    responseHeader.namespace = "Alexa";
    responseHeader.name = "ErrorResponse";
    responseHeader.messageId = responseHeader.messageId + "-R";
    return {
        event: {
            header: responseHeader,
            endpoint: {
                "scope":{
                    "type": "BearerToken",
                    "token": requestToken
                },
                "endpointId": deviceId,
            },
            payload: {
                "type": error_type,
                "message": message
            }
        }
    };
}

// 電源操作の応答(JSON)を生成する
function generatePowerResponse(request, powerResult) {
    var now = new Date();
    var contextResult = {
        "properties": [{
            "namespace": "Alexa.PowerController",
            "name": "powerState",
            "value": powerResult,
            "timeOfSample": now,
            "uncertaintyInMilliseconds": 0
        }]
    };
    return generateResonse(request, "Response", contextResult);
}

// 輝度調整の応答(JSON)を生成する
function generateBrightnessResponse(request, brightnessResult) {
    var now = new Date();
    var contextResult = {
        "properties": [{
            "namespace": "Alexa.BrightnessController",
            "name": "brightness",
            "value": brightnessResult,
            "timeOfSample": now,
            "uncertaintyInMilliseconds": 0
        }]
    };
    return generateResonse(request, "Response", contextResult);
}

// 照明の状態の応答(JSON)を生成する
function generateStateResponse(request, powerState, brightness) {
    var now = new Date();
    var contextResult = {
        "properties": [
            {
                "namespace": "Alexa.PowerController",
                "name": "powerState",
                "value": powerState,
                "timeOfSample": now,
                "uncertaintyInMilliseconds": 0
            },
            {
                "namespace": "Alexa.BrightnessController",
                "name": "brightness",
                "value": brightness,
                "timeOfSample": now,
                "uncertaintyInMilliseconds": 0
            },
            {
                "namespace": "Alexa.EndpointHealth",
                "name": "connectivity",
                "value": {"value": "OK"},
                "timeOfSample": now,
                "uncertaintyInMilliseconds": 0
            }
        ]
    };
    return generateResonse(request, "StateReport", contextResult);
}

function generateResonse(request, responseName, contextResult) {
    var responseHeader = request.directive.header;
    var endpoint = request.directive.endpoint;
    var deviceId = endpoint.endpointId;         // デバイス識別子

    responseHeader.namespace = "Alexa";
    responseHeader.name = responseName;
    responseHeader.messageId = responseHeader.messageId + "-R";
    return {
        context: contextResult,
        event: {
            header: responseHeader,
            endpoint: {
                "endpointId": deviceId,
            },
            payload: {}
        }
    };
}

// DynamoDBのSmartIRKitテーブルの、id:'LivingLight'項目を読み出す
function readDynamoItem(callback) {
    var docClient = new AWS.DynamoDB.DocumentClient();
    const params = {
        TableName: 'SmartIRKit',    // DynamoDBテーブル名
        Key:{ 'id': 'LivingLight' } // Primary Key
    };

    docClient.get(params, function (err, data) {
        if (err) {
            log("DEBUG: ", "Unable to read item. Error JSON:", JSON.stringify(err, null, 2));
            callback(null);
        }
        else {
            log("DEBUG: ", "GetItem succeeded:", JSON.stringify(data));
            callback(data);
        }
    });
}

// DynamoDBのSmartIRKitテーブルの、id:'LivingLight'項目を書き込む
function writeDynamoItem(powerState, brightness, callback) {
    var docClient = new AWS.DynamoDB.DocumentClient();
    var writeData = {
        TableName: 'SmartIRKit',        // DynamoDBテーブル名
        Item:{
            'id': 'LivingLight',
            'powerState': powerState,
            'brightness': brightness
        }
    };

    docClient.put(writeData, function (err) {
        if (err) {
            log("DEBUG: ", "Unable to write item. Error JSON:", JSON.stringify(err, null, 2));
        }
        else {
            log("DEBUG: ", "PutItem succeeded:", JSON.stringify(writeData));
        }
        callback(err);
    });
}

function log(message, message1, message2) {
    console.log(message + message1 + message2);
}

 これで、輝度を1〜99%に設定した場合は、リモコンの「調光」ボタンの信号がIRKitから送信されます。輝度100%に設定は電源オン、輝度0%に設定は電源オフとしています。「アレクサ、リビングの照明を暗くして」と指示すると、輝度を-25%するようです。なかなか賢いぞ、Alexa君。
 また、Alexaアプリでは以下のようにリビングの照明に輝度調整のスライドバーが表示されるようにもなりました。


< 過去の記事 [ 1月の 全てのカテゴリ リスト ] 新しい記事 >

2018 calendar
1月
123456
78910111213
14151617181920
21222324252627
28293031


掲示板
最新:08/15 17:19


GsBlog was developed by GUSTAV, Copyright(C) 2003, Web Application Factory All Rights Reserved.