ラベル Raspberry Pi の投稿を表示しています。 すべての投稿を表示
ラベル Raspberry Pi の投稿を表示しています。 すべての投稿を表示

2023年1月30日月曜日

Raspberry Piに2つのBluetoothスピーカーを接続してPythonで再生する

Raspberry Piに2つのBluetoothスピーカーを接続して、Pythonでその2つのスピーカーのうち1台を指定して音声ファイルを再生してみる。


環境

Raspberry Pi B+とRaspberry Pi OS。

$ lsb_release -dr
Description:    Raspbian GNU/Linux 11 (bullseye)
Release:        11
$ python3 -V
Python 3.9.2


2つのBluetoothスピーカーをRaspberry Piに接続する

今回使用するのはRaspberry Pi B+で、本体にBluetoothの機能がないので、USB接続のBluetoothアダプタ(ドングル)を2つ用意して接続する。Bluetoothアダプタが認識されているか確認しておく。

$ lsusb
Bus 001 Device 004: ID xxxx:xxxx Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 005: ID xxxx:xxxx Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
...


必要なモジュールのインストールと設定

Raspberry PiにBluetoothスピーカーをつなげると同様に、サウンドサーバとそのbluetoothモジュールをインストールし、使用するユーザ(pi)をbluetoothグループに追加しておく。

以下コマンドでモジュールのインストール。

$ sudo apt install pulseaudio pulseaudio-module-bluetooth

ユーザーをbluetoothグループに追加。

$ sudo usermod -a -G bluetooth pi
$ sudo reboot


2つのBluetoothスピーカーを接続

2つのBluetoothアダプタそれぞれにBluetoothスピーカーを接続する。基本的な接続方法はRaspberry PiにBluetoothスピーカーをつなげると同じだが、Bluetoothアダプタが2つ以上ある場合は、接続設定する前に、Bluetoothアダプタを選択しておく。bluetoothctlのlistコマンドでBluetoothアダプタの一覧が表示される(defaultが現在選択されているアダプタ)ので、selectコマンドで設定するアダプタを選択する。

$ bluetoothctl
[bluetooth]# list
Controller 11:11:11:11:11:11 raspberrypi #2 [default]
Controller 22:22:22:22:22:22 raspberrypi

以下のようにアドレスを指定して選択。

[bluetooth]# select 22:22:22:22:22:22
Controller 22:22:22:22:22:22 raspberrypi [default]

アダプタを選択したら、スピーカーを接続する。devicesコマンドで接続可能なBluetoothデバイスの一覧が表示されるので、接続するBluetoothスピーカーが一覧にあるのを確認する。

[bluetooth]# power on
[bluetooth]# scan on
[bluetooth]# devices

アドレスを指定してBluetoothスピーカーに接続する。

[bluetooth]# pair XX:XX:XX:XX:XX:XX
[bluetooth]# connect XX:XX:XX:XX:XX:XX
[bluetooth]# quit

selectコマンドでアダプタを切り替えて、もうひとつのBluetoothスピーカーも同様に接続する。


PythonでBluetoothスピーカーから音声ファイルを再生する

Pythonで音声ファイルを再生するには音声ファイルを再生するPythonコードをcron実行するのように、VLCで音声ファイルを再生する。まずは、PythonでVLCを制御するモジュールをインストール。

$ pip3 install python-vlc
$ pip3 show python-vlc
Name: python-vlc
Version: 3.0.16120
...

今回は2つのBluetoothスピーカーのうちひとつを選択して、音声ファイルを再生する。やり方としては、python-vlcでスピーカー名とそのアドレスを取得できるので、それをもとに再生するスピーカーを選択する。スピーカー名とそのアドレスは以下のように音声出力デバイス情報から取得できる。

import vlc

player = vlc.MediaPlayer()
mod = player.audio_output_device_enum()
while mod:
    mod = mod.contents
    address = mod.device.decode('utf-8').split('.')[1]
    print('--------------------')
    print('device =', mod.device)
    print('address =', address)
    print('name =', mod.description.decode('utf-8'))
    mod = mod.next

上記コードを実行すると以下のような結果が得られる。ひとつ目の内部オーディオはRaspberry Pi本体のオーディオ出力。今回は同じモデルのBluetoothスピーカーを2台使用しているので、スピーカー名は同じになる。このスピーカー名とアドレスで音声ファイルを再生するスピーカーを選択する。

device = b'alsa_output.platform-bcm2835_audio.analog-stereo'
address = platform-bcm2835_audio
name = 内部オーディオ Analog Stereo
--------------------
device = b'bluez_sink.AA_AA_AA_AA_AA_AA.a2dp_sink'
address = AA_AA_AA_AA_AA_AA
name = Speaker
--------------------
device = b'bluez_sink.BB_BB_BB_BB_BB_BB.a2dp_sink'
address = BB_BB_BB_BB_BB_BB
name = Speaker

以下のPythonスクリプトで、接続された2つのBluetoothスピーカーのうち1台を指定して音声ファイルを再生する。このスクリプトと同じディレクトリに再生する音声ファイル(ここではtest.mp3)を配置しておく。

import os
import sys
from time import sleep

import vlc

# スピーカー名とアドレスの設定
SPEAKER1 = {'name': 'Speaker', 'address': 'XX_XX_XX_XX_XX_XX'}
SPEAKER2 = {'name': 'Speaker', 'address': 'YY_YY_YY_YY_YY_YY'}
SPEAKERS = [SPEAKER1, SPEAKER2]

# 再生する音声ファイル
# スクリプトと同じディレクトリに配置する
SOUND_FILE = 'test.mp3'


def get_output_device(player, speaker):
    """指定のスピーカーを取得する"""
    speaker_name = speaker['name']
    speaker_address = speaker['address']
    mod = player.audio_output_device_enum()
    while mod:
        mod = mod.contents
        address = mod.device.decode('utf-8').split('.')[1]
        if speaker_name == mod.description.decode('utf-8') and speaker_address == address:
            return mod.device
        mod = mod.next

    return None


def play(speaker, volume):
    """指定したスピーカーで音声ファイルを再生"""
    script_dir = os.path.dirname(os.path.realpath(__file__))
    media_path = os.path.join(script_dir, SOUND_FILE)
    player = vlc.MediaPlayer(media_path)
    device = get_output_device(player, speaker)
    if device:
        player.audio_output_device_set(None, device)
        player.audio_set_volume(volume) # ボリューム設定
        player.play()

        # 再生が終わるまで実行を継続する
        sleep(10)


def main():
    # 引数としてスピーカーID(0か1)を指定する(デフォルトは0)
    speaker_id = 0
    if len(sys.argv) >= 2 and sys.argv[1] in ('0', '1'):
        speaker_id = int(sys.argv[1])

    play(SPEAKERS[speaker_id], volume=100)

if __name__ == '__main__':
    main()

2つ目のスピーカーから再生する場合は次のように実行する。

$ python3 sample.py 1


2022年12月22日木曜日

作成するDockerイメージを環境により切り替える

 Dockerイメージを作成するときに、環境によってDockerfileで実行する内容を変えたい場合がある。プログラミング言語のif文のように条件によって実行する内容を変えられればいいのだが、Dockerにはif文のようなものはない。そこで、シェルスクリプトのif文を使って、環境によりDockerfileで実行する内容を変えてみる。


環境

WSL2(Ubuntu20.04)。

