とあるユーチューバーの動画を観た事で、私の悩みが解決しました。この記事は、その備忘録です。

とあるユーチューバーの動画

Make your OctoPrint webcam 16:9

OctoPiに接続されたHD 1080PのWebカメラのアスペクト比が4:3になってしまう現象を、正しい16:9に変更する。

↓ OctoPrint設定のClassic Webcamでアスペクト比が16:9になっていても、4:3で表示されてしまう。

↓ HDカメラなんだから、このようにアスペクト比16:9で表示させたい。

 

解決方法は /boot/octopi.txt の内容を変更する。その後ラズパイ再起動。

$ sudo vi /boot/octopi.txt
$ sudo vi /boot/firmware/octopi.txt    <-- Pi5

#camera_usb_options="-r 640x480 -f 10"
camera_usb_options="-r 1920x1080 -f 30"

 

v4l2-ctl で確認すると。

$ v4l2-ctl --device=/dev/video0 --get-fmt-video
Format Video Capture:
	Width/Height      : 1920/1080
	Pixel Format      : 'MJPG' (Motion-JPEG)
	Field             : None
	Bytes per Line    : 0
	Size Image        : 4147789
	Colorspace        : sRGB
	Transfer Function : Rec. 709
	YCbCr/HSV Encoding: ITU-R 601
	Quantization      : Default (maps to Full Range)
	Flags             :

我の3Dプリンター Ender-3 V3 SE をLAN接続すべくOctoPiをRaspberry Pi 2 Model B(ラズパイ2)にインストールしました。

3Dプリンターのある部屋は別なので、LAN接続されてノズルやヒートベッドの温度が、離れた部屋から確認出来るようになって便利。

↓ OctoScreenもインストールしたので、タッチUIだよ。

【2024年9月13日追記】もう落ちた。インストールした翌日(正確には同日)にOctoScreenだけ落ちてLCDには普通のディスクトップ画面が表示。OctoPiは稼働していたけど。やっぱりラズパイ2には荷が重い?
え〜と、OctoScreenが重い?

【2024年10月2日追記】↑ でしたが、解決?
今迄の症状は、ラズパイ2(OS)起動時にOctoScreenの立ち上げが失敗して、普通のディスクトップ画面になる事が多く、何度かOS再起動してやっとOctoScreenが立ち上がると言うのが今迄の事。
でも、ネット記事で知った事だけど、OctoScreenを優先する為に、lightdmを無効にするといいと有る。実際これをしたらラズパイ2起動時、毎回OctoScreenが立ち上がるようになった。

$ sudo systemctl disable lightdm.service

↓ MacのChromeでOctoPiに接続。

 

Ender-3 V3 SEに有るUSB-C端子は何に使う?
と1年近く分からないで居たけど、こんな便利に使えるとは。もっと早く知れていれば良かったけど、今回知れて良かった。

3D model-viewer

↓ マグネットである必要は無いのですが、RPi5に使用する前はWindows PCに接続されていて、そのマグネットでPCの側面に貼り付けてあったのです。マグネットが引っかかりUSB HDDがズレ無い様に造形しました。

↑ Onshapeの断面ビューも便利です。

CAD素人の私が、Onshapeの使用感をFreeCADと比べると。断然Onshapeの勝ちです!
FreeCADは作業内容に適したワークベンチを選択して作業を進めて行くんですが、その幾つもあるワークベンチは、ワークベンチ毎に操作性と言うか、ワークベンチ毎にソフト開発者が異なる様な、ワークベンチ毎に考え方が異なる様な。CAD素人の私には取っ付き難い物でした。

CAD素人の私が使いたい3D CADはFusion 360なんです。PCBも出来て凄く良いと思いながらも、個人で無料使用だと制限が多く、特にPCBの基板サイズが小さ過ぎるので、仕方無くPCBはKiCadを使用しています。

現在、3Dプリンター用のデータはOnshape(無料だけどデータ公開)を気持ち良く使用しています。

参考サイト: gdbgui installation
https://www.gdbgui.com/installation/

 

Raspberry Pi OS(ラズパイ5)にgdbguiをインストール。

$ sudo apt install pipx

$ pipx install gdbgui

$ pipx ensurepath

