もぐてっく

人は1つ歳をとるたび、1ビットづつ大きくなれると信じてた。

ラズパイ内蔵テンキー「Keybow」のファームウェアをRaspbian化する

この度、Raspberry Pi Zero W搭載のテンキーボード「Keybow」を購入しました。

f:id:moguno:20190103230356j:plain:w300

LUAスクリプトで複雑なキーマクロが組めるのが特徴の製品ですが、コントローラがラズパイなのでもう何でもできるじゃんと思ってポチー!

製品の特長はあっきぃさんのブログが詳しいです。

akkiesoft.hatenablog.jp

しかしながらこれ。
ファームウェアに通常のRaspbianじゃなくて専用の軽量OS「Keybow OS」が採用されているのですが、まぁ出来ることが少ないと。

例えば無線LAN経由でRqspberry Pi Zero Wにsshでログインしてキーコンフィグファイルを直接編集したいなーと思っても、ざっとwpa_supplicant,openssh_server,vim,nanoなんかが足りないと。apt-getもgccも無いから割と詰んでるなぁと。

よく考えたらこれ別にOSを軽量化する必要あんまりなくね?と思ったので、KeybowのOSをRaspbianに差し替えることにしました。

方針

keybowコマンド

Keybowのキー入力やLEDの点灯は、/boot/keybowと言うC言語で書かれたコマンドで行っています。
Keybow OSからkeybowコマンドと動作に必要なLUAスクリプト群を移植すればよさそうです。

USBガジェットドライバ

Keybow OSはRaspberry Pi Zero WのUSB OTG機能を使って自身をHIDデバイス(キーボード)に見せかけています。
これにはlibcomposite.koと言うドライバが使われているので、これをRaspbian起動時にロードするようにします。

余談

ラズパイでUSB OTGシリアルコンソールを使うときはg_serialと言うドライバを使うので、てっきり類似品のg_hidを使ってると思って新年からハマりました。g_の付くドライバは設計が古く、最近はlibcompositeを使うのがナウいみたいです。

やってみる

Raspbian Stretch Liteをインストールする。

現時点で最新の2018/11/13版のRaspbian Stretch Liteを普通にインストールします。

GitHubからKeybow OSのファームウェアをダウンロードする。

$ cd /tmp/
$ wget https://github.com/pimoroni/keybow-firmware/archive/v0.0.2.tar.gz
$ tar xvf v0.0.2.tar.gz

ファームウェアからkeybowコマンド関係のファイルをコピーする。

$ cd keybow-firmware-0.0.2/sdcard
$ chmod 755 keybow
$ sudo cp -p keybow /usr/local/bin/
$ sudo cp -rp keys.lua keybow.lua default.png layouts/ patterns/ snippets/ /boot/

keybowを起動するsystemdのユニットファイルを作る。

/etc/systemd/system/keybow.service
[Unit]
Description=Keybow Daemon

[Service]
ExecStart=/usr/local/bin/keybow
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target

起動時にユニットファイルが実行されるようにします。

$ sudo systemctl enable keybow

/boot/cmdline.txtと/boot/config.txtを編集する

Raspbianの起動時にUSB OTG用ドライバdwc2と件のlibcomposite.koをロードするように記述します。

/boot/config.txt(dtoverlay=dwc2を追加)
/boot/cmdline.txt(行の末尾にmodules-load=dwc2,libcompositeを追加)
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=ffb833f2-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,libcomposite


ここでRaspbianを再起動して、KeybowのLEDが七色に輝きだしたら成功です。

Raspberry Pi meets XBMCなRaspbmcを無線アクセスポイント化してみた

Raspberry Piって超軽いから、出張に持って行っても苦にならないよね。

これをホテルに持ち込んでテレビに接続してAirPlayで動画を見れる様にしたら¥1,000/泊のメディア費削減になるんじゃね!