$ lsb_release -dr
Description:    Ubuntu 20.04.5 LTS
Release:        20.04

Raspberry Pi OS(64bit)。

$ lsb_release -dr
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11

今回はこの2つの環境で、Dockerイメージの内容を切り替えるDockrfileを作成する。


CPUアーキテクチャによりDockerfileの内容を切り替える

Raspberry Piで動作するSelenium+最新PythonのDockerコンテナを作成するでは、Raspberry Pi(ARM64)でDockerイメージを作成するためのDockerfileを作成した。Raspberry PiのCPUはARMで、インストールしているgeckodriverはARM用なので、このDockerfileを使ってWSL2のUbuntu(x86_64)上でDockerイメージを作成しても、インストールされたgeckodriverは動作しない。今回は、このDockerfileをRaspberry PiとWSLのどちらでも動作するようにする。

ただし、Dockerfileではif文のような条件分岐はできないので、CPUアーキテクチャによりインストールするgeckodriverを切り替えられるシェルスクリプトを用意して、それをDockerfileで実行する。


シェルスクリプトの作成

まずはgeckodriverをインストールするシェルスクリプトを作成する。CPUアーキテクチャで条件分岐するために、コマンドでCPUアーキテクチャを取得する。以下のコマンドで、CPUアーキテクチャの文字列を取得できる。


$ lscpu | grep -e Architecture: -e アーキテクチャ: | awk -F : '{print $2}' | xargs
 

上記コマンドの出力結果は、Raspberry Piでは「aarch64」、WSLでは「x86_64」となる。このコマンドを使って、次のシェルスクリプトを作成する。

#!/bin/sh

CPU_ARCH="`lscpu | grep -e Architecture: -e アーキテクチャ: | awk -F : '{print $2}' | xargs`"

if [ "${CPU_ARCH}" = "aarch64" ]; then
    curl -LO https://github.com/mozilla/geckodriver/releases/download/v0.32.0/geckodriver-v0.32.0-linux-aarch64.tar.gz
    tar xzvf geckodriver-v0.32.0-linux-aarch64.tar.gz
    mv geckodriver /usr/bin/
    rm geckodriver-v0.32.0-linux-aarch64.tar.gz
else
    curl -LO https://github.com/mozilla/geckodriver/releases/download/v0.32.0/geckodriver-v0.32.0-linux64.tar.gz
    tar xzvf geckodriver-v0.32.0-linux64.tar.gz
    mv geckodriver /usr/bin/
    rm geckodriver-v0.32.0-linux64.tar.gz
fi
 


Dockerfileの作成

シェルスクリプトが用意できたら、Dockerfileを作成する。Raspberry Piで動作するSelenium+最新PythonのDockerコンテナを作成するで作成したDockerfileを変更する。このDockerfileでgeckodriverのインストールをしている箇所を、作成したシェルスクリプトの実行に置き換える。composeファイルなどは前述の記事と同じものを使う。

FROM python:3.11-bullseye
 
WORKDIR /app
 
# Firefoxと日本語フォントインストール
RUN apt update \
    && apt -y install --no-install-recommends \
    # 日本語フォントインストール
    # 日本語フォントをインストールしておかないとFirefoxの表示が文字化けする
    fonts-noto-cjk \
    firefox-esr \
    && apt clean
 
# SeleniumのPythonバインディングインストール
RUN pip install selenium --no-cache-dir

# geckodriverのインストール
COPY ./install_geckodriver.sh ./
RUN chmod u+x install_geckodriver.sh \
    && ./install_geckodriver.sh
 

ファイル構成は以下のようになる。これで、Raspberry PiとWSLのどちらの環境でも、同じDockerfileでコンテナを起動して使える。

|-- Dockerfile
|-- app
| `-- sample.py
|-- install_geckodriver.sh
`-- docker-compose.yml


2022年11月27日日曜日

Raspberry Piで動作するSelenium+最新PythonのDockerコンテナを作成する

Raspberry Pi上のDockerでSeleniumを使う場合、Doker Hubにはselenium/standalone-chromeをforkしてRaspberry PiのCPUアーキテクチャであるARMに対応したseleniarm/standalone-chromiumがある。ただ、selenium/standalone-chromeのイメージにインストールされているPythonのバージョンは3.8と少し古い。そこで、今回はSeleniumと最新バージョンのPythonがインストールされたDockerコンテナを作成して、Raspberry Piで起動してみる。


環境

Raspberry Pi OS(64bit)。Raspberry Pi OS(64bit)にDockerとComposeをインストールする方法はRaspberry Pi OS 64bitでDockerを使うを参照。

$ lsb_release -dr
Description:    Raspbian GNU/Linux 11 (bullseye)
Release:        11
$ docker --version
Docker version 20.10.21, build baeda1f
$ docker-compose --version
docker-compose version 1.29.2, build unknown


Dockerfileの作成

Seleniumと最新Pythonが使えるDockerイメージを作成するためのDockerfileを用意する。ベースとなるDockerイメージとしてDocker Hubにある公式のPyrthonイメージの3.11-bullseyeを使う。Seleniumを使うにはブラウザとドライバが必要だが、今回はFirefoxとそのドライバであるgeckodriverをインストールする。Firefoxで日本語を表示できるように、日本語フォントの googlefonts / noto-cjkもインストールする。

FROM python:3.11-bullseye

WORKDIR /app

# Firefoxと日本語フォントインストール
RUN apt update \
    && apt -y install --no-install-recommends \
    # 日本語フォントインストール
    # 日本語フォントをインストールしておかないとFirefoxの表示が文字化けする
    fonts-noto-cjk \
    firefox-esr \
    && apt clean

# SeleniumのPythonバインディングインストール
RUN pip install selenium --no-cache-dir

# geckodriverインストール
RUN curl -LO https://github.com/mozilla/geckodriver/releases/download/v0.32.0/geckodriver-v0.32.0-linux-aarch64.tar.gz \
  && tar xzvf geckodriver-v0.32.0-linux-aarch64.tar.gz \
  && mv geckodriver /usr/bin/ \
  && rm geckodriver-v0.32.0-linux-aarch64.tar.gz


docker-composeファイルとSeleniumを使うサンプルスクリプトの用意

Dockerfileのほかに、docker-compose.ymlとSeleniumを使うPythonサンプルスクリプトを用意する。

version: '3'
services:
  selenium:
    build:
      context: .
      dockerfile: ./Dockerfile
    tty: true
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - ./app:/app


以下のPythonスクリプトでは、Firefoxを起動してGoogleで「Raspberry Pi」を検索し、結果のスクリーンショットを保存する。

import os

from selenium.webdriver import Firefox, FirefoxOptions
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

url = 'https://www.google.com'

# ブラウザーをヘッドレスで起動
options = FirefoxOptions()
options.add_argument('-headless')
service = Service(
    executable_path='/usr/bin/geckodriver', log_path=os.devnull
)
with Firefox(service=service, options=options) as driver:
    driver.get(url)

    # 画面表示を最大5秒まで待つ
    driver.implicitly_wait(5)
    # 検索ボックスのエレメントを取得(inputタグのname属性はq)
    elem = driver.find_element(By.NAME, 'q')
    # 検索ボックス内のテキストをクリア
    elem.clear()
    # 検索ワードを入力
    elem.send_keys('Raspberry Pi')
    # 検索ボタンがクリックできるようになるまで最大10秒待つ
    wait = WebDriverWait(driver, 10)
    input = wait.until(expected_conditions.element_to_be_clickable((By.XPATH, "//input[@value='Google 検索']")))

    if input.get_attribute('type') == 'submit':
        input.click()
    else:
        print('Failed to find submit button.')

    # 検索結果がなければ「No results found.」と表示する
    assert 'No results found.' not in driver.page_source

    # スクリーンショットを保存
    driver.save_screenshot('firefox_search.png')


