みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

yocto raspberrypi3でA2DP レシーバ

スマートフォンなどの音源を、Bluetooth経由でラズベリーパイ3からならす。 OSはいつもどおりyoctoprojectを使用して作成。

BluetoothではA2DPを使用し、sinkとなるようにする。 実際に音を鳴らすのはpulseaudioで、ラズベリーパイ3が他の端末からオーディオに見えるようにするための設定も pulseaudioのbluetooth-discoverモジュールで行う。

そのため、bluez5自体の設定ファイルはいらない。

ラズベリーパイ3上でBluetoothで接続された音源と音を鳴らすためのデバイスをloopbackしたり、経路を作る必要があり、 よそのサイトではこの部分にudevのルールを書いたりpythonスクリプトを書いたりしているのを見かけたが、 これもpulseaudioのbluetooth-policyモジュールで行うため今回は不要。

使用するyoctoprojectのバージョンはpyro(2.3) すでにrocko(2.4)が出ているが、今回は古いので行く。

環境構築

ソースの取得

$ git clone git://git.yoctoproject.org/poky.git -b pyro
$ git clone git://git.openembedded.org/meta-openembedded -b pyro
$ git clone git://git.yoctoproject.org/meta-raspberrypi -b pyro

環境変数設定

環境変数の読み込みとビルドディレクトリの作成

$ cd ..
$ source poky/oe-init-build-env

ビルド対象のレイヤを追加

$ bitbake-layers add-layer ../poky/meta-raspberrypi
$ bitbake-layers add-layer ../poky/meta-openembedded/meta-oe
$ bitbake-layers add-layer ../poky/meta-openembedded/meta-python

local.confの変更

下記をlocal.confに追加する

MACHINE = "raspberrypi3"

# systemd
DISTRO_FEATURES_append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""
IMAGE_INSTALL_append = " systemd-bash-completion \
             "

# pulseaudio
DISTRO_FEATURES_append = " pulseaudio pam"
IMAGE_INSTALL_append = " pulseaudio \
                 pulseaudio-server \
             pulseaudio-misc \
             pulseaudio-module-systemd-login \
             pulseaudio-module-bluez5-discover \
             pulseaudio-module-bluez5-device \
             pulseaudio-module-bluetooth-policy \
             pulseaudio-module-bluetooth-discover \
             pulseaudio-module-loopback \
             pulseaudio-bash-completion \
"

# bluez5
IMAGE_INSTALL_append = " bluez5"
IMAGE_INSTALL_append = " bluez5-testtools"

# connman
IMAGE_INSTALL_append = " connman connman-client"

# avahi
IMAGE_INSTALL_append = " avahi-daemon"

FETCHCMD_wget = "/usr/bin/env wget -t 2 -T 3000 -nv --passive-ftp --no-check-certificate"

# automount
DISTRO_FEATURES_append = " automount"

# util-linux 
IMAGE_INSTALL_append = " util-linux-bash-completion \
                 util-linux-lsblk"

# Enable UART for debug console
ENABLE_UART = "1"

# ALSA
IMAGE_INSTALL_append = " alsa-utils \
             alsa-utils-speakertest \
"

# sudo
IMAGE_INSTALL_append = " sudo"

若干不要なパッケージや設定も含まれているが気にしない。

pulseaudioのbluetoothモジュールのロード設定

/etc/pulse/system.paに下記を追加

### Automatically load driver modules for Bluetooth hardware
.ifexists module-bluetooth-policy.so
load-module module-bluetooth-policy
.endif

.ifexists module-bluetooth-discover.so
load-module module-bluetooth-discover
.endif

今回のケースでは、module-bluetooth-discoverはBluetoothの設定を自動化し module-bluetooth-policyはa2dp_sourceとsinkのloopbackを自動化する。

pulseaudioのシステムサーバ化

/etc/systemd/system/pulseaudio.serviceを下記の内容で作成

[Unit]
Description=PulseAudio system server

[Service]
Type=notify
ExecStart=/usr/bin/pulseaudio --daemonize=no --system --realtime --log-target=journal

[Install]
WantedBy=multi-user.target

サービスの登録、起動

