ここ何回かにわたって、pythonでFlask-BootstrapやAjaxのお話をしてきた。
なぜ急にFlaskでAjaxだったのか。実は今回のこれがやりたかった。
yoctoで作ったOSをより組み込み機器っぽくするために、Webインターフェースによる設定画面やハードウェア制御をできるようにしたかったのだ。
そのためyoctoでパッケージが提供されていて、使い方も比較的シンプルなものを探したところ、Flask-Bootstrapが見つかった。
作成するもの
こんな感じで、ブラウザからラズベリーパイに接続したLEDを点灯/消灯できるようにする。
サーバとPCはEthernetで接続し、データの送受信はAjaxで行なう。
Ajaxを使うのは、LED制御時にいちいち画面を遷移させたくないため。
ディレクトリ構成はFlaskの時と同じ。
.
├── led.py
└── templates
└── led.html
Ajaxでやり取りするのはJSON形式のデータで次のような構造を持っている。
例としてここでは例としてled
の値に13、status
の値に0を指定している。
{
"led": 13,
"status": 0
}
キー |
値 |
概要 |
led |
13 |
LEDのGPIO番号(BCM) |
status |
0 |
0=消灯/1=点灯 |
led.html(htmlファイル)
led.htmlの内容を次に示す。
分割して掲載する。
先頭〜contentブロック
{% extends "bootstrap/base.html" %}
{% block title %}LED{% endblock title %}
{% block content %}
<div class="container">
<div class="page-header">
{% for led in leds %}
<p>
<label for="led{{led}}">led{{led}} = off</label>
<button id="led{{led}}" value="1">on</button>
</p>
{% endfor %}
</div>
</div>
{% endblock content %}
ラベルとボタンの組み合わせを表示している。
ボタンの表示及びvalueは「押された時にどの状態にするか」を保持している。
leds
はLEDの番号が入ったリストをFlaskから渡される。
scriptsブロック
led.htmlの残りの部分を次に示す。
{{ super() }}
は忘れずに。
setStatus()関数と、各ボタンのclick()関数に分けることができる。
{% block scripts %}
{{ super() }}
<script>
function setStatus(label, button, data) {
if (data.status == 0) {
label.text('led'+data.led+' = off')
button.val(1+'');
button.text('on');
} else {
label.text('led'+data.led+' = on')
button.val(0+'');
button.text('off');
}
}
{% for led in leds %}
$(function() {
$('#led{{led}}').click(function() {
let status = parseInt($('#led{{led}}').val());
let jsonData = JSON.stringify({"led":{{led}}, "status":status});
console.log(jsonData);
$.ajax({
url: '{{ url_for('led_change') }}',
data: jsonData,
contentType: 'application/json;charset=UTF-8',
type: 'POST'
}).done(function(data){
let label = $('label[for=led'+data.led+']');
let button = $('#led'+data.led);
setStatus(label, button, data);
}).fail(function(){
console.log('fail');
});
})
});
{% endfor %}
</script>
{% endblock scripts %}
setStatus()はボタンの状態を変更する関数でajaxのdone()から呼び出される。
ボタンに設定するvalue
は文字列型である必要があるため1+''
や0+''
で変換している。
dataはajaxでサーバから返されたJSON形式の文字列。
内容はサーバに送信したものと同じになる。
各ボタンのclick()関数はボタンをクリックされた時に、対応するボタンのclick()関数が呼び出される。
これらの関数はleds
のリストに入っている分だけ生成される。
処理内容は、JSON形式の文字列を生成し、ajaxでサーバに送信。
帰ってきた内容をdone()で処理する。
done()では、ボタンとラベルを検索し、setStatus()関数を呼び出している。
python側のスクリプトを次に示す。
from flask import Flask, render_template, request, jsonify
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
leds = [13, 14, 15]
@app.route('/')
def root():
return render_template('led.html', leds=leds)
@app.route('/ledChange', methods=['POST'])
def led_change():
id = request.json['led']
status = request.json['status']
GPIO.output(id, status)
return jsonify({'led': id, 'status': status})
if __name__ == '__main__':
GPIO.setmode(GPIO.BCM)
GPIO.setup(leds, GPIO.OUT)
app.run(host='0.0.0.0')
GPIO.cleanup()
サイトのルートにアクセスされると、led.htmlの内容を返す。
ブラウザでボタンをクリックされると「/ledChange」にアクセスされ、led_change()
関数が呼び出される。
led_change()ではGPIO.output()により、GPIOの状態を設定している。
GPIO.cleanup()はおそらく到達することは無いとおもう。
yocto環境
今回はrockoブランチを使用している。
local.conf
このスクリプトが動作する環境をyoctoで作るにはlocal.conf
を次のようにする。
MACHINE = "raspberrypi3"
DL_DIR = "${TOPDIR}/../downloads"
IMAGE_INSTALL_append = " \
rpi-gpio \
python-flask \
python-flask-bootstrap \
"
bblayers.conf
必要なレイヤを追加するには次のレイヤを追加する。
- meta-oe
- meta-python
- meta-raspberrypi