ファイル配置は以下のようになる。

|-- Dockerfile
|-- app
| `-- sample.py
`-- docker-compose.yml


コンテナ上でSeleniumを使う

ファイルの準備ができたら、Dockerイメージを作成してSeleniumをPythonで使ってみる。まずはイメージをビルドする。

$ docker-compose build

続いて、コンテナ上で、作成したPythonスクリプトを実行する。

$ docker-compose run selenium python sample.py

スクリプトを実行すると、以下のようなfirefox_search.pngというFirefoxのスクリーンショットファイルが保存される。


2022年11月11日金曜日

音声ファイルを再生するPythonコードをcron実行する

Raspberry Piで音声ファイルを再生するPythonコードを作成したが、それをcron実行しても音声が再生されなかった。調べたところ、環境変数を設定する必要があった。cronでPythonスクリプトを実行して音声を再生させる手順をまとめておく。


環境

Raspberry Pi OS。

$ lsb_release -dr
Description:    Raspbian GNU/Linux 11 (bullseye)
Release:        11
$ python3 -V
Python 3.9.2


Pythonで音声ファイルを再生する

まずは音声ファイルをPythonで再生してみる。ここではPythonからVLCを使って再生する。そのために、PythonでVLCを制御するモジュールをインストールする。

$ pip3 install python-vlc

インストールされたバージョン。

$ pip3 show python-vlc
Name: python-vlc
Version: 3.0.16120
...

以下のコードでスクリプトファイルと同じディレクトリにあるtest.mp3を再生できる。

import os
import time
import vlc

script_dir = os.path.dirname(os.path.realpath(__file__))
media_path = os.path.join(script_dir, 'test.mp3')
p = vlc.MediaPlayer(media_path)
p.play()

# 再生が終わるまで実行を継続する
time.sleep(10)


cronでPythonコードを実行する

作成したスクリプトファイルtest.pyを~/appディレクトリ配下に配置して、cronで実行してみる。

$ crontab -e

ここでは以下のようにcronに設定を追加する。動作確認のために1分ごとに実行させて、ログファイル(test.log)を出力する。

* * * * *  python3 ~/app/test.py > ~/app/test.log 2>&1

ただし、上記のようにcronで実行させても音声ファイルを再生できない。以下のエラーメッセージが出力される。

vlcpulse audio output error: PulseAudio server connection failure: Connection refused


cronで音声ファイルを再生する

cronで音声ファイルを再生できるようにするには、サウンドサーバであるpulseaudioが使用するディレクトリを環境変数PULSE_RUNTIME_PATHに設定する。今回は以下のようなシェルスクリプトのファイル(test.sh)をPythonスクリプトと同じディレクトリに作成し、cronで実行する。

#!/bin/sh

export PULSE_RUNTIME_PATH="/run/user/$(id -u)/pulse"
python3 ~/app/test.py

作成したスクリプトファイルに実行権限を付与しておく。

$ chmod u+x test.sh

このスクリプトファイルをcronで実行すると音声ファイルを再生できる。

$ crontab -e 

動作確認のために以下のような設定をcronに追加する。こうすると1分ごとにPythonスクリプトが実行されて、音声ファイルが再生される。

* * * * *  ~/app/test.sh


2022年8月30日火曜日

Dockerコンテナ上のPythonアプリをホストのcronで実行する

Dockerコンテナ上のPythonアプリをホストのcronで実行することがあったので、その方法をまとめておく。


環境


Raspberry Pi OS 64bit with desktop。Docker環境はRaspberry Pi OS 64bitでDockerを使うで作成したもの。
$ lsb_release -dr
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11
$ docker --version
Docker version 20.10.17, build 100c701
$ docker-compose --version
docker-compose version 1.29.2, build unknown


Dockerコンテナの準備

まずはDockerコンテナを準備する。イメージはDocke公式Pythonイメージを使う。docker-compose.yml、Dockerfileの他にapp.pyを用意する。app.pyは「Hello Test」と表示するだけのPythonアプリで、これをコンテナ上で実行する。

version: "3"
services:
  testpy:
    build:
      context: .
      dockerfile: Dockerfile
FROM python:3.10-bullseye

WORKDIR /usr/src/app

COPY . .

CMD [ "python", "./app.py" ]
print('Hello Test')


コンテナ上のアプリを実行する

ファイルの準備ができたら、とりあえずcronを使わずにコンテナ上のアプリを実行してみる。まずはイメージをビルドする。

$ docker-compose build

そしてコンテナ上のアプリを実行してみる。

$ docker-compose run testpy
Creating testpy_testpy_run ... done
Hello Test


ホストでcron設定をする

Dockerコンテナ上のアプリが実行できることを確認できたので、ホストのcronでコンテナを起動する。

まずはコンテナを起動するスクリプトファイル作成する。cronではこのスクリプトファイルを実行する。docker-compose.ymlなどのファイルは~/docker/testpyにあるとする。cronで実行するときはTオプションをつけておかないとエラーになる。

#!/bin/sh

cd ~/docker/testpy
/usr/local/bin/docker-compose run -T testpy

作成したスクリプトに実行権限を付与しておく。

$ chmod u+x testpy.sh

cronの設定

$ crontab -e

ここでは実行結果を後で確認できるように、以下のように出力結果をログに保存しておく。

* * * * * ~/docker/testpy/testpy.sh > ~/testpy.log 2>&1

cronでスクリプトが実行できたらログファイルを確認する。cronでDockerコンテナ上のPythonアプリが実行できた。

$ cat ~/testpy.log
Creating testpy_testpy_run ...
Creating testpy_testpy_run ... done
Hello Test


2022年8月14日日曜日

Raspberry Pi OSでMariaDB10.8公式Dockerコンテナを使う

Raspberry Pi OSでMariaDB10.8の公式Dockerコンテナを起動してみたので、その手順をまとめておく。


環境


Raspberry Pi OS 64bit with desktop。Docker環境はRaspberry Pi OS 64bitでDockerを使うで作成したもの。
$ lsb_release -dr
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11
$ docker --version
Docker version 20.10.17, build 100c701
$ docker-compose --version
docker-compose version 1.29.2, build unknown


ComposeでMariaDBコンテナを起動

ComposeでMariaDBコンテナを起動するために以下のファイルを用意する。MariaDBのrootパスワードなどは環境変数で設定できる。ここではComposeのサービス名はtestdbとする。MariaDBのデータベースデータは、作成したボリュームをマウントして永続化することで、コンテナを停止しても保持できるようにしておく。

version: "3"
services:
  testdb:
    image: mariadb:10.8
    environment:
      MYSQL_ROOT_PASSWORD: password # データベースrootパスワード
      MYSQL_DATABASE: testdb # 作成するデータベース
      MYSQL_USER: testuser # 作成するデータベースユーザー
      MYSQL_PASSWORD: password # ユーザーパスワード
      MYSQL_ROOT_HOST: localhost # rootでの接続をloalhostからに限定
      TZ: 'Asia/Tokyo' # タイムゾーン設定
    volumes:
      - test_data:/var/lib/mysql # データ保管場所(永続化ボリューム)
      