$ systemctl --system enable pulseaudio.service 
$ systemctl --system start pulseaudio.service 

dbus設定

/etc/dbus-1/system.d/pulseaudio-system.confに下記の行を追加

 <busconfig>
                                                                          
   <!-- System-wide PulseAudio runs as 'pulse' user. This fragment is
        not necessary for user PulseAudio instances. -->
                                                                          
   <policy user="pulse">
     <allow own="org.pulseaudio.Server"/>
+    <allow send_destination="org.bluez"/>
+    <allow send_interface="org.bluez.Manager"/>
   </policy>
                                                                     
 </busconfig> 

pactlの使い方

sudo でpulseユーザーを指定して実行する

$ sudo -u pulse pactl list short sinks

blueoothの設定

discoverableを有効にする。

$ connmanctl enable bluetooth
$ bluetoothctl
> power on
> discoverable on
> agent on
> quit

ここまででペアリング可能になる。

このままではAccessDeniedが発生し接続できないが、相手のデバイスをtrustすることで接続できるようになる。

自動化

bluetoothctlではバッチ処理に不向であることと、相手のデバイスをtrustする手順が煩雑になるため、 bluez5-testtoolsに含まれるスクリプトを使用し、ある程度自動化できるようにする。

まずはバグフィックス

$ cd /usr/lib/bluez/test/
$ find -type f | xargs sed -i 's/iteritems/items/g'

次のコマンドを実行する。

$ cd /usr/lib/bluez/test
$ ./test-adapter discoverable on
$ ./simple-agent -c NoInputNoOutput

simple-agentがtrustまで行ってくれるので、bluetoothctlの手順よりも多少は簡略化できる。 これでも、相手とのペアリングのタイミングでプロンプトがでるため、完全にヘッドレスでの運用はできない。 一度、接続が成功し相手の端末に接続設定が保存されてしまえば、ヘッドレス運用もできると思う。

その場合はシステム起動時にsimple-agentを起動し、 GPIOで物理ボタンなどをつけて、押されたタイミングでdiscoverable onを行うなど工夫が必要になる。

内蔵オーディオについて

ラズベリーパイ内蔵のオーディオを使用するためにはsnd_bcm2835ドライバを組み込む必要がある。

SDカードのブートパーティションにあるconfig.txtに下記を追加すると、ブート時にドライバが組み込まれるようになる。

dtparam=audio=on

まとめ

ここまでの設定を行った後で再起動してsimple-agentを立ち上げると、 スマートフォンipodなどとBluetoothで接続して、音源を鳴らすことが出来るようになる。

yoctoで日本語入力

今まで、yoctoで作成したLinuxでは日本語を表示できる環境はよく見かけたが、 meta-oeにはuimanthyのパッケージが存在するのに、日本語を入力できる環境を見たことがなかった。

今回、仕事がらみで日本語入力環境について調べたところ、uimのレシピに不具合があり、 正しい日本語入力環境が作成できなくなっていたことがわかった。 いろいろ修正した結果日本語が入力できる環境を作成することができたので、 これらの修正パッチをmeta-openembeddedのMLに投稿したところ、取り込んでもらえた。 そのため、masterブランチおよび次期バージョンではuim+anthyによる日本語入力環境が作成できるようになる。

ここではmasterブランチを使用し、qemu環境で日本語を入力できる環境を構築する。

poky及びmeta-openembeddedの取得

$ git clone git://git.yoctoproject.org/poky.git
$ cd poky
$ git clone git://git.openembedded.org/meta-openembedded

環境変数の設定

$ cd ../
$ source poky/oe-init-build-env build

これで自動的にbuildディレクトリへ移動する。

レイヤの追加

$ bitbake-layers add-layer ../poky/meta-openembedded/meta-oe

local.confの修正

IMAGE_INSTALL_append = " uim-xim uim-utils uim-common uim-gtk2.0 uim-gtk3 uim-anthy"
IMAGE_INSTALL_append = " ttf-vlgothic ttf-sazanami-gothic ttf-sazanami-mincho"
IMAGE_INSTALL_append = " setxkbmap"

