BluetoothモジュールRN-42をお試し

Microchip製のBluetoothモジュールRN-42を使ってみました。このモジュールは、シリアルから設定を簡単に変更でき、キーボードとしてパソコンに接続できることを確認しました。マイコンをBluetoothで通信させるのに便利そうです。

パソコンに定形文字列を入力する方法を探していました。そこでマイコンをキーボードなどのHIDとするためのライブラリーやモジュールを探していて、Microchip製のBluetoothモジュールRN-42を見つけました。このモジュールを使うとシリアル端子に送った文字があたかもBluetooth接続のキーボードから入力されたように見せかけられます。そこでどんな感じなのか試してみました。

RN-42とは

RN-42は、Microchips製1のBluetooth Ver. 2.1に対応したモジュールです。内蔵プロファイルにはシリアル接続するためのSPP(Serial Port Profile)の他にHID(Human Interface Device)プロファイルが組み込まれており、モジュールにシリアル端子からコマンドを送ることでプロファイルや設定を簡単に切り替えることができます。

海外の無線モジュールを使用する時に問題になる法律面の制限(いわゆる技適マーク)もクリアしています

RN-42の購入

RN-42はモジュールとして電子部品店で容易に購入することができます。ただ今回は簡単に使い方を試してみたかったので、USB端子に接続するだけで電源とシリアル通信回路が提供される評価キットを購入しました。

秋月電子ではMicrochip純正の評価キットマイクロチップ Bluetooth評価キット RN-42-EKと秋月電子オリジナルのRN-42使用 Bluetooth無線モジュール評価キットを扱っています。どちらの評価キットもUSB-シリアル変換チップを搭載していて、電源もUSBから供給できるようになっています。

二つに機能的な違いは無いようなので、値段の安い秋月オリジナルの評価キットを購入しました。

RN-42を設定

RN-42評価モジュールをUSB端子に接続したら、ターミナルソフトを起動します。私はUbuntu上のcuコマンドを使用しました2。WindowsならばTeraTermなどが使用できます。接続するときのパラメータは、初期設定では次のようになっています。

設定
速度 115200 bps
長さ 8 bit
パリティ なし
ストップビット 1

RN-42のコマンドは、RN41/RN42 Bluetooth Data Module Command Reference User’s Guideを参照しました。

次のはRN-42との通信例です。ここで「#」で始まる行は操作のコメントを示します。太文字の部分が実際に入力した行です。

$ cu -l /dev/serial/by-id/usb-FTDI_FT231X_USB_UART_DQ00380E-if00-port0 -s 115200
Connected.
# コマンドモードに入る。
# $$$の後には[Enter]が不要です。
# それ以外のコマンドでは、コマンドの後に[Enter]が必要です。
$$$
CMD
# コマンドモードに入ると、緑色のLEDが非常に早く点滅します。

# RN-42のfirmware versionを確認。
V
Ver 6.15 04/26/2013
(c) Roving Networks
AOK
# このfirmwareは、HIDモードではGPIO4によるFactory Reset機能が働きません。
# 新しいver. 6.30ではこの問題を解消済みです。

# 工場出荷時の設定に戻す。
SF,1
AOK

# ペアリングに必要なPINを設定する。初期設定では1234となっています。
SP,01234
AOK

# pairing modeに設定する。電源が入ると自動的にペアリングした相手と接続する。
SM,6
AOK

# HIDモードにする。
S~,6
# キーボードに設定する。工場出荷時のデフォルト。
SH,0200
AOK

# デバイスに名前を付ける。
# 名前を付けるコマンドには二種類あります。SNコマンドは、指定した名前がそのまま設定されます。一方S-コマンドは、名前の後にデバイスのMAC Adressの最後2バイト分が加えられます。
SN,RN42HID
# or
S-,RN42HID

# 設定内容は、多くの場合設定コマンドのSをGに変えたコマンドで取得できます。
GP
01234

# リブートして設定を反映させる。
R,1
Reboot!

R,1コマンドでリブートすると、緑色LEDがゆっくりと点滅するようになります。そしてRN-42と別のパソコンとのペアリングを完了すると、ゆっくりと点滅していた緑色LEDが点灯したままとなります。

言うまでもありませんが、RN-42を接続したパソコンでペアリングすると、入力した文字が無限ループしてしまいます。

RN-42のキーボードはUS配列

RN-42の設定とペアリングが完了したら、RN-42に送った文字が相手側のパソコンに入力されることを確認します。

この時、一部の文字が文字化けすることがあります。例えば「”@:」と入力すると、「*”+」となります。これは、キーボードの配列をパソコンが間違って認識しているためです。

HIDプロファイルで送られてくるデータは、「どの文字が入力されたか」ではなく「どのキーが押されたか」というデータです。RN-42は、シリアルに送られてきた「文字」をUS配列のキーボードで「どのキーが押されたか」に変換してパソコンに送ります。そのためパソコンがJIS配列のキーボードとして認識してしまうと一部の文字で文字化けが発生します。

同じ理由から「日本語」などASCII以外の文字列を送ることができません。

レイアウトをUS配列に設定

この文字化けを解消するには、パソコン側の設定を変更してRN-42のキーボードがUS配列のキーボードだと認識させる必要があります。

Windows Vistaに接続したキーボードをUS配列と認識させる方法は、WindowsでBTキーボードだけをUS配列で使う(るびゅ備忘録)を参考にさせてもらいました。

デバイスインスタンスを調べる

まず設定からデバイスマネジャを開いて、HIDキーボードのプロパティを確認します。この詳細タブを開いて「デバイスインスタンスパス」の値を調べます。

rn42-devmng

レジストリを変更する

次にコマンドプロンプトからregeditコマンドを起動して、該当するエントリーの「Device Parameters」に次のエントリーを追加します。

名前 種類 データ
KeyboardTypeOverride REG_DWORD 4
KeyboardSubtypeOverride REG_DWORD 0

rn42-regedit

設定を変更した後で、再度RN-42を起動またはリブートすると入力した文字がパソコンにも正しく入力されるようになります。

RN-42のハマりポイント

コマンドモードに入れない。
コマンドモードへ入るには、電源が入った時またはリブート後60秒以内に時間が制限されています。この時間は、STコマンドで変更できます。またペアリングが完了して接続している場合(HIDの時のみ?)もコマンドモードには入れません。
ファクトリーリセットできない。
RN-42モジュールは、GPIO4をHにして起動し、L,H,L,Hと1秒以上開けて変更することで工場出荷時の設定に戻すことができます。詳しいリセット方法は、RN41/RN42 Bluetooth Data Module Command Reference User’s Guideの3.7 DESIGN CONCERNSの3.7.1.2 FACTORY RESETを参照して下さい。
ただし、firmware ver. 6.30未満では、HIDプロファイルに設定しているとGPIO4によるファクトリーリセット機能が働きません。iPhoneにUSBキーボードを接続するための装置の作成 2:RN-42(mswinvksの忘備録)のコメント参照。この制限は、RN41/RN42 v6.30 RELEASE NOTES – MicrochipにあるようにVer. 6.30で解消されています。
文字化けする。
この問題は、RN-42のキーボードはUS配列を参照して下さい。