volumes:
  test_data:


適当なフォルダ(ここではmariadb)を作成し、docker-compose.ymlを配置する。以下のコマンドでコンテナを起動する。

$ docker-compose up -d
Creating network "mariadb_default" with the default driver
Creating mariadb_testdb_1 ... done


コンテナの状態を確認してみる。StateがUpなら起動している。

$ docker-compose ps
      Name                     Command              State    Ports
--------------------------------------------------------------------
mariadb_testdb_1   docker-entrypoint.sh mariadbd   Up      3306/tcp 


コンテナが起動したらデータベースへ接続確認してみる。Composeの環境変数MYSQL_PASSWORDで設定したパスワードで接続できる。

$ docker-compose exec testdb mysql -u root -p -D testdb
Enter password:


接続できたらユーザー情報を確認。rootがlocalhostからの接続に限定されている。

> SELECT host, user FROM mysql.user;
+-----------+-------------+
| Host      | User        |
+-----------+-------------+
| %         | testuser    |
| localhost | mariadb.sys |
| localhost | root        |
+-----------+-------------+


MariaDBの設定

MariaDBコンテナを使うときに、MariaDBの設定を変更したい場合がある。コンテナ上で設定ファイルを変更しても、コンテナを停止したら変更前の状態に戻ってしまうので、MariaDBの設定ファイルをホスト側に用意しておいて、その設定ファイルをマウントしてコンテナ上で有効になるようにする。

MariaDBの設定ファイルはコンテナ上の /etc/mysql/my.cnfで、このファイル内で以下2つのディレクトリ配下の拡張子cnfのファイルを読み込むように設定されている。/etc/mysql/conf.d/配下は空で、デフォルトの状態だと/etc/mysql/mariadb.conf.d/配下のファイルの設定が読み込まれる。

/etc/mysql/mariadb.conf.d/
/etc/mysql/conf.d/

Docker HubのMariaDB公式イメージによると、コンテナのMariaDB設定を変えるには、ホストにあるカスタム設定ファイルを、コンテナ上の/etc/mysql/conf.d/配下のファイルとしてマウントするといいらしい。

ここでは、コンテナ上の/etc/mysql/mariadb.conf.d/配下のデフォルトの設定ファイルをホストにコピーして、設定を変更し、それらのファイルをコンテナの/etc/mysql/mariadb.conf.d/にマウントする。

まずは、コンテナ上の/etc/mysql/mariadb.conf.d/配下の設定ファイルを、ホストに作成したconfigディレクトリ配下にconf.dとしてコピーする。

$ mkdir config
$ docker cp mariadb_testdb_1:/etc/mysql/mariadb.conf.d config/conf.d

コピーしたファイルのうち、設定ファイル以外のファイルを削除しておく。

$ rm -rf config/conf.d/99-enable-encryption.cnf.preset


今回は、文字コードと照合順序の設定を変えてみる。文字コードはutf8mb4、照合順序はutf8mb4_binにする。ホストからコピーしたファイルのうち、50-server.cnfと50-client.cnfをそれぞれ以下のように変更または追記する。

50-server.cnf

[mysqld]
character-set-server  = utf8mb4
collation-server = utf8mb4_bin

50-client.cnf

[client]
default-character-set = utf8mb4


docker-compose.ymlには、MariaDB設定ファイルをマウントする行を追加する。
version: "3"
services:
  testdb:
    image: mariadb:10.8
    environment:
      MYSQL_ROOT_PASSWORD: password # データベースrootパスワード
      MYSQL_DATABASE: testdb # 作成するデータベース
      MYSQL_USER: testuser # 作成するデータベースユーザー
      MYSQL_PASSWORD: password # ユーザーパスワード
      MYSQL_ROOT_HOST: localhost # rootでの接続をloalhostからに限定
      TZ: 'Asia/Tokyo' # タイムゾーン設定
    volumes:
      - test_data:/var/lib/mysql # データ保管場所(永続化ボリューム)
      - ./config/conf.d:/etc/mysql/conf.d:ro # MariaDBの設定ファイル
      
volumes:
  test_data:

設定を反映させるためにコンテナを再起動する。
$ docker-compose restart

変更した設定が反映されているか確認する。まずはDBに接続。
$ docker-compose exec testdb mysql -u root -p -D testdb
Enter password:


文字コードの設定を確認する。

MariaDB [testdb]> SHOW VARIABLES LIKE "%character%";
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8mb3                    |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+ 


照合順序の設定を確認するには以下コマンド。

MariaDB [testdb]> SHOW VARIABLES LIKE "%collation%";
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb4_general_ci |
| collation_database   | utf8mb4_bin        |
| collation_server     | utf8mb4_bin        |
+----------------------+--------------------+


2022年7月28日木曜日

Raspberry Pi OS 64bitでDockerを使う

Raspberry Pi OS 64bitをインストールしたので、それを機にRaspberry PiでDockerを使ってみることにした。64bit OSだと、32bitのときに比べて使える公式Dockerイメージが多い。今回はDocker Compose v1をインストールして、DockerでDjango+PostgreSQL環境を作成すると同様に、Django+PostfreSQLの環境をDockerで作成してみる。


環境


OSはRaspberry Pi OS 64bit with desktop。
$ lsb_release -dr
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11


DockerとComposeのインストール

まずはDockerのインストール。インストールスクリプトをダウンロードして実行するだけ。

$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh

インストールされたDockerのバージョン。

$ docker --version
Docker version 20.10.17, build 100c701

Dockerをインストールしたら、root権限なしでDockerコマンドを使用できるようにしておく。

$ sudo usermod -aG docker ${USER}
usermodの変更を反映させるために再起動。
$ sudo reboot
続いて、Docker Composeをインストール。ここではv1をインストールする。
$ sudo apt install libffi-dev
$ sudo pip3 install docker-compose 
インストールしたバージョンの確認。
$ docker-compose --version
docker-compose version 1.29.2, build unknown

OS起動時にDockerが自動起動するようにしておく。

$ sudo systemctl enable docker


Dockerコンテナを使う準備

Dockerが使えるようになったので、起動するDockerイメージのファイルなどを準備する。

今回は、DockerでDjango+PostgreSQL環境を作成するで作成したファイルで起動させてみる。以下のファイルを同じディレクトリに配置するが、docker-compose.ymlについては、マウントするホスト側のパス指定をWindows用からLinux用に変更しておく。

Dockerfile_web
requirements.txt
Dockerfile_db
docker-compose.yml
.env


docker-compose.yml

Composeファイル。PostgreSQLのデータベース領域は、後で作成するボリュームを指定する。

version: '3'

services:
  db:
    build:
     context: .
     dockerfile: Dockerfile_db
    ports: 
        - "5432"
    environment:
      POSTGRES_DB:
      POSTGRES_USER:
      POSTGRES_PASSWORD:
    volumes:
      - postgres_db:/var/lib/postgresql/data
  web:
    build:
     context: .
     dockerfile: Dockerfile_web
    command: python3 /app/manage.py runserver 0.0.0.0:8000
    volumes:
      - ./app:/app
    ports:
      - "8000:8000"
    depends_on:
      - db

