ラズパイのGPIO割り込み検出とsystemd設定について

ラズパイにseismicサービスを組み込む時に関連したメモ

① 現状Rust(rppal)ではGPIOで割り込みを検出する手段は提供されていない様子

作ればいいんだろうけど、今の所クレートは存在していないから、従来通りそこだけはPythonのサービスを起動、以下のソースで個別にサービス定義して起動時に実行させておく

#
# wait switch push interrupt and issue shutdown command
#
import time
import datetime
import os
import sys
import RPi.GPIO as GPIO
import subprocess

# Shut down sw is assigned to GPIO17
# GPIO initialize
SHUTDOWN = 17

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(SHUTDOWN, GPIO.IN)
GPIO.setup(SHUTDOWN, GPIO.IN, pull_up_down=GPIO.PUD_UP)

def handle_sw_input():
# wait key inout event
    def switch_callback(gpio_pin):
        subprocess.call('sudo shutdown -h now', shell=True)
#
    GPIO.add_event_detect(SHUTDOWN, GPIO.FALLING,bouncetime=500)
    # when the sw was pushed, call the 'call back routine' 
    GPIO.add_event_callback(SHUTDOWN, switch_callback) 
    return

handle_sw_input()

while True:
    time.sleep(100)

② 今更ですが。/lib/systemd/systemのserviceファイルを変更した時には、以下の処理が必要(既存ファイルの書き換え替えが反映されなかった)

・サービスを登録する(編集したときの反映時にも必要)

$ sudo systemctl daemon-reload

$ sudo systemctl enable seismic.service

 

admin

FFTライブラリの実行速度(Python numpy, Rust Crate)

FFTの実行速度をbumpy FFTとRustのクレートである、

https://docs.rs/rustfft/latest/rustfft/

と比較してみた

<条件>

Python numpy/rustfftでM1 Macとラズパイzero W

 

・実行条件

sampling_rate = 2000  # サンプリング周波数(Hz)

T = 1 / sampling_rate  # サンプリング間隔

t = np.arange(0, 1.0, T)  # 時間ベクトル

# 信号生成(50Hzと120Hzのサイン波を重ねたもの)

f1 = 100  # Hz

f2 = 300  # Hz

signal = np.sin(2*np.pi*f1*t) + 0.5*np.sin(2*np.pi*f2*t)

<実行速度>

M1 MacBook Air: np.fft.fft:     0.000027 [sec] 

ラズパイzero W: np.fft.fft: 0.001830 [sec] 

ふーむ、およそ60倍ぐらい違うか、想定範囲だけど

 

<rustfftで実行>

・実行条件

const N: usize = 1024;              // FFT size

const SAMPLE_RATE: f32 = 100.0;     // sampling rate

const INPUT_FREQ: f32 = 20.0;       // input frequency

どちらもreleaseモードでコンパイル、ほぼ条件は同じでnumpyfftとrustfftは同等の実行速度で、Cで記述されているだろうnumpyだからある意味当然の結果です

M1 MacBook Air
Elapsed time: 31.583µs

(参考値:debugモードでコンパイル)
Elapsed time: 1.878708ms


ラズパイzero W
$ ./fft
Elapsed time: 1.957008ms

・Rustのコード

fftは複素数で計算していますが、データはre部分だけ作成、結果は複素数になるので絶対値を求めてVecに格納、このクレートは入力データのVecに計算結果が格納されるという変則的なクレートのように思う

// Computes a forward FFT
//
use plotters::prelude::*;
use rustfft::{FftPlanner, num_complex::Complex};
use std::f32::consts::PI;
use std::time::Instant;

const N: usize = 1024;              // FFT size
const SAMPLE_RATE: f32 = 100.0;     // sampling rate
const INPUT_FREQ: f32 = 20.0;       // input frequency

// prepare target data
fn complex_vector(length: usize, dt: f32, frequency: f32) -> Vec<Complex<f32>> {
    (0..length)
        .map(|i| {
            let t = i as f32 * dt;
            let phase = frequency * 2.0 * PI * t;
            let phase2 = 3.0 * frequency * 2.0 * PI * t;
            Complex::new(phase.sin() + phase2.sin(), 0.0)
        })
        .collect()
}