脚注

  1. RN-42はMicrochipの製品ですが、元々Roving Networks社が開発していました。そのため製品名にRNが入っているのだと思います。現在Roving Networks社はMicrochips社に吸収されています。

  2. Linuxでシリアルデバイスを指定する時には、/dev/serial/by-id/*を指定するとどれに接続しているか分かりやすいです。

Z80マイコンボードの製作

2015年夏に偶然Z80(正確にはTMPZ84C00)を見つけた事をきっかけに、今更ながらCP/Mマシンを製作することにしました。とりあえず試作回路で上手く行きそうな感触がつかめたので、試作回路にあった無駄な部分を省くと共にデータ通信の高速化を目指した基板を新しく作成しました。基板の設計データはGitHub上に公開しています。

CP/Mマシンに必要な仕様

CP/Mを動かすために必要な仕様は次のとおりです。

  • CPUはi8080とマシン語レベルで互換性があること。
  • メモリはO番地から最低20KiBのRAMがあること。
  • 入出力のためのコンソール端末があること。
  • 最低一台のディスクドライブがあること。
  • 割り込み機能は使用していない。

ちなみにCP/Mが全盛期だった頃のクロック周波数は数MHzと現在の1/1000という世界でした。

以下に各項目を具体的に検討していきます。

CPU

CP/Mが全盛時代にはi8080よりもZ80が一般的に使われていました。よってZ80はi8080とのマシン語レベルでの互換性に問題がないことは確かです。

今回はZ80と言っても現在手に入るZ80の後継チップであるZ84C00と通信機能などを集積したZ8S180を使用します。これらもソフトウェア的にはZ80と互換性があるのでCP/Mを動かすことに問題はありません。

Z80の時代はクロック周波数は数MHzでしたが、Z84C00とZ8S180は20MHz版1が手に入るのでより高速なCP/Mマシンを実現できそうです。

メモリ

必要なメモリー容量は今となっては誤差のような容量です。むしろ少容量すぎて入手できない可能性さえあります。しかし現在Z80の後継CPUが入手可能なように小容量なメモリーも種類が少ないながらもちゃんと販売されています。

秋月電子のページを見ると、小容量とは言っても昔とは比べられ物にならないアクセス速度のSRAMが販売されています。容量についても128KiBで十分すぎます。

HM678127UHJ-12(12ns)とM68AF127B(55ns)のどちらを使うかですが、55nsはほとんど20MHzの1サイクルの相当にするのでこれで十分かと余り考えずにM68AF127B(55ns)を選択しました。

タイミングの検討

余り考えずに選んだメモリですが、M68AF127B(55ns)で最高どのくらいの周波数まで使用できるかデータシートのタイムチャートを確認してタイミングを検討してみました。

Z80のメモリアクセスで一番厳しいのは命令を読み込む時です。通常メモリーを読み込むときはT3の立ち下がりに読み込むのですが、命令を読み込む場合は半クロック早いT3の立ち上がりで取り込まれます。

命令読み込みのタイミングチャート。Z8400/Z84C00 Z80 CPU Product Specificationより引用。
命令読み込みのタイミングチャート。 Z8400/Z84C00 Z80 CPU Product Specificationより引用。

Z84C00とZ8S180を比べるとZ84C00の方が時間がかかるようなので、Z84C00のデータを使ってタイミングを検討します。

Z84C00のデータシートを見るとタイミングは次のようになっています。

  • アドレスの値が確定するのは、T1の立ち上がりから最大57ns。
  • MREQとRDがLになるのは、T1の立ち下がりから最大40ns。
  • データは、T3の立ち上がりの最低12ns前に確定している必要がある。

またメモリのタイムチャートは次のようになっています。

  • tAVQV(アドレスの確定からデータの確定までの時間)は最大55ns。
  • tELQV(チップ選択からデータの確定までの時間)も最大55ns。
  • tGLQV(出力ゲートが開いてからデータが確定するまでの時間)は最大25ns。
メモリーのタイミングチャート。M68AF127Bのデータシートより引用。
メモリーのタイミングチャート。 M68AF127Bのデータシートより引用。

よって確定が一番遅いMREQとRDがLになってからT3が立ち上がるまでの時間は、次の式になります。

40ns(T1の立ち下がりからMREQがL)+ 55ns(EがLからデータが確定) + 12ns(T3立ち上がり前の余裕) = 1.5T(wait無し)

この等式を計算するとT = 71nsとなります。よって最大の周波数は約14MHzとなります。

また同様にZ8S180の場合を計算すると

25ns(T1の立ち下がりからMREQがL)+ 55ns(EがLからデータが確定) + 10ns(T3立ち上がり前の余裕) = 1.5T(wait無し)

となり、T = 60nsとなります。よって最大周波数は16.6MHzとなります。

リセット直後のプログラム

Z80はリセットした時に0番地からプログラムをスタートします。そのためメモリの最初の方はシステムの初期設定やCP/Mをディスクから読み込むプログラムがリセット前に書き込まれている必要があります。そのため一般的にはその領域に電源を切っても内容が消えないROMが配置され、CP/Mが起動する時にはRAMに置き換えるようになっていました。

現在も紫外線で消去できるUV-ROMは手に入ります。しかしそれを使うには、書き込みと消去の装置が必要になります。さらにアクセス速度が遅いのでwait回路が必要になり、RAMとの切り替え回路も必要となります。

そこでメモリは全てRAMにし、リセット直後に必要なプログラムはスイッチを操作してメモリにマシン語を一ステップずつ書き込むというより古い手法を使うことにしました。もっともこれは面倒なので、一ステップずつメモリに書き込む操作は現代的なマイコンに肩代わりしてもらいます。

コントローラ

リセット直後に必要なプログラムの書き込みには使い慣れているAtmelのAVRマイコンを使用します。Arduinoに使われているATmega328を考えていたのですが、これだとピン数が足りずにアドレスなどの値をラッチしておく回路が必要になります。そこで少し値段は高くなりますがアドレスとデータバスを一対一で割り当てられるATmega64aを使用することにしました。

このコントローラは、初期プログラムの書き込みだけでなく、Z80の起動後は次のように端末やディスクドライブのインタフェイスとしても機能します。

ATmega64aはクロック回路を内蔵していますが、少しでも早いほうが良いかと思いZ80に供給するクロックをATmega64でも使用できるようにしました。この場合最大周波数がATmega64aの最大周波数16MHzに制限されます。

ATmega64aのハマりポイント

ATmega64aは、買ってきたままだとATmega64aではありません。

ATmega103というMMUとの互換モードになっています。mainルーチン内だけで済むプログラムだと気が付かないのですが、別の関数を呼び出すとフリーズしてしまい「あれ?」となります。

ATmega64aを使う前には、まずfuse設定を変更してATmega103互換モードをOFF(unprogrammed)にします。

またPFを使うときにも注意が必要です。

ATmega64aはJTAGインタフェイスを持っていますが、そのインタフェイスがPFに割り当てられており、入出力端子としてPFを使用できません。

これも必要ならばfuse設定を変更してOFF(unprogrammed)にするか、プログラム内でMCUCSRのJTDをセットします。MCUCSRのJTDは、誤動作で書き換えてしまうことを防ぐために、二回連続してセットする必要があります2

入出力

Z80の入出力もATmega64aが担います。メモリ上に実行したい入出力操作を書き込んでおき、準備が整ったところでATmega64aに実行を依頼します。

ATmega64aに入出力を要求するシグナルを送る簡単な出力回路も検討しましたが、Z80のHALT信号をATmega64aへのシグナルに使う回路3が試作で上手く行ったので外付け部品が不要なこの方法を使うことにしました。

具体的にはATmega64aに実行してもらいたい入出力操作をメモリに書いておきhalt命令を実行してHALT信号をLにします。ATmega64はHALT信号がLになったことを確認したらメモリ上に書かれた入出力操作を実行して、Z80を割り込みでhalt命令の次から実行を再開させます。RESETでもHalt状態から抜けられますが、通常のRESETと区別する必要や再開後の設定など面倒なので割り込みシグナルを使用することにしました。特にNMIは間違って割り込みを無効にしてしまう心配が無く好都合です。

ただしこのNMIを使ってHalt状態から抜けだすのは、NMIの割り込みベクタがCP/Mの予約領域にあるという問題点があります。そこでhaltを実行する前に割り込みベクタである0x66番地のデータを退避しておき、NMIでHaltから抜けだした後に書き戻すことにしました。またCP/Mでは他の割り込みを使用しませんが、一応入出力操作が完了するまで待つビジーウエイトを入れておきます。

実際のルーチンは次のようになります。

viotrap0:
	ld	hl,(NMI_VECT)	; save value on NMI_VECT
	ld	bc,045edh	; set RETN code
	ld	(NMI_VECT),bc
	halt
viowait:
	ld	a,(virtio_command)
	or	a
	jr	NZ,viowait
	ld	(NMI_VECT),hl

LED

電子工作で動作確認の基本はLチカなので、Z80でLチカを出来るようにするためLEDを点灯できるようにします。

ディスクドライブ

ディスクドライブには、実物のフロッピーディスクドライブやSDメモリカードを使用する方法を考えましたが、データのやり取りが面倒そうなのでパソコン上のディスクイメージに直接アクセスすることにしました。これによりパソコン上で作成したバイナリやテキストファイルをディスクイメージにコピーするだけでZ80側から使用することができるようになります。

実際のディスクイメージへのアクセスは、Z80からATmega64aにディスクの読み書きを依頼し、ATmega64aがパソコン上のプログラムにディスクイメージのデータを読み書きする命令を送ります。

パソコンとATmega64aは、電源供給も兼ねてUSBで接続します。ただしATmega64aはUSBに直接接続できないので、FTDIのFT240XというUSB-FIFO変換チプを使用しました。FT240XはZ80のデータバスに直接接続できるので、データを直接メモリに書き込むことができ最高1Mbyte/secのデータ転送が可能です4

FT240Xは、ATmega64aから見ると8bitパラレルのFIFOデバイスですが、パソコンからはシリアル端末に接続したように見えます。そのためパソコン側のプログラムには特別なライブラリは不要です。例えばターミナルソフトを開いてAという文字を送ると、そのままTF240XのデータバスにAのASCIIコードが現れます。

コンソール端末

コンソール端末は簡略化のため自前でキーボードやディスプレイを持たずにパソコンを昔のディスプレイターミナルとして使用します。この通信にもディスクドライブと同じUSBを使用します。

コンソール端末はATmega64aの要求で動くディスクドライブと違ってパソコン側から非同期でデータが送られます。そのためディスクでのデータと混じってしまわないように非常に簡単なプロトコールでディスクのデータとコンソールのデータを多重化します。

リセット

リセットスイッチはZ80に直接接続しないで、ATmega64aに接続したスイッチが押されたらZ80のリセット端子をLにすることにしました。これによりチャタリング回避やリセット時間を確保する回路をなくすことができます。

基板の設計と組み立て

そうして完成したZ84C00とZ8S180ボードの回路図は次のようになります。

Z84C00を使ったZ80ボードの回路図。
Z84C00を使ったZ80ボードの回路図。
Z8S180を使ったZ80ボードの回路図。
Z8S180を使ったZ80ボードの回路図。

基板はKiCadを使って設計し、プリント基板の製造は格安なElecrowに注文しました5

必要な部品は、秋月電子Mouser千石電商から購入しました。

回路図や基板の設計データはGitHub上に公開しています。これをフォークしてより素晴らしい物を作る基板としてもらえると幸いです。

Z84C00用のプリント基板

Z8S180用のプリント基板

プリンプ基板は10枚単位での注文なため何枚か基板が余っています。もし欲しいという方がおりましたら送料のみでお分けいたします。ただし基板制作後にも回路図と基板設計に手を入れているので、GitHub上のデータとは多少パターンなどが異なります。

次は

Z80ボードが完成したので、次はATmega64aのプログラムとCP/MのBIOSを作成してCP/Mを動かします。

脚注

  1. Z8S180は20MHz版の他に30MHz版も存在します。ただし値段が急に高くなるので今回は20MHz版を購入しました。

  2. JTDビットは、正確には4CPUサイクル以内に2回セットする必要があります。

  3. halt命令をトラップに使用するアイデアは我ながら冴えた方法だと思ったのですが、既にネット上に公開されていました。T:(@T_colon) 部品を減らす工夫 – neko Java Home Page – GMOとくとくbb

  4. 試作段階ではパソコンとの接続に一般的なUSB-シリアル変換チップを使用しましたが、これでもデータ転送がそれほど遅くはありませんでした。

  5. 数千円と約1週間(国際宅急便使用)でプリント基板を製造してもらうことができます。精度などでは国内のしっかりした業者には及ばないのかも知れませんが、個人的に使用するには全く問題がないレベルです。手配線や自分でプリント基板を作る手間や時間を考えれば、このサービスを使わない手はありません。

MongoDB 3.2(stable)をUbuntu 16.04にインストール

現在(2016年6月10日)MongoDB 3.xのUbuntu 16.04用のパッケージは、MongoDBから提供されていません。そのためUbuntu 14.04用のパッケージを使うことによる問題で、16.04用のパッケージが公開された時には解決しているはずです。

2016年8月26日:Ubuntu 16.04用のパッケージが提供されています。そのためもうsystemd用の起動設定ファイルを作成する必要はありません。

これまでUbuntu 14.04でMongoDBを運用してきましたが、新しいLTSであるUbuntu 16.04にアップグレードしたらmongodが起動しなくなってしまいました。

ここではUbuntu 16.04にMongoDB 3.2(stable)をインストール(アップグレード)する手順と、使用できるようにするまでのworkaroundを説明します。

MongoDB 3.2のインストール

MongoDBをアップグレードする場合は、データと/etc/mongod.confをバックアップしておきます。

MongoDBのインストール方法は、MongoDBが公開しているInstall MongoDB Community Editionに基づいて行います。

まず改変されていないか確認するための鍵を登録します。この鍵は、以前と変わっているので、アップグレードの場合も必要です。

$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927

次にパッケージのレポジトリを登録します。ここで3.2というバージョン指定をstableに変更すると、MongoDB 3.3が安定版になったらそのままアップグレードされ、常に最新の安定版MongoDBを使用できるようになります。

$ echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list
$ sudo apt update

実際にMongoDBをインストールします。アップグレードの場合は、mongodb-orgだけでなくそのメタパッケージが指すパッケージ群を指定しないとアップグレードできません1

# 新規インストールの場合
$ sudo apt install mongodb-org

# アップグレードの場合
$sudo apt install mongodb-org mongodb-org-mongos mongodb-org-server mongodb-org-shell mongodb-org-tools

本来ならばこれでmongodが起動するはずですが、起動ファイル(systemdの設定)が存在しないので自動では起動しません。たぶんUbuntu 16.04用のパッケージが公開されれば修正されるはずです。

mongodを自動起動させるworkaround

Ubuntu16.04用のパッケージが用意されているので、起動設定ファイルの作成ステップは不要です。Ubuntu 16.04用パッケージでは、起動用設定ファイルが/lib/systemd/system/mongod.serviceに作成されます。

mongodが起動しないのは、インストールしたパッケージがUbuntu 14.04用なのでUbuntu 16.04で自動起動させるために必要なsystemd設定が無いからです2

そこでsystemd用の起動設定ファイルmongodb.serviceRunning mongodb on ubuntu 16.04 LTSを参考にして作成します。

$ cat /etc/systemd/system/mongod.service
[Unit]
Description=MongoDB Database Service
Wants=network.target
After=network.target

[Service]
ExecStart=/usr/bin/mongod --config /etc/mongod.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
User=mongodb
Group=mongodb
StandardOutput=syslog
StandardError=syslog

[Install]
WantedBy=multi-user.target

mongod.serviceを作成したら、起動するか確認しておきます。

$ sudo systemctl start mongod
# or
$ sudo service mongod start

$ sudo systemctl status mongod
# or
$ sudo service mongod status
● mongod.service - MongoDB Database Service
   Loaded: loaded (/etc/systemd/system/mongod.service; disabled; vendor preset: 
   Active: active (running) since 金 2016-06-10 09:47:06 JST; 7s ago
 Main PID: 1479 (mongod)
   CGroup: /system.slice/mongod.service
           └─1479 /usr/bin/mongod --config /etc/mongod.conf

 6月 10 09:47:06 rerun systemd[1]: Started MongoDB Database Service.

起動することが確認できたら、自動起動するようにしておきます。

$ sudo systemctl enable mongod
Created symlink from /etc/systemd/system/multi-user.target.wants/mongod.service to /etc/systemd/system/mongod.service.

mongoコマンドの警告表を止める方法

mongoコマンドでサーバに接続すると次のような警告メッセージが表示されることがあります。

$ mongo
MongoDB shell version: 3.2.7
connecting to: test
Server has startup warnings: 
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] 
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] 
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2016-06-10T08:19:18.872+0900 I CONTROL  [initandlisten] 

この場合は、Disable Transparent Huge Pages (THP)を参考にしてkernelのtransparent huge pages機能を無効にします。

機能を無効にするには、/etc/rc.localにkernelの仮想設定ファイルに設定を書き込む命令を加えます。参照したページのように起動ファイルを作成したら、実行される順番によるのか警告を止めることができませんでした。

具体的には、/etc/rc.localexit 0の前に次の文を加えます。

if [ -d /sys/kernel/mm/transparent_hugepage ]; then
    echo never > /sys/kernel/mm/transparent_hugepage/enabled
    echo never > /sys/kernel/mm/transparent_hugepage/defrag
fi

PHPのmongoライブラリ

Ubuntu 16.04では、PHP7が標準になりました。そのためmongodbライブラリをpeclコマンドでインストールすると、次のようなエラーが出てインストールできません。

$ sudo pecl install mongo
WARNING: "pecl/mongo" is deprecated in favor of "channel:///mongodb"
pecl/mongo requires PHP (version >= 5.3.0, version <= 5.99.99), installed version is 7.0.4-7ubuntu2.1
No valid packages found
install failed

そこで素直にパッケージに入っているmongoライブラリをインストールします。

$ sudo apt install php-mongo

脚注

  1. メタパッケージをアップグレードすれば、自動的にメタパッケージでインストールパッケージ群もアップグレードされるのだとずっと考えていました。

  2. Ubuntu 16.04ではsystemdがデーモンを起動させますが、Ubuntu 14.04ではUpstartが起動させていました。そのためUbunt 14.04用のパッケージにはsystemdで必要な設定ファイルが含まれていません。
    systemdに置き換えられたのは15.04からなので、Ubuntu 15.04と15.10でも同じ問題が発生します。

Raspbian(jessi)でのIPアドレスの固定とresolv.conf設定

Debian(Raspbian)のjessieでIPアドレスを固定する方法とresolv.confファイルの設定方法

Debian(Raspbian)では、jessieからネットワーク周りの設定方法が変わりました。そのためDHCPではなくIPアドレスを固定して使用する方法と、関連してresolv.confファイルの設定でこれまでの経験が使えなくなってしまいました。

ここではDebian(Raspbian) jessieでIPアドレスを固定する方法と、resolv.confファイルの設定する方法をメモしておきます。

設定はdhcpcd.confで

Debian(Raspbian) jessieでは、ネットワーク周りの設定は/etc/dhcpcd.confファイルで行います。/etc/network/interfaces/etc/resolv.confファイルを編集してもdhcpcdにより上書きされてしまいます。

IPアドレスを固定する方法

DHCPサーバからIPアドレスをもらって設定するのではなく、指定したIPアドレスを使いたい場合は、dhcpcd.confに次の設定を加えます。

interface eth0
static ip_address=10.0.0.130/24
static routers=10.0.0.254

この設定によりeth0に10.0.0.130(netmask 10.0.0.255)というIPアドレスが設定されます。またstatic routersにより、デフォルトルータに10.0.0.254が指定されます。

$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether b8:27:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.130/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::XXXX:XXX:XXXX:XXXX/64 scope link 
       valid_lft forever preferred_lft forever

$ netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         10.0.0.254      0.0.0.0         UG        0 0          0 eth0
10.0.0.0        0.0.0.0         255.255.255.0   U         0 0          0 eth0

resolv.confの設定

上の設定を完了すればIPアドレスを指定して通信することが可能です。しかしホスト名を指定して通信するには、DNSに問い合わせてホスト名をIPアドレスに変換できなければなりません。そのための設定ファイルがresolv.confです。

resolv.confファイルにDNSのアドレスや自分のドメイン名を設定するには、dhcpcd.confに次の設定を加えます。

static domain_name_servers=10.0.0.123 10.0.0.234
static domain_name=nosuz.jp
#static domain_search=sample.jp

この設定により名前解決する時には10.0.0.123と10.0.0.234のDNSに問い合わせるようになります。またホストを指定する時にドメイン名まで指定する必要がなくなり、hostnameを指定するとdomain_nameで指定したドメイン名を付けてDNSに問い合わせるようになります。

domain_searchを設定すると、domain_nameに指定したホスト名が見つからない場合には、domain_searchで指定した別のドメイン名にないか調べるようになります。

WordPress REST APIを使った投稿と更新

WordPressのREST API(v2)を使用して記事の投稿と更新をしてみました。どうやってOAuth POSTをPHPからするか少し悩みましたが、分かってみると簡単にWordPressのREST APIを使用出来ました。

セットアップ

WordPress REST APIを使うには、REST APIのプラグインと認証プラグインをインストールして有効にしておく必要があります。

REST APIプラグイン

REST API(v2)プラグインは、通常のプラグインと同様にWordPressのダッシュボードからプラグインを検索してインストールします。

Application Passwordを使用する場合

Application Passwordは、ネット上を生のパスワードが流れます。 ローカル環境で使用するときには簡単で便利ですが、本番環境では必ず通信経路が暗号化されたHTTPS(SSL/TSL)で接続する必要があります。

Application Passwordを使用する場合は、OAuthプラグインと同様にApplication Passwordプラグインのページからアーカイブをダウンロードしてインストールします。

プラグインを有効にしたら、WordPressのダッシュボードから「ユーザー > あなたのプロフィール」と進んで、ページの最後の方にある「Application Passwords」でパスワードを自動生成します。このパスワードは再表示と修正はできないので、表示されたら安全な所に記録しておきます。修正や再確認が必要なときは、「Revoke」ボタンで現在のパスワードを無効にして再生成することになります。

Oauthを使用する場合

OAuthを使用する場合は、OAuthプラグインのGitHubページからアーカイブを取得して、OAuthプラグインをインストールします。

プラグインのインストールして有効にしたら、OAuthキーを取得しておきます。OAuthキーの取得方法は、WORDPRESS REST APIのOAUTHトークンを取得を参照して下さい。

OAuthライブラリ

プログラムからOAuth認証を使用するにはライブラリーが必要です。

言語 ライブラリ名
Ruby Oauth
PHP HTTP_OAuth
Pyhton Requests-OAuthlib

記事の投稿と更新サンプル

Application Passwordでのサンプル

Application Passwordは普通のBASIC認証と同じなので、curlコマンドにユーザ名1とアプリケーションパスワードを引数として渡すことで簡単にAPIを叩くことができます。

#!/bin/bash

read -r -d '' json << _EOM_
  {
    "title": "Post new draft with cat 2",
    "content": "TEST<br/>TEST",
    "categories": [2],
    "status": "draft"
  }
_EOM_

curl -X POST \
     -u "user:applicatopn_password" \
     -H "Content-Type: application/json" \
     -d "$json" \
     https://MY_WORDPRESS_SITE/wp-json/wp/v2/posts

-uオプションを指定しないでも、エンコード済みのAuthorizationヘッダー2を加えてもAPIにアクセスできます。

curl -X POST \
     -H "Authorization: Basic dXNlcjphcHBsaWNhdG9wbl9wYXNzd29yZA==" \
     -H "Content-Type: application/json" \
     -d "$json" \
     https://books.nosuz.jp/wp-json/wp/v2/posts

BASIC認証方法は、パスワードをBase64でエンコードしているだけなので簡単にデコードして読み取ることができます。

$ echo dXNlcjphcHBsaWNhdG9wbl9wYXNzd29yZA==|base64 -d
user:applicatopn_password

OAuthでのサンプル

OAuthのサンプルは、PHPとそのOAuthライブラリHTTP_OAuthを使用しました。ライブラリのインストール方法は、WORDPRESS REST APIのOAUTHトークンを取得を参照して下さい。

PHPからOAuth認証でPOSTする時に、HTTP_OAuthライブラリにデータを指定する方法が分からず少し困りました。しかしHTTP_Request2のインスタンスにデータをセットし、それをHTTP_OAuthに渡すことでOAuth認証でPOSTできました。

require_once('HTTP/OAuth/Consumer.php');

$oauth = new HTTP_OAuth_Consumer(
    $oauth_secret->client_key,
    $oauth_secret->client_secret,
    $oauth_secret->access_key,
    $oauth_secret->access_secret
);

# POST OAuth
$http_request = new HTTP_Request2();
$http_request->setHeader("Content-Type: application/json");
$http_request->setBody(json_encode($article));

$consumer_request = new HTTP_OAuth_Consumer_Request();
$consumer_request->accept($http_request);
$oauth->accept($consumer_request);
$result = $oauth->sendRequest($post_endpoint)->getResponse();

これを使ってWordPressに記事を投稿と更新するプログラムを書いてみました。このプログラムは、HTTP_OAuthライブラリの他にテンプレートエンジンのTwigを使用しています。

phpのビルトインサーバを起動させて

php -S localhost:8080

http://localhost:8080/wp-post.phpにアクセスします。最初にアクセスした時には新規投稿となり、結果には記事のIDが埋め込まれているので次からは更新となります。

参照と脚注

  1. Application PasswordによるBASIC認証でのユーザ名は、WordPressのユーザ名でありApplication Passwordを登録した時のアプリケーション名ではありません。これを勘違いしていて私はだいぶ時間をロスしました。

  2. Authorizationヘッダーは、ユーザ名とパスワードを:でつないでBase64でエンコードしただけです。

    $ echo -n 'user:applicatopn_password' | base64
    dXNlcjphcHBsaWNhdG9wbl9wYXNzd29yZA==
    

WordPress REST APIのOAuthトークンを取得

WordPress REST APIで使用するOAuthトークンを取得するPHPプログラムを作成した。プログラムを作成したと言っても、TwitterのOAuthを取得するプログラムからエンドポイントを変更しただけでWordPress用のOAuthトークンを取得できました。

WordPress REST APIは、ユーザを認証する三種類(通常のパスワード、アプリケーションパスワード、OAuth)の方法をサポートしています。この内通常のパスワードとアプリケーションパスワードを使った方法は、通信経路が暗号化されていないHTTP://というサイトではパスワードが丸見えになってしまい危険で使用できません。

そのような場合には、OAuthを使ったユーザ認証を使用します。このOAuthを使った認証では、自分と相手のみが知っている鍵(トークン)を使って署名を作成し、その署名だけを送ります。そのため通信が覗かれても署名に使われたトークンを知られることがなく安全です。

ただしこのOAuthに使用するトークンを取得するには、WordPressを使用するサイトと何度か通信する必要があります。手動でやり取りしてトークンを取得することも可能ですが、Webサーバとブラウザがあればとても簡単です。

そこで今回は、OAuthトークンを取得するプログラムをPHPで作成し、PHPが持っているビルトインサーバを使用して走らすことでWordPress REST APIで使用するOAuthトークンを取得しました。わざわざApacheなどの本格的サーバを準備すること無くテスト用OAuthトークンを簡単に取得出来ました。

準備

WordPressのプラグインをインストール

WordPressにOAuthプラグインをインストールします。またREST APIのプラグインはOAuthトークンを取得する時に必要ないと思いますが、動作確認の時に必要なので合わせてインストールしておきます。

WordPress REST API(Version 2)は、WordPressのダッシュボード「プラグインを追加」で検索すると見つかるので、それをインストールします。登録済みOAuthプラグインに無いようなので、OAuthプラグインのページからアーカイブをダウンロードしてインストールします。

どちらもプラグインをインストールした後は、忘れずにインストールしたプラグインを 有効 にしておきます。

REST APIが有効になっているかは、次のコマンドで確認できます。

$ curl https://tech.nosuz.jp/wp-json/wp/v2/posts

これで投稿が取得できればREST APIに関しては問題ありません。

PHPとOAuthライブラリをインストール

手元のパソコンでは、Ubuntu 15.10が動いています。

まずパッケージからPHPをインストールします。

$ sudo apt install php5-cli php-pear

普通のphp5はApacheなどもインストールされてしまうので、php5-cliをインストールします。またPHPのライブラリをインストールするためにpearもインストールします。

次はOAuthライブラリのインストールですが、HTTP_OAuth1はα版とのことでインストールする時にバージョンを指定する必要があります。

$ sudo pear install HTTP_OAuth
Failed to download pear/HTTP_OAuth within preferred state "stable", latest release is version 0.3.2, stability "alpha", use "channel://pear.php.net/HTTP_OAuth-0.3.2" to install
install failed

$ sudo pear install HTTP_OAuth-0.3.2

PHPでOAuthトークンを取得するプログラム

このライブラリを使ってOAuthトークンを取得するプログラムget-oauth-token.phpを作成しました2

OAuthトークンの取得

アプリケーションの登録

まずOAuthを使用するアプリケーションを、WordPressのダッシュボードにある「ユーザー > Application」で登録します。

application

AddNewApplication

「Add New」ボタンをクリックして、アプリケーションの名前と簡単な説明、callbackアドレスを記入して「Add Consumer」ボタンをクリックすれば完了です。

EditApplication

今回手元のPHPビルトインサーバ機能を使用するので、callbackは次のように記入しました。このようにcallback先のURLは自分がアクセスできればネットに公開されていなくても大丈夫です。

http://localhost:8080/get-oauth-token.php

登録されるとOAuth Credentialsが表示されるので、Client KeyとSecretをコピーしてJSON形式で別のファイルに保管しておきます。これらの値は、プログラムにハードコードしないでファイルから読み込んで使用します。

OAuthトークンを取得

PHPビルトインサーバを起動して、上で作成したOAuthトークンを取得するプログラムへのアクセスを待ちます。ビルトインサーバを起動するには、OAuthを取得するプログラムが有るディレクトリに移動し、そこで次のコマンドを実行します。

$ php -S localhost:8080

次にブラウザからhttp://localhost/get-oauth-token.phpにアクセスすると、WordPressのユーザ認証ページにリダイレクトされます。ここで「Authorize」をクリックすると取得したOAuthトークンが表示されます。

authorize

このトークンは後で使用するので、安全なファイルに記録しておきます。

参照と脚注

  1. HTTP_OAuthを使用したのは、PHPのOAuthライブラリーを検索した時に結果の最初の方に有ったからです。まだα版ということですが、特に問題はありませんでした。

  2. このWordPress用のOAuthトークンを取得するプログラムは、APIのエンドポイントURLをWordPress用に修正したのみでTwitterのOAuthトークンを取得するプログラムと全く同じです。

ヘッドレスRaspberry PiにRaspbian(jessie)をインストール

ディスプレイ等を接続しないでRaspberry Pi 2にRaspbian(jessie)をインストールしました。固定IPアドレスを使用するには、dhcp.confに設定します。

ディスプレイやキーボードが接続されていないヘッドレスRaspberry PiへRaspbian1をインストールする方法は、設定をネットワークから行うだけで基本的な手順はディスプレイ等が接続されたRaspberry Piへのインストールと同じです。

必要な物

Raspberry Pi本体
今回は、Raspberry Pi2を使用しました。
USB-ACアダプタ
急速充電対応のアダプタをオススメ。特にRaspberry Pi3を使用するときには、取れる電流値に注意する必要があります。
SDメモリカード
Raspberry Pi2以降は、マイクロSDカードを使用します。容量は最低4GBとなっていますが、私の4GBカードはOSイメージが入りきりませんでした。値段は大して変わらないので、8GBを使用することをオススメします。また、できればClass 10の製品を使ったほうがファイルへのアクセス速度が上がります。
ネットワーク
いきなり無線LANで接続するよりは、有線でまずつないでインストールすることをオススメします。
OS
今回Raspbian (jessie)をインストールします。取得方法などは、この後に説明します。
またOSのイメージファイルをSDカードに書き込むためにパソコンが必要です。私はUbuntu 16.04が動いているマシンを使用しましたが、Windowsでもイメージを直接書き込めるソフト2を使用すれば問題ありません。

Raspbianの取得

Raspbianは、公式サイトのダウンロードページまたは国内のミラーサイトからダウンロードします。国内のミラーサイトを使用したほうが、もちろん早くダウンロードが完了します。

ダウンロードが完了したら、ファイルが壊れていないか(改変されていないか)SHA-1フットプリントを見て一応確認しておきます。

$ sha1sum 2016-05-10-raspbian-jessie.zip 
66a50545358e80229d77ebba89ab01f1c0fb4a02  2016-05-10-raspbian-jessie.zip

この値が、公式サイトに書かれているSHA-1の値と同じになっているはずです。もし違っているときには壊れているので捨てます。問題なければ解凍してディスクイメージを取り出します。

$ sudo apt install zip
$ zipinfo -1 2016-05-10-raspbian-jessie.zip
2016-05-10-raspbian-jessie.img
$ unzip 2016-05-10-raspbian-jessie.zip

SDメモリカードへの書き込み

ディスクイメージのファイルを取り出せたら、SDメモリカードに書き込みます。この時普通のコピーではなく、ddまたはddrescueを使用してブート領域の部分から直接上書きします。

# `usb-Multi_Flash_Reader_058F0O1111B1-0\:0`の部分は、SDメモリカードリーダ(アダプタ)に依って変わります。

$ sudo dd bs=4M if=2016-05-10-raspbian-jessie.img of=/dev/disk/by-id/usb-Multi_Flash_Reader_058F0O1111B1-0\:0

# or

$ sudo apt install ddrescue
$ sudo ddrescue 2016-05-10-raspbian-jessie.img /dev/disk/by-id/usb-Multi_Flash_Reader_058F0O1111B1-0\:0

このコマンドを実行する時に/(root)のあるドライブを上書きしてしまう可能性があります。そのミスを防ぐために/dev/sd*ではなく、/dev/disk/by-id/usb-*を使用します。これならば、USB接続のストレージだと一目で分かります。

またddを使用した場合は、途中で進行状況が表示されません。進行状況を確認するには、別の端末から

$ pkill -USER1 dd

を実行して進行状況を表示させるシグナルを送ります。

Raspbianを起動

ディスクイメージをSDメモリカードに書き込んだら、Raspberry Piにセットして通電します。

電源が入ると赤と緑のLEDが両方共点灯します。その後一瞬緑のLEDが消えた後に細かく点滅を繰り返します。緑のLEDはディスク(SDメモリカード)アクセスを示しているので、ブートプロセスが完了すると大体の時間消灯するようになります。

ブートプロセスが完了したらDHCPサーバまたはルータのログを確認して、このRaspberry Piが使用しているIPアドレスを探します。

ログなどを調べられない場合は、IPアドレスの範囲をスキャンして当たりをつけます。この例では、10.0.0.1から10.0.0.126の範囲内で反応するIPアドレスを見つけます。

$ for i in {1..126}; do echo $i; ping -c 1 -W 1 10.0.0.$i >/dev/null && echo 10.0.0.$i; done

Raspberry Piが使用しているIPアドレスが分かったら初期ユーザでログインします。例えば10.0.0.4を使っているとすると、次のようにログイン名を指定して接続します。WindowsのTeraTermでも同じ書き方で接続アドレスとユーザ名を指定できます。

$ slogin pi@10.0.0.4
# 初回は本当に接続するか確認を求められるので、`yes`と入力して続行します。
pi@10.0.0.4's password: # 初期パスワードは`raspberry`となっています。

ログインできれば、後はヘッドレスもディスプレイ等を接続したRaspberry Piもすることは同じです。

固定IPアドレスの設定

Jessieでは、固定IPアドレスを使用する設定を/etc/dhcpcd.confに設定を書き込みます。これまでは/etc/network/interfacesに設定していましたが、変更されています。

固定IPアドレスを使用するには、man dhcpcd.confを参考にして/etc/dhcpcd.confの最後に次の設定を書き加えます。

interface eth0
static ip_address=10.0.0.130/24
static routers=10.0.0.254
static domain_name_servers=10.0.0.127 10.0.0.254

セットアップ

新規ユーザ登録

まずはadduserコマンドで自分のアカウントを作成します。パスワードや名前などを質問されるので、それに答えて入力していきます。

$ adduser nosuz
Adding user `nosuz' ...
Adding new group `nosuz' (1001) ...
Adding new user `nosuz' (1001) with group `nosuz' ...
Creating home directory `/home/nosuz' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
Changing the user information for nosuz
Enter the new value, or press ENTER for the default
	Full Name []: 
	Room Number []: 
	Work Phone []: 
	Home Phone []: 
	Other []: 
Is the information correct? [Y/n] 

新しく追加したユーザが管理者権限で色々できるようにするため、/etc/sudoersに次のエントリーを追加しておきます。

#nosuz ALL=(ALL) NOPASSWD: ALL
nosuz ALL=(ALL) ALL

NOPASSWD:を指定すると、自分のパスワードを確認されずにコマンド(ここでは全てのコマンド)を管理者権限で実行できます。どちらにするかは、各サイトのセキュリティプリシーによります。

新しいユーザでログインして管理者権限を使えることを確認したら、ユーザ登録作業は完了です。

デフォルトユーザpiの削除

Raspbianにはpiというユーザがいることが公開されておりブルートフォース攻撃の標的になるので、ユーザpiを削除しておきます。

まず/etc/sudoersを編集してユーザpiのエントリーを削除しておきます。削除しなくても良いのですが、後々piというユーザを作成して意図しないで管理者権限で操作できてしまうことを防ぎます。

ユーザを削除するには、deluserコマンドを使用します。piでログインしている場合は、事前にログアウトしておきます。

$ sudo deluser --remove-home pi
Looking for files to backup/remove ...
Removing files ...
Removing user `pi' ...
Warning: group `pi' has no more members.
Done.

ソフトのアップデート

パッケージのダウンロードを速くするために、Raspbianのmirrorサイト3を参考にして近場のサイトからダウンロードするようにapt edit-sourcesコマンドで/etc/apt/sources.listの内容を変更します。直接ファイルを編集しても問題ありませんが、この方法だと書式に問題が有ると教えてくれます。

$ sudo apt edit-sources
# 初回はエディタの選択メニューが表示されます。

$ cat /etc/apt/sources.list
#deb http://mirrordirector.raspbian.org/raspbian/ jessie main contrib non-free rpi
deb http://ftp.yz.yamagata-u.ac.jp/pub/linux/raspbian/raspbian/ jessie main contrib non-free rpi
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
#deb-src http://archive.raspbian.org/raspbian/ jessie main contrib non-free rpi

$ sudo apt update && sudo apt full-upgrade

# Raspberry Piのファームウェアをアップデート。しばらく時間がかかります。
$ sudo rpi-update

ディスク領域の拡張

SDメモリーカードの全領域をRaspbianから使えるようにするには、raspi-configコマンドでパーティションを拡張します。

$ sudo raspi-config
┌─────────┤ Raspberry Pi Software Configuration Tool (raspi-config) ├─────────┐
│                                                                             │
│    1 Expand Filesystem             Ensures that all of the SD card          │
│    2 Change User Password          Change password for the default          │
│    3 Boot Options                  Choose whether to boot into a de         │
│    4 Wait for Network at Boot      Choose whether to wait for netwo         │
│    5 Internationalisation Options  Set up language and regional set         │
│    6 Enable Camera                 Enable this Pi to work with the          │
│    7 Add to Rastrack               Add this Pi to the online Raspbe         │
│    8 Overclock                     Configure overclocking for your          │
│    9 Advanced Options              Configure advanced settings              │
│    0 About raspi-config            Information about this configura         │
│                                                                             │
│                                                                             │
│                    <Select>                    <Finish>                     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

# メニューの一番を選択します。上下の矢印で移動させて、1のところで`Enter`キーを押すと選択されます。

# `Tab`キーで`Finish`に移動して`Enter`キーを押すとrebootするか聞かれるので、再起動します。
# 再起動したら、`df -h`コマンドでディスク領域が拡張していることを確認します。

終了

終了するときは、次のコマンドで安全に終了させます4

$ sudo shutdown -h now

このコマンドを実行すると緑色LEDがしばらく点滅します。この点滅が収まり緑色LEDが消灯したら、10秒ほど見守ってもう点滅することがないのを確認して電源を切ります。

-hオプションの代わりに-rを指定するとリブートします。

参照と脚注

  1. Rasbianと間違えやすいのですが、Raspbianとbの前にpが入ります。間違えるのは私だけ?

  2. Win32DiskImagerやDD for Windowsなどを使用します。

  3. Raspbian Mirrors 私は山形大学のミラーを使用しています。

  4. もっとも電源をいきなり切っても大抵問題になりません。

PT2で地デジと衛星放送を録画

chardev版のPT2ドライバをUbuntu 16.04にインストールしてテレビ放送(地デジ)を録画できるようにしました。以前は録画できるようになるまで結構苦労した気がするのですが、今回はすんなると簡単に録画サーバになりました。

これまでUSB接続の地デジチューナーを使用していましたが、しばらく前に壊れて録画できなくなっていました。しかしサーバを新しくセットアップしたので、改めて録画サーバ機能をセットアップしました。

いくつかの地デジチューナーを使用できるようですが、今回はあまり手間を掛けたくなかったので、デファクトスタンダードなPT2を使用することにしました。PT2はPCIバス接続という制限がありますが枯れたカードで、思いの外すんなりと必要なソフトをインストールすることができました。

構成

OS Ubuntu 16.04
チューナーカード PT2 Rev. B
ICカードリーダー SCR3310-NTTCom
B-CASカード 地デジ専用または衛星放送対応
チューナーカード
Linuxで使用できるチューナーカードはいくつかありますが、デファクトスタンダードはアースソフトのPT2とPT3です。しかしPT2はとっくに製造が終了しており、PT3も製造終了1となってしまいました。そのため最近はプレミア価格でPT3がヤフオクに出回っています。
今回は、手頃な価格で中古が手に入るPT2を使用しました。ただしこのカードは現在一般的なPCI Expressではない前世代のPCIバスに接続するので、購入前にPCIカードを取り付けられるか確認しておく必要があります。
B-CASカード
地デジ放送や衛星放送を視聴するには、テレビで視聴するときと同じようにそれぞれに対応したB-CASカードが必要です。PT2(PT3)にはこのカードが付属しません。そのためテレビやレコーダから抜いてくるか、B-CASカード付きのチューナーを別に購入する必要があります。
ICカードリーダ
B-CASカードの情報を読み取るために、USB接続の接触型ICカードリーダが必要です。
今回は以前から使用しているSCR3310-NTTComを使用しましたが、現在はこちらも製造終了しているようです。こちらも中古が結構流通しているようですが、別のICカードリーダーも結構な確率で使用できそうです。

ソフトのインストール

ICカードリーダーのパッケージをインストール

まずICカードリータのパッケージをインストールします。

$ sudo apt install pcscd pcsc-tools

# B-CAS を認識できるか確認
$ pcsc_scan

...
Japanese Chijou Digital B-CAS Card (pay TV)

pcsc_scanコマンドを実行しして、Japanese Chijou Digital B-CAS Card (pay TV)が表示に含まれればOKです。このコマンドは実行したままになるので、Ctrl-Cで停止させます。

dvb版PT2ドライバーを削除

地デジチューナのPT2を組み込むとdvb版ドライバがロードされます。このドライバを使用しないのでカーネルから削除します。

$ lsmod | grep earth_pt1
earth_pt1              28672  0
dvb_core              122880  1 earth_pt1

$ sudo rmmod earth_pt1

# ドライバーのロードを禁止する。
$ nano /etc/modprobe.d/blacklist.conf

また次回再起動した時に自動的にロードしないように/etc/modprobe.d/blacklist.confを編集しておきます。次の内容をファイルの最後に加えておくと、earth_pt1をロードしなくなります。

blacklist earth_pt1

chardev版PT2ドライバをインストール

新しくchardev版のPT2用ドライバーを取得してインストールします2

$ wget http://hg.honeyplanet.jp/pt1/archive/tip.tar.bz2
# tarのaオプションは、拡張子から自動的に圧縮アルゴリズムを識別してくれて便利。
$ tar axvf tip.tar.bz2
$ cd pt1-c8688d7d6382/driver

ただしこのままではコンパイルに失敗するので、次のパッチをpt1_pci.cに当てます3

--- pt1_pci.c.orig	2016-05-07 05:49:56.494036485 +0900
+++ pt1_pci.c	2016-05-07 05:50:05.597766146 +0900
@@ -16,6 +16,9 @@
 #include <asm/system.h>
 #endif
 #include <asm/io.h>
+#if LINUX_VERSION_CODE >=KERNEL_VERSION(4,2,0)
+#include <linux/vmalloc.h>
+#endif
 #include <asm/irq.h>
 #include <asm/uaccess.h>

このままインストールすると、カーネルをアップデートするたびに毎回コンパイルと再インストールが必要になります。そこでカーネルがアップデートされるたびに自動的に再コンパイルと再インストールされるようにDKMSに登録しておきます4

まず必要なパッケージをインストールして、ソースをコピーします。

$ sudo apt install dkms

$ sudo cp -r ../driver /usr/src/pt1-c8688d7d6382
# DKMS用の設定ファイルを作成する。
$ sudo nano /usr/src/pt1-c8688d7d6382/dkms.conf

DKMSに登録するためには、設定ファイルdkms.confを作成してソースのディレクトリに保存しておきます。設定ファイルの内容は、次のようになります。

PACKAGE_NAME="pt1"
PACKAGE_VERSION="c8688d7d6382"
CLEAN="make clean"
MAKE="make"
BUILT_MODULE_NAME="pt1_drv"
DEST_MODULE_LOCATION="/kernel/drivers/video/"
AUTOINSTALL="YES"

DKMSを使ってドライバーをインストールします。

$ sudo dkms install -m pt1 -v c8688d7d6382
$ sudo dkms status | grep pt1
pt1, c8688d7d6382, 4.4.0-21-generic, x86_64: installed

# ドライバーをカーネルにロードする。
$ sudo modprobe pt1_drv
$ lsmod|grep pt1
pt1_drv                40960  0

ただ私の環境だけかチューナのデバイスファイルのモードが、rootのみ読み書き可能で、他のユーザはアクセスできないように設定されてしまいます。

$ ls -l /dev/pt1video*
crw------- 1 root root 245, 0  5月  7 07:20 /dev/pt1video0
crw------- 1 root root 245, 1  5月  7 07:20 /dev/pt1video1
crw------- 1 root root 245, 2  5月  7 07:20 /dev/pt1video2
crw------- 1 root root 245, 3  5月  7 07:20 /dev/pt1video3

このままでは録画する時に不便なので、次のエントリーをrootのcrontabに登録して再起動するたびにデバイスファイルのモードを変更しています。

@reboot chmod a+rw /dev/pt1video*

rootのcrontabに登録するには、次のコマンドを使用します。

$ sudo crontab -e

これで誰でもアクセスできるようになりました。

$ ls -l /dev/pt1video*
crw-rw-rw- 1 root root 245, 0  5月  7 12:48 /dev/pt1video0
crw-rw-rw- 1 root root 245, 1  5月  7 12:48 /dev/pt1video1
crw-rw-rw- 1 root root 245, 2  5月  7 12:48 /dev/pt1video2
crw-rw-rw- 1 root root 245, 3  5月  7 12:48 /dev/pt1video3

録画ソフトをインストール

PT2用のchardevドライバーをインストールしたら、録画するプログラムをインストールします。

ただし放送のデコードに必要なライブラリarib25が含まれていないので、昔のソースから取り出して事前にインストールしておく必要があります。

$ sudo apt install zip pkg-config libpcsclite-dev

$ wget http://hg.honeyplanet.jp/pt1/archive/c44e16dbb0e2.zip
$ unzip c44e16dbb0e2.zip && cd pt1-c44e16dbb0e2/arib25
$ make && sudo make install

arib25をインストールしたら、録画プログラムをインストールします。

$ cd ~/pt1-c8688d7d6382/recpt1
$ sudo apt install autoconf automake
$ ./autogen.sh
$ ./configure --enable-b25
$ make && sudo make install

録画テスト

全ての準備が整ったら、まずは受信信号を調べてみます。

$ checksignal 13
device = /dev/pt1video2
C/N = 33.592474dB

オプションの13は、地デジのチャンネルです。これは一般的なNHKなら1chとかのチャンネルとは別で、周波数チャンネルです。この対応は地域(送信塔)毎に異なっているので、対応表で自分の所の周波数チャンネル5を調べておく必要があります。

checksignalを終了するには、Ctrl-Cを入力します。

最後は実際に1分ほど13chを録画して見ます。

$ recpt1 --b25 --strip 13 60 test.mpg
using B25...
enable B25 strip
pid = 6840
C/N = 33.701023dB
Recording...
ecorded 62sec

これで100MiB前後のファイルが作成されるので、VLCなどの再生ソフトでちゃんと録画出来ているか確認します。

実際に使用するには、周波数チャンネルを指定したり時間を秒で指定するのが面倒です。そこで簡単なラップスクリプトを作っておくと便利になります。

参照と脚注

  1. アースソフトの地デジチューナー「PT3」が生産終了に – AKIBA Hotline!

  2. pt2/chardev

  3. パッチの作成には、Linux/テレビ関連/PT2を参考にさせてもらいました。

  4. PT2ドライバをDKMSに登録 – Kung Noi Blog

  5. 放送の周波数チャンネルを調べるには、マスプロの地上デジタル放送 チャンネル一覧表が便利です。

Ubuntu 16.04のZFSで/homeを冗長化

Ubuntu 16.04からZFSがサポートされたので、/homeをミラーリングしたZFS上に作成して冗長化してみました。

これまで使っていたディスクが壊れてしまい、新しくUbuntu 16.04をインストールすることになりました。そこでせっかくなので、このリリースからサポートされたZFSを使ってムフフなファイルたちを無くさないように大切な/homeを冗長化することにしました1

ここでは、ZFSをインストールして使用する方法を簡単にまとめます。ZFSならばバックアップの作成も簡単ですが、ここでは触れません。

システム構成

CPU Pentium G620
RAM 16GiB
Disk 1TB x 2
OS Ubuntu 16.04 Server
メモリ
最低4GB。できれば8GBで、多ければ多いほど良い。もっとも4GB未満でも動かないということもないようです。今回のマシンは、KVMホストもしているので16GBにしています。
ZFSのメモリというとECC付きメモリ or notという話は避けられません。ECC付きのメモリでないとファイルが知らない間に壊れているかもという恐ろしい話があります。一方で、それは他のファイルシステムでも同じで、ZFSだから壊れやすいとはことでもないという話もあります。私は個人的な用途なので気にせずECC無しのメモリを使用しています。
ディスク
ミラーリングの場合は、ディスクが二台必要です。三台でミラーリングして、冗長度を上げることも可能です。またRAIDZの場合は最低3台、RAIDZ2の場合は最低4台のドライブが必要です2
ZFSを構成するドライブ(パーティション)のサイズは、Btrfsと異なり全て同一である必要があります。容量の小さなドライブが混じっていると、全体がその最低サイズに合わせられます3。またディスクを入れ替える場合も、同じかそれ以上のサイズが必要です。
OS
Ubuntu 16.04 Server(64bit)。ZFSを使用できるのは、64bit版のみです。もっとも現在のUbuntuではDesktopとServe共に64bit版が標準なので、それほど気にする必要はありません。しかしアップグレードしてきた場合は32bit版を使っているかも知れないので、その場合は注意が必要です。

システムをインストール

grubブートローダにはちょっとした問題4があるので、ZFSはデータ用のファイルシステムとして使用することをオススメします。/(root)の復旧は/etcとインストール済みのパッケージ一覧があれば/(root)の復旧は簡単ですので、システム全体をZFSにして/(root)自体も冗長化する利点はそれほど大きくありません。

そこで今回は/(root)には冗長性を持たせないで、/homeだけにRAID1の冗長性を持たせることにしました。その代わり復旧に大切な/etcとパッケージ一覧、crontabcronにより適期的にZFS上にコピーしておきます。もちろんZFSのsnapshotを作成して過去に遡れるようにして。

まずsdaに小さなパーティション(8GiBで十分)を作成して、そこに通常通りシステムをインストールします。耐久性はそれほど要求されないので、USBフラッシュメモリにシステムをインストールしても良いかも知れません。

ZFSをインストール

ZFSに必要なカーネルモジュールzfs.koはカーネルに同梱されているので、必要なユーティリティをインストールするだけで完了です。このモジュールがコンパイル済みでカーネルに含まれているので、これまでのDKMSによるzfs.koのコンパイルを待つ面倒臭さがなくなりました。

$ sudo apt install zfsutils-linux

ユーティリティのインストールが完了したら、ZFSに必要なカーネルモジュールがロードされファイルシステムが使えるようになっているか一応確認しておきます。

$ lsmod |grep zfs
zfs                  2813952  3
zunicode              331776  1 zfs
zcommon                57344  1 zfs
znvpair                90112  2 zfs,zcommon
spl                   102400  3 zfs,zcommon,znvpair
zavl                   16384  1 zfs

$ cat /proc/filesystems |grep zfs
nodev	zfs

ミラーリング(RAID1)プールの作成

まずインストールしてあるディスクを確認します。

今後ディスクにアクセするときは、sdaなどの名前ではなく/dev/disk/by-idに表示される名前を使用します5。この名前は、インターフェイス(ata)とディスクのモデル名とシリアル番号の一部から作成されます。そのため今後ディスクを交換する時に、交換が必要なディスクを識別しやすくなります。

$ ls /dev/disk/by-id | grep ^ata
ata-ST31000524AS_9VPCZPQV
ata-ST31000524AS_9VPCZPQV-part1
ata-TOSHIBA_DT01ACA100_36D1KYUNS

ディスクを確認したら、それぞれに同じ容量のパーティションを作成します。

$ sudo cfdisk /dev/disk/by-id/ata-ST31000524AS_9VPCZPQV
$ sudo cfdisk /dev/disk/by-id/ata-TOSHIBA_DT01ACA100_36D1KYUNS

$ ls /dev/disk/by-id | grep ^ata
ata-ST31000524AS_9VPCZPQV
ata-ST31000524AS_9VPCZPQV-part1
ata-TOSHIBA_DT01ACA100_36D1KYUNS
ata-TOSHIBA_DT01ACA100_36D1KYUNS-part1

なぜかブートドライブに作成したパーティションが見えないのでudevadmin triggerコマンドを試してみます。ダメなときは再起動します。

$ ls /dev/disk/by-id/ | grep ^ata
ata-ST31000524AS_9VPCZPQV
ata-ST31000524AS_9VPCZPQV-part1
ata-ST31000524AS_9VPCZPQV-part2
ata-TOSHIBA_DT01ACA100_36D1KYUNS
ata-TOSHIBA_DT01ACA100_36D1KYUNS-part1

パーティションを作成せずにディスク丸ごとZFSにすることも可能で、この方がパフォーマンスが良いとのことです。しかし同じ1TBとして売られているディスクが、メーカーや製品が違ってもセクター数が同じという自信がありません。そこで私はいつも最大サイズより少し小さいパーティションを作成して使っています。

また最近のディスクはセクターサイズが4KiBなので、パーティションの区切りを4KiBのセクターサイズに合わせます。もっとも最近のfdiskなどは、そのことを考慮して配置してくれるので特に気にする必要はありません。

ディスクにパーティションを作成したら、プールを作成します。

$ sudo zpool create -o ashift=12 -o autoexpand=on \
  tank mirror \
  ata-ST31000524AS_9VPCZPQV-part2 \
  ata-TOSHIBA_DT01ACA100_36D1KYUNS-part1

$ sudo zpool status
  pool: tank
 state: ONLINE
  scan: none requested
config:

	NAME                                        STATE     READ WRITE CKSUM
	tank                                        ONLINE       0     0     0
	  mirror-0                                  ONLINE       0     0     0
	    ata-ST31000524AS_9VPCZPQV-part2         ONLINE       0     0     0
	    ata-TOSHIBA_DT01ACA100_36D1KYUNS-part1  ONLINE       0     0     0

errors: No known data errors

-o ashift=12は、セクターサイズの指定です。
この指定によりセクターサイズを2^12B(4KiB)だと指定しています。通常は指定する必要はありませんが、512B/secのディスクを使用していると将来4KiB/secのディスクしか手に入らなくなった時に困ったことになります。

-o autoexpand=onは、将来ディスクを交換した時に可能ならプールのサイズを自動的に拡張する指定です。
このオプションがなくても特に困ることはありません。必要ならば、ディスクの交換後にプールのサイズをマニュアルで拡張することが可能です。ただし拡張できるのは、いずれの場合も冗長構成のディスク全てが現在のプールサイズよりも大きくなった場合に限られます。

tankは、プールの名前です。
/(root)直下のディレクトリ名(binなど)でなければ自由に選べますが、tankpoolが慣習として使われます。

また-O compression=lz4を一緒に指定してファイルシステム全体を圧縮することも可能です。後で見るように、ファイルシステムを作る時に個別に圧縮の有無と方式を指定することも可能です。

ファイルシステムを作成

プールを作成すると自動的に/にプール全体がマウントされますが、目的に応じてファイルシステムを切り出します。今回は、/home用に切り出します。

$ sudo zfs create -o atime=on -o relatime=on \
  -o compression=lz4 tank/home
$ sudo zfs list
NAME        USED  AVAIL  REFER  MOUNTPOINT
tank        372K   868G    96K  /tank
tank/home    96K   868G    96K  /tank/home

-o atime=on -o relatime=onは、ファイルのアクセスタイムの更新方式を指定します。
Linuxはファイルにいつアクセスしたかという情報を記録しており、ファイルにアクセスするたびにファイルの情報を書き換えています。しかしこの情報が使われることはほとんどありません。そこで-o atime=offとしてアクセスタイムの更新を止めてしまうと、ファイルアクセスのパフォーマンスが上がります。しかし一部このアクセス情報を使用するプログラムもあるので、だいたい問題ない程度に更新の頻度を減らすようにします。

-o compression=lz4は、ファイルシステムを圧縮する方法を指定します。
ただ将来さらに優れた圧縮方法が導入されたら自動的にそれを使うように-o compression=onとしておいたほうが良かったかも知れません。zfs setコマンドで後から変更できます。

ファイルシステムを作成したら/homeの内容を移動させて置き換えます。

$ sudo rsync -av /home/ /tank/home
$ cd /
$ sudo rm -rf /home/*
$ sudo zfs set mountpoint=/home tank/home

ZFSはファイルシステムをマウントする時に/etc/fstabを見ないので、/etc/fstabを書き換える必要はありません。

swap on ZFS

スワップをわざわざZFS上に取る必要はないのですが、お試し的にスワップ領域をZFS上に取ってみました。

スワップ領域を作成

スワップ領域をZFS上に作成します6

$ sudo zfs create -V 16G \
  -b $(getconf PAGESIZE) tank/swap
$ sudo zfs set com.sun:auto-snapshot=false tank/swap
$ sudo zfs set sync=always tank/swap

ブロックサイズをページサイズと同じくすることで空きメモリが少ない状況で大きなブロックの書き換えを防ぎます。

sync=alwaysとすることで、キャッシュをすぐに空にしてメモリの使用量を抑えます。

スワップを有効にする

スワップボリュームを作成したら、後は通常のスワップ領域を追加するときと同じです。

まず/etc/fstabを編集して、次のエントリーを追加します。

# swap
/dev/zvol/tank/swap none	swap	default	0	0

次に実際にスワップ領域をフォーマットしてシステムに追加します。

$ sudo mkswap -f /dev/zvol/tank/swap 

$ sudo swapon --all --verbose
swapon /dev/zd32
swapon: /dev/zd32: found swap signature: version 1d, page-size 4, 同じ byte order
swapon: /dev/zd32: pagesize=4096, swapsize=17179869184, devsize=17179869184

fstabを設定してあるので、再起動しても自動的にスワップ領域が有効になります。

参照と脚注

  1. Linuxで使用できるディスクの冗長化には、歴史のあるmdや既に安定期に入りつつ有るBtrfsを使用できます。なのにZFSをなぜ選択したか? 単純にmdやBtrfsよりZFSのコマンドが私にとって分かりやすかったからです。

  2. RAIDZやRAIDZ2に必要なドライブの数は、次の式が成り立つ必要があります。ここで128KiBは、ZFSのデフォルトレコードサイズです。

    128KiB % (ドライブ数 – パリティ数) == 0
    %は、剰余を求める演算記号

    よって、RAIDZはパリティ1なので3,5,9台、RAIDZ2はパリティ2なので4,6,10台となります。

  3. Btrfsは、容量の異なるディスク(パーティション)を組み合わせても、だけ有効に使ってZFSよりも容量を稼げることがあります。またファイルシステムに空きが有る場合は、現在よりも容量の小さいドライブに置き換えることも可能です。

  4. 少し手間はかかりますが、/(root)自体をZFSにすることも可能です。
    ただしgrubブートローダに問題があり、/(root)の入ったプールを作成する時にいくつかのZFS機能を無効にする必要があります。最初の時だけ気にするなら良いのですが、zpool statusコマンドを実行するとzpool upgradeしてくださいというメッセージが親切にも表示されます。そこでzpool upgradeしてしまうと、システムが起動できなくなってしまいます。
    もちろん開発中のgrubではこの制限が取り除かれています。それでもZFSに新しい機能が導入されるたびにブートできなくなる不安がつきまといます。

  5. ZFSのプールを作成する時に/dev/sdaなどの名前を使用した場合は、zpool importコマンドでインポートし直せば/dev/disk/by-idでの名前に変更可能です。ZFSパッケージ登録記念:UBUNTU 15.10にZFS ON LINUX

  6. HOWTO use a zvol as a swap device
    スワップボリューム – archlinux
    Using a zvol for a swap device – FAQ zfsonlinux

dpkg -i相当のワンライナー

インストール済みのパッケージ一覧を得るには、/var/lib/dpkg/info/*を調べれば良い。

ディスクイメージからインストール済みのパッケージ一覧を取得する必要があり、そのためにワンワイナーを作成しました。

パッケージ情報

dpkg -iが実際に何を見ているのか分かりませんが、/var/lib/dpkg/infoディレクトリに各パッケージのファイル一覧が書かれたファイルなどが置かれています。

インストール済みのパッケージ名は、*.listというファイルから得られます。また一度インストールされた後に削除removeされたパッケージは、*.listのみで*.md5sumsというファイルが無くなっています。

そこで*.list*.md5sumsの両方が有るパッケージを探すことで、インストール済みのパッケージ一覧を取得できます。

ワンライナー

インストール済みのパッケージ一覧

$ ls /var/lib/dpkg/info/* | grep -e '\.list$' -e '\.md5sums$' | sed -e 's/\/var\/lib\/dpkg\/info\///' -e 's/\.list$//' -e 's/\.md5sums$//' | sort | uniq -c | grep '^\s*2 ' | sed -e 's/^\s*2 //'

インストール済みのパッケージは*.list*.md5sumsの両方あるので、拡張子を削除すると同じbasenameが2つ出てきます。そこで、basenameを数えて二つ有るbasenameを抜き出します。

削除したパッケージも一覧に含める

dpkg -iのように削除したパッケージ名も一覧に含めるには、basenameの数でインストール済みか削除済みかをマークします。

$ ls /var/lib/dpkg/info/* | grep -e '\.list$' -e '\.md5sums$' | sed -e 's/\/var\/lib\/dpkg\/info\///' -e 's/\.list$//' -e 's/\.md5sums$//' | sort | uniq -c | sed -e 's/^\s*2 /ii /' -e 's/^\s*1 /rc /'