SORACOM LTE-M ButtonでSlackにモールス信号を送れるようにした

## はじめに
- ある日、会社にたくさんのSORACOMボタンが届いた
<img src="https://qiita-image-store.s3.amazonaws.com/0/3507/97e12271-4da6-56c3-9d6c-da504bb5f03f.jpeg"　 width=60%>

- 何か開発したいと思いつつSORACOMさんの[ドキュメント](https://blog.soracom.jp/blog/2018/10/25/are-you-ready-for-lte-m-button/
)を読んでみる
- 1つのボタンでシングルクリック、ダブルクリック、長押しが出来るようだ
- モールス信号が打てますね！思いついたらすぐ開発していく

## 開発方法
- まずはSORACOMさんの素晴らしい[ドキュメント](https://dev.soracom.io/jp/aws_button/getting-started/)を参考にボタンを押したらSlackへ通知できる状態にする
    - この2つのドキュメントを読めばOKです
      - [SORACOM LTE-M Buttonを SORACOM のガジェット管理に登録する](https://dev.soracom.io/jp/start/aws_button_registration/)
      - [SORACOM LTE-M ButtonをクリックしてSlack通知にする](https://dev.soracom.io/jp/start/aws_button_slack/)

- この状態をベースにモールス信号処理部分のコードを加えていく

### 仕様
- [和文モールス符号](https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%BC%E3%83%AB%E3%82%B9%E7%AC%A6%E5%8F%B7#%E5%92%8C%E6%96%87%E3%83%A2%E3%83%BC%E3%83%AB%E3%82%B9%E7%AC%A6%E5%8F%B7)を扱えるようにする
- クリックはそれぞれ以下のように扱う
  - シングルクリック: トン
  - 長押し: ツー
  - ダブルクリック: 区切り文字
      - (e.g.)シングルクリック、長押し、ダブルクリック => イ
- ダブルクリックが2連続で来たらSlackへ通知させる
- 1回のシングルクリック、ダブルクリック、長押しごとにAWS Lambdaがコールされてくるが全信号の終わりまで状態を持つ必要がある
- AWS Lambdaは`/tmp`を扱えるのでファイルを作成しダブルクリックが2連続で来るまで状態を管理する

### コード
```javascript:index.js
const https = require('https');
const url = require('url');
const fs = require('fs');

const defaultSlackUrl = process.env['SLACK_URL']
const tmpFilePath = "/tmp/morse_signal.txt"
const getSignal = {
    "SINGLE": ".", // トン
    "DOUBLE": " ", // デリミタ
    "LONG": "-" // ツー
}

const wabunMorseCode = {
    ".-": "イ",
    ".-.-": "ロ",
    "-...": "ハ",
    "-.-.": "ニ",
    "-..": "ホ",
    ".": "ヘ",
    "..-..": "ト",
    "..-.": "チ",
    "--.": "リ",
    "....": "ヌ",
    "-.--.": "ル",
    ".---": "ヲ",
    "-.-": "ワ",
    ".-..": "カ",
    "--": "ヨ",
    "-.": "タ",
    "---": "レ",
    "---.": "ソ",
    ".--.": "ツ",
    "--.-": "ネ",
    ".-.": "ナ",
    "...": "ラ",
    "-": "ム",
    "..-": "ウ",
    ".-..-": "ヰ",
    "..--": "ノ",
    ".-...": "オ",
    "...-": "ク",
    ".--": "ヤ",
    "-..-": "マ",
    "-.--": "ケ",
    "--..": "フ",
    "----": "コ",
    "-.---": "エ",
    ".-.--": "テ",
    "--.--": "ア",
    "-.-.-": "サ",
    "-.-..": "キ",
    "-..--": "ユ",
    "-...-": "メ",
    "..-.-": "ミ",
    "--.-.": "シ",
    ".--..": "ヱ",
    "--..-": "ヒ",
    "-..-.": "モ",
    ".---.": "セ",
    "---.-": "ス",
    ".-.-.": "ン",
    "..": "゛",
    "..--.": "゜"
}

const writeSignal = (signal) => {
    console.log(signal);
    fs.appendFileSync(tmpFilePath, signal);
}

const readSignal = () => {
    return fs.readFileSync(tmpFilePath, 'utf8');
}

const decodeWabunMorseCode = (signal) => {
    return wabunMorseCode[signal] ? wabunMorseCode[signal] : "?" // デコードできない場合は?を返す
}

const sendSlack = (event, context, callback, message) => {
    var slackUrl = (event.placementInfo.attributes.slackUrl) ? event.placementInfo.attributes.slackUrl : defaultSlackUrl
    if (!slackUrl) {

    }
    var slackReqOptions = url.parse(slackUrl);
    slackReqOptions.method = 'POST';
    slackReqOptions.headers = { 'Content-Type': 'application/json' };

    var payload = { 'text': message }

    if (event.placementInfo.attributes.username) {
        payload.username = event.placementInfo.attributes.username;
    }
    if (event.placementInfo.attributes.iconEmoji) {
        payload.icon_emoji = event.placementInfo.attributes.iconEmoji;
    }
    if (event.placementInfo.attributes.iconUrl) {
        payload.icon_url = event.placementInfo.attributes.iconUrl;
        payload.as_user = false;
    }
    if (event.placementInfo.attributes.slackChannel) {
        payload.channel = event.placementInfo.attributes.slackChannel;
    }
    var body = JSON.stringify(payload);
    slackReqOptions.headers = {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
    };
    var req = https.request(slackReqOptions, function(res) {
        if (res.statusCode === 200) {
            console.log('Posted to slack');
            callback(null, { "result": "ok" });
        }
        else {
            callback(false, { "result": "ng", "reason": 'Failed to post slack ' + res.statusCode })
        }
        return res;
    });
    req.write(body)
    req.end();
}

exports.handler = (event, context, callback) => {
    console.log('Received event:', JSON.stringify(event, null, 2));

    writeSignal(getSignal[event.deviceEvent.buttonClicked.clickType]);
    const singals = readSignal();
    console.log("\"" + singals + "\"");

    // デリミタが連続で来たらメッセージ送信
    if (singals.match(/.+  $/)) {
        var message = singals.split(" ").map(function(signal) {
            console.log("signal: " + signal);
            if (signal === "") { // デリミタのみを無視
                return "";
            }
            else {
                return decodeWabunMorseCode(signal)
                // return wabun_morse[signal];
            }
        });
        // console.log(message.join(""));
        sendSlack(event, context, callback, message.join(""));
        fs.unlinkSync(tmpFilePath);
    }
};
```
- GitHubにも置いた
  - https://github.com/ishihama/soracom_lte-m_button/blob/master/morse.js

## モールス信号を送ってみる
- 送りたいメッセージを決めて間違えないようにボタンをクリックする
- この記事のなかでこの作業が一番難しいです

```
----    コ
.-.-.   ン
-.-.    ニ
..-.    チ
-...    ハ
.---.   セ
.-..    カ
.-      イ
```

- 最後にダブルクリックが連続になるようにしてSlackへ送る

![Screenshot from 2018-11-11 19-19-42.png](https://qiita-image-store.s3.amazonaws.com/0/3507/b0a94986-7495-e2c3-e02c-df231be369cb.png)

- いい感じですね

## 終わりに
- 1回のクリックから送信完了までそこそこ時間がかかってしまう
- 本当はモールス信号でQiita記事を書きたかったんですが無理すぎました
- 電池とクリック回数の減りがマッハ