fn draw(x: Vec<usize>, y: Vec<f32>) -> Result<(), Box<dyn std::error::error="">> {
    let image_width = 1080;
    let image_height = 720;

    let root = BitMapBackend::new("plot.png", (image_width, image_height)).into_drawing_area();

    root.fill(&WHITE)?;

    //   https://qiita.com/lo48576/items/343ca40a03c3b86b67cb
    let (y_min, y_max) = y
        .iter()
        .fold((0.0 / 0.0, 0.0 / 0.0), |(m, n), v| (v.min(m), v.max(n)));

    let caption = "DFT result";
    let font = ("sans-serif", 20);

    let mut chart = ChartBuilder::on(&root)
        .caption(caption, font.into_font())
        .margin(10) 
        .x_label_area_size(16) 
        .y_label_area_size(42) 
        .build_cartesian_2d(
            *x.first().unwrap()..*x.last().unwrap(), 
            y_min..y_max,                            
        )?;

    chart.configure_mesh().draw()?;

    let line_series = LineSeries::new(x.iter().zip(y.iter()).map(|(x, y)| (*x, *y)), &RED);
    chart.draw_series(line_series)?;

    Ok(())
}

fn main() {
    // fft data preparation
    let sr = 1.0/SAMPLE_RATE;
    let mut buffer = complex_vector(N, sr, INPUT_FREQ);

    // fft mode setting
    let mut planner = FftPlanner::new();
    let fft = planner.plan_fft_forward(N);    

    let start_time = Instant::now();
    // fft exec
    fft.process(&mut buffer);

    let end_time = Instant::now();
    println!("Elapsed time: {:?}", end_time.duration_since(start_time));

    // absolute value calc
    let y: Vec<f32> = buffer.iter().map(|z| z.norm()).collect();
    // drwa graph
    let n = N;
    let x: Vec<usize> = (1..=n).collect();
    let _ = draw(x, y);
}

ビジュアル化した結果、x軸の512を境に対称形になっています、レベル(ピーク値)が違うのは今のところ謎

 

admin

h264decoder共有ライブラリはBoost.Python使っている?

以下の記事中で、

https://isehara-3lv.sakura.ne.jp/blog/2022/10/28/telloで画像転送python-h-264共用ライブラリ/

c++のライブラリをPythonから呼び出すためにpybind11のインストールが指定されていますが、c++ライブラリのラッパーファイル(h264decoder_python.cpp)を見てみると、pybind11ではなくてBOOST_PYTHONが使われているからpybind11は不要だよねと思いました。

BOOST_PYTHON_MODULE(libh264decoder)

試しに、仮想環境(v_python)からpybind11をpip uninstall pybind11しても動作はするから、やはり不要らしい。

Boost.Pythonをpybind11と比較するとサイズが巨大らしい。pybind11の方が後発だから、恐らく機能的には優っているだろうしユーザーも多いのではないかと思います。

P.S. 2022/10/31

H.264 decoderのビルドのためのCMakeLists.txtを見てみるとpybind11を探して、もし存在しなければ持ってきてるからビルドには必要とされているようです、何故だろう?

find_package(pybind11)
if(pybind11_FOUND)
  message("Using existing pybind11 v${pybind11_VERSION}")
else()
  message("Fetching pybind11")
  include(FetchContent)
  FetchContent_Declare(
    pybind11
    GIT_REPOSITORY https://github.com/pybind/pybind11
    GIT_TAG v2.5.0)
  FetchContent_MakeAvailable(pybind11)
endif()

P.S. 2022/11/7

今更ながらですが、改めて見るとpybind11使ってました。じゃ最初のソースは何見たんだろう?とりあえずすっきりはしましたが、

PYBIND11_MODULE(h264decoder, m)
{
  PyEval_InitThreads(); // need for release of the GIL (http://stackoverflow.com/questions/8009613/boost-python-not-supporting-parallelism)
  py::class_(m, "H264Decoder")
                            .def(py::init<>())
                            .def("decode_frame", &PyH264Decoder::decode_frame)
                            .def("decode", &PyH264Decoder::decode);
  m.def("disable_logging", disable_logging);
}

 

admin

Telloで画像転送(Python + H.264共用ライブラリ)

以下でコマンドは送信できましたが、画像の受信には追加の手段が必要になります。