ついでにこれが無線ルータになったら、ホテルのネット回線も使い放題!超実用的!!

AirPlayはRaspbmcと言うRaspberry PiをXBMC箱にするディストリを使えば可能。
これに出っ張らないUSB無線LANをつなげてアクセスポイントにしてやればいいね。

と、言う訳でRaspbmcの無線アクセスポイント化スタートです。

用意するもの

・Raspbmc導入済みのRaspberry Pi(Model B、256MB版)
・PLANEX GW-USNano2

PLANEX ハイパワー無線LAN ゲーム機用 Wi-Fi USBアダプタ GW-USNANO2-G

PLANEX ハイパワー無線LAN ゲーム機用 Wi-Fi USBアダプタ GW-USNANO2-G

USB無線LANRealtekのRTL8192CUチップを指名買い。たまたまじゃんぱらで安かったPLANEXのGW-USNano2にしました。

指名買いの理由は、Linuxでのアクセスポイント利用をサポートしてた(公式ドライバに専用のhostapdが同梱されてる)から。独自改変したhostapdを使うってのはなんかダサいんですが、裏を返せばこのチップ専用にフィッティングされてる訳で、細かいトラブルを避けられると思った次第。

ドライバのダウンロード

まずはブラウザの使えるクライアントでRealtekのサイトにアクセスして、RTL8192CUのドライバを取ってきます。今回はRTL8192xC_USB_linux_v3.4.4_4749.20121105.zipを使います。

RaspbmcにはRTL8192CUのドライバが標準で入っているので、今回使うのはこの中のhostapdだけです。

取りあえずRaspbmcにログイン

Raspbmcが起動したのを確認して、sshでアクセスします。
AvahiやBonjourが生きてる環境なら名前でアクセス出来るから超便利。なんでWindowsはサポートしないんだろ。

なお、パスワードはraspberryです。

$ ssh pi@raspbmc.local


初回ログイン時にロケールとかタイムゾーンとかを訊いてきますが、まあお好みで。
(ここでははen_US.UTF-8とAsia/Tokyoにしました。)

今後sudoが面倒なのでrootになっておきます。
(なんかいい方法があったはずだけど忘れた。)

$ sudo su -
#


後、別の端末を開いてさっきダウンロードしたドライバをRaspbmcに転送しときます。

$ scp -p ~/Downloads/RTL8192xC_USB_linux_v3.4.4_4749.20121105-1.zip pi@raspbmc.local:/tmp/

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

ここからはRaspbmcにログインした端末での作業です。
hostapdのコンパイルに必要なパッケージとお手軽DHCP&DNSサーバであるところのdnsmasqを導入します。

# apt-get update
# apt-get install unzip build-essential dnsmasq

専用hostapdのインストール

なぜかzipなアーカイブを解凍しつつ、最深部(※)にあるソースコードをコンパイルすればこのステージはクリアです。
(※:パスが100文字超になって笑けます。)
途中に設定ファイルのテンプレートが落ちているので、忘れずにゲットして/etc/にコピーしておきましょう。

# unzip /tmp/RTL8192xC_USB_linux_v3.4.4_4749.20121105-1.zip
# cd RTL8188C_8192C_USB_linux_v3.4.4_4749.20121105/wpa_supplicant_hostapd/
# cp -p rtl_hostapd_2G.conf /etc/hostapd.conf
# unzip wpa_supplicant_hostapd-0.8_rtw_20120803.zip
# cd wpa_supplicant_hostapd-0.8/hostapd/
# make
# make install

/etc/hostapd.confの修正

さっき手に入れた設定ファイルをカスタマイズします。
WPSがONだとWPA設定でもWEPのアクセスポイントになってしまうのでWPSを切ります。

 13 ##### Wi-Fi Protected Setup (WPS) #############################################
 14
 15 eap_server=1
 16
 17 # WPS state
 18 # 0 = WPS disabled (default)
 19 # 1 = WPS enabled, not configured
 20 # 2 = WPS enabled, configured
 21 wps_state=0      # 2→0

