アナログ電圧計を使って、ヘッドレスPCのCPU使用率表示を作る

tl;dr

  • アナログ電圧計は、PWMでお手軽にアナログメーターを作ることができるよ。その記事を見つけて、非常に惹かれたよ。
  • ESP32-C3をWebサーバとして、送られてきた値をPWMに流すファームウェアを作ったよ。
  • GoでCPU使用率をHTTPで送るプログラムを作って、使ってみたよ。ヘッドレスLinuxPCとして使っているOrangePi5+のCPU使用率を表示できる様にしたよ。
  • LEDとかで光らせても良かったけど、自分には不要と感じたので、CPUとメモリ使用率を表示して完成としたよ。

アナログ電圧計をPCのCPUモニタとして使う記事

Hackadayでアナログ電圧計をCPUモニタとして使う記事を見つけました(ina_aniさんの紹介で知りました)。

hackaday.com

www.youtube.com

これを見て、非常に簡単で面白いアイディアだと思いました。実際に針が動くのは面白く、加えてアナログ電圧計であればPWMがあれば簡単に制御できます。

自分はMacを使いながら、ほとんどの開発はVS CodeのリモートSSH機能を使って、OrangePi5+上のUbuntuで開発しています。その時、CPUやメモリ使用率はターミナル上で表示させることはできますが、結構煩雑です。ですので、これを表示できるようにしたいという需要がありました。

アーキテクチャ

以下のような仕組みにするように考えました。

  • PCとマイコンの間はWiFiを使い、HTTP通信で送るようにする
  • ESP32-C3を使い、WebServerを立てる。受け取った値を、PWMとして出力して、表示する
  • PC上で、CPU使用率をHTTPで送るプログラムを動かす

PCとマイコン間は、USBで接続しても良いのですが、設置の簡単さからWiFiを使うことにしました。ESP32であれば、簡単にArduinoでWebServerを立てることができます。REST APIとして作ることにしました。以下のように送ることになります。なお、自宅内ネットワークのため、認証等はありません。

POST http://192.168.1.105/cpu

{"value": 50}

私はIoT機器の電子工作にはESP32-C3を良く使っています。ESP32-C3-WROOM-02は秋月で310円と、非常に安価に購入できます。今回もESP32-C3を使うようにしました。

今回は簡単な回路であり、加えて複数個作る予定もないため、PCBを制作せず、ユニバーサル基板で実装することにしました。回路図は以下に置いてあります。

KiCanvas 回路図

https://kicanvas.org/?github=https%3A%2F%2Fgithub.com%2F74th%2Fanalog-cpu-meter%2Fblob%2Fmain%2Fsemantics%2Fanalog-cpu-meter.kicad_sch

アナログ電圧計を用意する

アナログ電圧計には、表示上限の電圧があり、マイコンのIOの3.3Vで操作する都合、3Vの電圧計を使うことにしました。1個400円ほどで購入できました。

アナログ電圧計 長方形アナログボルトパネルメーターゲージ 10 スタイルボルト T - AliExpress

https://ja.aliexpress.com/item/1005006415730772.html

ESP32-C3で、出力するPWMを受け付けるWebServerを作る

ESP32-C3でWebServerを作るのは、Arduinoを使えば簡単です。加えてJSONを処理できるArduinoJsonを使えば、JSONの読み取りも比較的簡単にできます。

// src/main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <ssid.h>

const char *ssid = WIFI_SSID;
const char *pass = WIFI_PASSWORD;
WebServer server(80);

void handleCPU()
{
    const char *path = "cpu";

    if (server.method() != HTTP_POST)
    {
        server.send(405, "application/json", "{\"message\": \"Method Not Allowed.\"}");
        return;
    }
    Serial.printf("POST /%s\r\n", path);

    String requestBody = server.arg("plain");
    Serial.printf("request body: %s\r\n", requestBody.c_str());

    DynamicJsonDocument doc(1024);
    DeserializationError error = deserializeJson(doc, requestBody);

    if (error)
    {
        Serial.println("failed to deserialize");
        Serial.printf("POST /%s 400\r\n", path);
        server.send(400, "application/json", "{\"message\": \"Bad Request.\"}");
        return;
    }

    int32_t value = doc["value"].as<int32_t>();

    if (value < 0 || 100 < value)
    {
        Serial.printf("POST /%s 400\r\n", path);
        server.send(400, "application/json", "{\"message\": \"Bad Request. Unexpected value.\"}");
        return;
    }

    Serial.printf("input value: %d\n", value);

    // ここにPWM制御を記述する

    Serial.printf("POST /%s 200\r\n", path);
    server.send(200, "text/json", "{\"message\": \"Success.\"}");
}