gdbguiを立ち上げる。

$ pipx run gdbgui

次のアドレスをブラウザで開く。

http://127.0.0.1:5000/dashboard

 

gdbguiの操作は左下のコマンドラインで行う感じです。gdbの操作と同じで
b main
run
n
みたいに操作。便利なのは変数などの値が右窓に表示される事ですね。

2024年9月6日ごろ、ラズパイ5を sudo apt upgrade したら?
gpiochip4が無い。

ソースコードのchipname部分をgpiochip4からgpiochip0に変更しました。

const char *chipname = “gpiochip4”;

const char *chipname = “gpiochip0”;

gpioinfo で確認すると ↓

アップグレード前のgpiochip0は32linesだけど、アップグレード後は54linesになっている。これはgpiochip4と同じ数。

 

2024年9月7日上記追記。以下、元記事のまま変更していません。ソースコードは上記を参考に変更して下さい。

 

外国のYouTuberの動画と、そこからのリンク先のページが参考になります。

外国のYouTuberの動画
Use Raspberry Pi 5 GPIO – Push button input – LED output – ChatGTP for code – GPIOD [PART 2 OF 2]

リンク先のページ
GPIO Programming: Exploring the libgpiod Library

以下、素人の私が書く事ですので、何処かに誤りがあるかも知れません。C言語のソースコードなのに、拡張子がCPPである事を指摘しないで下さい。それに、私本人は以下のプログラムが動くので、取り敢えずこれで良しと思っています。それを承知の上で参考にして下さい。

ラズパイ5のGPIOはgpiodで使うみたい。ラズパイ4まではpigpioでLチカ出来ていた。pigpioを使用したソースコードでもラズパイ5でコンパイルは成功する。だけど、そのソフトを走らせると不動作で「ラズパイでは無いようです」みたいに言われてしまう。

↓ この画像は、ラズパイ5にgpiod等をインストールして、/dev/gpiochip4を利用してLチカしている所です。

 

↓ C言語でGPIOを使用したい時に必要。

$ sudo apt install gpiod libgpiod2 libgpiod-dev

↓ chronyc sources

↑ PPSはこのラズパイ5。pi4やzero2にpi2は同じLAN内のラズパイで、どれもNTPサーバーです。PPSのラズパイ5、pi4、zero2の3つのラズパイは各々に繋がれたGPSの1PPSに同期中。pi2はLAN内のNTPサーバーを参照しています。

↓ 3つのLEDをchronyc sourcesの結果に応じて点灯させるプログラムのソースコード。

// Raspberry Pi 5
// pi@pi5:~ $ g++ -Wall -o chronyCheck chronyCheck.cpp -l gpiod

#include <err.h>
#include <cstring>
#include <iostream>
#include <gpiod.h>
#include <unistd.h>
#include <stdio.h>

#define BUF 256
#define H 1
#define L 0
#define PPS 10
#define NTP 9
#define NG 11

using namespace std;

enum kekka{
    pps_hantei = 10,
    pps_ntp_hantei,
    ntp_hantei,
    ng_hantei
};

int mainLoop(void);

int main(void)
{
    if(daemon(0, 0) == 0) { // デーモン関数
        mainLoop();
    } else {
        cout << "error" << endl;
    }
    return 0;
}