にっくきNetworkManagerとの戦い

近頃クライアント系で流行のNetworkManager。
GUIで設定が出来たり、ケーブル接続時に通知メッセージが出たりとかで格好良いんですけど、そいつが古き良きネットワーク設定の仕組みを見事にぶち壊してくれています。
ifconfigでIPアドレス設定してても、LANケーブルを抜いたらそれが吹っ飛ぶとか勘弁して欲しいです。

今回も例によってNetworkManagerがしっかり無線LANを管理しちゃってて、hostapdの動きをことごとく邪魔してくれやがります。シネバイイノニ。

しかしながら安易にNetworkManagerを停止させると今度はRaspbmcのXBMCが文句を言い出すので、今回はなんとか共存の道を探らねばなりません。

爆乳の魔王が相手なら和平交渉も楽しいんでしょうが、相手バイナリファイルだし。面倒いなぁ。

まぁいいや。んでは、色々ファイルを書き換えていきます。

/etc/NetworkManager/NetworkManager.confの編集


「ごめんなさい。そして、ありがとうございました。
 私に、私のwlan0を返してください。それは私が背負うべき物でした。」


などと、NetworkManager様に丁重にお願いします。ちっ。

あらかじめ、ifconfigで無線インターフェースのMACアドレスを控えておきます。

# ifconfig wlan0
wlan0     Link encap:Ethernet  HWaddr 00:22:cf:b7:a5:da  ←コレ
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


メモしたMACアドレスをNetworkManagerの管理対象外にします。

  1 [main]
  2 plugins=ifupdown,keyfile
  3
  4 no-auto-default=B8:27:EB:9B:C0:8C,
  5
  6 [ifupdown]
  7 managed=false
  8
  9 # 以下追加。11行目のmac以降は調べたMACアドレス
 10 [keyfile]
 11 unmanaged-devices=mac:00:22:cf:b7:a5:da


これを保存すると即座に対象のインターフェースがダウンするので、その誠実さにちょっと感激したりしました。

/etc/network/if-up.d/secure-rmcの編集

まだいました!お邪魔設定ファイル!

こやつはifupしたタイミングで実行されて、そのインターフェース用の基本的なnetfilterルールを設定しやがります。
(インターフェースのサブネットからのパケットはACCEPT、それ以外はDROP。)

通常の用途だと毒にも薬にもならないルールですが、今回のアクセスポイント化に当たっては、クライアントからのDHCPパケット(0.0.0.0→255.255.255.255)が捨てられてしまうのでダメダメです。

# こいつのせいでifupで立ち上げたらうまくDHCPIPアドレスが取れなくて、ifconfigでなら正常動作と言う謎の動作になって数時間悩んだよ。。。

ルールを追加してやります。

125 logger -t iptables "Configuring ip tables for interface $IFACE"
126 if [ "$IFACE" != "lo" ]; then
127     NETMASK=$(get_subnet $IFACE)
128     iptables -A INPUT -s $NETMASK -i $IFACE -j ACCEPT
129     # DHCPを許可するルールを追加
130     iptables -A INPUT -s 0.0.0.0 -i $IFACE -j ACCEPT
131     iptables -A INPUT -i $IFACE -j DROP
132 fi

/etc/network/interfacesの編集

ここからは前向きな設定作業です。
無線インターフェースのIPアドレス他を設定していきます。
hostapdのinitスクリプトを書くのが面倒なのでpost-up/pre-downで起動・停止を制御することにします。
さらに、IPマスカレードの設定も入れときます。

