0. はじめに
今回はラズパイを使った簡単なIoTプロジェクトとして、LEDをWebブラウザから制御するシステムを作ってみましたので備忘録もかねて記事にしました。
最近、ラズパイでforgejoサーバーを立ち上げました。1
forgejoの運用だけではもったいないと思い、ラズパイでWebサーバーも立ち上げることにしました。
IoTにも興味があったため、ラズパイでホストしたWebアプリから別のラズパイへ信号を送るシステムを作ってみました。
Webアプリからラズパイへ信号を送る方法としてはクラウドサービスを利用する方法が有力だと思いますが、お金がかかったりクラウドについての学習も必要なため、ハードルが高いように感じます。
今回はラズパイのみで構成されているため、ハードルは低いと思います。(ラズパイを2台使用しているため、お金は少しかかっていますが、、、)
ラズパイを使って電子工作をちょっとやったことはあるが、Webにはあまり詳しくない方が本記事の対象になります。
1. やりたいこと
WebアプリからLEDを操作したい。
2. 実装概要
構成図
この図は今回作成したものの構成図を示しています。
WebサイトサーバーにRaspberry Pi 5を、WebアプリケーションサーバーにRaspberry Pi 4を使用していますが、家にあったものを使っているだけなので特に意味はありません。
現状はPCのWebブラウザからはアクセスできるのですが、スマホのWebブラウザからWebアプリケーションサーバーにリクエストを送信することはできません。
原因がすぐにわかっていないため対処できていないですが、今後スマホのWebブラウザからでもリクエストを送信できるようにしたいです。
シーケンス図
この図はブラウザからラズパイにリクエストが送信され、LEDを制御する全体の流れを示しています。
- ユーザーがPCのWebブラウザからWebサーバーにアクセスし、Webページを閲覧する。
- Webページのボタンを押すと、WebブラウザからWebアプリケーションサーバーであるRaspberry Pi 4にリクエストが送られ、Raspberry Pi 4につながっているLEDが点灯/消灯する。
3. Webサーバー実装
Webサーバーのミドルウェアとしてはapacheとnginxが有名なようです。2
今回は私がWebに明るくないということもあり、比較的簡単に設定ができると噂のapacheを採用しました。
本項目の作業はWebサイトサーバーを実装するラズパイで実行してください。
apache2の設定
確か参考サイトのように設定したと思います、、、
3
apache2のインストール
sudo apt update
sudo apt upgrade
sudo apt install apache2
ラズパイと同じプライベートネットワーク内の別のPCからWebページにアクセスしてみましょう。
まずラズパイでifconfig
コマンドを実行し、IPアドレスを調べます。
次に別のPCのWebブラウザからそのIPアドレスにアクセスしてページが表示されたらOK!
httpsの設定
Webサイトをhttps設定しないと、Webブラウザ、Webサイトサーバー、Webアプリケーションサーバーが同一のプライベートネットワークに所属していてもWebブラウザからWebアプリケーションサーバーにhttpリクエストを送ることができません。4
参考サイトに基づいて設定しましたが、一部操作(下記6,7)が異なったので、備忘録もかねて記載します。
- SSLモジュールの導入
sudo a2enmod ssl
- 秘密鍵の生成
openssl genpkey -algorithm RSA -out server.key
いろいろ聞かれますが、とりあえず全部Enterを押せばOKです。
- サーバ証明書の生成
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
- 秘密鍵と証明書を適切な場所に配置
sudo mv server.key /etc/apache2/ssl/
sudo mv server.crt /etc/apache2/ssl/
- 証明書と秘密鍵のパーミッション設定
sudo chmod 600 /etc/apache2/ssl/server.key
sudo chmod 644 /etc/apache2/ssl/server.crt
- SSLモジュールの適切な設定
sudo nano /etc/apache2/sites-available/default-ssl.conf
変更前
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
変更後
SSLCertificateFile /etc/apache2/ssl/server.crt
SSLCertificateKeyFile /etc/apache2/ssl/server.key
- 設定へのパス付け
sudo apache2ctl configtest
sudo apachectl configtest
sudo service apache2 restart
Webサイト実装
下記コマンドでapache2でホストされているhtmlを表示します。(上3行で元のindex.htmlのバックアップを取っています)
cd /var/www/html/
sudo mkdir backup
sudo mv index.html backup/
sudo nano /var/www/html/index.html
その後、index.htmlを下記のように編集します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="styles.css">
<script language="javascript" type="text/javascript">
function OnButtonClick() {
var request = new XMLHttpRequest();
request.open("GET", "http://127.0.0.1:5000/on");
request.send();
}
function OffButtonClick() {
var request = new XMLHttpRequest();
request.open("GET", "http://127.0.0.1:5000/off");
request.send();
}
</script>
</head>
<body>
<h1>LED</h1>
<button class="led_turn_on"
type="button" onclick="OnButtonClick();">turn on</button>
<button class="led_turn_off"
type="button" onclick="OffButtonClick();">turn off</button>
</body>
</html>
cssも実装して、少し見た目をよくします。
sudo nano /var/www/html/styles.css
h1 {
font-size: 400%;
width: 25%;
position: relative;
padding: 0.25em 1em;
border-top: solid 2px black;
border-bottom: solid 2px black;
}
h1:before, h1:after {
content: '';
position: absolute;
top: -7px;
width: 2px;
height: -Webkit-calc(100% + 14px);
height: calc(100% + 14px);
background-color: black;
}
h1:before {
left: 7px;
}
h1:after {
right: 7px;
}
.led_turn_on{
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
width: 300px;
height: 300px;
background: #3eb370;
color: #FFF;
text-decoration: none;
text-align: center;
margin: 10px 0;
font-size: 400%;
}
.led_turn_off{
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
width: 300px;
height: 300px;
background: #7d7d7d;
color: #FFF;
text-decoration: none;
text-align: center;
margin: 10px 0;
font-size: 400%;
}
動作確認
その後、WebブラウザからWebサーバーにアクセスし、ページが表示されていればOKです。
4. Webアプリケーションサーバー実装
Webアプリケーションフレームワークとしては様々なものがありますが、今回は下記の2点からflask5を採用しました。
- ラズパイのGPIOをpythonで操作するため、pythonを使用したい。
- Webアプリケーションに詳しくないので、軽量なフレームワークを使用したい。
本項目の作業はWebアプリケーションサーバーを実装するラズパイで実行してください。
前準備
下記コマンドからモジュールをインストールしてください。
pip3 install Flask
pip3 install -U flask-cors
フォルダ構成
参考サイトを基に作成したフォルダ構成です。6
.
├── app
│ ├── __init__.py
│ ├── app.py
│ └── light_emitting_diode.py
└── run.py
実装
run.py
Webサーバを立ち上げる際に実行するファイルです。
from app.app import app
if __name__ == "__main__":
app.run()
app.py
アプリロジックを書くファイルです。リクエストされたURLに応じてどのような処理を実施するかを指定します。
<ラズパイのIPアドレス>はWebサイトを実装するラズパイのIPアドレスに書き換えてください。
from flask import Flask, jsonify
from flask_cors import CORS
from app.light_emitting_diode import LED
# ポイント⓵
app = Flask(__name__)
CORS(app, resources={
r"/on": {"origins": "<ラズパイのIPアドレス>"},
r"/off": {"origins": "<ラズパイのIPアドレス>"}
})
# インスタンス生成
led = LED()
@app.route("/on")
def led_turn_on():
"""http://127.0.0.1:5000/onにリクエストされると実行される関数"""
led.turn_on()
return jsonify({"status": "success", "message": "Device turned on"}), 200 # ポイント⓶
@app.route("/off")
def led_turn_off():
"""http://127.0.0.1:5000/offにリクエストされると実行される関数"""
led.turn_off()
return jsonify({"status": "success", "message": "Device turned off"}), 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=False)
解説
ポイント⓵
CORSを設定しないと、ブラウザの開発者ツールを確認するとエラーコードが出力されます。
CORSについてはこちらの動画が参考になりました。
ポイント⓶
returnを返さないとhttpリクエストを受信したときに正常に処理は実施されるのですが、flask側でワーニングが出力されます。
そのため、本実装では使用しないですがreturnを返しています。
light_emitting_diode.py
GPIOからLEDを操作する処理を記載しています。
import RPi.GPIO as GPIO
class LED:
def __init__(self):
"""初期化をする"""
self.ledGpio = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.ledGpio, GPIO.OUT)
def turn_on(self):
"""LEDを点灯させる"""
GPIO.output(self.ledGpio, True)
def turn_off(self):
"""LEDを消灯させる"""
GPIO.output(self.ledGpio, False)
動作確認
コードを実装したフォルダに移動し、下記コマンドから実装したflaskコードを実行します。
python3 run.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
のように表示されたら、別のターミナルを立ち上げ、下記コマンドを実行しましょう。
curl http://127.0.0.1:5000/on
{"message":"Device turned on","status":"success"}
とターミナルに表示されれば成功です!
(すでにラズパイに電子回路をつないでいれば、LEDが点灯します。)
同様にoffのリクエストも実施してみましょう。
curl http://127.0.0.1:5000/off
5. 電子回路実装
今回は簡単な回路を作成しました。
抵抗は390Ωのものを使用しました。
6. テスト
PCのWebブラウザからWebサーバーにアクセスし、Webサイトのボタンを押しましょう。
"1.やりたいこと"の動画のようにLEDが点灯すると成功です!!
7. さいごに
クラウドサービスを利用せずになんとかPCとラズパイ2台をつなげることができました。
普段は組み込みをメインで行っているので、モノとモノをネットワークでつなげることができてとてもうれしかったです。
実装中はWebブラウザからWebアプリケーションサーバーにリクエストをなかなか送ることができず苦労しましたが、その分いい勉強になりました。(CORSやプライベートネットワークの概念、リバースプロキシなど)
今回は電子回路を単純なLED点灯だけにしましたが、今後ももう少し機能を増やしたり、外部のネットワークからもWebサイトにアクセスできるようにしていきたいです。
99. https設定をしなくてもWebアプリケーションサーバーにリクエストを送信する方法
私の環境ではWebブラウザがのデフォルトの設定がプライベート ネットワーク リソースへのリクエストは、HTTPS ウェブページからのみ許可されるように設定されていたため、httpのホームページからWebアプリケーションサーバーにリクエストを送信することができなかったようです。
設定を変えるためにgoogle chromeの検索窓に下記のように入力してください。
chrome://flags/#block-insecure-private-network-requests
Block insecure private network requests.の項目をDisableに設定し、ブラウザを再起動してください。
すると、httpのホームページからWebアプリケーションサーバーにリクエストを送信することができるようになります。
しかし、セキュリティ上よくないと思いますので、あまりこのような設定はしない方がよいのではないかと思っています。