volumes:
  postgres_db:
    external: true


Dockerコンテナの起動

Dockerファイルなどの準備ができたら、Dockerを起動してみる。

まずは、PostgreSQL用のボリュームを作成しておく。

$ docker volume create --name postgres_db
postgres_db 

続いて、ここではsampleというDjangoプロジェクトを作成する。

$ docker-compose run web django-admin.py startproject sample .

データベース接続などのプロジェクトの設定を変更するため、app/sample/settings.pyを編集する。

...
ALLOWED_HOSTS = ['*']
...
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'db',
        'PORT': 5432,
    }
}
...
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo' 

そして、コンテナを起動。

$ docker-compose up -d

ブラウザでhttp://192.168.1.10:8000にアクセス(192.168.1.10はRaspberry PiのIPアドレス)して、以下の画面が表示されれば問題なし。


PostgreSQlへの接続確認は以下のコマンド。
$ docker-compose exec db psql -U postgres 

Djangoのマイグレーションも実行してみる。

$ docker-compose run web python3 manage.py migrate
Creating django_web_run ... done
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK


2022年7月5日火曜日

Raspberry Pi OSで初回起動時にSSH接続できなくなった?

Raspberry Pi OSの64bit版が正式にリリースされてしばらくたったので、Raspberry Pi OSを64bitに切り替えることにした。SDカードにイメージを書き込んで、起動後にSSH接続しようとしたが接続できない。今までは、イメージを書き込んだあとに/boot/sshファイルを作成しておけば、初回起動時にSSHサーバーが起動し、デフォルトユーザーpiでSSH接続できていた。

調べたところ、セキュリティ対策のため、以前は存在したデフォルトユーザーpiがなくなったとのこと。

An update to Raspberry Pi OS Bullseye

公式ツールのRaspberry Pi Imagerを使えば、ユーザー作成ウィザードでユーザーを作成できるらしい。他の方法としては、書き込んだイメージに/boot/userconfを作成し、そこにユーザー名とパスワードハッシュを記述しておくと、初回起動時にユーザーが作成され、そのユーザーでSSH接続できるようになる。パスワードハッシュの作成方法は以下の通り。

opensslが使用できる環境で以下のコマンドを実行。ここではWSL2のUbuntuで実行した。

$ openssl passwd -6 "password"
$6...

passwordがユーザーのパスワードで、オプションの-6を指定するとSHA512でハッシュ化する。出力される$6ではじまる文字列がパスワードハッシュ。

ユーザーpitestを起動時に作成する場合は、/boot/userconfに以下のように書き込む。

pitest:パスワードハッシュ

こうしておくと、/boot/sshが存在すれば初回起動時にSSHが起動し、userconfで設定したユーザーでSSH接続できる。


2022年1月24日月曜日

Raspberry PiにGeoPandasをインストールする

PythonでGISデータを扱うときにはGeoPandasが使える。例えばPythonでGIS用シェープファイルを可視化するやPythonでシェープファイルから都道府県境界図を作成するなど。名前の通りGISデータに対応したPandasのようなもので、GISデータをPandasのデータフレームと同じように操作できる。

しかしRasoberry Piでpipを使ってインストールしようとしても失敗する。どうやら依存関係があるpyprojのインストールでエラーになっている。そこでRaspberry PiにGeoPandasをインストールする手順をまとめておく。


環境

Raspberry Pi OS(bullseye)。

$ lsb_release -dr
Description:    Raspbian GNU/Linux 11 (bullseye)
Release:        11
$ python3 -V
Python 3.9.2


GeoPandasをインストールする方法

pipによるインストールで失敗するのでaptコマンドを使う。aptだとすんなりインストールできる。

$ sudo apt install python3-geopandas
$ pip3 show geopandas
Name: geopandas
Version: 0.8.2

ただaptコマンドでインストールできるバージョンは0.8.2で最新の0.10.2と比べるとけっこう古い。そこでpipコマンドでアップグレードする。これで無事GeoPandasの最新バージョンがインストールできた。

$ pip3 install geopandas --upgrade
$ pip3 show geopandas
Name: geopandas
Version: 0.10.2


2021年2月20日土曜日

Raspberry PiのPythonとSeleniumでヘッドレスブラウザを操作する

Raspberry Piにターミナルで接続してブラウザをPythonで操作するではRaspberry PiでPythonとSeleniumでヘッドレスブラウザを操作してみたが、このときは他のパッケージの依存関係に影響がでるという副作用があった。

最近Raspberry Pi 4で試したところ簡単にインストールできたので、その手順をまとめておく。

Dockerコンテナを使う方法は以下を参照(2023/1/25追記)。


環境


OSはRaspberry Pi OS(Buster)。


SeleniumのPythonバインディングのインストール

インストールは以前と同じでpipでインストールできる。インストールされたバージョンは3.141.0。


Chromiumドライバのインストール

以前はChromiumドライバのインストールが面倒だったが、今回はaptコマンドでインストールできるドライバが使用できたので簡単になった。

インストールされたドライバのパスを確認しておく。


PythonでヘッドレスChromeを操作する。

試しにPythonでヘッドレスChromiumを操作してみる。コードは前回と同じで、Googleで「Raspberry Pi」を検索し、その結果のスクリーンショットをとる。

from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

if __name__ == '__main__':
    url = 'https://www.google.com'

    # ブラウザーをヘッドレスで起動
    options = ChromeOptions()
    options.add_argument('-headless')
    # Chromedriverのパスを指定
    driver = Chrome(executable_path='/usr/bin/chromedriver', options=options)

    driver.get(url)

    # 画面表示を最大5秒まで待つ
    driver.implicitly_wait(5)

    # 検索ボックスのエレメントを取得(inputタグのname属性はq)
    elem = driver.find_element_by_name('q')
    # 検索ボックス内のテキストをクリア
    elem.clear()
    # 検索ワードを入力
    elem.send_keys('Raspberry Pi')

    # 検索ボタンがクリックできるようになるまで最大10秒待つ
    wait = WebDriverWait(driver, 10)
    input = wait.until(expected_conditions.element_to_be_clickable((By.XPATH, "//input[@value='Google 検索']")))

    if input.get_attribute('type') == 'submit':
        input.click()
    else:
        print('Failed to find submit button.')

    # 検索結果がなければ「No results found.」と表示する
    assert "No results found." not in driver.page_source

    # スクリーンショットを保存
    driver.save_screenshot('chromium_search.png')

    driver.close()


結果は次のように期待通りに検索できた。






2020年11月28日土曜日

Raspberry Pi OSをStretchからBusterにアップグレードする

OSがStretchのまま使い続けていた Raspberry PiがあったのでBusterにアップグレードした。 そのときの手順をまとめておく。


バックアップ

OSをアップグレードすると不具合が起きることがあるので事前にイメージバックアップをとっておく。ddコマンドを使う場合は次のような方法でできる。

Raspberry PiのSDカードイメージをバックアップする

Raspberry Piのイメージバックアップのサイズを小さくする


アップグレード前の設定変更


パッケージの取得元をStretchからBuster用に変更する。

次のコマンドで何も表示されなければOK。


OSのアップグレード


次のコマンドでOSをアップグレードする。

途中何度か既存の設定ファイルを保持するか新しいバージョンのものに更新するか聞かれるのでその都度選択する。

アップグレードが完了したら不要になったパッケージを削除しておく。