IMAGE_LINGUAS ?= "ja-jp ja-jp.euc-jp"
GLIBC_GENERATE_LOCALES = "ja_JP.UTF-8 ja_JP.EUC-JP"

日本語入力に必要なパッケージと、日本語フォント、キーボードレイアウトの設定。 そしてロケールの設定を追加している。

bitbake

$ bitbake core-image-sato

QEMUの起動

buildディレクトリで次のコマンドを実行するとQEMUが起動する。

$ runqemu

日本語設定

そのままでは、日本語の表示び入力が有効化されていないので、~/.profileを以下の内容で作成し、rebootする。

export LC_ALL=ja_JP.UTF-8
export GTK_IM_MODULE="uim"
export XMODIFIERS="@im=uim-anthy"
uim-xim &
uim-toolbar-gtk-systray &
setxkbmap -layout jp

日本語入力

再起動後、キーボードも日本語配列になり、 「shift+スペース」で日本語入力ができるようになる。

きちんと試してはいないが、Qt系のアプリではおそらく効果はないと思う。(QT_IM_MODULEの環境変数を設定しても)

f:id:mickey_happygolucky:20170917053328p:plain
日本語入力の様子

これらのパッケージは、もちろんQEMU以外の実機でも動作する。

Ubuntu16.04でローカルのdebファイルを依存関係を解決しながらインストール(gdebi)

debファイルをインストール

例えばUbuntugoogle chromeなどをインストールする場合、debファイルをダウンロードしてきてインストールする必要がある。通常だと次のような感じでdpkgコマンドを使用する。

$ sudo dpkg -i ./google-chrome-stable_current_amd64.deb

この場合、インストールしたいパッケージが他のパッケージを必要としているなどの依存関係があったとしても、自動的に解決してはくれない。 aptでは自動的に依存するパッケージをインストールしてくれるが、ローカルにdebファイルをインストールすることができない。

gdebiコマンド

gdebiファイルを使用すると、ローカルのdebファイルをインストールするときに、aptのように依存関係を解決してくれる。

gdebiコマンドそのものはaptでインストールできる。

$ sudo apt install gdebi

gdebiコマンドでgoogle chromeをインストールする場合は次のようにする。

$ sudo gdebi ./google-chrome-stable_current_amd64.deb

便利。

ラズベリーパイ3 pyroでweston

meta-weston-rpiにRPi3対応しないの?というコメントがついたので、一応調べてみた。

すでに、waylandはrpi-backend.soのサポートをやめてしまっているので、実はmeta-weston-rpiは使えない。

もっと言うと、vc4graphicsが使用できることから、これがなくてもwestonは動かせるはず。

なので、ちょっとやってみた。

buildディレクトリはクリーンな状態で作業する必要がある。 DISTRO_FEATURESとか変更するので、ゴミが残っていると謎のエラーが頻発する。

yocto環境の取得

次の3つをcloneする。

  • poky
  • meta-raspberrypi
  • meta-openembedded

meta-openembeddedはmeta-oeを使いたいから(実は不要なのかも。。。)

$ git clone git://git.yoctoproject.org/poky.git -b pyro
$ cd poky
$ git clone git://git.yoctoproject.org/meta-raspberrypi -b pyro
$ git clone git://git.openembedded.org/meta-openembedded -b pyro

buildディレクトリの作成と環境変数の設定

$ cd ..
$ source poky/oe-init-build-env

自動的にbuildディレクトリに移動される。

layerの設定

$ bitbake-layers add-layer ../poky/meta-raspberrypi
$ bitbake-layers add-layer ../poky/meta-openembedded/meta-oe

local.confの設定

local.confをエディタで編集。下記ではemacsを起動している。

$ emacs ./conf/local.conf

次の内容をlocal.confの上の方に記述する。

MACHINE ?= "raspberrypi3"

DISTRO_FEATURES_append = " wayland opengl"
DISTRO_FEATURES_remove = " x11"
MACHINE_FEATURES += "vc4graphics"

bitbake実行

$ bitbake core-image-weston

SDの作成

下記コマンドでイメージをSDカードに書き込む。

