Pythonコード(第四回)

Raspberry Pi でのコード開発を想定しています。ラズパイにSSH接続できたらPythonコードを作成してください。

目次

  1. ラズパイのCPU温度を表示する
  2. CPU情報を表示するAPIアプリをFlaskサーバーで動かす
  3. CPU情報表示を5秒毎に更新するHTMLをつくる
  4. CPU情報をグラフ表示する

1. ラズパイのCPU温度を表示する

LED点灯で学習したように、PCのハードウェア情報はファイルに記述されているので、ファイルを読むと情報がわかる。
コンピュータのCPU温度もファイルを読んで表示できるので試してみよう。

新しいファイルを[$ nano try-gettemp.py]で作る

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def get_cpu_temp():
    """ Raspberry Pi のCPU温度を取得するコード """
    f = open("/sys/class/thermal/thermal_zone0/temp", "r")
    t = int(f.read().strip())
    f.close()
    return(t / 1000.0)

if __name__ == "__main__":
    temp = get_cpu_temp()
    print(f"CPU温度: {temp:.1f} °C")

実行してみる

$ python try-gettemp.py
CPU温度: 39.4 °C

LEVEL UP

ファイルを読めなかったなどエラーが発生したとき f.close() が実行されずファイルを閉じれない場合に備えてエラー対策をしたコード例
try: で実行したコードが失敗しても finally: の次が実行される

def get_cpu_temp():
    try:
        f = open("/sys/class/thermal/thermal_zone0/temp", "r")
        t = int(f.read().strip())
    finally:
        f.close()
    return(t / 1000.0)

さらにLEVEL UP

ファイル open と close を一行で実行する書き方。
温度情報が存在しなかった場合の例外も考慮した書き方。つまり完璧版

def get_cpu_temp():
    try:
        with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
            t = int(f.read().strip())
        return t / 1000.0  # ミリ度 → 度C
    except Exception:
        return None

練習

CPU情報を表示してみる。下記を実行してどんな情報がわかるか調べてみよう
例えば、プロセッサー番号(processor)が何個あるか調べるとCPUコア数がわかる

$ cat /proc/cpuinfo

今度はメモリ情報を表示してみる。
MemTotal(kB)に1024をかけるとGB表記になる[8008420 kB × 1024 = 8,200,622,080 バイト(約 8GB)]

$ cat /proc/meminfo

2. CPU情報を表示するAPIアプリをFlaskサーバーで動かす

最初にFlaskサーバーが動くか復習しよう。下記コードを新規作成する[$ nano try-gettemp-api.py]。

# !/usr/bin/env python3
# -*- coding: utf-8 -*-

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return 'ラズパイからの応答です。'

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=3001)

APIアプリを起動する

$ python try-gettemp-api.py

ブラウザで実行してみる(ラズパイ番号は自分の番号にすること)

http://razpi00.local:3001/

使っていないポート番号を使う。もし、既にポートが使われている場合はポート番号を変更する
ポート番号が使われているか確認するコマンド。下記を実行して、使われていたらポート番号が表示される

$ ss | grep :3001

CPU情報を表示する。[if name == “main”:]より前に下記[/cpu_temp]機能を追加する。

@app.route('/cpu_temp')
def get_cpu_temp():
    """ Raspberry Pi のCPU温度を取得して表示するコード """
    try:
        with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
            t = int(f.read().strip())
        temp = t / 1000.0
    except Exception as e:
        temp = 0
    finally:
        return f"CPU温度: {temp:.1f} °C"

ブラウザで実行してみる(ラズパイ番号は自分の番号にすること)

http://razpi00.local:3001/cpu_temp

3. CPU情報表示を5秒毎に更新するHTMLをつくる

HTMLファイルは、pythonコードと同じフォルダから配信するので[send_from_directory]モジュールを追加する
新しいファイルを作成する[$ nano try-gettemp-api-html.py]

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Raspberry Pi の CPU 温度を
1) ブラウザから HTTP で取りに行く
2) 5 秒ごとに画面を自動更新する
ためのシンプルな学習用サーバです。

