Python3 + Flask + PySNMP + Highcharts + Apache2.4で、PX-105のインク残量を取得・表示し、Gmailでインク残量を送信する

以前、Ruby + SinatraでPX-105のインク残量を取得・表示したことがありました。
Ruby + Sinatra + SNMPでPX-105のインク残量を取得・表示する - メモ的な思考的な

 
最近さわっているPythonでも同じことができないかと思い、Python3 + Flask + PySNMP + Highcharts + Apache2.4 + Gmail APIで実装してみました。

 
ソースコードGitHubへアップしてあります。
thinkAmi/printer-status-py

 

環境

  • Windows10 x64
  • Python 3.5.1 32bit
  • Flask 0.11
  • google-api-python-client 1.5.1
  • PySNMP 4.3.2
  • Highcharts
  • Apache 2.4.10
    • 他のアプリ向けのバーチャルホストがすでに設定されている前提
  • mod_wsgi 4.5.2

 

Flaskアプリについて

PX-105からPySNMPを使ってインク情報を取得する

大きな流れはRubyで行った時と同じです。

PySNMPで

g = getCmd(SnmpEngine(),
    CommunityData(config.COMMUNITY, mpModel=0),
    UdpTransportTarget((config.PRINTER_HOST_IPV4, 161)),
    ContextData(),
    ObjectType(ObjectIdentity("{mib_id}{tail}".format(**vars()))))
    
errIndication, errorStatus, errorIndex, varBinds = next(g)

とすると、変数varBindsにGET response値が設定されるため、欲しい情報はvarBinds[0][1]で取り出せます。

ただ、varBinds[0][1]の値はOctetStringオブジェクトであり、そのままでは扱いづらいことから、必要に応じてint()やstr()で変換します。

 
なお、Flaskには以下のようにconfigファイルから設定値を取得できる機能があります。
python - How to import from config file in Flask? - Stack Overflow

ただ、今回は後述の通り、タスクマネージャーから動かすPythonスクリプトでもその設定値を参照するため、使えませんでした。

 
また、ローカル変数の列挙・展開をするため、以下を参考にvars()を使いました。
vars()関数によるローカル変数の列挙 | Python Snippets

 

FlaskでHighchartsを使う

以下を参考に、Highchartsへ値を渡します。
Using Flask to output Python data to High Charts

今回、Highchartsの棒グラフを複数表示するため、chart用オブジェクトのリストをテンプレートへと渡しています。

main.py

from flask import Flask, render_template
from printer import PX105

app = Flask(__name__)
app.config.from_object('config')

class HighCharts(object):
    def __init__(self, chart_id, chart, series, xAxis, plot_options):
        self.chart_id = chart_id
        self.chart = chart
        self.series = series
        self.xAxis = xAxis
        self.plot_options = plot_options


@app.route("/")
@app.route("/px105")
def index():
    p = PX105()
    
    charts = []
    for i, tank in enumerate(p.tanks):
        id = "chart_{}".format(i)
        charts.append(HighCharts(
            chart_id = id,
            chart = {"renderTo": id, "type": "column", "height": 400,},
            series = [{"name": "使用済", "data": [100 - tank.rest_volume], "pointWidth": 40, "color": "gray"},
                      {"name": "インク残量", "data": [tank.rest_volume], "pointWidth": 40, "color": tank.color}],
            xAxis = {"categories": [tank.name]},
            plot_options = { "column": { "stacking": "percent"}},
        ))
        
    return render_template("index.html", charts=charts, title="PX-105")


if __name__ == "__main__":
    app.run(debug = True, host="0.0.0.0", port=8080, passthrough_errors=True)

 
Highchartsで描画する部分のテンプレートは以下の通りです。

templates\index.html

<div id="container">
    {% for chart in charts %}
        <div id={{ chart.chart_id|safe }} class="chart" style="height:400px; width: 200px;"></div>
        
        <script>
            $(document).ready(function() {
                var options = {
                    "title": { "text": null },
                    "legend": { "layout": "vertical" },
                    "yAxis": { "title": { "text": null } },
                    "credits": { "enabled": false },
                    "chart": {{ chart.chart|safe }},
                    "xAxis": {{ chart.xAxis|safe }},
                    "series": {{ chart.series|safe }},
                    "plotOptions": {{ chart.plot_options|safe }},
                };
                $({{ chart.chart_id|safe }}).highcharts(options);
            });
        </script>
    {% endfor %}
</div>

 
static\style.css

div.chart {
    display: table-cell;
}

 
ここまででローカルで実行する準備ができたため、

(env) D:\printer_status_py>python main.py

のようにしてFlaskを起動し、http://localhost:8080/へアクセスして、動作を確認します。

 

Apacheで動作させる

以下を参考に、ApacheでFlaskを動かす設定を行います。

 

app.wsgiファイルの作成

Flaskのドキュメントに従って記述します。

app.wsgi

import sys, os

sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))

from main import app as application

 

Apache用のconf\extra\httpd-vhosts.conf へ追加

すでにApache2.4がバーチャルホストで動作している環境へと追加するため、以下の項目を httpd-vhosts.conf ファイルへと追加します。

...
# 既存のWSGIPythonPathに、今回のアプリとvirtualenv環境を追加
# Windowsなので、区切りはセミコロン(;)
WSGIPythonPath "...;D:\printer_status_py;D:\printer_status_py/env/Lib/site-packages"

<VirtualHost *:80>
    # DNSのCNAMEで追加した名前
    ServerName px105-status
    
    ErrorLog "logs/printer-status-error.log"

    # Flask app
    WSGIScriptAlias / "D:/printer_status_py/app.wsgi"
    <Directory "D:/printer_status_py">
        <Files app.wsgi>
            Require all granted
        </Files>
    </Directory>
</VirtualHost>

 
あとはApacheのサービスを再起動して、http://px105-status/にアクセスした時に、PX-105のインク残量が確認できればOKです。

 

インク残量の定期メール送信

Ruby + Sinatraではrufus-schedulerを使っていたので、Flaskでも同じことができないかを探したところAPSchedulerがありました。Flask用もあり良さそうでした。

ただ、mod_wsgiを使っている環境だとダメそうでした。
python: APScheduler in WSGI app - Stack Overflow

 
そのため、Windowsのタスクスケジューラーを使って、メール送信スクリプトを定期的に叩くようにしました。なお、スクリプトは以前の記事を参考に作成します。
Python3 + google-api-python-clientで、Gmail APIを使ってメールを送信する - メモ的な思考的な

タスクスケジューラのタスクの作成にて設定する内容は以下の通りです。

  • 操作タブのプログラム/スクリプトは、virtualenv環境のpython.exe
  • 操作タブの引数の追加は、定期メール送信のPythonスクリプト(gmai.reminder.py)

をそれぞれフルパスで指定します。

タブ 項目
全般 名前 printer-status (任意)
全般 タスクの実行時に使うユーザーアカウント 任意のアカウント
全般 ユーザーがログオンしているかどうかにかかわらず実行する ●選択
全般 最上位の特権で実行する ■チェックする
トリガー 開始 毎日
トリガー 間隔 1日
操作 操作 プログラムの開始
操作 プログラム/スクリプト D:\printer_status_py\env\Scripts\python.exe
操作 引数の追加 D:\printer_status_py\gmail_reminder.py
操作 開始 空白

 
タスクを作り終えたら、タスクを実行して問題なく動作するかを確認します。

 

ソースコード

GitHubに上げました。
thinkAmi/printer-status-py

 

その他参考

Flask でアプリケーションを作る際のメモ(2015 年版) - Memo