Flask-Bootstrap + Ajax(jQuery) + RPi.GPIOでLEDを制御してみる

ここ何回かにわたって、pythonでFlask-BootstrapやAjaxのお話をしてきた。

なぜ急にFlaskでAjaxだったのか。実は今回のこれがやりたかった。 yoctoで作ったOSをより組み込み機器っぽくするために、Webインターフェースによる設定画面やハードウェア制御をできるようにしたかったのだ。

そのためyoctoでパッケージが提供されていて、使い方も比較的シンプルなものを探したところ、Flask-Bootstrapが見つかった。

作成するもの

こんな感じで、ブラウザからラズベリーパイに接続したLEDを点灯/消灯できるようにする。

f:id:mickey_happygolucky:20180402233900p:plain

サーバとPCはEthernetで接続し、データの送受信はAjaxで行なう。 Ajaxを使うのは、LED制御時にいちいち画面を遷移させたくないため。

ディレクトリ構成

ディレクトリ構成はFlaskの時と同じ。

.
├── led.py
└── templates
    └── led.html

JSONデータ

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()関数を呼び出している。

led.py(pythonスクリプト)

python側のスクリプトを次に示す。

# coding: utf-8
from flask import Flask, render_template, request, jsonify
from flask_bootstrap import Bootstrap
#import RPi.GPIO as GPIO


# for Flask-Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)

# GPIO pins for LED
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