いっぺーちゃんの いろいろやってみよ~

Espruino on ESP32 で LINE(その1)

Twitterへのアクセスにも飽きてきたので、今回はLINEにメッセージを投げてみます。
LINEにはWebサービスから簡単にメッセージを送信できる、LINE Notifyというサービスがあります。これを使用すると簡単にLINEにメッセージを送信できます。

 

LINE Notifyでメッセージを送信するには、アクセストークンを取得する必要があります。
例によって、先人の知恵を拝借してアクセストークンを取得しましょう。

[超簡単]LINE notify を使ってみる - Qiita

自分にだけメッセージを送信したい場合は、トークルームではなく
「1:1でLINE Notifyから通知を受け取る」
を選択します。
通知先は変更できません。変更するにはアクセストークンを再発行してください。
アクセストークンを他人に知られると、勝手にメッセージが送られることがあるので、注意しましょう。

アクセストークンを無効にするには、LINEのマイページにある、「連携中のサービス」から対象のサービスの解除ボタンをクリックしてください。

 

それでは、LINE Notifyにメッセージを送信するモジュールです。

以下をtiny_line.jsというファイル名でオンボードストレージの /node_modules ディレクトリに格納してください。

var tls      = require("tls");

if (typeof(E) ==='function') {
    // platform is espruino
     var platform = 'espruino';
}
else {
    // platform is node.js
    var platform = 'node';
    var events = require('events');
    var util = require('util');
}

// ######## percent encording ################################
var RFC3986_encode = function(str) {
    // var ret = encodeURIComponent(str);
    // change according to RFC3986
    /* ********** RegEx are not supported in Espruino
    var ret = encodeURIComponent(str).replace(/[!*'()]/g, 
            function(p) {
                return "%" + p.charCodeAt(0).toString(16);
            });
     ********** */
    var tmp = encodeURIComponent(str);
    var ret = "";
    for (var i = 0; i < tmp.length; i++) {
        var c = tmp[i];
        if ((c==="!") || (c==="*") || (c==="'") || (c==="(") || (c===")")) {
            ret += '%'+c.charCodeAt(0).toString(16);
        } else {
            ret += c;
        }
    }
    return ret;
}


// ######## tiny_line class ################################
var tiny_line = function(access_token, _DEBUG_)
{
    this.access_token    = access_token;
    this._DEBUG_         = _DEBUG_;

    if (platform == 'node') {
        // for EventEmitter
        events.EventEmitter.call(this);
    }
}

if (platform == 'node') {
    // for EventEmitter
    util.inherits(tiny_line, events.EventEmitter);
}

// ######## debug print ################################
tiny_line.prototype.debug_print = function(str) {
    if (this._DEBUG_) {
        //console.log(Date().toString());
        console.log(str);
    }
}


// ######## make request message ################################
tiny_line.prototype.makeRequestMessage = function(method, server, endpoint, message){
    method = method.toUpperCase();          // to upper

    // make message body
    var body = 'message=' + message;

    // make request
    var request = method + ' ' + endpoint + ' HTTP/1.1\n';

    // make message header
    var header  = 'Host: ' + server + '\n'
                + 'User-Agent: espruino line Bot v0.1\n'
                + 'Accept: */*\n'
                + 'Connection: close\n'         // これがないとレスポンス受信後も接続状態が保持されてしまう
                + 'Authorization: Bearer ' + this.access_token +'\n'
                + 'Content-Type: application/x-www-form-urlencoded\n'
                + 'Content-Length: ' + body.length.toString() + '\n';

    // request message
    var ret = request + header + '\n' + body + '\n';

    return ret;
}

// ######## Send message ################################
tiny_line.prototype.sendmessage = function(host, msg) {
    var _self = this;
    var client = tls.connect(host, function() {
        client.on('data', function(data) {
            var rcv_header = data.toString();       // 文字列に変換
            var p0 = rcv_header.indexOf('\n');      // 最初の改行の位置
            var line = rcv_header.slice(0, p0);     // 最初の1行取り出し

            var p1 = line.indexOf(' ') + 1;         // 最初のスペースの次(エラーコードの位置)
            var p2 = line.indexOf(' ', p1) + 1;     // 二個目のスペースの次(エラーメッセージの位置)
            var err_code = parseInt(line.slice(p1, p2));
            var err_msg = line.slice(p2);
            _self.emit("response", err_code, err_msg, rcv_header);
        });
        client.on('end', function() {
            _self.emit("end");
        });
        _self.emit("connect");
        client.write(msg);
    });
}