interfaces超便利!

  1 allow-hotplug wlan0
  2
  3 iface wlan0 inet static
  4     address 192.168.0.1
  5     netmask 255.255.255.0
  6     broadcast 192.168.0.255
  7     network 192.168.0.0
  8     post-up /usr/local/bin/hostapd -B /etc/hostapd.conf
  9     post-up iptables -t nat -A POSTROUTING -o eth0 -s 192.168.0.0/24 -j MASQUERADE
  9     pre-down kill `pidof hostapd`
 10     pre-down iptables -t nat -D POSTROUTING -o eth0 -s 192.168.0.0/24 -j MASQUERADE


なぜかauto wlan0ではインターフェースが上がらなかったので、allow-hotplugにしてます。

/etc/dnsmasq.confの編集

無線インターフェースに対するDHCPサーバの設定を行います。

90行目のinterfaceのコメントを外して、wlan0を指定します。
これを忘れると家中のIPネットワークが混乱の渦に巻き込まれるので注意。

 86 # If you want dnsmasq to listen for DHCP and DNS requests only on
 87 # specified interfaces (and the loopback) give the name of the
 88 # interface (eg eth0) here.
 89 # Repeat the line for more than one interface.
 90 interface=wlan0


147行目のdhcp-rangeのコメントを外します。

143 # This is an example of a DHCP range where the netmask is given. This
144 # is needed for networks we reach the dnsmasq DHCP server via a relay
145 # agent. If you don't know what a DHCP relay agent is, you probably
146 # don't need to worry about this.
147 dhcp-range=192.168.0.50,192.168.0.150,255.255.255.0,12h

/etc/sysctl.d/ipv6.confの新規作成

RaspbmcのAirPlayでハマったでござる - もぐてっく

で、書いたAirPlayのためのIPv6無効化を行っておきます。

  1 net.ipv6.conf.all.disable_ipv6 = 1

/opt/xbmc-bcm/xbmc-bin/share/xbmc/addons/script.raspbmc.settings/autostart.pyの編集

Raspbmcはリセットボタンを押さずに電源を引っこ抜くと、次回起動時に

「ちゃんと画面端のパワーボタン押して終了せぇやボケぇ!死ぬかと思たわ!!」

と、怒られます。

その主張はごもっともなんですが、外出先でこのダイアログが出たら消す方法が無いので、当該コードをコメントアウトします。

126 #       if DISTRO == "Raspbmc" and os.path.isfile("/home/pi/.bootstatus") and check_service_running("wd") == "true" and not os.path.isfile("/home/pi/.nowarning"):
127 #             import xbmcgui
128 #             dialog = xbmcgui.Dialog()
129 #             dialog.ok("Raspbmc did not shut down properly", "Raspbmc should always be shut down via the\npower icon in the lower left corner")

リブート!

これでRaspberry Piの無線AP化は完了です。リブートして成果を確認します。

お手持ちの無線クライアントを見ると、rtwapってアクセスポイントが増えています。
パスワード87654321で接続出来れば成功です!

これでダメなら諦めた方が良い程度にRasberry Piのカーネルクロスコンパイル手順をまとめてみた。

(内容古いです。)

Raspberry Piのクロスコンパイルの方法は、既にいくつものサイトで解説されてるんですが、汎用のクロスコンパイラを適切なオプションでコンパイルせなあかんとかでなんか面倒かったり、手順の途中が「そんなの常識だよね〜」的に端折られててなんか面倒かった訳です。

んで、この度公式?のクロスコンパイラを触ってみたら割とあっさりと動いちゃったんで、手順をまとめとこうと思った次第です。

Ubuntu Desktop 12.10 32bitのインストール

ロスコンパイルを行うOSをインストールします。
Server版でも一向に構わないと思いますが、手元にあったので。
64bit版は不可。

必要そうなパッケージをインストール

$ sudo apt-get install build-essential libncurses5-dev openssh-server avahi-daemon

build-essentialはmakeコマンドとmenuconfig用のセルフコンパイル環境のため。多分いらんパッケージも入るけど、まぁいいかと。
openssh-serverは糞重いUnity対策。MBAのターミナルから接続出来るようにしときます。
avahi-daemonは趣味です。avahi可愛いよavahi。