さらにキャッシュしたのみでインストールされていないファイルも削除する。


アップグレード後の確認


OSがアップグレードされていることを確認。

カーネルのバージョンも確認。


pipのインストール


OSをアップグレードしたらPython3のpipが使えなくなってしまった。

pipインストール用のスクリプトがあるのでダウンロードしてインストールする。


2020年8月30日日曜日

Raspberry PiにBluetoothスピーカーをつなげる

しばらく使っていなかったRaspberry Pi Model B+にBluetoothスピーカーをつなげてみたので、その手順をまとめておく。このモデルにはBluetooth機能はないのでBluetooth USBアダプタを使う。


環境


OSはRaspbian Buster(Lite)。



Bluetoothアダプタの確認


Bluetooth USBアダプタをRaspberry Piに接続して認識されているか確認。一番上のデバイスがBluetoothアダプタなので、ちゃんと認識されている。



必要なモジュールのインストールと設定


サウンドサーバとそのbluetoothモジュールをaptコマンドでインストール。


使用するユーザ(pi)をbluetoothグループに追加していったんリブート。



Bluetoothスピーカーの接続


リブートが完了したらbluetoothctlでBluetoothスピーカーを接続する。Bluetoothスピーカーをペアリング可能な状態にしてから、scan onで接続するBluetoothスピーカーのMACアドレスを確認する。


MACアドレスを確認したらBluetoothスピーカーに接続する。



Bluetoothスピーカーから再生してみる


Bluetoothスピーカーに接続できたので、試しにmp3を再生してみる。mp3プレイヤーとしてmpg321をインストールする。

mp3ファイルを再生して音がでればOK。


2020年1月5日日曜日

Raspberry Piに新しいバージョンのSciPyを簡単にインストールする

Raspberry PiのPythonでnumpyとscipyを使う(Jessie版)と同様に、RaspbianのBusterではaptコマンドでSciPyをインストールできる。



ただ、現時点でのSciPyの最新バージョンは1.4.1で、aptコマンドでインストールできるバージョンはけっこう古い。Pythonライブラリはpipでもインストールできるが、SciPyの場合は、libatlas-base-devやgfortranなどの必要なパッケージをインストールしてもエラーでインストールできない。

そこで、piwheelsを利用して最新ではないが比較的新しいバージョンのSciPyをインストールする。piwheelsはARM向けのPythonパッケージリポジトリで、Raspberry Piをサポートしている。


環境


Raspberry Pi 3 Model BとRaspbianのBuster。



piwheelsからパッケージをダウンロードしてインストール


SciPyのパッケージはLinks for scipyからダウンロードできる。現時点の最新の1.3.3をインストールする。バージョンごとに4種類のパッケージがあるが、Raspberry PiのCPUとPythonのバージョンでダウンロードするパッケージを決める。Raspberry Pi 3 Model Bなのでarmv7、Pythonのバージョンは3.7なのでcp37。よって今回はscipy-1.3.3-cp37-cp37m-linux_armv7l.whlをダウンロードする。

ダウンロードしたパッケージはpipでインストールできる。


SciPyのバージョン1.3.3がインストールされた。



2019年10月3日木曜日

Raspberry Piにターミナルで接続してブラウザをPythonで操作する

Pythonでブラウザを操作するではSeleniumのPythonバインディングでUbuntuのChromeを操作してみた。SeleniumはWebサイトのUIテストを自動化するツールで、GUIがないヘッドレスブラウザも利用できるので、GUIが使えないターミナルでも使用できる。そこで、SeleniumのPythonバインディングを使って、Raspberry Piにターミナルで接続してヘッドレスブラウザを操作してみる。

aptコマンドでインストールできるChromiumドライバが使えるようになった。
Raspberry PiのPythonとSeleniumでヘッドレスブラウザを操作する 
(2021/2/20追記)


環境


Raspberry Pi(Raspbian Buster) with desktop。



SeleniumのPythonバインディングのインストール


SeleniumのPythonバインディングはpipでインストールできる。



ChromeDriverのダウンロードとChromiumのインストール


Seleniumでブラウザを操作するには、ブラウザごとにドライバが必要になる。Pythonでブラウザを操作するではChromeDriver - WebDriver for ChromeからChromeDriverをダウンロードして使用したが、RaspbianにはChromeはインストールできないので、今回はこのドライバは使えない。代わりに、Chromiumとそのドライバを使う。Chromiumはオープンソースのブラウザで、Raspbian Buster with desktopにはデフォルトでインストールされている。ChromeはオープンソースのChromiumにGoogleが独自の機能を追加したもので、Chromium用のドライバはchromium-chromedriverとなっている。Chromium用のドライバはaptコマンドでインストールできるが、このドライバではSeleniumを使うときにエラーが出てしまった。


そこで、Chromiumとそのドライバを別のところから調達。LaunchpadのリポジトリからUbuntu Xenial用のパッケージをダウンロードしてインストールした。

ただし注意点として、もともとインストールされていたChromiumよりも新しいバージョンをインストールすることになるので、依存関係のためにaptコマンドでufwがインストールできなくなる。


その場合ufw 0.35-0ubuntu2 (armhf binary) in ubuntu xenialをダウンロードしてインストールできるが、他にもこうしたパッケージがあるかもしれない。

ダウンロードするUbuntu Xenial用パッケージはhttps://launchpad.net/ubuntu/xenialから検索できる。Chromiumとそれと依存関係にあるパッケージ2つ、それにChromedriverをダウンロード。


続いて、ダウンロードしたパッケージをインストール。


Chromedriverをインストールしたら、パスを確認しておく。



PythonでヘッドレスChromiumを操作する


SeleniumとPythonでヘッドレスChromiumを操作してみる。Raspberry Piにターミナルで接続し、以下のコードを実行する。ヘッドレスChromiumが起動して、Googleで「Raspberry Pi」を検索し、その結果をスクリーンショットにとる。
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

if __name__ == '__main__':
    url = 'https://www.google.com'

    # ブラウザーをヘッドレスで起動
    options = ChromeOptions()
    options.add_argument('-headless')
    # Chromedriverのパスを指定
    driver = Chrome(executable_path='/usr/bin/chromedriver', options=options)

    driver.get(url)

    # 画面表示を最大5秒まで待つ
    driver.implicitly_wait(5)

    # 検索ボックスのエレメントを取得(inputタグのname属性はq)
    elem = driver.find_element_by_name('q')
    # 検索ボックス内のテキストをクリア
    elem.clear()
    # 検索ワードを入力
    elem.send_keys('Raspberry Pi')

    # 検索ボタンがクリックできるようになるまで最大10秒待つ
    wait = WebDriverWait(driver, 10)
    input = wait.until(expected_conditions.element_to_be_clickable((By.XPATH, "//input[@value='Google 検索']")))

    if input.get_attribute('type') == 'submit':
        input.click()
    else:
        print('Failed to find submit button.')

    # 検索結果がなければ「No results found.」と表示する
    assert "No results found." not in driver.page_source

    # スクリーンショットを保存
    driver.save_screenshot('chromium_search.png')

    driver.close()

結果は以下の通りで、ちゃんと検索できた。


2019年8月14日水曜日

Raspberry Piのイメージバックアップのサイズを小さくする