// ######## notify API ################################
tiny_line.prototype.notify = function(msg, encoded) {
    if (!msg) {
        throw new Error("message is required."); 
    }
    var server = 'notify-api.line.me';
    var host = { host: server, port: 443};

    // メッセージがエンコードされていなければエンコードする
    if (!encoded) {
        msg = RFC3986_encode(msg);
    }

    var reqMessage = this.makeRequestMessage('POST', server, '/api/notify', msg);

    this.debug_print("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
    this.debug_print(reqMessage);
    this.debug_print("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");

    this.sendmessage(host, reqMessage);
}

module.exports = tiny_line;
module.exports.RFC3986_encode = RFC3986_encode;

準備ができたら、以下のプログラムを実行してみましょう。

var access_token    = '取得したアクセストークン';

if (typeof(ESP32) ==='function') {
    // platform is espruino on ESP32
    // Wi-Fi アクセスポイントへの接続
    var wifi = require('MyWifi');
}
if (typeof(E) ==='function') {
    // platform is espruino
    // set time zone  fixed to 'JST'
    E.setTimeZone(9);
}

// モジュール読み込み
var tiny_line   = require("tiny_line");

// 初期化
var tl = new tiny_line(access_token, true);

tl.on("connect", function() {
    console.log("%%%% CONNECTED %%%%");
});
tl.on("response", function(code, msg, data) {
    if (code == 200) {
        console.log("%%%% RESPONSE : " + code.toString() + " : " + msg + " %%%%");
    } else {
        console.log("%%%% RESPONSE ERROR!!! : " + code.toString() + " : " + msg + " %%%%");
        console.log("#### RESPONSE ####");
        console.log(data);
        console.log("############");
    }
});
tl.on("end", function() {
    console.log("%%%% END %%%%");
});

// メッセージ送信
tl.notify("good morning! (or afternoon)", false);

 

 プログラムを実行すると、LINEに以下のようなメッセージが送信されます。

「[トークン名] good morning! (or afternoon)」

 

 

 以下、プログラムの説明です。

 

 Twitterのときにも使った、ESP32専用設定、Espruino専用設定の部分です。

if (typeof(ESP32) ==='function') {
    // platform is espruino on ESP32
    // Wi-Fi アクセスポイントへの接続
    var wifi = require('MyWifi');
}
if (typeof(E) ==='function') {
    // platform is espruino
    // set time zone  fixed to 'JST'
    E.setTimeZone(9);
}

 

 モジュールを読み込みます。

// モジュール読み込み
var tiny_line   = require("tiny_line");

 

 tiny_lineのインスタンスを生成します。
第1パラメータは↑で取得したアクセストークンです。
第2パラメータはモジュール内部のdebug_print (今回はtypo直しました)を表示するか否かを指定しています。
色々表示されてうっとうしい場合はfalseにしてください。

// 初期化
var tl = new tiny_line(access_token, true);

 

 接続処理時のイベントハンドラです。イベントは"open"、"response"、"end"があります。
"response"イベントのパラメータは
    code    エラーコード
    msg     エラーメッセージ
    data    受信データ全体
です。

tl.on("connect", function() {
    console.log("%%%% CONNECTED %%%%");
});
tl.on("response", function(code, msg, data) {
    if (code == 200) {
        console.log("%%%% RESPONSE : " + code.toString() + " : " + msg + " %%%%");
    } else {
        console.log("%%%% RESPONSE ERROR!!! : " + code.toString() + " : " + msg + " %%%%");
        console.log("#### RESPONSE ####");
        console.log(data);
        console.log("############");
    }
});
tl.on("end", function() {
    console.log("%%%% END %%%%");
});

 

 メッセージの送信処理です。
第1パラメータが送信する文字列、
第2パラメータが文字列がパーセントエンコード済みか否かを示すフラグです。これがfalse または省略されていると、送信文字列をnotify関数内部でエンコードします。

// メッセージ送信
tl.notify("good morning! (or afternoon)", false);

 

 メッセージ送信部分を以下のように変更すると、
「[トークン名] 現在の時刻は Sat Jul 29 2017 11:15:37 GMT+0900 です」
のような日本語を含んだメッセージを送信することもできます。

var date = new Date();
date_str = tiny_line.RFC3986_encode(date.toString());
var str1 = "%E7%8F%BE%E5%9C%A8%E3%81%AE%E6%99%82%E5%88%BB%E3%81%AF%20"; // 現在の時刻は 
var str2 = "%20%E3%81%A7%E3%81%99";                                     //  です
tl.notify(str1 + date_str + str2, true);

 

 notify関数の第1パラメータにパーセントエンコード済みの送信文字列を、第2パラメータにtrueを指定します。
パーセントエンコードはRFC2396でも大丈夫なようですが、念のためRFC3986にしてあります。

全角文字を含む静的な文字列のパーセントエンコードは、以下のサイトなどで行えます。
http://www.tagindex.com/tool/url.html
(文字コードで「UTF-8」を選択してから「エンコードする」ボタンをクリックしてください)
動的に変更したい半角文字列はtiny_line の RFC3986_encode 関数がエクスポートされているので、それを使えばOK。