RaspberryPiとMESH(ボタンタグ+カスタムタグ)を使ったPCスクリーショット(Mac)の保存操作

# 経緯

* 趣味の自転車で使っている室内トレーニングアプリ(zwift,rouvyなど)でトレーニング中にスクリーンショットを保存したい
* Macではスクリーショットはcommand+shift+3で保存できますが、トレーニング中に操作するのは危険なので、片手でも実行できるようにしたい

# 作ったもの
MESHのボタンタグを押すとMacのスクリーンショットを指定したフォルダに保存します。
![IMG_9500_10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1832768/6c5af3aa-5aee-fc72-9a65-ad525e53a4b3.jpeg) ![IMG_8446_10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1832768/75b89f95-00c0-618b-cdab-3cce7ab0ab7c.jpeg)


# MESHを選択した理由
* バッテリー内蔵で単体で動作し、自転車に取付け可能で片手で操作できること
* SDK(JavaScript)があり、RaspberryPiと連携できること

# 構成
### MESH
* MESHハブアプリケーションでボタンタグをトリガーにHTTP GETを送るカスタムタグのレシピを作成します

カスタムタグとレシピは[MESHハブアプリケーションを試してみる](https://www.google.co.jp/amp/s/4009.jp/post/archives/85)を参考にCode:Executeに下記コードを書いて作成します。

```
var apiURL = "http://ローカルホスト名:ポート番号/";
 
ajax ({
    url : apiURL,
    type : "get",
    timeout : 1000,
    success : function ( contents ) {
        log("HTTP receive.");
        callbackSuccess( {
            resultType : "continue"
        } );
    },
    error : function ( request, errorMessage ) {
        log("http request error occured");
        callbackSuccess( {
            resultType : "continue"
        } );
    }
});
 
return {
    resultType : "pause"
};
```

### RaspberryPi 
[Raspberry Piに「MESH ハブ」アプリをインストールする方法を教えてほしい](https://support.meshprj.com/hc/ja/articles/115002125554)を参考にMESHハブで作成したレシピの実行環境をラズパイに構築し、HTTPリクエストを受けるローカルサーバー(Node.js + Express + child_process)を起動し、SSH接続（公開鍵認証）でAppleScriptをosascriptコマンドで実行します。

```js
const express = require('express')
const app = express()
const {exec} = require('child_process')

//要ユーザ設定ファイル作成(~/.ssh/config) 
const sshcmd = "ssh [接続するsshのエイリアス名]"
//ファイル形式
const fileformat = "defaults write com.apple.screencapture type [ファイル形式]"
//画像ファイルの保存先パス
const savefilepath ="defaults write com.apple.screencapture location [画像ファイルの保存先パス] "
//AppleScriptのパス
const applescriptpath = "osascript [AppleScriptのパス] "
//AppleScript実行（ファイル形式と保存先指定）
//const cmd = sshcmd + fileformat + "&& " + sshcmd + savefilepath + "&& " + sshcmd + applescriptpath
//AppleScript実行
const cmd = sshcmd + applescriptpath

app.get('/', function (req, res) {
    exec(cmd, (error, stdout, stderr) => {
        if (error) {
            console.error(`exec error: ${error}`);
            return;
        }
        res.status(200)
        res.send('OK')
    })
})

app.listen(3000,() => console.log('Listening on port 3000'));
```
プログラムはサーバーが落ちても再起動するようにforeverコマンドを指定します。
[【Node.js】 RaspberryPiのプログラムを自動起動・永続化・SSH ログアウト後もプロセスを残す](https://qiita.com/mochifuture/items/ed863857affc80d0189f)を参考にサービス登録、再起動し、Foreverのプロセスを下記コマンドから確認します。

```/etc/rc.local
sudo -u pi /usr/local/bin/node /usr/local/bin/forever start -a -d [動かしたいスクリプトのパス]
```

```
$ forever list
info:    Forever processes running
data:        uid  command             script                 forever pid  id logfile                    uptime       
data:    [0] EL4C /usr/local/bin/node [動かしたいスクリプトのパス]  1447    1705    /home/pi/.forever/EL4C.log 0:0:0:14.962
```
SSHはMacから公開鍵を取得し、ユーザ設定ファイル(~/.ssh/config)を作成します。

```~/.ssh/config
Host [接続するsshのエイリアス名]
  HostName [ローカルホスト名]
  User [ユーザー名]
  IdentityFile [秘密鍵のパス]
  Port [ボート番号(デフォルトは22)]
```

# Mac
* スクリーショットを実行するAppleScriptをスクリプトエディタで作成します

##### AppleScript形式
```
on fullscreenshot()
	tell application "System Events"
　　　　　　　　　　　　　　　　// Command+Shift+3を入力
		key code 20 using {command down, shift down}
	end tell
end fullscreenshot

fullscreenshot()
```

##### JavaScript形式

```js
sys = Application("System Events");
// Command+Shift+3を入力
sys.keyCode("20", { using: ["command down", "shift down"]}); 
```

スクリプト保存時にファイルフォーマットを「スクリプト」に指定すると「.script」で保存されるため、これを[osascriptコマンド](https://support.apple.com/ja-jp/guide/terminal/trml1003/mac)で実行します。
なお、AppleScript形式の場合、ファイルフォーマットを「テキスト」に指定すると「.applescript」で保存されますが、それぞれosascriptで実行することが可能です。

# 最後に
このスクリプトを作成した後、Zwiftでは[ショートカットキーの設定](https://support.zwift.com/ja/keyboard-shortcuts-rkGrgwd4B)でF10キーでスクリーンショットを指定のフォルダに保存できることを知りました。
ただ、スクリーンショット操作は運動に集中しているとキーでもボタンでも押せないことや押し忘れることがあったため、自動で実行できるような仕組みがあると良いかと思いました。こちらは今後検討予定ですが、映像解析や他デバイスからのトリガーから実行できる仕組みを検討してみようと思います。