int mainLoop(void)
{
    FILE *fp;
    char *ret;
    char str[512], *ptr;
    const char* cmdline = "chronyc sources";
    
    enum kekka hantei;
    
    gpiod_chip *chip;
    gpiod_line *line_PPS, *line_NTP, *line_NG;
    //For Raspberry Pi 5 use gpiochip4 (For Raspberry Pi 4 use gpiochip0)
    const char *chipname = "gpiochip4";
        
    // Open GPIO chip
    chip = gpiod_chip_open_by_name(chipname);

    // Open GPIO lines
    line_PPS = gpiod_chip_get_line(chip, PPS);
    line_NTP = gpiod_chip_get_line(chip, NTP);
    line_NG = gpiod_chip_get_line(chip, NG);

    // Open LED lines for output
    gpiod_line_request_output(line_PPS, "chronyCheck", 0);
    gpiod_line_request_output(line_NTP, "chronyCheck", 0);
    gpiod_line_request_output(line_NG, "chronyCheck", 0);
    
   
    // 無限ループ
    while (1) {
        
        // コマンドの chronyc sources 実行
        if ((fp=popen(cmdline,"r")) == NULL) {
            err(EXIT_FAILURE, "%s", cmdline);
        }
        
        // chronyc sources 実行結果を1行づつチェックする
        // それを必要なら最後の行まで繰り返す。
        do {
            ret = fgets(str, BUF, fp); // 1行読み取り
            ptr = strstr(str, " 377 "); //  377 を検索(377前後のスペースも含む)
            if (ptr != NULL) {
                ptr = strstr(str, "#*"); // #* を検索
                if (ptr != NULL) {       // 検索行に #* と 377 があった時だけ1PPS同期判定とする
                    hantei = pps_hantei;
                    // cout << "Synchronizing to 1PPS & 377" << endl;
                    ret = NULL; // ここに来たらdo-whileから抜けて1行読み取りを終了する
                } else {
                    ptr = strstr(str, "^*");
                    if (ptr != NULL) {
                        hantei = ntp_hantei;
                        // cout << "Synchronizing to NTP" << endl;
                        ret = NULL; // ここに来たらdo-whileから抜けて1行読み取りを終了する
                    } else {
                        ptr = strstr(str, "^+");
                        if (ptr != NULL) {
                            hantei = ntp_hantei;
                            // cout << "Synchronizing to NTP" << endl;
                            ret = NULL; // ここに来たらdo-whileから抜けて1行読み取りを終了する
                        } else {
                            ptr = strstr(str, "^-");
                            if (ptr != NULL) {
                                hantei = ntp_hantei;
                                // cout << "Synchronizing to NTP" << endl;
                                ret = NULL; // ここに来たらdo-whileから抜けて1行読み取りを終了する
                            } else {
                                hantei = ng_hantei; // ここに来た場合は、1PPSにもNTPにも同期せず
                                // cout << "NG" << endl;
                            }
                        }
                    }
                }
            } else {
                ptr = strstr(str, "#*");
                if (ptr != NULL) {
                    hantei = pps_ntp_hantei; // 377は無いが #* の場合
                    ret = NULL;
                } else {
                    ptr = strstr(str, "^*");
                    if (ptr != NULL) {
                        hantei = ntp_hantei;
                        ret = NULL;
                    } else {
                        hantei = ng_hantei;
                    }
                }
            }
        } while (ret != NULL);
    
        // 全LEDを消灯
        gpiod_line_set_value(line_PPS, L);
        gpiod_line_set_value(line_NTP, L);
        gpiod_line_set_value(line_NG, L);
    
        // hanteiに応じてLEDを点灯
        switch (hantei) {
            case pps_hantei:
                gpiod_line_set_value(line_PPS, H);
                sleep(70); // PPS同期判定時は70秒待機
                break;
            case pps_ntp_hantei: // PPS? NTP? どっちの時は、PPS用LED、NTP用LED両方とも点灯
                gpiod_line_set_value(line_PPS, H);
                gpiod_line_set_value(line_NTP, H);
                sleep(16); // PPS? NTP? 同期判定時は16秒待機
                break;
            case ntp_hantei:
                gpiod_line_set_value(line_NTP, H);
                sleep(16); // NTP同期判定時は16秒待機
                break;
            case ng_hantei:
                gpiod_line_set_value(line_NG, H);
                sleep(4); // NG判定時は4秒待機
                break;
            default: // ここに来るはず無いので意味合いとしては異常表示
                gpiod_line_set_value(line_PPS, H);
                gpiod_line_set_value(line_NTP, H);
                gpiod_line_set_value(line_NG, H);   
        }

        pclose(fp);
        
    }

    // 以下は無限ループ外なので不要?
    gpiod_line_release(line_PPS);
    gpiod_line_release(line_NTP);
    gpiod_line_release(line_NG);
    gpiod_chip_close(chip);
    
    return 0;
}

↑ デーモン関数はOS起動と共に起動して常に作動する為の物。それにはユニットファイル作成等も必要だけど。ここには記載していません。

コンパイル

$ g++ -Wall -o chronyCheck chronyCheck.cpp -l gpiod