- /cpu_temp   : 今の CPU 温度を文字で返す API
- /monitoring : CPU 温度を表示する HTML ページ
"""

from flask import Flask, send_from_directory

app = Flask(__name__)


def read_cpu_temp():
    """Raspberry Pi の CPU 温度(℃)を 1 回だけ読み取って返す関数"""
    try:
        # /sys/class/thermal/... という特別なファイルから温度(ミリ度)を読む
        with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
            t = int(f.read().strip())
        # 例えば 38000 → 38.0 ℃ に直す
        return t / 1000.0
    except Exception:
        # 何かエラーが起きたら None を返す
        return None


@app.route("/cpu_temp")
def get_cpu_temp():
    """ブラウザから /cpu_temp にアクセスされたときに呼ばれる関数"""
    temp = read_cpu_temp()

    if temp is None:
        # 温度が読めなかったときのメッセージ
        return "CPU温度を取得できませんでした"

    # 正常に読めたときは「CPU温度: 38.9 °C」のような文字列を返す
    return f"CPU温度: {temp:.1f} °C"


@app.route("/monitoring")
def monitoring_page():
    """CPU 温度を 5 秒ごとに見るための HTML を返す"""
    # この Python ファイルと同じフォルダにある try-gettemp.html を返す
    return send_from_directory(".", "try-gettemp.html")


if __name__ == "__main__":
    # 0.0.0.0:3001 で待ち受ける
    # ブラウザから http://ラズパイのIPアドレス:3001/monitoring にアクセスする
    app.run(host="0.0.0.0", port=3001)

HTMLファイルを作成する[$ nano try-gettemp.html]

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>Raspberry Pi CPU温度モニタ (5秒ごと更新)</title>
  <style>
    body {
      font-family: sans-serif;
      margin: 2rem;
    }
    h1 {
      font-size: 1.8rem;
    }
    #cpu-temp {
      font-size: 2rem;
      margin-top: 1rem;
    }
    #updated-at {
      margin-top: 0.5rem;
      font-size: 1rem;
      color: #555;
    }
    #status {
      margin-top: 0.5rem;
      font-size: 0.9rem;
      color: #0066cc;
    }
  </style>
</head>
<body>
  <!-- 画面のタイトル -->
  <h1>Raspberry Pi CPU温度モニタ</h1>

  <!-- 今の CPU 温度を表示する場所 -->
  <div id="cpu-temp">CPU温度: --.- °C</div>

  <!-- 最後に更新した時刻を表示する場所 -->
  <div id="updated-at">更新時刻: --:--:--</div>

  <!-- 通信の状態(成功 / 失敗など)を表示する場所 -->
  <div id="status">状態: まだ取得していません</div>

  <script>
    // HTML から要素を 3 つ取り出して、変数に入れる
    const cpuTempDiv = document.getElementById("cpu-temp");
    const updatedAtDiv = document.getElementById("updated-at");
    const statusDiv = document.getElementById("status");

    // 1回だけ /cpu_temp にアクセスして、CPU温度をとってくる関数
    function fetchCpuTempOnce() {
      // 状態を「取得中」にしておく
      statusDiv.textContent = "状態: CPU温度を取得中...";

      // 同じサーバー(3001番ポート)の /cpu_temp にアクセスする
      // HTML も同じサーバーから配信されているので、相対パスでOK
      fetch("/cpu_temp")
        .then(function (response) {
          // サーバーからの返事(response)が OK (200系) かどうかをチェック
          if (!response.ok) {
            throw new Error("HTTPエラー: " + response.status);
          }
          // 返事の中身を「文字」として取り出す
          return response.text();
        })
        .then(function (text) {
          // text には「CPU温度: 38.9 °C」などが入っている
          // その文字を、そのまま画面に表示する
          cpuTempDiv.textContent = text;

          // ここで「今の時刻」を作って、きれいな形(時:分:秒)にする
          const now = new Date();
          const hh = String(now.getHours()).padStart(2, "0");
          const mm = String(now.getMinutes()).padStart(2, "0");
          const ss = String(now.getSeconds()).padStart(2, "0");

          // 最終更新時刻を画面に表示
          updatedAtDiv.textContent = "更新時刻: " + hh + ":" + mm + ":" + ss;

          // 状態を「成功」に更新
          statusDiv.textContent = "状態: 最新のCPU温度を取得しました";
        })
        .catch(function (error) {
          // ネットワークエラーなどが起きたときにここに来る
          statusDiv.textContent = "状態: CPU温度の取得に失敗しました (" + error.message + ")";
        });
    }

    // ページを開いたときに、まず1回だけ取得してみる
    fetchCpuTempOnce();

    // そのあと、5秒(5000ミリ秒)ごとに繰り返し取得する
    const intervalMs = 5000; // ミリ秒単位
    setInterval(fetchCpuTempOnce, intervalMs);
  </script>
</body>
</html>

2つのステップで検査する。
STEP1. APIアプリを起動する[$ python try-gettemp-api-html.py]
STEP2. ブラウザでアクセスする[http://razpi00.local:3001/monitoring]

4. CPU情報をグラフ表示する

グラフ描画は Google Charts を使う。さまざまなグラフがあるのでWEBサイトを見学しておいてください。

pythonコード[try-gettemp-api-html.py]に下記コードを追加する

@app.route("/graph")
def graph_page():
    """CPU 温度の時系列グラフを表示する HTML を返す"""
    # 同じフォルダにある try-gettemp-graph.html を返す
    return send_from_directory(".", "try-gettemp-graph.html")

新しいHTML[$ nano try-gettemp-graph.html]を作ります

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>Raspberry Pi CPU温度グラフ</title>

  <!-- 画面の見た目を少し整えるCSS -->
  <style>
    body {
      font-family: sans-serif;
      margin: 2rem;
    }

    h1 {
      font-size: 1.8rem;
      margin-bottom: 1rem;
    }

    #current-temp {
      font-size: 1.4rem;
      margin-top: 0.5rem;
    }

    #current-time {
      margin-top: 0.3rem;
      color: #555;
    }

    #status {
      margin-top: 0.3rem;
      font-size: 0.9rem;
      color: #0066cc;
    }

    /* グラフを表示するエリア(幅100%、高さ400px) */
    #chart-container {
      width: 100%;
      height: 400px;
      margin-top: 1.5rem;
      border: 1px solid #ccc;
    }
  </style>

  <!-- Google Charts のライブラリを読み込むスクリプト -->
  <script src="https://www.gstatic.com/charts/loader.js"></script>

  <script>
    // ================================================
    // 1. Google Charts の準備
    // ================================================

    // "corechart" というパッケージを使う(折れ線グラフなど)
    google.charts.load("current", { packages: ["corechart"] });

    // ライブラリの読み込みが終わったら、initChart 関数を呼ぶように予約する
    google.charts.setOnLoadCallback(initChart);

    // グラフの「元データ」を入れておく表(DataTable)
    let dataTable;

    // 折れ線グラフを表示するためのオブジェクト
    let chart;

    // グラフの見た目の設定(タイトルや軸ラベルなど)
    let chartOptions;

    // 「直近何点までグラフに表示するか」を決める(例: 60点 → 約5分分など)
    const MAX_POINTS = 60;

    // ================================================
    // 2. 初期化処理(最初の1回だけ呼ばれる)
    // ================================================
    function initChart() {
      // HTML 上のいろいろな要素を JavaScript から触れるようにしておく
      const currentTempDiv = document.getElementById("current-temp");
      const currentTimeDiv = document.getElementById("current-time");
      const statusDiv = document.getElementById("status");
      const chartContainer = document.getElementById("chart-container");

      // ---- DataTable(表)の作成 ----
      // new DataTable() で「表」を作るイメージ
      dataTable = new google.visualization.DataTable();

      // 列(カラム)を2つ追加する
      // 1列目: 時刻(Date型)
      dataTable.addColumn("datetime", "時刻");
      // 2列目: CPU温度(数値)
      dataTable.addColumn("number", "CPU温度(℃)");

      // ---- グラフオブジェクトの作成 ----
      // 折れ線グラフ(LineChart)を、この chartContainer の中に描く
      chart = new google.visualization.LineChart(chartContainer);

      // ---- グラフの見た目の設定 ----
      chartOptions = {
        title: "CPU温度の推移",
        legend: { position: "bottom" },
        hAxis: {
          title: "時刻",
          // 時刻のフォーマット(例: 12:34:56)
          format: "HH:mm:ss",
        },
        vAxis: {
          title: "CPU温度 (℃)",
        },
        // 線をなめらかにする(true にすると曲線、false だと直線)
        curveType: "function",
      };

      // 最初に「空のグラフ」を1回描いておく
      chart.draw(dataTable, chartOptions);

      // ---- 最初の1回だけデータを取りに行く ----
      fetchAndUpdateOnce(currentTempDiv, currentTimeDiv, statusDiv);

      // ---- その後は一定間隔(ここでは5秒)ごとに繰り返す ----
      const intervalMs = 5000; // 5000ミリ秒 = 5秒
      setInterval(function () {
        fetchAndUpdateOnce(currentTempDiv, currentTimeDiv, statusDiv);
      }, intervalMs);
    }

    // ================================================
    // 3. 1回分のデータ取得とグラフ更新の処理
    // ================================================
    function fetchAndUpdateOnce(currentTempDiv, currentTimeDiv, statusDiv) {
      // 状態を「取得中」にする
      statusDiv.textContent = "状態: CPU温度を取得中...";

      // 同じサーバー上の /cpu_temp にアクセスして、温度の文字列をもらう
      // 例: 「CPU温度: 38.9 °C」
      fetch("/cpu_temp")
        .then(function (response) {
          if (!response.ok) {
            // 200番台以外のステータスコードならエラーとみなす
            throw new Error("HTTPエラー: " + response.status);
          }
          // レスポンスの中身を「文字」として取り出す
          return response.text();
        })
        .then(function (text) {
          // text には例えば「CPU温度: 38.9 °C」が入っている

          // ---- 文字列から数値の部分だけを取り出す ----
          // コロン(:)で分けて、右側をさらに空白で分ける…など簡単な方法でOK
          let tempValue = null;
          try {
            // 「CPU温度: 38.9 °C」を["CPU温度", " 38.9 °C"]に分割
            const parts = text.split(":");
            if (parts.length >= 2) {
              // 右側 " 38.9 °C" から数値だけを取り出す
              const right = parts[1];
              // 空白で分けて、最初の要素を数値に変換
              const numStr = right.trim().split(" ")[0];
              tempValue = parseFloat(numStr);
            }
          } catch (e) {
            tempValue = null;
          }

          if (isNaN(tempValue)) {
            throw new Error("温度の数値を読み取れませんでした: " + text);
          }

          // ---- ここからグラフ用の処理 ----

          // 今の時刻を取得(Dateオブジェクト)
          const now = new Date();

          // DataTable に 1 行追加する
          // [今の時刻, 温度の数値] という形
          dataTable.addRow([now, tempValue]);

          // データが多くなりすぎないように、古い行を削除する
          const numRows = dataTable.getNumberOfRows();
          if (numRows > MAX_POINTS) {
            // 一番古い行(0行目)を1つ削除
            dataTable.removeRow(0);
          }

          // 現在の温度を画面に表示
          currentTempDiv.textContent = "現在のCPU温度: " + tempValue.toFixed(1) + " ℃";

          // 現在時刻を「HH:MM:SS」形式の文字列にする
          const hh = String(now.getHours()).padStart(2, "0");
          const mm = String(now.getMinutes()).padStart(2, "0");
          const ss = String(now.getSeconds()).padStart(2, "0");
          currentTimeDiv.textContent = "最終更新: " + hh + ":" + mm + ":" + ss;

          // 状態を「成功」に更新
          statusDiv.textContent = "状態: 最新のCPU温度を取得しました";

          // グラフを再描画する(同じ dataTable と chartOptions を使う)
          chart.draw(dataTable, chartOptions);
        })
        .catch(function (error) {
          // ネットワークエラーや、上で投げたエラーが来たときにここに入る
          statusDiv.textContent = "状態: CPU温度の取得に失敗しました (" + error.message + ")";
        });
    }
  </script>
</head>

<body>
  <h1>Raspberry Pi CPU温度グラフ</h1>

  <!-- 現在のCPU温度を文字で表示する場所 -->
  <div id="current-temp">現在のCPU温度: --.- ℃</div>

  <!-- 最後にデータを更新した時刻を表示する場所 -->
  <div id="current-time">最終更新: --:--:--</div>

  <!-- 通信状態(取得中 / 成功 / 失敗など)を表示する場所 -->
  <div id="status">状態: まだ取得していません</div>

  <!-- 折れ線グラフを描くための箱(div) -->
  <div id="chart-container"></div>
</body>
</html>

APIアプリを停止して再起動します。
ブラウザでアクセスします[http://razpi00.local:3001/graph]

グラフの見た目を変更

折れ線グラフにするには107行目の[curveType: “function”,]をコメントアウトします(先頭に//を追加)

 // curveType: "function",

棒グラフにするには92行目のグラフ名を変更します

// chart = new google.visualization.LineChart(chartContainer); // 折れ線グラフ
chart = new google.visualization.ColumnChart(chartContainer); // 棒グラフ

今月はここまで。

コメントを残す