$ sudo dd if=tmp/deploy/images/raspberrypi3/core-image-weston-raspberrypi3.rpi-sdimg of=/dev/sdb bs=100M

/dev/sdbはSDカードのデバイス名。環境によっては/dev/sdcなどになる。

実行画面

f:id:mickey_happygolucky:20170531010930j:plain

無事、動いた。

Python ラズベリーパイ3でHTTPサーバを動作させ、ネットワーク越しにLEDを操作する

PythonでのHTTPサーバの実装には、BaseHTTPServerを使用する。

まず、プログラム全体を載せる。

HTTP server with python

メインプログラム

メインプログラムの流れは 図 1 のようになる。

f:id:mickey_happygolucky:20170527141423p:plain

図 1: メインプログラム.

非常に簡単。 このプログラムの肝となるのは、TestHandlerの実装となる。

TestHandlerの実装

TestHandlerのインターフェイスは 図 2 のようになる。

f:id:mickey_happygolucky:20170527141430p:plain

図 2: TestHandlerクラス.

do_GET()の実装

do_GET()の流れは 図 3 のようになる。

f:id:mickey_happygolucky:20170527141456p:plain

図 3: do_GET()の流れ.

do_GET()はサーバに対してGETリクエストを投げられた時にコールバックされる。

path変数にはアクセス先が格納されている。

ui.htmlへアクセスされた場合は、html変数に設定してある内容を返す。 htmlには、このサーバの機能をブラウザから呼び出せるようにするためのページが記述されている。

それ以外のページにアクセスされた場合は404を返す。

do_POST()の実装

do_POST()の流れは 図 4 のようになる。

f:id:mickey_happygolucky:20170531005428p:plain

図 4: do_POST()の流れ.

do_POST()はサーバに対してPOSTリクエストを投げられた時にコールバックされる。

path変数にはアクセス先が格納されている。fromデータはcgi.FieldStorage()で取得する。

ここではstateの値によってLEDを点灯、消灯する。

process_post_haeder()の実装

process_post_haeder()は 図 5 のようになる。

f:id:mickey_happygolucky:20170527141521p:plain

図 5: process_post_header()の流れ.

get_ip_address()について

ui.htmlで返すHTMLのform actionには、サーバのIPアドレスを設定する必要がある。 しかし、スクリプトが実行されるIPアドレスは実行時までわからないため、@HOST@という文字列にしておき、 下記のようにget_ip_address()で取得したIPアドレスで置換している。

    html = html.replace('@HOST@', get_ip_address())

get_ip_address()の処理の流れは 図 6 のようになっている。

f:id:mickey_happygolucky:20170527141554p:plain

図 6: get_ip_address()の流れ.

socketを開き、試しに8.8.8.8へ接続を試み、その時に使用されたネットワークインターフェイスのアドレスを取得する。

Ledクラスについて

特に言うこともないので省略する。

raspberrypi3 yoctoでavahi

avahiを使うと、DHCPなどでIPアドレスが一定しない環境でも、hostname.localのように固定的な名前でネットワーク越しにアクセスできるようになる。

avahi自体はzeroconfのLinux向けの実装のことらしい。これが、yocto環境でも使えるということなので、試してみたが、ちょっとハマった。

問題

local.confに次のように追加してbitbakeを実行した。

IMAGE_INSTALL_append = " avahi"

すると次のようなエラーがでた。

ERROR: rpi-basic-image-1.0-r0 do_rootfs: avahi not found in the feeds (raspberrypi3 cortexa7t2hf-neon-vfpv4 cortexa7t2hf-neon cortexa7t2hf-vfp cortexa7hf-neon-vfpv4 cortexa7hf-neon cortexa7hf-vfp armv7vet2hf-neon-vfpv4 armv7vehf-neon-vfpv4 armv7vet2hf-neon armv7vehf-neon armv7vet2hf-vfp armv7vehf-vfp armv7at2hf-vfp armv7ahf-vfp armv6thf-vfp armv6hf-vfp armv5tehf-vfp armv5ehf-vfp armv5thf-vfp armv5hf-vfp noarch any all) in /home/XXXXXXX/work/yocto/rpi-morty/build/tmp/deploy/rpm.
ERROR: rpi-basic-image-1.0-r0 do_rootfs: This is often caused by an empty package declared in a recipe's PACKAGES variable. (Empty packages are not constructed unless ALLOW_EMPTY_<pkg> = '1' is used.)
ERROR: rpi-basic-image-1.0-r0 do_rootfs: Function failed: do_rootfs
ERROR: Logfile of failure stored in: /home/XXXXXXX/work/yocto/rpi-morty/build/tmp/work/raspberrypi3-poky-linux-gnueabi/rpi-basic-image/1.0-r0/temp/log.do_rootfs.30111
ERROR: Task (/home/XXXXXXX/work/yocto/rpi-morty/poky/meta-raspberrypi/recipes-core/images/rpi-basic-image.bb:do_rootfs) failed with exit code '1'