カーネルソースとクロスコンパイラをもらってくる。

githubからRaspberry Piのカーネルソースとクロスコンパイラをありがたく頂きます。

$ wget --no-check-certificate https://github.com/raspberrypi/linux/archive/rpi-3.6.y.tar.gz
$ wget --no-check-certificate https://github.com/raspberrypi/tools/archive/master.tar.gz

今回は何も考えずにホームディレクトリに展開しときます。

$ tar xvzf rpi-3.6.y.tar.gz
$ tar xvzf master.tar.gz

Raspberry Piからカーネルコンフィグを取ってくる。

Raspberry Piのprocファイルシステムには現状のカーネルのコンフィグが格納されてるのでぶっこ抜きます。
ここではRaspbmcから取って来てますが、Raspbianもきっと同じカーネルコンフィグのはず。

$ cd linux-rpi-3.6.y
$ scp pi@raspbmc.local:/proc/config.gz .
$ zcat config.gz > .config

ぶっこ抜いたカーネルコンフィグの反映

make oldconfigを実行してコンフィグを反映します。
カーネルのバージョンが違ってた場合は、新規追加されたコンフィグをどうするか聞かれますが、基本デフォルトでOKです。
(今回の場合は差分はありませんでした。)

$ make CROSS_COMPILE=~/tools-master/arm-bcm2708/arm-bcm2708hardfp-linux-gnueabi/bin/arm-bcm2708hardfp-linux-gnueabi- ARCH=arm oldconfig

どどど・・・ドッピオぉぉ!?

make menuconfigでお好きなフレーバーをカーネルに追加しよう!

$ make CROSS_COMPILE=~/tools-master/arm-bcm2708/arm-bcm2708hardfp-linux-gnueabi/bin/arm-bcm2708hardfp-linux-gnueabi- ARCH=arm menuconfig

カーネルをコンパイル

$ make CROSS_COMPILE=~/tools-master/arm-bcm2708/arm-bcm2708hardfp-linux-gnueabi/bin/arm-bcm2708hardfp-linux-gnueabi- ARCH=arm

Enterを叩いた瞬間、Raspberry Piではあり得なかった光の世界がそこに広がります。
(意訳:クロコンやばい。超やばい)

モジュールもコンパイル

$ make CROSS_COMPILE=~/tools-master/arm-bcm2708/arm-bcm2708hardfp-linux-gnueabi/bin/arm-bcm2708hardfp-linux-gnueabi- ARCH=arm modules


一先ず/tmp/123ってディレクトリにインストールして、余計なシンボリックリンクを削除しときます。

$ sudo make CROSS_COMPILE=~/tools-master/arm-bcm2708/arm-bcm2708hardfp-linux-gnueabi/bin/arm-bcm2708hardfp-linux-gnueabi- ARCH=arm INSTALL_MOD_PATH=/tmp/123 modules_install
$ sudo rm /tmp/123/lib/modules/3.6.11/build
$ sudo rm /tmp/123/lib/modules/3.6.11/source

出来上がったカーネルとモジュールをRaspberry Piにコピー

なぜか/tmp/だと容量不足だと怒られるので、piのホームディレクトリに置きます。

$ scp -rp /tmp/123/lib arch/arm/boot/zImage pi@raspbmc.local:~/

コピーしたカーネルとモジュールを正規場所にコピー

Raspberry Piにログインして、さっきコピーしたファイルを移動します。

$ sudo cp -rp lib /
$ sudo cp -rp zImage /boot/kernel.img

下のメッセージが出ますがFAT32ファイルシステムに起因する無害なものです。無視しましょう。
cp: failed to preserve ownership for `/boot/kernel.img': Operation not permitted

リブート

そしてリブート!

$ sudo reboot

きっとあなたは新たなRaspberry Piに出会うことでしょう!