Android×Pythonで気圧計作ってみた

## はじめに

&emsp;山形大学大学院修士1年のHagianです。この記事は[身の回りの困りごとを楽しく解決！ by Works Human Intelligence Advent Calendar 2022](https://qiita.com/advent-calendar/2022/works-hi-1)の15日目となります。

&emsp;突然ですが、私は頭痛を抱えています。月に1回程度来る偏頭痛のほかに、気圧変動（特に上昇時）によって緊張型頭痛がやってきます。前兆をつかむため、気圧を知りたいな〜と思うことがよくあります。

&emsp;しかし気圧計が手近にある場合を除いて、気圧を知ることは意外と難しいように思います。気象庁および各地の気象台では観測が行われていて、気象庁のWebサイトから知ることができますが、毎正時かつ海面更正された値であり、実測値ではありません。そこで本稿では、**スマホで手軽に気圧の実測値を得られる**ツールを作成したので紹介します。

### 困っていること

* 知りたいときに気圧を知れない
* 自分の現在地の気圧を実測値で知りたい

### 実現したいこと

* リアルタイムで現在地の気圧を知りたい
* 大まかな標高も確認したい
* 手元にある機材を有効活用したい

## 筆者の状況・開発言語

&emsp;以前から気圧の変動を把握するため、「頭痛ーる」というアプリを活用していました。このアプリでは、現在地の気圧や6時間でどれくらい気圧が変化するかを知ることができます。

https://zutool.jp/

かなり機能が充実していて使いやすいのですが、標高差による気圧の変動が考慮されていない点（&rarr;同一市区町村内の山に登ったとしても地上の気圧として表示される）もあります。そこで、先述のとおり、標高も可視化したいと考えました。

&emsp;つづいて開発環境です。私は少しだけPythonを書くことができますが、JavaやKotlinなどアプリ開発でよく用いられる言語は全くの素人です。そこで、Pythonでコーディングできることを目標にしました。 ~~（ものぐさなのでJavaやKotlinをゼロから勉強する発想にはならなかった）~~

## センサーとしての「スマホ」

&emsp;スマホは多種多様なセンサーを搭載しています。代表的な例は加速度センサーです。このセンサーが搭載されているおかげで歩数を測ることができています。今や欠かせない機能になりつつあるのではないでしょうか。一方、あまり知られていないセンサーもあります。その1つが気圧センサーです。現在発売されているスマホのうち、ミドル〜ハイエンドモデルはほとんどが搭載しています。これらのセンサーの値は、「CPU-Z」のようなアプリを使うと確認することができます。

https://play.google.com/store/apps/details?id=com.cpuid.cpu_z

筆者のスマホ（Pixel 6）でセンサー一覧をみると、以下のように色々搭載されていることがわかります。

![Screenshot_20221211-163003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021972/14f2cb79-45df-4899-2b39-66ffddf9d259.png)

この中で「ICP10101 Pressure Sensor」と表示されているのが気圧センサーです。0.1hPa単位で取得できることがわかります。

## 開発環境

### スマホ
* Google Pixel 6

### PC
* MacBook Pro (M1,2020)

### 言語
* Python 3.10.8

### エディタ
* Visual Studio Code 1.74.0

## 手順

&emsp;作成にあたり、以下のサービスを使用しました。

![スクリーンショット 2022-12-11 16.54.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021972/f57d0e73-efca-a270-1690-c882384369d3.png)


### ①Termux

&emsp;Termuxはターミナルエミュレータのひとつで、AndroidやChrome OS上で動作します。端末をroot化しなくてもLinuxを動かすことができるほか、APIやDialog、Widget等も用意されているため、幅広い開発が可能です。**過去にはGoogle Playでも提供されていましたが現在はサポートが打ち切られています。最新版はF-Droidから入手可能です。**

https://wiki.termux.com/wiki/Installing_from_F-Droid

今回、以下のリンクを参考にセットアップを行いました。

https://zenn.dev/ryuu/scraps/6b2584294674b7

https://zenn.dev/shyne/articles/dd05315aea291d

&emsp;上記リンクにもあるとおり、インストールしたらパッケージを最新に更新した後、以下のコマンドを実行し、内部ストレージへのシンボリックリンクを作成します。

```terminal
termux-setup-storage
```

実行するとポップアップが出現して、アクセス許可を求められるので許可します。シンボリックリンクは```~/storage/```配下に作成され、```.bashrc```や```.zshrc```で特に指定しない限り、起動するとカレントディレクトリは```/data/data/com.termux/files/home```となります。次に**Termux:API**、**Termux:Widget**を導入します。

&emsp;これが終わったら、必要なパッケージをインストールします。
```terminal
pkg install curl python vim wget
```

上記のパッケージがインストールできたら、pipを最新版にアップデートし、必要なライブラリをインストールします。今回標準ライブラリ以外で使ったのは、```requests```のみです。

### ②Pushbullet

&emsp;Pushbulletはデバイス間で通知を共有できるサービスです。現在はAndroidのほか、Google Chrome、Firefox、Windowsがサポートされており、スマホに届いた通知をパソコン等でも確認することができます。過去にはApp StoreでiOS版が入手可能でしたが、2020年に打ち切られています。

https://www.pushbullet.com/

このPushbulletにはAPIが公式で用意されており、プログラムから簡単にプッシュ通知を送信することができます。今回は以下のリンクを参考に実装しました。

https://zlog.hateblo.jp/entry/2019/10/12/pushbullet-notification-python

## 実装方法

&emsp;コードはPCで記述しました。~~（スマホで全部打ち込むよりはVSCode使ったほうが楽だから…）~~ 次にスマホのダウンロードフォルダ（```~/storage/downloads```）内に新たにフォルダを作成し、ここにPythonコードを保存します。Androidがファイルへのフルアクセスを許可しないため、**アクセス権のあるフォルダ**に保存する必要があるからです。

&emsp;最後に、Termuxで```~/.shortcuts/```に移動し、コードを実行するためのシェルスクリプトを記述・保存します。そうすることで、使いたいときはワンタップで実行することができます。

## 実装例

```python:main.py
import csv
import datetime
import json
import requests
import subprocess

def get_altitude():
    # 位置情報から標高の取得
    location = subprocess.getoutput("termux-location")
    # デフォルトではGPSから位置情報を取得するが、タイムアウトすると出力が空になってしまう。
    if not location:
        # GPSから取得できなかった場合、ネットワークからの取得を試みる。
        location = subprocess.getoutput("termux-location -p network")
    else:
        pass
    location_dict = json.loads(location)
    altitude = "標高：" + str(round(location_dict["altitude"],2))
    return altitude

def get_pressure():
    # 気圧の取得
    pressure = subprocess.getoutput("termux-sensor -s \"Pressure Sensor\" -n 1")
    pressure_dict = json.loads(pressure)
    pressure_now = "気圧：" + str(round(pressure_dict["ICP10101 Pressure Sensor"]["values"][0],2))
    return pressure_now

def get_time():
    # 現在時刻の取得
    now = datetime.datetime.now()
    # 日本国内での使用を想定
    now_timezone = str(datetime.timezone(datetime.timedelta(hours=9)))
    now = now.strftime("%Y-%m-%d %H:%M ") + "(" + now_timezone + ")"
    return now

def time_diff_trans(diff):
    # 経過時間の差分を算出
    minute, second = divmod(diff, 60)
    hour, minute = divmod(minute, 60)
    day, hour = divmod(hour, 24)
    return day, hour, minute, second

def push_message(body):
    # プッシュ通知送信
	token = "ここにPushbulletのAPIトークンを入力"
	url = "https://api.pushbullet.com/v2/pushes"
	
	headers = {"content-type": "application/json", "Authorization": 'Bearer '+token}
	data_send = {"type": "note", "title": "現在地の標高・気圧", "body": body}

	message = requests.post(url, headers=headers, data=json.dumps(data_send))


altitude = get_altitude()
pressure = get_pressure()
time = get_time()
trim_time = datetime.datetime.strptime(time[:-12], "%Y-%m-%d %H:%M")

# 前回実行時のデータを取得
with open("diff.csv",mode="r") as old:
    pre_data_lst = [i for i in csv.reader(old)][0]
    pre_att = pre_data_lst[1]
    pre_prs = pre_data_lst[2]
    pre_time = pre_data_lst[0]

pre_time = datetime.datetime.strptime(pre_time, "%Y-%m-%d %H:%M:%S")

# 今回の実行結果をdiff.csvには"上書き"保存
with open("diff.csv", mode="w") as new:
    writer = csv.writer(new)
    new_data_lst = [trim_time,altitude[3:],pressure[3:]]
    writer.writerow(new_data_lst)

# history.csvには"追記"保存
with open("history.csv", mode="a") as out:
    writer = csv.writer(out)
    writer.writerow(new_data_lst)

# 標高の差分算出
att_diff = round(float(altitude[3:]) - float(pre_att),2)

# 差分の正負判定と単位記入、正なら"+"記号追加
if att_diff > 0:
    att_diff = "+" + str(att_diff) + "m"
else:
    att_diff = str(att_diff) + "m"

# 気圧の差分算出
prs_diff = round(float(pressure[3:]) - float(pre_prs),2)

# 差分の正負判定と単位記入、正なら"+"記号追加
if prs_diff > 0:
    prs_diff = "+" + str(prs_diff) + "hPa"
else:
    prs_diff = str(prs_diff) + "hPa"

# 時刻の差分算出
time_diff = trim_time - pre_time
time_diff = time_diff.total_seconds()
# 差分を秒単位に直してから経過日時取得
time_calc = time_diff_trans(time_diff)
time_elap = str(int(time_calc[0]))+ "日，" + str(int(time_calc[1])) + "時間" + str(int(time_calc[2])) + "分"

res = time + "\n" + altitude + "m (" + att_diff + ")\n" + pressure + "hPa (" + prs_diff + ")\n\n前回計測からの経過時間：" + time_elap
push_message(res)

```

&emsp;ポイントとして、Pythonの他プロセス呼び出しライブラリである```subprocess```を用いて、コード内でTermuxのコマンドを動作させています。
気圧だけを知らせるシンプルな機能でもよかったのですが、海面更正していない実測値であることを認識するために、位置情報から標高のみ取得するようにしました。さらに、継続的に使用することを考え、前回実行からの経過時間も取得するように実装しました。ファイル構造は以下のようになっています。

```
~/
├　storage/
    ├ downloads/
        ├ BarometerForAndroid/
            ├ diff.csv # 実行結果を保存（1回分）
            ├ history.csv　　# 実行履歴を保存（実行した回数分保存）
            └ main.py # メインのソースコード
├　.shortcuts/
    └ BarometerForAndroid.sh #ホーム画面から実行するためのシェルスクリプト
```

Pythonコード、ならびにシェルスクリプトは[筆者のGithub](https://github.com/PyRadiolarus/for-qiita/tree/master/BarometerForAndroid)で公開しています。

&emsp;実行したスクリーンショットを示します。

![widget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021972/3e6c80da-240f-9d1d-b415-32b45f6fb5ef.png)

ホーム画面に追加したTermux:widgetに表示されたシェルスクリプトをタップして、1~2分待つと…？

![ntify.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1021972/7a53bc49-e323-7eb8-90c0-b47c03b07c9b.png)

このように、現在地の標高・気圧と前回計測からの経過時間がプッシュ通知されるようになりました。この通知はPushbulletを追加したブラウザからも確認でき、便利です。

## 感想・今後の予定

&emsp;Androidでも案外簡単にPythonコードを実行できることがわかりました。今回は気圧計を作りましたが、他にも色々自作できそうな気がします。今後は実行履歴を保存しているCSVからデータを読み込んで、気圧の変化を可視化したいと考えています。