つまり、avahiのパッケージはないということらしい。レシピはあるのに。

正解

local.confに次のように追加すると、解決する。

IMAGE_INSTALL_append = " avahi-daemon"

raspberrypi3では次のようにアクセスが可能になる。

$ ping raspberrypi3.local

便利だ。

blockdiagを試す

テキストから図を生成するツールの一つにblockdiagがある。 次のような特徴を持つ。

パケット図の生成は他のツールを使用していた時に、微妙に困ったのでこれは良さそう。 また、日本人が開発していることも都合が良さそう。日本語への対応もある。

plantumlやgraphvizとかぶっているところもあるが、たとえば、ブロック図ではgraphvizに比べてノードシェイプの種類やスタック属性が書けるなど、比較的こちらのほうがやりたいことに近いかもしれない。

インストール

$ sudo pip install blockdiag actdiag seqdiag nwdiag

ブロック図

blockdiag {
    A -> B -> C 
}

実行方法

$ blockdiag simple.diag -Tsvg 

emacsとの連携

blockdiag-mode

blockdiag-modeがmelpaからインストールできる。

M-x list-packages -> blockdiag-modeを検索しInstallする。

プレビュー

blockdiagのプレビューは次のようなelispで行う。

(defun blockdiag-buffer ()
  (interactive)
  (let ((blockdiag-output "/tmp/blockdiag-buffer.png")
        (current-file (buffer-file-name (current-buffer))))
    (call-process "blockdiag" nil nil t "-o" blockdiag-output current-file)
    (switch-to-buffer-other-window
     (get-buffer-create "*blockdiag*"))
    (erase-buffer)
    (insert-file blockdiag-output)
    (image-mode)))
  
(push '("*blockdiag*") popwin:special-display-config)

出典はここ

pandocから使用する

フィルタを入手

matthiasbeyerさんのpandoc-paper-templateから使用させてもらう。

$ ~/bin
$ wget https://raw.githubusercontent.com/matthiasbeyer/pandoc-paper-template/master/scripts/blockdiag-filter.py
$ chmod +x blockdiag-filter.py

日本語対応パッチ

labelなどに日本語を食わすと「UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 」とエラーを吐くので、パッチを当てる。

--- blockdiag-filter.py.orig 2016-11-01 13:06:12.199636652 +0900
+++ blockdiag-filter.py   2017-04-12 09:17:00.707985814 +0900
@@ -13,7 +13,7 @@
 from pandocfilters import toJSONFilter, Str, Para, Image, attributes
 
 def sha1(x):
-  return hashlib.sha1(x).hexdigest()
+  return hashlib.sha1(x.encode('utf-8')).hexdigest()
 
 imagedir = "blockdiag-images"
 
@@ -22,7 +22,7 @@
 
 def save(data):
     fd, name = tempfile.mkstemp()
-    os.write(fd, data)
+    os.write(fd, data.encode('utf-8'))
     os.close(fd)
     return name
 

使用方法

次のように使用する。

$ pandoc -F blockdiag-filter.py [PANDOC-OPTIONS] [FILE-NAME ...]

次のようなマークダウンから

```rackdiag
rackdiag {
    7U;
    1: MAC
    2: IP
    3: TCP
    3: UDP
}
```

このようになる。

rackdiag {
    7U;
    1: MAC
    2: IP
    3: TCP
    3: UDP
}