https://isehara-3lv.sakura.ne.jp/blog/2022/10/26/dji-telloはudpでテキストコマンド送れば制御できる/

コマンド/レスポンスと画像転送はポートが分かれています、ある意味当然。

DJIのリンクにもH.264デコーダーは掲載されてますが古すぎて(四年前)動かない、そもそもがPython2.x系用だし。

で、環境に合わせてビルド必要ですが、以下のサイトがよくまとまっています。

Windws/Linux/Mac(intel/mx)と全てのプラットホームが解説されています。

https://take6shin-tech-diary.com/tello-video-python3/

<動作環境>

・intel Mac ventura

・Python3.9(環境はAnacondaで構築)

ビルドされたライブラリのディレクトリは、

c++をMacのPythonから呼び出すという情報が名称に含まれています。

 

<動作させてみる>

ドローンは飛行させなくても、画像は送られてくるので最初の画像はTelloの梱包箱。

UIもPythonのライブラリ(tkinter)使って作られます。

次はopencv使って、画像認識らしいことをやらせてみることでしょう。

 

admin

Timemachineのバックアップ初めて使った、

condaが挙動不審、

conda installを実行すると、

% conda install anaconda-clean

# >>>>>>>>>>>>>>>>>>>>>> ERROR REPORT <<<<<<<<<<<<<<<<<<<<<<

    Traceback (most recent call last):
      File "/Users/usamiryuuichi/miniforge3/lib/python3.10/site-packages/conda/exceptions.py", line 1129, in __call__
        return func(*args, **kwargs)

以下省略〜〜

と言われて実質何もできなくなったから。仮想ディレクトリでも同じだし、そもそも中身を変更するようなコマンドは全滅で、新しい仮想ディレクトリも作れないからデッドロック状態。

それゆえNASのTimemachineバックアップから、

miniforge3ディレクトリを復元したらとりあえず復旧したようです。ディレクトリ復元だけではうまくいかない場合もあるでしょうが、今回は問題なくということでしょう。

原因は恐らくbasicディレクトリでpipインストールしたからの可能性が一番高い。

 

admin

PyBind11 for c++

c++の高速性が必要でなおかつ、Pythonの書きやすさが必要な場合にはPythonからc++の処理の呼び出しが必要です。

いろいろな方法がありそうですが、PyBind11は2011年ごろに登場と比較的に新しく、それ故に機能も洗練されているだろうから動かしてみた。

ネットにも情報が多いから問題解決が難しくないだろうと思う。

<本家>

https://pybind11.readthedocs.io/en/stable/index.html#

“pybind11 is a lightweight header-only library that exposes C++ types in Python and vice versa,”

とあるようにpythonからc++も呼べるし、逆もまた可なりのヘッダーファイルライブラリにすぎないと。

<install>

$ pip3 install pybind11

—Installed path—

/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pybind11/include/pybind11

<sample program & compile>

https://qiita.com/takuyakubo/items/b7503d7555bbc6c44aba

このリンク先にある、以下のclangコンパイルで作成できた。本家のドキュメントと全く同じですが。

% clang++ -O3 -Wall -shared -std=c++17 -fPIC `python -m pybind11 –includes` -undefined dynamic_lookup py_call.cpp -o example`python3-config –extension-suffix`

『コンパイラオプションは、最適化(-O3)、共有ライブラリとしてビルド(-shared -fPIC)、言語標準の指定(-std=c+17)、インクルードディレクトリの追加( `python3 -m pybind11 –includes`)、コンパイル対象(py_call.cpp)、出力(cpplcm`python3-config –extension-suffix`)となります』

https://buildersbox.corp-sansan.com/entry/2019/12/09/110000

の説明から引用。

<sample code : py_call.cpp>

PYBIND11_MODULE以下がマクロで、pybind11で解釈されます。

m.def()中の”add”はPythonから呼び出される時の関数名で、&addはint add()関数がそれに相当すると定義しています。

#include <pybind11/pybind11.h>

int add(int i, int j) {

  return i + j;

}

PYBIND11_MODULE(example, m) {

  m.doc() = "pybind11 example plugin";                          // optional module docstring

  m.def("add", &add, "A function which adds two numbers");

}


<call from python>