Raspberry Piでイメージバックアップをとるとき、ddコマンドなどでSDカード全体のイメージをファイルにする方法がある。この方法で問題になるのが、同じ容量のSDカードでも微妙に使用できる容量が違うことがあって、例えば、16GBのSDカードからとったイメージバックアップを別の16BGのSDカードに正常にリストアできないことがある。また、大きなサイズのSDカードを使っていると、無駄にサイズが大きなバックアップファイルになってしまうこともある。そういうわけで、イメージバックアップを作成するときに、パーティションの使っていない領域を削って上記の問題が起きないようにする方法をまとめておく。


使うもの


Raspbery Pi 3 Model 3
縮小バックアップをとるRaspbian(Stretch)がインストールされたマイクロSDカード
Raspbian with desktopのイメージ(パーティション縮小やバックアップに使う)
USBメモリ(Raspbian with desktopをインストールする)
マイクロSDカード(縮小バックアップのリストア確認用)


バックアップ&パーティション縮小環境の作成


イメージバックアップのサイズを減らすためにパーティションのサイズを縮小するが、そのためにはGpartedを使う。Gpartedが使えるUbuntuなどの環境があればそれでもできると思うが、ない場合はUSBメモリにインストールしたRaspbianでGpartedを使う。

まずはUSBメモリにデスクトップ環境が使えるRaspbian with desktopのイメージを書き込む。作成しておいたイメージバックアップでもいいし、Raspberry PiのサイトからダウンロードしたイメージでもOK。USBメモリにイメージを書き込んだら、Raspberry Pi 3でUSBブートするにあるように/boot/config.txtに「program_usb_boot_mode=1」を追記する。


このUSBメモリをRaspberry Piに挿入し(SDカードは抜いておく)、電源を入れて起動させる。USBメモリからブートして初期設定などが終わったら、Gpartedをインストールする。メニュー>設定>Add/Remove Softwareで「Gparted」で検索すると「GNOME partition editor」が見つかるのでこれをインストールする。


パーティションの縮小


バックアップをとるSDカードにあるパーティションのサイズをGpartedで縮小する。パーティションをいじるので、万が一のことに備えて前もってSDカードのイメージバックアップ(フルサイズ)を作成しておいた方が安心。

USBメモリから起動したRaspberry Piに縮小バックアップをとるSDカードをスロットに挿入して、設定>GpartedでGpartedを起動する。

SDカードを挿入すると自動的にマウントされるので、Gpartedが起動したら対象のSDカードを選択してアンマウントする。

続いて、サイズを縮小するパーティションを選択してメニューから「リサイズ」を選ぶ。

ウィンドウ上部の枠をマウスでドラッグすると左右に動かせるので、それで縮小後のサイズを決める。ギリギリまで縮小するとリストアしたときに空き容量不足になってしまうかもしれないので、余裕を持たせておく。

最後に、変更を適用する。

※パーティションサイズを縮小したSDカードを後で使う予定なら、縮小バックアップ後にパーティションサイズを戻しておく。


縮小したイメージのバックアップ


ddコマンドで縮小したパーティションサイズに合わせてイメージバックアップを作成する。まずは、ターミナルを起動してバックアップを作成するSDカードのパーティションを確認する。


確認するのはバックアップを作成するSDカードのデバイス名(/dev/mmcblk0)。それから、セクターサイズ(Sector size)と/dev/mmcblk0p2(最後のパーティション)のEndセクターの値。ここではそれぞれ512と20639743で、/dev/mmcblk0p2の終端は512バイトのセクター20639743個分の位置にあることになる。

ddコマンドはブロックサイズ単位で読み書きをするが、ブロックサイズはbsオプションで指定する。また、コピーするブロック数はcountオプションで指定できる。ddコマンドでバックアップを作成するときに/dev/mmcblk0p2の終端を含むようにオプションを指定すればデータが欠損することなくイメージバックアップを作成できる。

ddコマンドでブロックサイズを4MBに指定する場合、countオプションに指定する値を求める式は以下のようになる。

(セクターサイズ)x (/dev/mmcblk0p2のEndセクター数)/ (ddコマンドのブロックサイズ)

今回の場合は次のようになる。
512 x 20639743 / (4 x 1024 x 1024) = 2519.499...

countオプショには小数点以下を切り上げした2520を指定し、以下のddコマンドでSDカード(mmcblk0)のイメージバックアップをbackup.imgとして保存する。



バックアップしたイメージの確認


最後にバックアップしたイメージをSDカードに書き込んで、それで問題なく起動できることを確認しておいた方が安心。USBメモリから起動したRaspbianのddコマンドでSDカードに書き込んでもいいが、今回はWindows PCでSDカードにイメージを書き込む。

バックアップファイルはext4のドライブに保存したので、特別なソフトウェアなどを使わないとWindowsではバックアップファイルを読めない。そこで、NASを経由してWindows PCにコピー。

RaspbianでNASをマウントするには以下のコマンド。nasuser、naspasswordはNASにアクセスするユーザーとそのパスワード。192.168.1.100はNASのIPアドレスでbackupは共有名。


あとは、Windowsで縮小バックアップしたイメージをSDカードに書き込んで、そのSDカードで正常に起動できることが確認できれば完了。



2019年7月15日月曜日

Raspberry PiのファイルをGoogleドライブにバックアップする

Raspberry Piにあるファイルを、万が一のときのためにGoogleドライブにバックアップできるようにする。Raspberry PiからGoogleドライブにアクセスするにはrcloneを使う。rcloneはファイルをGoogleドライブやDropboxなどのクラウドサービスと同期できるコマンドラインプログラム。


環境


Raspbian Stretch。



rcloneのインストール


rcloneインストール用のスクリプトが用意されているのでそれを使う。インストールされたバージョンはv1.48.0。



rcloneでGoogleドライブを利用する設定


rclone configコマンドで各種サービス用の設定ができる。


Googleドライブ用の設定についてはGoogle driveに詳細な説明がある。以下、rclone configの設定内容。

新規リモート(利用するサービス)の設定作成
n) New remote
s) Set configuration password
q) Quit config
n/s/q> n

新規リモート名の設定(任意)
name> gdrive

設定するサービス(12 / Google Drive)を選択
Storage> 12

client_idとclient_secretはブランクでOK
client_id>
client_secret>

アクセス権をフルアクセス(1 / Full access all files, excluding Application Data Folder)に設定
scope> 1

root_folder_idとservice_account_fileはブランクでOK
root_folder_id>
service_account_file>

advanced設定はしない
Edit advanced config? (y/n)
y/n> n

リモートの設定を自動でしない(ターミナルで設定しているのでnを選択)
Remote config
Use auto config?
* Say Y if not sure
* Say N if you are working on a remote or headless machine
y/n> n

表示されるリンクをブラウザで開いて、rcloneによるアクセスを許可するとコードが表示されるのでコピーしてペーストする
Log in and authorize rclone for access
Enter verification code> *****

チームドライブとして設定しない
Configure this as a team drive?
y/n> n

設定した情報が表示されるのでOKならy
y) Yes this is OK
e) Edit this remote
d) Delete this remote
y/e/d> y

設定の終了
e/n/d/r/c/s/q> q


rcloneによるGoogleドライブの操作


rcloneでGoogleドライブのファイル一覧を表示したり、Raspberry PiにあるファイルをGoogleドライブにアップロードできる。いくつか操作を試してみる。

Googleドライブトップ階層のディレクトリ一覧を表示


Googleドライブの全ファイル一覧を表示