void setup()
{
    Serial.begin(9600);

    // WiFiの接続
    WiFi.begin(ssid, pass);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
    }
    Serial.printf("IP:%s \r\n", WiFi.localIP());

    // Webサーバの開始
    server.on("/", handleRoot);
    server.on("/cpu", HTTP_POST, handleCPU);
    server.onNotFound(handleNotFound);
    server.begin();
}

void loop()
{
    server.handleClient();
}

なお、このssid.hはWiFiSSIDを記述したファイルです。いつもこのファイルを作っておき、.gitignoreに加えています。ArduinoIDEではなく、PlatformIOを使うと、Arduino開発であっても、普通のC++の開発として利用できます。

// src/ssid.h
#define WIFI_SSID "__WIFI_SSID__"
#define WIFI_PASSWORD "__WIFI_PASSWORD__"

PWMも、以下のような形でできます。

// src/main.cpp
#define METER_PIN_1 GPIO_NUM_0
#define METER_PIN_2 GPIO_NUM_1
#define METER_PWM_CH_1 0
#define METER_PWM_CH_2 1

void setup()
{
    // 略

    // PWMの設定
    pinMode(METER_PIN_1, OUTPUT);
    ledcSetup(METER_PWM_CH_1, 1000, 8);
    ledcAttachPin(METER_PIN_1, METER_PWM_CH_1);
    ledcWrite(METER_PWM_CH_1, 0);
    pinMode(METER_PIN_2, OUTPUT);
    ledcSetup(METER_PWM_CH_2, 1000, 8);
    ledcAttachPin(METER_PIN_2, METER_PWM_CH_2);
    ledcWrite(METER_PWM_CH_2, 0);
}

void handleCPU()
{
    // 略

    // 3.3Vを上限とすると、若干低くなったため、3.1Vとして計算
    float_t analogValue = 256 * 3 / 3.1 * value / 100;
    uint16_t pwmValue = (uint16_t)analogValue;

    Serial.printf("pwm value: %d\n", pwmValue);

    ledcWrite(pwm_ch, analogValue);

    // 略
}

ソースコードは以下に置いてあります。

github.com

CPU使用率を送るプログラム

CPU使用率を送るプログラムはGoを使って記述しました。

マルチプラットフォームに対応したCPU・メモリ使用率を取得する便利なライブラリがあり、こちらを利用しました。

github.com

ソースコードは以下に置いてあります。

github.com

これをUbuntuのsystemd上で自動起動するようにしました。

[Unit]
Description=CPU Usage Sender Service
After=network.target

[Service]
Type=simple
User=nnyn
Group=nnyn
ExecStart=/usr/local/bin/send-cpu-usage -h http://192.168.1.105 -i 1000ms
ExecStop=/bin/kill ${MAINPID}
Restart=always

[Install]
WantedBy=multi-user.target
sudo cp ./send-cpu-usage /usr/local/bin/
sudo cp systemd/send-cpu-usage.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable send-cpu-usage.service
sudo systemctl start send-cpu-usage.service
sudo systemctl status send-cpu-usage.service

とりあえずブレッドボードで運用

まずはブレッドボードで実装して運用することにしました。使っていると、欲しい機能が見えてくると思ったためです。元の記事にあるように光らせたらカッコ良いなど、やりたくなるのではと思っていました。

しかし、2週間運用してみて、常に視界に入るものなので、光る必要はないなという結論に至りました。3Dプリンタでケースを作ることも考えたのですが、モニタの上にちょこんとアナログメーターがあるだけで良いと思い、ケースも作らないことにしました。ユニバーサル基板用の簡易ケースだけ作るつもりです。

運用してみた感想は、VS Codeにつなぎに行くとき、結構CPU使うなーとか、Python実装している時保存する度にmypy、black、pylintが動くのかキュキュっとCPUが触れるので、わりと実装時もCPU使うなーとか、そのくらいでした。

ユニバーサル基板に移す

ユニバーサル基板で実装し、稼働させました。ツイート中の動画で、ESP32のファームウェアをビルドをしてCPUが触れるのを確認できます。

まとめ

アナログでカッコ良いCPU・メモリ使用率モニタを、ESP32-C3を使って作ることができました。 開発サーバとして使っているヘッドレスPCであっても、CPU使用率が常に見えるので良い感じです。

アナログ電圧計はPWMで簡単に扱うことができるので、今後もおしゃれにしたいときに組み込んでいきたいと思います。