コマンドラインで実行して、import exampleが実行できればモジュールが作成できています。

% python

Python 3.10.3 (v3.10.3:a342a49189, Mar 16 2022, 09:34:18) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin

Type "help", "copyright", "credits" or "license" for more information.

>>> import example

>>> example.__doc__

'pybind11 example plugin'

>>> example.add(24, 43)

67

作成されるのは、共有ライブラリファイルで、”example.cpython-310-darwin.so“がPythonから呼び出されるファイルになります。MacOSなのでこんな名前になります。Pythonも実装はcだから、インターフェースは出来て当たり前ということでしょう。

-rwxr-xr-x   1 hogehoge  staff  148016  4 28 10:07 example.cpython-310-darwin.so

このケースは一番単純な機能確認ですが、実際には配列などを引数にするときにはc++とPython間で何らかの変換が必要になるでしょう。

 

admin

VScodeでPyGame動作

コマンドラインからでなく、VScode環境で動かそうとすると、

import pygame

のリンクが見つからないとなる。

実行すると問題なく実行されるので、エディタ環境と実行環境でPythonのパスが違うようです。

 

“PYTHON : ENVIROMENTS” (Pythonマーククリック)

で、”Set as active workplace interpretor”でコンソールから実行する最新版数を設定したらimport pygameのリンク見つからないエラーは消えました。

 

admin

PyGame(pythonのゲームライブラリ)

Pythonでゲームを作るというのは、他の言語を使うよりは相対的な敷居は低そうです。Python自体にすでに豊富なライブラリもあるから。PyGameはゲームエンジンというよりはゲームライブラリで、ゲームに必要なGUIとかボタン処理(event listener)、あるいは計算量の多い処理をライブラリ化することで、Pythonでのゲーム作成を簡単にできるようにしてくれます。

・本家

https://www.pygame.org/news

取っ掛かりのページ、

IMG_0425.PNG

・PyGameをインストール

% pip3 install pygame    

python起動して、

>>> import pygame

が問題なければ正常にインストールされてます。

・サンプルプログラム

https://www.pygame.org/download.shtml

からダウンロードして、tar.gzを解凍するとsamplesディレクトリがあるのでそれを持ってくる。

Numpyとpyopenglが必要なサンプルあったのでインストール。

% pip3 install numpy

% pip3 install PyOpenGL

Pythonとゲームの構造の両方を習得できるから良いツールでは無いかと思う。

 

admin

pythonからシェルコマンドを起動

・pythonでshellコマンド使う(sw押されたらシャットダウン処理)

>> import subprocess

>> subprocess.call([“ls”, “-l”])

二行目は、

>> subprocess.call(‘ls -l’, shell=True)

shell=True指定でベタ書きできるけど、インジェクションリスクはある。

shutdownは、

>> subprocess.call(‘sudo shutdown -h now’, shell=True)

・起動時のスクリプト自動起動(co2センサーのログ取りとdio制御スクリプト)

/etc/rc.localのexit前に以下のように記述しますが、

/usr/bin/python /home/pi/gpio.py &

ルート権限実行なのでフルパスで記述することと、終了しないスクリプトの場合にはバックグランド動作とするために最後に”&”を付加します。

 

admin

Python 3を使う@raspberry pi zero

パッケージの互換とか面倒になって来たので、python3を標準とするように変更。元々のイメージにはpython2とpython3はインストール済みです。

・シンボリックリンクの切り替え

$ cd /usr/bin

$ sudo unlink python

$ sudo ln -s python3 python

でpythonコマンドでpython2.7でなくpython3.7が呼び出されます。この先考えるとpython3の方が確実に便利になる筈。

・mysqlへの接続モジュール追加インストール

python3にしたらpythonスクリプトからはmysqlに接続できなくなって、python3用としてのmysqlcliantをインストール

$ sudo apt-get install python3-dev ; 事前準備でこのドライバー必要と

さらにpython3でpip使えないので、

$ sudo apt install curl ; curl入ってないのでインストール

$ curl “https://bootstrap.pypa.io/get-pip.py” -o “get-pip.py” ; ソースを持ってくる

$ sudo python get-pip.py ; pipインストール

ここまでが準備で、

pipがインストールできたのでmysqlclientをインストール

$ pip install mysqlclient

 

admin