Googleドライブにtestディレクトリを作成


ファイルをGoogleドライブに作成したディレクトリにコピー


Googleドライブのディレクトリtestにあるファイルをダウンロード



Raspberry PiのディレクトリとGoogleドライブのディレクトリを同期させる


Raspberry PiのディレクトリtestとGoogleドライブのディレクトリtestを同期させてみる。

Raspberry Pi内ディレクトリtestのファイル一覧を確認。


Googleドライブ内ディレクトリtestのファイル一覧を確認。


まずは--dry-runオプションで実際に同期させずに動作を確認する。同期させたときに予期せずにファイルが削除されることがありえるので、--dry-runオプションではじめに確認する方が安心。


まだファイルは同期されていない。


実際に同期させる。


ファイルが同期された。



Googleドライブを使用するときの制限


Google driveによると、Googleドライブにはレートリミットがあって、rcloneで使用するときには1秒間に2ファイルぐらいしか転送できない。

Drive has quite a lot of rate limiting. This causes rclone to be limited to transferring about 2 files per second only.

ファイル数が多い場合はアーカイブにするなどすれば良いかも。


cloneでバックアップジョブの設定


自動で定期的にGoogleドライブにバックアップするように、Raspberry Piにcloneジョブを追加する。cronで毎日2時に同期バックアップを実行するには、crontabに「00 2    * * * pi     rclone sync test gdrive:test」を追記する。



2019年6月30日日曜日

Raspberry PiでUSBマイクを使う

Raspberry Pi 3にUSBマイクを接続して録音できるようにしたので、その手順をまとめておく。

使ったUSBマイクはこちら。
ANOTHER DIMENSION USB マイク PS4 ボイスチャット Win・Mac 対応


環境


Raspbian Stretch。



Raspberry PiにUSBマイクを接続


Raspberry PiにUSBマイクを接続し、まずはOSに認識されているか確認。一番上のDevice 004が今回接続したUSBマイク。



使用ユーザーをaudioユーザーグループへ追加


デフォルトユーザーのpi以外の新規に作成したユーザーを使用する場合は、audioグループに作成したユーザーを追加しておく。そうしておかないと、以降で使うalsamixerやarecordコマンドにroot権限が必要になる。

usernameというユーザーの場合。



ボリューム調整


録音するボリュームを調整する。まずはサウンドカードの確認。AK5371が今回接続したUSBマイク。


以下のコマンドで、サウンドカードAK5371を指定して録音機能を設定するGUIが起動する。


以下のようのGUIでボリュームの設定などができる。


録音テスト


arecordコマンドで録音できるが、あらかじめサウンドカードの番号とデバイス番号を確認しておく。接続したUSBマイクのカード番号は1、デバイス番号は0になっている。


以下のコマンドで、CD音質で10秒の録音がtest.wavに保存される。




2019年1月27日日曜日

PythonでWikimedia APIを使ってWikipediaの記事を取得する

WikipediaにはMediaWikiというAPIがあって記事データなどを取得できる。このAPIとPythonでWikipediaの記事内容を取得してみる。


環境


Raspberry PiとRaspbian Stretch。



エンドポイントとリクエストクエリ


MediaWikiのページにはAPI用のエンドポイントが記載されているが、日本語の場合はhttps://ja.wikipedia.org/w/api.phpを使う。

取得できるデータのフォーマットにはjsonやxmlがあるが、API:データ形式によるとxmlでの取得はこの先廃止されるらしいのでここではjsonで取得する。

APIで記事内容を取得するクエリはAPI:Query/jaのSample queryを参照。ただし、formatはjsonに変更して使用する。

最終的に、以下のようなリクエストを送信する。titlesには取得するページのタイトルを指定する。

https://ja.wikipedia.org/w/api.php?action=query&prop=revisions&rvprop=content&format=json&formatversion=2&titles=Main%20Page


APIでWikipediaの記事を取得してみる


以下のコードで「ウィキペディア」ページの記事内容を取得する。

from urllib.request import Request, urlopen
from urllib.parse import urlencode
from urllib.error import URLError, HTTPError
import json

def set_params(page_title):
    params = {
        'action': 'query',
        'prop': 'revisions',
        'rvprop': 'content',
        'titles': page_title,
        'formatversion': 2,
        'format': 'json',
    }

    return params

def main():
    request_url = 'https://ja.wikipedia.org/w/api.php?' + urlencode(set_params('ウィキペディア'))

    req = Request(request_url)

    try:
        with urlopen(req) as res:
            res_json = res.read()
    except HTTPError as e:
        print('HTTPError: {}'.format(e.reason))
    except URLError as e:
        print('URLError: {}'.format(e.reason))
    else:
        wiki = json.loads(res_json.decode('utf-8'))
        print(wiki['query']['pages'][0]['revisions'][0]['content'])

if __name__ == '__main__':
    main()

結果は以下のように、Wikipedia書式(ヘルプ:書式整形)のテキストが得られる。


2019年1月14日月曜日

PythonでRのスクリプトを実行する

Raspberry Piに最新のR(3.5.2)をインストールするでRaspberry PiにRをインストールしたので、PythonからRを使ってみる。

PythonからRを使う方法としてはPypeRなどがあるらしいが、Rで検定を実行してその結果を表示するだけなら、Pythonのsubprocessモジュールでも十分。Pythonのsubprocessでコマンドを実行するのようにPythonからコマンドを実行できるので、RscriptをPythonから実行する。


環境


Raspberry PiとRaspbian Stretch with Desktop。



RスクリプトをPythonで実行する


Rスクリプトをコマンドラインで実行するにはRscriptコマンドを使う。このコマンドは引数を受け取れるので、PythonでRスクリプトファイルを作成して、引数を含めてsubprocessモジュールで実行する。

以下のコードでは、平均が異なる正規分布に従う値を10ずつ生成してt検定を行うRスクリプトファイルを作成し、Rscriptで実行した結果を表示する。

※Rスクリプトがエラーのときにメッセージを出力するようにコード変更(2019.2.11)

結果は以下の通り。




2019年1月3日木曜日

Raspberry Piに最新のR(3.5.2)をインストールする

Raspberry Piにプログラミング言語のRをインストールしようとしたが、aptコマンドでインストールできるのはバージョン3.3.3。現時点での最新は3.5.2なので、ちょっと古い。そこで、Raspberry Piにソースコードから最新のR(Ver.3.5.2)をインストールする。参考はInstall the Latest Version of R (3.5.1) on Raspberry Pi from Source。



環境


Raspberry PiとRaspbian Stretch with Desktop。



必要パッケージのインストール


最新のRをインストールするためにソースコードからビルドするが、まずはビルドに必要なパッケージをインストールする。



ソースコードのダウンロード


ソースコードはRStudioのCRANミラーサイトからダウンロードできる。


ダウンロードしたファイルを解凍しておく。



ソースコードからインストール


Install the Latest Version of R (3.5.1) on Raspberry Pi from Sourceによると、configureファイルの修正が必要らしいので、ビルドの前に修正しておく。バージョン3.5.2のconfigureでは27539行の「::」を「;;」に修正する。viでは「:27539+Enterキー」で27539行へ移動できる。


configureファイルを修正したら、ビルドしてインストールする。


インストールが完了したらバージョンを確認。



Rの動作確認


インストールしたRで、正規分布に従う乱数を10出力してみる。