IoT学習HATキットを使って緊急地震速報アラームを作った

## はじめに
秋月電子さんが販売している。[IoT学習HATキット](https://akizukidenshi.com/catalog/g/g114568/)を使用して緊急地震速報アラームを作りました。
## 準備するもの
- IoT学習HATキット(Raspberry Pi Zero WH用)
  https://akizukidenshi.com/catalog/g/g114568/
- RaspberryPi zero WH
  https://akizukidenshi.com/catalog/g/g112961/

IoT学習HATキットは部品の状態で届きます。ハンダ付け作業がありますのでハンダ付けのツールが無い方はそれも揃えておきましょう。
## IoT学習HATキットのハンダ付け作業
説明書が付属していますので特に難しいことは無いですがGPIOヘッダピンは先に付けた方が良いと思いました。私は最後に付けたのですがハンダ付け時にLEDなどと干渉してしまいハンダ付けが少し難しかったです。
## RaspberryPiの準備
OSのインストール方法などは沢山情報があるかと思いますので割愛します。インターネットを使用して地震データを取得しますのでインターネットに繋がる状態にしておきましょう。また、I2Cを使用しますのでこちらも使用できるようにしておきましょう。
## OSのバージョンなどの環境情報
今回、OSなどのソフトウェアバージョンは以下を使用しました。
- OS Raspbian GNU/Linux 11 (bullseye)
- Python 3.9.2
## 動作の概要
[P2P地震情報](https://www.p2pquake.net/develop/json_api_v2/)のRESTAPIを使用して地震データを取得し結果をIoT学習HATキットのLCDディスプレイ、LED、ブザーを使用して通知します。
### 地震が発生していない時
- LCD データの取得時間と地震が発生していない旨を表示します。
- LED 緑色を1回だけ点灯します。
- ブザー　使用しません。
### 地震が発生した時
- LCD 地震の発生時間と震度と現在地（※1）からの距離を表示します。
- LED 赤色を震度の回数点灯します。
- ブザー　震度の回数ブザーを鳴らします。

※１　今回GPSセンサーなどは使用していませんので手動でコードに書く必要があります。
### ネットワークの問題などで地震データが取得できなかった時
- LCD データの取得時間とデータが取得できなかった旨を表示します。
- LED 黄色を1回だけ点灯します。
- ブザー　使用しません。
## コーディング
:::note warn
注意
[P2P地震情報](https://www.p2pquake.net/develop/json_api_v2/)さんのREST APIを使用して情報を取得します。データ取得回数などのルールは必ず守ってください。
:::
### メモ
コード中の
cu_loc = [35.6, 139.74]
の部分で現在地の北緯と東経を設定してください。（デフォルトは東京タワーのものになっています）
### コード
```python:eqAlart.py
import datetime
import requests
import json
from geopy.distance import geodesic
import smbus
import time
import RPi.GPIO as GPIO
import wiringpi

#CONFIG
cu_loc = [35.6, 139.74] #YourLcation NorthLatitude,EastLongitude

#FUNCTION
def i2cCommand(code):
    i2c.write_byte_data(i2c_addr, 0x00, code)
    time.sleep(0.1)
 
def writeLcd(row_num, message):
    mojilst=[]
    if row_num == 1:
        i2cCommand(0x01)
    elif row_num == 2:
        i2cCommand(0x40 + 0x80)
    for moji in message:
        mojilst.append(ord(moji)) 
    i2c.write_i2c_block_data(i2c_addr, 0x40, mojilst)
    time.sleep(0.1)
 
def initLcd():
    i2cCommand(0x38) #FunctionSet
    i2cCommand(0x39) #FunctionSet
    i2cCommand(0x14) #InternalOsc
    i2cCommand(0x73) #Contrast
    i2cCommand(0x56) #Power_ICONContrast
    i2cCommand(0x6c) #Follwer
    i2cCommand(0x38) #FunctionSet
    time.sleep(0.2)
    i2cCommand(0x01) #clear
    i2cCommand(0x0f) #DisplayOnOff   
 
#INIT
##COMMON
GPIO.setmode(GPIO.BCM)
##P2PQUAKE_RESTAPI
url = "https://api.p2pquake.net/v2/history?codes=551&limit=1"
lasteqid = 0
##LCD
BK_LED = 26 #BackLightPinNumber
GPIO.setup(BK_LED,GPIO.OUT)
GPIO.output(BK_LED,True)
i2c = smbus.SMBus(1) #i2cBusNumber
i2c_addr = 0x3e #lcdBusAddress
initLcd()
##LED
GPIO.setmode(GPIO.BCM)
P_RED = 17 #redLedPinNumber
P_YEL = 27 #yellowLedPinNumber
P_GRN = 22 #greenLedPinNumber
GPIO.setup(P_RED, GPIO.OUT)
GPIO.setup(P_YEL, GPIO.OUT)
GPIO.setup(P_GRN, GPIO.OUT)
##Buzzer
P_BUZ = 16 #buzzerPinNumber
BUZ_FREQ = 2500
GPIO.setup(P_BUZ, GPIO.OUT)
wiringpi.wiringPiSetupPhys()
wiringpi.softToneCreate(P_BUZ)

#MAIN
try:
    while True:
        dt_now = datetime.datetime.now()
        tm_now = dt_now.strftime("%H:%M:%S")
        try:
            res = requests.get(url)
        except:
            writeLcd(1, "{0}".format(tm_now)) #LcdRowNumber(1or2),message
            writeLcd(2, "ReqErr")
            GPIO.output(P_YEL, GPIO.HIGH)
            time.sleep(0.5)
            GPIO.output(P_YEL, GPIO.LOW)
            time.sleep(0.5)
        if res.status_code == 200:
            data = json.loads(res.text)
            if lasteqid == data[0]["id"]:
                writeLcd(1, "{0}".format(tm_now))
                writeLcd(2, "NoQuakes")
                GPIO.output(P_GRN, GPIO.HIGH)
                time.sleep(0.5)
                GPIO.output(P_GRN, GPIO.LOW)
                time.sleep(0.5)
            else:
                temp_scale = int(data[0]["earthquake"]["maxScale"])
                if temp_scale == 10:
                    scale = 1
                elif temp_scale == 20:
                    scale = 2
                elif temp_scale == 30:
                    scale = 3
                elif temp_scale == 40:
                    scale = 4
                elif temp_scale == 45:
                    scale = 5
                elif temp_scale == 50:
                    scale = 5
                elif temp_scale == 55:
                    scale = 6
                elif temp_scale == 60:
                    scale = 6
                elif temp_scale == 70:
                    scale = 7
                else:
                    scale = 0
                tgt_loc = (data[0]["earthquake"]["hypocenter"]["latitude"], data[0]["earthquake"]["hypocenter"]["longitude"])
                try:
                    dis = geodesic(cu_loc, tgt_loc).km
                except:
                    dis = "?"
                writeLcd(1, "{0}".format(data[0]["time"][11:19]))
                if dis == "?":
                    writeLcd(2, "S{0} D{1}".format(scale, dis))
                else:
                    writeLcd(2, "S{0} D{1:.0f}".format(scale, dis))
                lasteqid = data[0]["id"]
                for i in range(scale):
                    GPIO.output(P_RED, GPIO.HIGH)
                    wiringpi.softToneWrite(P_BUZ, BUZ_FREQ)
                    time.sleep(0.5)
                    GPIO.output(P_RED, GPIO.LOW)
                    wiringpi.softToneWrite(P_BUZ, 0)
                    time.sleep(0.5)
                time.sleep(10)
        else:
            writeLcd(1, "{0}".format(tm_now))
            writeLcd(2, "ReqErr")
            GPIO.output(P_YEL, GPIO.HIGH)
            time.sleep(0.5)
            GPIO.output(P_YEL, GPIO.LOW)
            time.sleep(0.5)
        time.sleep(5) #RoopTime ReadRule(https://www.p2pquake.net/develop/json_api_v2/)
    
except KeyboardInterrupt:
    print("End of Program")
    GPIO.cleanup()
    i2cCommand(0x01)
```
## 参考サイト
- 秋月電子さんが公開しているサンプルコードではエラーとなってしまいLCDのテストができませんでした。そのためLCDの表示方法については[CLOVER FIELD](https://www.cloverfield.co.jp/2019/06/12/%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4%E3%81%A7aqm0802i2c%E6%8E%A5%E7%B6%9A%E5%B0%8F%E5%9E%8Blcd%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%BF%E3%81%8A%E3%81%86/?doing_wp_cron=1711411890.8239998817443847656250)さんの記事なども参考にさせていただきました。なお、LCDのデータシートについては[秋月電子](https://akizukidenshi.com/catalog/g/g109422/)さんのサイトで公開されていますのでこちらも参考になるでしょう。
## 最後に
~~バックライト付きのLCDであるためはじめは明るく光っていたのですが途中から光らなくなってしまいました。コードなどを見直してみたのですが原因不明です。部品の故障かな？何かわかる方があればコメントくださると嬉しいです。~~
追記　LCDのバックライトが点灯しなかった件は自己解決しました。秋月電子さんの公開されているサンプルプログラムを見直していたところ、バックライトはそもそもi2c側で制御するのではなくGPIO側で制御することに気がつきました。そのためINITの部分にバックライト点灯のため以下コードを追記しています。
```python
BK_LED = 26 #BackLightPinNumber
GPIO.setup(BK_LED,GPIO.OUT)
GPIO.output(BK_LED,True)
```
これでばっちりピカピカに点灯してくれています。

## 課題
- 震央の座標がうまく取れない場合があり（元データがない？）、その場合はエラーでプログラムが停止してしまいます。
  - 少しコードが複雑になってしまいましたが、一旦データが不正な場合は震央までの距離を？で表示することにしました。