はじめてOSSライブラリを育てた話
公開してるOSSライブラリが自分史上初めて(微々たるものではあるけれども)誰かに使われるようになったので振り返ってみる。
対象のライブラリはこれ。
開発のきっかけ
Raspberry Piを使ったデバイス開発をしていると、システムに明るくないエンドユーザー向けに何かとネットワーク設定を変更できるようにしたいユースケースが出てくる。
そんなときはWebやUART経由などでRaspberry PIの外からdhcpcd.confやwpa_supplicant.confを編集出来るようにUIを作って提供するのだけれど、設定ファイルのパーサーから書いたりしなければならず、これがなかなかに面倒くさい。
一方で、Linuxのネットワーク設定というとNetwork-Managerが主流ではあるけど、Raspberry PI OSではデフォルトになっておらず、またNetwork-ManagerというとGUIによる設定の印象が強かったので、どうにかならんかなとは思っていた。
ある日、CLIのnmcliがあるバージョンから結構な範囲で色々できることに気づき、これでできそうだなーとPythonからCLIを叩いて使えるライブラリを作成した。
開発当初のコンセンプト
実装的にはコマンド叩いてパースする程度なので、作り自体は単純なもの。
そんな中で、とにかく型は定義したい、dataclassなども使いたいということでPythonは3.7以降対象として、可能な限りテストも書いた。
APIのインターフェイスはなるべく直感的にわかるようにnmcliのコマンド・サブコマンドから連想できるようにした(そのためにPythonで__call__とか初めて使った)。
エラーもnmcliのステータスコードに準拠して例外を定義した。
また、テストのときなどに都度モック作らずに良いように最初からdummyのモッククラスを搭載した(現在はおまけ要素程度になっている)。
公開当初とPyPIへの登録
最初はPyPIにも登録せず、ビルドしたwheelファイルをGithubのReleaseに添付する形で公開した。そのためライブラリ名を決める際に特に悩むことなくnmcliそのままにした(そのせいでCLIの方のnmcliへの質問がIssueで来たりしてちょっと困ったが)。
公開してしばらくしてからフォークして使ってくれている人が出てきて、自分はサポートしていなかったnmcliのコマンドに対応しようとしている事に気づいた。それならサポートの幅を広げてみるかと、一度nmcliの搭載しているコマンドをざっと洗い出して、コンパチビリティテーブルを作って対応した。また、このタイミングでPyPIに登録することにした。
コントリビューターがついた
PyPIに登録してから不具合修正や機能追加のPRをもらうようになり、DeepLで翻訳しながらコントリビューターとやり取りするようになった。未対応だったサブコマンドに対しても熱い要望をもらって対応した。
英語そこまで得意な方ではないので翻訳しながらではあったけど、自分のライブラリを好んで使ってくれているメッセージが入ると素直に嬉しかった。
2021年にはバージョンも0.3.1から0.9.0まで上がり、途中文字コード的な問題への対応などもあったけどそれなりに順調に来ている。
今後の課題
もらえるPRはテスト書いてくれていない人もいるので、強制まではするつもりはないけどGithub Actionとかでテスト壊したのが気付けるようにはしようかなーと考えている。
コマンドやサブコマンドのオプションパラメータはサポートしていたりしていなかったりするので、必要出てきたら足していかないとなと思っている。
所感
Webのフロントエンドに興味が全振りしているはずの自分が、初めて人に使ってもらえるところまで行ったのがデバイス用のライブラリか…と思わないこともないけど、微々たるものでも誰かの役に立てているというのが素直に嬉しい。
世界を大きく変えるようなものはできないけど、ちょっとしたお役立ちを目指して今後もコードを書いていきたい。
シリアル接続同士をプロキシするツールを作った
とある通信モジュールをM5Stack(UART 2)で使う必要があったのだけど、通信モジュールの評価ボードがUSBでシリアル接続するものなので直接繋げないという状態になった。
幸い、USBシリアル変換ケーブルのTTL-232R-3V3が手元にあったので、通信モジュール -> USBケーブル -> (なにか) -> TTL-232R-3V3 -> M5Stack とすれば繋げられるなと思ったので、次のPythonでスクリプトを書いてしのいだ(socatでできそうな気がしたんだけど、うまくできなくて投げ出した)。
import serial from threading import Thread import time com1 = serial.Serial("/dev/ttyUSB0", 115200, timeout=1) com2 = serial.Serial("/dev/ttyUSB1", 115200, timeout=1) def pipe1(): while True: b = com1.read(com1.in_waiting or 1) if b: com2.write(b) def pipe2(): while True: b = com2.read(com2.in_waiting or 1) if b: com1.write(b) th1 = Thread(target=pipe1, daemon=True) th2 = Thread(target=pipe2, daemon=True) th1.start() th2.start() while True: time.sleep(1)
そこそこ便利だったので、書き直してデバッグ用のコンソール表示機能つけた。
Raspbianのネットワーク管理をNetwork-Managerにした
Raspbianのネットワーク設定周り、大昔は/etc/network/interfaces
だったのが、
いつの頃からか/etc/dhcpcd.conf
で設定するようになって、固定IPにするときとか
/etc/dhcpcd.conf
を編集している。
加えて、Wifiを設定するのであれば/etc/wpa_supplicant/wpa_supplicant.conf
を編集し、
USBドングルを使ってモデムで繋ぐならばwvdialやpppconfigを...と言った感じで、
とにかく設定が散らばっていてめんどくさい。
以前から普通のデスクトップLinuxみたいにNetwork-Managerでやってしまったほうが楽なのでは... と思っていたけど、バージョン的にnmcli(Network-ManagerのCUIクライアント)でできることが少なくて見送っていた。
去年からRaspbianのBusterを使うようになり、 「そう言えばnmcliのバージョンってどうなったんだろ?」と思って試しに入れてみたら、 結構バージョン上がっていて色々とできるようになっていた。 この際なのでネットワークまわりをNetwork-Manager使うようにしてみた。
環境設定
使ったraspbian OSのバージョンはRaspbian Buster Liteの2020-02-13バージョン。
aptでnetwork-manager
をインストールして、dhcpcdは止めて再起動する。
$ sudo apt-get update $ sudo apt-get install -y network-manager $ sudo systemctl disable dhcpcd.service $ sudo reboot
これで準備は完了。
Wifiや有線の設定
まずはnmcli connection
で接続設定を一覧取得する。
$ sudo nmcli connection NAME UUID TYPE DEVICE Wired connection 1 00000000-0000-0000-0000-000000000000 ethernet eth0
それからnmcli device
でネットワークインターフェイスの状態を取得する。
$ sudo nmcli device DEVICE TYPE STATE CONNECTION eth0 ethernet connected Wired connection 1 wlan0 wifi disconnected -- lo loopback unmanaged --
とりあえずWifi繋ぐためにnmcli device wifi
でWifiネットワークを探す(実際の値は????????????ではない)。
$ sudo nmcli device wifi IN-USE SSID MODE CHAN RATE SIGNAL BARS SECURITY ???????????? Infra 1 130 Mbit/s 89 ▂▄▆█ WPA1 WPA2 ???????????? Infra 11 195 Mbit/s 70 ▂▄▆_ WPA1 WPA2 ???????????? Infra 36 270 Mbit/s 57 ▂▄▆_ WPA1 WPA2 ???????????? Infra 5 260 Mbit/s 54 ▂▄__ WPA2 ???????????? Infra 11 195 Mbit/s 49 ▂▄__ WPA2 ???????????? Infra 11 195 Mbit/s 49 ▂▄__ WPA2 ???????????? Infra 108 405 Mbit/s 44 ▂▄__ WPA1 WPA2 ???????????? Infra 100 540 Mbit/s 40 ▂▄__ WPA2 ???????????? Infra 6 54 Mbit/s 39 ▂▄__ WEP ???????????? Infra 6 270 Mbit/s 37 ▂▄__ WPA2 ???????????? Infra 6 135 Mbit/s 25 ▂___ WPA2 ???????????? Infra 116 405 Mbit/s 25 ▂___ WPA2 ???????????? Infra 3 130 Mbit/s 22 ▂___ WPA1 WPA2 ???????????? Infra 52 405 Mbit/s 22 ▂___ WPA2
nmcli device wifi connect
でWifiを設定する。
$ nmcli device wifi connect <SSID> password <password> Device 'wlan0' successfully activated with '00000000-0000-0000-0000-000000000000'.
これでWifiは繋がる。
次にnmcli connection add
で有線接続を固定IPにする。
$ sudo nmcli connection add con-name Home \ type ethernet \ ifname eth0 \ ipv4.addresses 192.168.1.99/24 \ ipv4.gateway 192.168.1.1 \ ipv4.dns 8.8.8.8 ipv4.method manual Connection 'Home' (00000000-0000-0000-0000-000000000000) successfully added.
作った接続を使うためにnmcli connection down
でデフォルトの有線接続を止める(sshでログイン中の場合、切断されるので注意)。
$ sudo nmcli connection down Wired\ connection\ 1
改めて接続設定を確認する。
$ sudo nmcli connection NAME UUID TYPE DEVICE ?????????????? 00000000-0000-0000-0000-000000000000 wifi wlan0 Home 00000000-0000-0000-0000-000000000000 ethernet eth0 Wired connection 1 00000000-0000-0000-0000-000000000000 ethernet --
作った接続を使うので、nmcli connection delete
でデフォルトの有線接続は削除する。
$ sudo nmcli connection delete Wired\ connection\ 1 Connection 'Wired connection 1' (00000000-0000-0000-0000-000000000000) successfully deleted.
コマンドで操作した設定ファイルは/etc/NetworkManager/system-connections
配下に保存される。
設定ファイルを手で触ってもいいけど、接続の追加、編集、削除が全部コマンドでできて、かなり楽。
USBドングルでアクセスポイントに繋ぐ
wvdialをやめるために、USBドングルの設定も試す。手持ちのL-02Cを使った。 そういえばいつの間にかRaspbianにusb_modeswitchって標準搭載されてる気がする。 L-02Cはudevのルール書かなくても自動でスイッチされてモデムとして認識される。
USBポートに挿してネットワークインターフェイスの状態を見る。
$ sudo nmcli device DEVICE TYPE STATE CONNECTION eth0 ethernet connected Home wlan0 wifi connected ???????????? ttyUSB2 gsm disconnected -- lo loopback unmanaged --
iijmioのアクセスポイントで設定してみる。
$ sudo nmcli connection add autoconnect true \ con-name iijmio \ type gsm \ apn iijmio.jp \ user mio@iij \ password iij \ gsm.number *99***1# \ ppp.refuse-eap true \ ppp.refuse-mschap true \ ppp.refuse-mschapv2 true \ ifname "*" Connection 'iijmio' (00000000-0000-0000-0000-000000000000) successfully added.
設定完了してPPPが繋がると接続状態は次のようになる。
$ sudo nmcli connection NAME UUID TYPE DEVICE Buffalo-G-5209 00000000-0000-0000-0000-000000000000 wifi wlan0 Home 00000000-0000-0000-0000-000000000000 ethernet eth0 iijmio 00000000-0000-0000-0000-000000000000 gsm ttyUSB2
PPP接続は時々ちゃんと繋がらないことがあり、syslogみると次みたいなエラーになっている。
Feb 29 17:13:20 raspberrypi NetworkManager[343]: <warn> [1582964000.7050] modem-broadband[ttyUSB2]: failed to connect modem: 34
こういう場合はModemManagerを再起動して、それからコネクションを開始する。
$ sudo systemctl restart ModemManager.service $ sudo nmcli connection up iijmio Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/10)
この辺りのワークアラウンドはwvdial使っていても遭遇したことがあって、プロセス監視してだめだなと検知したら落としてもう一度みたいにやってたのに近いかも。
とりあえず設定を一本化できるのでやはり楽。
Wifiアクセスポイント化
Raspberry PI自身をWifiアクセスポイント化して接続させるやつ。 前回はhostapdでやったけど、これもNetwork-Managerだけでできる。
wifiタイプの接続を作るときにモードをap
にすればいい。
$ sudo nmcli connection add con-name RPi-AP \ type wifi \ ifname wlan0 \ wifi.ssid RPi-AP \ wifi.mode ap \ wifi-sec.psk raspberry \ ipv4.method manual \ ipv4.addresses 192.168.100.1/24 \ ipv4.gateway 192.168.100.1 \ wifi-sec.key-mgmt wpa-psk Connection 'RPi-AP' (00000000-0000-0000-0000-000000000000) successfully added.
ログの調整
デフォルトだとsyslogにinfoレベルでログが出過ぎるので、/etc/NetworkManager/NetworkManager.conf
にログレベルを設定して調整する。
[main] plugins=ifupdown,keyfile [ifupdown] managed=false [logging] level=WARN
感想
設定を一本化できて楽。しばらく動かしてみたが、dhcpcdをやめて駄目になった部分もみられない。 もう標準でNetwork-Managerでいいんじゃないかな...という印象。
ついでにプログラムから設定変更できるようにしたいのでpython用のライブラリ作った。
とりあえず必要最低限程度に実装。この前やったWifi設定のをもっといい感じにしてみたい。
Raspberry PIをWifiアクセスポイントにしてWifiネットワーク設定をできるようにした
タイトルが変なので何言ってるんだコイツ状態になるけど。
時々見かける、最初はデバイス自身がWifiのアクセスポイントになって、PCなどから接続して実際に使うネットワーク設定を行う...というやつをRaspberry PIでやってみるという話。
作ったものはこれ。
環境準備
まずはhostpadをインストールする。
$ sudo apt-get update $ sudo apt-get install -y hostapd
hostapd.serviceはmaskされているのでインストール時点では起動に失敗する。
設定ファイルをexampleからコピーしてきて/etc/hostapd/hostapd.conf
を編集する。
$ sudo cp /usr/share/doc/hostapd/examples/hostapd.conf /etc/hostapd/hostapd.conf
とりあえず、デフォルトから変更した項目は次の通り。
ssid=RasPi-AP ieee80211n=1 ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40] auth_algs=1 wpa=2 wpa_passphrase=raspberry wpa_key_mgmt=WPA-PSK rsn_pairwise=CCMP
続いて、dhcpサーバをインストールする。
$ sudo apt-get install -y isc-dhcp-server
こちらもインストール時点では起動に失敗する。
/etc/dhcp/dhcpd.conf
を編集して末尾に次の設定を追加する。
subnet 192.168.100.0 netmask 255.255.255.0 { range 192.168.100.100 192.168.100.105; option routers 192.168.100.1; option broadcast-address 192.168.100.255; default-lease-time 600; max-lease-time 7200; }
/etc/default/isc-dhcp-server
を編集してwlan0
をインターフェイスとして有効にする。
INTERFACESv4="wlan0"
isc-dhcp-serverは自動起動せずに使うので自動起動を無効化しておく。
$ sudo update-rc.d isc-dhcp-server disable
とりあえず動作確認のために簡易起動スクリプトをざっと書いて実行してみる。
#!/bin/sh sudo hostapd /etc/hostapd/hostapd.conf & sudo ip addr flush dev wlan0 sudo ip addr replace 192.168.100.1/24 dev wlan0 sudo /etc/init.d/isc-dhcp-server start
スクリプトを実行したら、wlan0
に192.168.100.1
が割り当てられるので、PCなどからアクセスポイントに接続してIPアドレスを貰えればOK。
ブラウザからWifi設定するためのWebサーバを用意
アクセスポイントに接続してPC側がクライアントになることはできるようになったので、アクセスポイントのIPアドレスにブラウザでアクセスしてWifi設定を行えるようにWebサーバを作る。
とりあえず前に作ったtornado使ったソースを流用してざっくり作成。
import os import subprocess import tornado.ioloop import tornado.web class SystemControl(object): def read_wpa_supplicant_conf(self, config_path): with open(config_path) as f: return f.read() def write_wpa_supplicant_conf(self, config_path, data): subprocess.check_call('sudo sh -c "echo -n \'%s\' > %s"' % (data, config_path) , shell=True) def auto_start_off(self): subprocess.run('sudo sed -i --follow-symlinks s/AUTO_START=ON/AUTO_START=OFF/g /etc/default/pi-wifi-admin', shell=True) def reboot(self): subprocess.run('sudo reboot', shell=True) class MainHandler(tornado.web.RequestHandler): def initialize(self, sys_ctrl, wpa_supplicant_conf_path): self._sys_ctrl = sys_ctrl self._wpa_supplicant_conf_path = wpa_supplicant_conf_path def get(self): html = """<html> <head><title>Wifi Setting</title></head> <body> <form method='POST' action='/'> <textarea name='config' style='width:800px;height:600px;'>%s</textarea> <input type='submit' value='Save' /> </form> </body> </html> """ conf = self._sys_ctrl.read_wpa_supplicant_conf(self._wpa_supplicant_conf_path) self.write(html % conf) self.set_status(200) def post(self): conf = self.get_body_argument('config') self._sys_ctrl.write_wpa_supplicant_conf(self._wpa_supplicant_conf_path, conf.replace('\r', '').replace('"', '\\"')) self._sys_ctrl.auto_start_off() self._sys_ctrl.reboot() self.redirect('/', permanent=True) def make_app(sys_ctrl, wpa_supplicant_conf_path): return tornado.web.Application([ (r'/', MainHandler, { 'sys_ctrl': sys_ctrl, 'wpa_supplicant_conf_path': wpa_supplicant_conf_path }) ]) if __name__ == '__main__': try: port = int(os.getenv('PORT', '8080')) sys_ctrl = SystemControl() app = make_app(sys_ctrl, '/etc/wpa_supplicant/wpa_supplicant.conf') app.listen(port, '0.0.0.0') tornado.ioloop.IOLoop.instance().start() except KeyboardInterrupt: pass
やってることは/
にアクセスがあったらwpa_supplicant.conf
の中身を書きだしたtextareaのあるフォームを表示する。フォームのPOSTでwpasupplicant.conf
を上書きして設定アプリケーションの自動起動停止、OSを再起動するだけ。ちゃんとAPI作る気力が今回はなかったので超ざっくりに。
これを/usr/local/lib/pi-wifi-admin
ディレクトリを作って設置し、venvでtornadoを入れて使えるようにしておく。
アクセスポイント化とWebサーバの起動をまとめてサービス化
環境準備で用意したhostapdやisc-dhcp-serverの起動と設定用Webサーバを起動するスクリプトを次の通り用意して/usr/local/sbin/pi-wifi-admin
として設置する。
毎回起動されても困るので、環境変数のAUTO_START
を見て完全に起動を行うかを判定している。
#!/bin/bash AUTO_START=${AUTO_START:-OFF} WIFI_IFNAME=${WIFI_IFNAME:-wlan0} IP_ADDR=${IP_ADDR:-192.168.100.1/24} APP_HOME_DIR=/usr/local/lib/pi-wifi-admin if [ "$AUTO_START" == "OFF" ]; then echo "auto start disable" exit 0 fi systemctl stop wpa_supplicant systemctl stop dhcpcd hostapd -B -P /tmp/hostapd.pid /etc/hostapd/hostapd.conf ip addr flush dev $WIFI_IFNAME ip addr replace $IP_ADDR dev $WIFI_IFNAME systemctl start isc-dhcp-server $APP_HOME_DIR/venv/bin/python $APP_HOME_DIR/app.py &
使用する環境変数は/etc/default/pi-wifi-admin
に設定として置いておく。
AUTO_START=ON WIFI_IFNAME=wlan0 IP_ADDR=192.168.100.1/24
これらをsystemd用に次の/etc/systemd/wifi-pi-admin.service
として設定を用意してサービス化する。
[Unit] Description = PI Wifi Admin After=network.target [Service] ExecStart = /usr/local/sbin/pi-wifi-admin EnvironmentFile = /etc/default/pi-wifi-admin Type = oneshot RemainAfterExit = yes [Install] WantedBy = multi-user.target
あとはサービスを有効化して終わり。
$ sudo systemctl enable pi-wifi-admin.service
使い方
PCでRasPI-AP
にWifi接続して、ブラウザでhttp://192.168.100.1:8080を開く。
wpa_supplicant.conf
の設定フォーマットで設定してSaveを押す。
レスポンスは返る前にOSが再起動してしまうのでタイムアウトすると思う。
課題
スマホでWifi接続したら、外部に通信送れないせいか「このWifi駄目だ」と判断されて自動で切り替わってしまった。PCでももしかしたらそういうことあるかも。
ファイル直接触るのやめてちゃんとしたAPIで設定できるようにしたい。
設定保存した後にOSを再起動せずに変更を反映できるようにしたい。
Webプログラマが業務でIoTデバイス開発をして思ったこと
自分は本来Webプログラマのつもりなのだけれど、会社のやんごとなき事情により、今年はRaspberry PIやESP32を使ったいわゆるIoTデバイスアプリの開発をすることが多かった(あと、Kotlin使ってAndroidモバイルアプリ開発もやった)。
もともと、時々調査手伝ったり、プロトタイプ的なデバイスアプリ作ったりはしていたので、全く未経験ってわけではなかったけれど、自分一人で全部やって納品まで持っていったのは初めてだった。
少ないハードウェアリソースの中で専門的にやっている人たちに比べれば、自分のしたことはママゴトみたいなものなのだろうけど、開発を通して知ったこと・気づいたことがあったので記録として残しておく。
知ったこと・気づいたこと
ボード上で直接コードを変更しない
Raspberry PIやBeagle BoneでPython使ってアプリ作る時の話。ボードのOSがLinuxなので書こうと思えばSSHで入って直接書くことはできる。特にGPIOとかSPIとかI2Cみたいなボードの入出力使う必要がある場合、PCだとできない動作確認をする都合もあって、ついついソースコードを直接編集したくなる。
でも、ボードの方で編集しているとPCの方に持ってくるのを忘れてたり、うっかりボードのOS固有のファイルパスとかをハードコーディングしてしまって移植性が落ちるみたいなことになるので、「PCでコード書いて、ボードに転送して動作確認」を徹底したほうが良い。
自分はlsyncdを非デーモンモードで動かしてファイル変更したら自動転送して作っていた。
PythonのPyPIモジュールはAPTで入れない
GPIO触るやつとか、OpenCVまわりとか、APTで直接OSに入れられるものもあるけど、requirements.txtに載っていない依存になったりして後々混乱するはめになるので、pipでインストールするほうが良い。最近はpiwheelsでも配布されていたりして、意外と大丈夫だったりする。
requirements.txtに載ってないけど、コード上はimportしているみたいなのはOSへのインストール忘れてたりして危なっかしくなる。
デバイスOSの環境セットアップはスクリプトにしておく
Python3のvenvであったりPyPIモジュールが依存するOSの共有ライブラリなどのインストール、ちょっとした環境設定などはシェルスクリプトでセットアップスクリプトにしておくと良い。
ただし、どんなケースでも動くセットアップスクリプトを目指すと大変になるので、実行できる設定ドキュメントぐらいのノリで書いておくと良い。
shやbashで書くのが辛くなったら、いっそのことpython3で書いてしまえばいい。標準ライブラリの範囲内であればOSのプリインストール範囲内で結構できる。
国際化設定はちゃんと設定しておく
言語設定、タイムゾーン設定、キーボードレイアウト設定はデフォルトではen_GB環境になっているので、ちゃんと設定変えておいたほうが良い(en_GBな環境が対象ならばそのままでいいけど)。Wifi使うならWifiの地域設定も同様。後からキーボード繋いでちょっと調査...みたいなときに日本語キーボード繋いだらレイアウトあってなくて大混乱とかマジ大変。
いちいちraspi-configで設定するのが面倒ならば一通り設定するスクリプトを作っておけばいい。
raspi-configのコード見ればどのファイル編集すればいいか・コマンド叩けばいいかは判断できる。
OS(Raspbian)のリリース情報はこまめにチェックする
Raspbian、結構な頻度でバージョンアップするので公式のリリースはチェックして、チェンジログとか見ておいたほうが良い。rootじゃなくても実行できる権限増えてたり、ハードウェア乱数生成ツールがプリインストール対象にされていたりするし、OverlayFS利用したファイルシステムの読み込み専用化みたいな「コレもう標準でできるんじゃん」が結構出てきたりする。
ボード自体のアップデート(Raspberry PI 4のリリースとか)があると、旧バージョンのディストリだともう動かないとか出てくるので、最新リリースのディストリ(現在はBuster)と一つ前の(現在はStretch)は抑えて置くくらいの感覚でいたほうが良い。
ただし、ディストリの新バージョンが出た直後だとまだちょっと安定してない...?みたいな動作をする時があるので、先は見すえつつ採用は慎重にみたいな感じ。
ネットで見つかる環境設定方法などは鮮度に注意する
デバイス開発に限った話じゃないけれど。
たとえばRTCの設定方法とか、「それもうdtoverlayでできるよ」みたいなのが見つかったり、自分でコマンド実装して叩いているやつがサービスとしてすでに入っていたりするので、ネットで見つけた情報は参考にしつつも公式の情報にないか調べてみると良い。
雑な見分け方として/etc/rc.localに実行コマンド足して何かしている系は、より良いやり方見つけられることが多い。
udevやsystemdなど活用してOSでできることはOSでやる
USBカメラをつないだら/dev/cameraシンボリックリンクができるようにudevルールを設定しておいて、/dev/video[0-9]+を直接探さなくても良いようにするとか、アプリケーションが動く前に確実にNTP同期が取れているようにsystemdの依存関係をうまく組み合わせて動かすとか。
アプリケーションに何でも仕事を持たせすぎずにOSでできることはOSに任せたほうが良い。
時計はマジで重要
HTTPSの接続とかWifiの証明書つかった認証とか、システムクロックがあっていないと途端にうまくいかなくなることがたくさんあるので、時刻があっていることを重視したほうが良い。
NTP頼みならば同期が確実に取れるまで待機させるとか、可能であればRTCの搭載を積極的に行うとか。とにかく時計は大事。
工夫すればテストは意外と書ける
以前はデバイスアプリの開発はどうしてもハードウェアへの依存が多くなるので、単体テストとか書きにくそうだなーと思っていた。 ただ、Webアプリケーションを使う場合でも、テストしにくいもの(データベースとか、ネットワーク越しの外部APIとか)を使うことになるモジュールをテストしやすくする方法はあるので、結局同じことかなーと思って工夫してみた。
UARTを使うpyserialや、SPI用のspidev、I2C用のsmbus-cffiとか、ビジネスロジックの中でなるべく直接使わずに、薄くラップしてインターフェイスを意識して利用することで分離し、ビジネスロジックをハードウェアの異存なしにテストできるようにした。
簡単な例で書くと説明しにくいのだけれど、例えば次のようにAppクラスのコンストラクタでシリアルコネクションを生成するとテストし難いコードになる。
import serial class App(object): def __init__(self, serial_port): self._serial_conn = serial.Serial(serial_port, 115200, timeout=1) # その他初期化処理... def run(self): b = self._serial_conn.read(self._serial_conn.in_waiting or 1) if b: # 読み取りバッファを使った何らかの処理...
そこで、コンストラクタではシリアルコネクションを受け取って使うようにして、依存を外に追い出す。これだけでシリアルコネクションをモックに差し替えてテスト可能になる(実際はpyserialそのまま使わずに、もっと抽象化したものを使う)。
import serial class App(object): def __init__(self, serial_conn): self._serial_conn = serial_conn # その他の初期化処理... def run(self): b = self._serial_conn.read(self._serial_conn.in_waiting or 1) if b: # 読み取りバッファを使った何らかの処理...
この辺りはRobert C.Martin先生のClean CodeとかClean Architectureとか読んだことが活かせた気がする(そういえばハードウェアを抽象化した話もあったような気がする)。
Pythonの場合だけではなくて、ESP32用にC++で書いていた時もAuniterでテストするにあたって同じようにやれた。
SDカードからイメージを作るときはサイズに注意
同じ8GBや16GBのSDカードだからといって、実際の細かいサイズまで同じとは限らない(メーカーの違いとかで変わる)。 8GBのSDカードに焼いたOSをddコマンドで取り出して、別のSDカードに焼こうとして容量不足で書けないとかザラにある。
SDカードの容量いっぱいまで使わないサイズにパーティションを意識して作成して、ddで取り出す時もcountオプションでパーティションの終わりまで切って取り出したほうが良い。
モバイルバッテリーで動かすときは消費電力に注意が必要
電源取れない場所で使うデバイスでモバイルバッテリー使うことになってわかったけど、一般のモバイルバッテリーにはオートパワーオフ機能がついていて、一定以上の電力消費が無いと供給をやめてしまう機能が付いている。
で、困ったことにこの基準となる消費電力のスペックがあまり公開されていない(Ankerの場合、日本法人のWebサイトにはなく、本家のWebサイトに載っていた)ので、満たせるかどうか綱渡り感がある。
とくにESP32は消費電力を抑えようとしてディープスリープ入れたりすると供給止められちゃうので要注意。
ESP32のディープスリープ時の時計は当てにならない
ESP32のディープスリープで起床時間を指定して眠りに入る機能があるけど、ディープスリープ時の時計が結構ずれやすく、長く寝させれば寝させるほど予定の時間に起きなかったりする。
しょうが無いのでRTCのDS3231を積んで、DS3231に付いているアラーム機能でWake upの信号を入れるようにして正しく起床させた。やはり時計は重要。
Arduinoの開発するならVSCodeがおすすめ
ESP32の開発はArduino IDEでできるんだけど、VS CodeにArduino用拡張当てて使うほうが断然開発効率が良い。 シリアルでの入力だけはArduino IDEのほうがよい。
Arch Wikiは役にたつ
何かと行き着くArch Wiki。困った時に読むと情報載ってたりして役に立つ。 Raspberry PI用のページもあって非常に良い。
まとめ
他にもあった気がするけど、ざっとこんな感じ。
本業の方のWebプログラミングはさっぱり進歩がなくて、会社の他のメンバーのほうがやってたんじゃないかなーくらいな状態で、正直なところ焦ってる。将来を考えた上で非常にまずい感じなので、今後は「デバイス開発も素人程度にはできるWebプログラマ」になれるように努めていきたい。
raspi-configのAdvanced OptionsでOverlay FSを使ってみた
きっかけ
先日、メモリの設定を変えようとraspi-configを起動して、Advanced Options
を開いたらメニューにOverlay FS
が増えていることに気がついた。
説明にEnable/Disable read-only file system
とあるので、これって/
パーティションを読み込み専用にするやつじゃね?と思い、素のRaspbian OSイメージをSDカードに焼いて試してみた。
試したイメージはRaspbian Buster Lite 2019-09-26でAPTでパッケージを最新に上げたもの。
Overlay FS の有効化
Overlay FS
を選択すると、有効にするか聞かれるので、Yes
を選択する。
すると、initramfsの生成が行われ、完了メッセージが表示される。
次に/boot
パーティションを書き込み保護するか聞かれる。Yes
を選択した場合は/boot
パーティションが読み込み専用になり、No
にした場合/boot
パーティションはそのままとなる。
とりあえずNo
を選択して実行すると、/boot
の扱いについて表示される。
このあとraspi-configを終了すると再起動を促されるので、OSを再起動してログインし直す。
ログイン後、mount状態を見ると次のようになる。
$ mount sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) proc on /proc type proc (rw,relatime) udev on /dev type devtmpfs (rw,nosuid,relatime,size=465128k,nr_inodes=116282,mode=755) devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000) tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime,size=94832k,mode=755) overlay on / type overlay (rw,noatime,lowerdir=/lower,upperdir=/upper/data,workdir=/upper/work) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev) tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k) tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755) cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd) cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices) cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer) systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct) mqueue on /dev/mqueue type mqueue (rw,relatime) sunrpc on /run/rpc_pipefs type rpc_pipefs (rw,relatime) debugfs on /sys/kernel/debug type debugfs (rw,relatime) configfs on /sys/kernel/config type configfs (rw,relatime) /dev/mmcblk0p1 on /boot type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro) tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=94828k,mode=700,uid=1000,gid=1000)
overlay on / type overlay (rw,noatime,lowerdir=/lower,upperdir=/upper/data,workdir=/upper/work)
とあり、/
パーティションがoverlayfsで保護されているのがわかる。
この時、/boot/cmdline.txt
は次のようになっている。
boot=overlay console=serial0,115200 console=tty1 root=PARTUUID=6c586e13-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
先頭にboot=overlay
が追加されている。また、/boot/config.txt
の末尾には次のようにinitramfs initrd.img-4.19.75-v7+-overlay
が追加されている。
[all] #dtoverlay=vc4-fkms-v3d initramfs initrd.img-4.19.75-v7+-overlay
Overlay FS の無効化
raspi-configのAdvanced Options
でOverlay FS
を選択し、disableにすればよい。再起動後にマウントの状態は次のように戻る。
$ mount /dev/mmcblk0p2 on / type ext4 (rw,noatime) devtmpfs on /dev type devtmpfs (rw,relatime,size=469544k,nr_inodes=117386,mode=755) sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) proc on /proc type proc (rw,relatime) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev) devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000) tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755) tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k) tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755) cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer) cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls) cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=33,pgrp=1,timeout=0,minproto=5,maxproto=5,direct) sunrpc on /run/rpc_pipefs type rpc_pipefs (rw,relatime) mqueue on /dev/mqueue type mqueue (rw,relatime) debugfs on /sys/kernel/debug type debugfs (rw,relatime) configfs on /sys/kernel/config type configfs (rw,relatime) /dev/mmcblk0p1 on /boot type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro) tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=94828k,mode=700,uid=1000,gid=1000)
/boot
パーティションの書き込み保護
Overlay FS
を有効化するときに/boot
パーティションの書き込み保護も有効にすると、再起動後にマウント状態は次のようになる。
$ mount sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) proc on /proc type proc (rw,relatime) udev on /dev type devtmpfs (rw,nosuid,relatime,size=465128k,nr_inodes=116282,mode=755) devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000) tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime,size=94832k,mode=755) overlay on / type overlay (rw,noatime,lowerdir=/lower,upperdir=/upper/data,workdir=/upper/work) tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev) tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k) tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755) cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls) cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices) sunrpc on /run/rpc_pipefs type rpc_pipefs (rw,relatime) debugfs on /sys/kernel/debug type debugfs (rw,relatime) systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=35,pgrp=1,timeout=0,minproto=5,maxproto=5,direct) mqueue on /dev/mqueue type mqueue (rw,relatime) configfs on /sys/kernel/config type configfs (rw,relatime) /dev/mmcblk0p1 on /boot type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro) tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=94828k,mode=700,uid=1000,gid=1000)
/dev/mmcblk0p1 on /boot type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)
となっており、/boot
パーティションが読み込み専用(ro
)になっていることがわかる。
これは/etc/fstab
の設定によって行っているようで、/etc/fstab
を見ると次のようになっている。
$ cat /etc/fstab proc /proc proc defaults 0 0 PARTUUID=6c586e13-01 /boot vfat defaults,ro 0 2 PARTUUID=6c586e13-02 / ext4 defaults,noatime 0 1 # a swapfile is not a swap partition, no line here # use dphys-swapfile swap[on|off] for that
PARTUUID=6c586e13-01 /boot vfat defaults,ro 0 2
とあり、ro
が設定されていることがわかる。
ちなみに/boot
の書き込み保護も有効にすると、Overlay FS
の解除はraspi-configの1度の実行だけでは駄目で、2度行う必要がある。1度目の解除実行時に下のようなメッセージがでる。
想像だけど、一度目の解除実行では/boot
のマウントを書き込み可で再マウントして、/boot/cmdline.txt
や/boot/config.txt
を修正してOverlay FS
を無効化するが、/etc/fstab
はOverlay FS
の解除後でないと変更できないからとかじゃないかと思う。
気になること
initramfsのファイル名をinitrd.img-4.19.75-v7+-overlay
という感じで-overlay
付きで作っているけど、カーネルバージョンアップデート時の自動的なinitramfsの作り直しに対応できているのかな?というのはちょっと気になったりもする。
まとめ
Raspberry PIでなにも対策せずに動かしているとSDカード壊れるのは昔からの課題だったので、公式にOverlay FS
で読み込み専用にできるようになったのは素直に嬉しい。
/
は読み込み専用にしつつ特定のディレクトリは書き込み可にしておきたいということで、パーティションを分割する需要は今後も増えそう。
Raspberry PIでRTC(DS3231)を使った
Raspberry PIでなんやかんややっていると、HTTPSやWifiの証明書あり認証とかで時計の重要性に気がつくことが多い。
で、NTPで確実に同期取れるの待つsystemd用サービスとか作って対処したりしていたけど、やっぱリアルタイムクロックあったほうが良いよなーと思ったので使い方を覚えてみることにした。
使ったRTCモジュール
Amazonでも売ってたこれを使った。モジュールはDS3231。I2Cで接続するもの。
試したOS
Raspbian Buster Liteの2019-09-26バージョンでパッケージを最新まで上げたやつ。
設定方法
まずraspi-configでI2Cを有効にする。
続いて、fake-hwclockを無効化する。
$ sudo systemctl disable fake-hwclock $ sudo apt-get -y remove fake-hwclock
RTCへの時刻設定とRTCからシステムクロックへの反映については、ざっと調べた感じudevの/lib/udev/rules.d/85-hwclock.rules
が次のようになっていて、rtcデバイスを見つけたら/lib/udev/hwclock-set
を実行してくれるみたい。
# Set the System Time from the Hardware Clock and set the kernel's timezone # value to the local timezone when the kernel clock module is loaded. KERNEL=="rtc0", RUN+="/lib/udev/hwclock-set $root/$name"
ただし、/lib/udev/hwclock-set
はデフォルトだと/run/systemd/system
を見つけて処理せずに中断してしまう様子。
#!/bin/sh # Reset the System Clock to UTC if the hardware clock from which it # was copied by the kernel was in localtime. dev=$1 if [ -e /run/systemd/system ] ; then exit 0 fi if [ -e /run/udev/hwclock-set ]; then exit 0 fi if [ -f /etc/default/rcS ] ; then . /etc/default/rcS fi # These defaults are user-overridable in /etc/default/hwclock BADYEAR=no HWCLOCKACCESS=yes HWCLOCKPARS= HCTOSYS_DEVICE=rtc0 if [ -f /etc/default/hwclock ] ; then . /etc/default/hwclock fi if [ yes = "$BADYEAR" ] ; then /sbin/hwclock --rtc=$dev --systz --badyear /sbin/hwclock --rtc=$dev --hctosys --badyear else /sbin/hwclock --rtc=$dev --systz /sbin/hwclock --rtc=$dev --hctosys fi # Note 'touch' may not be available in initramfs > /run/udev/hwclock-set
なので次のように/run/systemd/system
のチェックをコメントアウトした。
#if [ -e /run/systemd/system ] ; then # exit 0 #fi
で、あとはRTCをOS起動時にI2Cで有効化するだけなのだけど、検索してみると/etc/rc.local
やsystemdなどでコマンドを実行するやり方を見かける。
例えば/etc/rc.local
で行う場合は次みたいなやつ。
#!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. echo ds3231 0x68 > /sys/class/i2c-adapter/i2c-1/new_device # Print the IP address _IP=$(hostname -I) || true if [ "$_IP" ]; then printf "My IP address is %s\n" "$_IP" fi exit 0
echo ds3231 0x68 > /sys/class/i2c-adapter/i2c-1/new_device
を追加して、DS3231をRTCとして認識させるわけだけど、これでやるとログが次のようになる。
Dec 7 16:48:03 raspberrypi systemd-modules-load[169]: Inserted module 'i2c_dev' Dec 7 16:48:03 raspberrypi systemd[1]: Starting Flush Journal to Persistent Storage... Dec 7 16:48:03 raspberrypi systemd[1]: Started Create Static Device Nodes in /dev. Dec 7 16:48:03 raspberrypi systemd[1]: Starting udev Kernel Device Manager... Dec 7 16:48:03 raspberrypi systemd[1]: Started Set the console keyboard layout. Dec 7 16:48:03 raspberrypi systemd[1]: Started Flush Journal to Persistent Storage. Dec 7 16:48:03 raspberrypi systemd[1]: Reached target Local File Systems (Pre). Dec 7 16:48:03 raspberrypi systemd[1]: Started udev Coldplug all Devices. ****** 長いのでちょっと省略 ****** Dec 7 16:48:11 raspberrypi systemd[1]: Starting /etc/rc.local Compatibility... Dec 7 16:48:11 raspberrypi systemd[1]: Condition check resulted in fast remote file copy program daemon being skipped. Dec 7 16:48:11 raspberrypi kernel: [ 40.700114] i2c i2c-1: new_device: Instantiated device ds3231 at 0x68 Dec 7 16:48:11 raspberrypi systemd[1]: Starting Permit User Sessions... Dec 7 16:48:11 raspberrypi kernel: [ 40.838077] rtc-ds1307 1-0068: registered as rtc0 Dec 7 16:48:11 raspberrypi systemd[1]: Starting OpenBSD Secure Shell server... Dec 7 16:48:11 raspberrypi systemd[1]: Started /etc/rc.local Compatibility. Dec 7 16:48:12 raspberrypi systemd[1]: Started Permit User Sessions. Dec 7 16:48:12 raspberrypi systemd[1]: Started Serial Getty on ttyAMA0. Dec 7 16:48:12 raspberrypi systemd[1]: Started Getty on tty1. Dec 7 16:48:12 raspberrypi systemd[1]: Reached target Login Prompts. Dec 7 22:20:06 raspberrypi systemd[1]: Starting Daily apt download activities... Dec 7 22:20:06 raspberrypi systemd[1]: Started OpenBSD Secure Shell server. Dec 7 22:20:06 raspberrypi avahi-daemon[304]: Server startup complete. Host name is raspberrypi-3.local. Local service cookie is 410738562.
OSが起動してから、ディスクに保存されていた最終日時でログが記録されていき、/etc/rc.local
が実行されてからようやく正しい日時で記録されるようになる。なので、/etc/rc.local
が動く前の日時は当てにならない。
そこで、/boot/overlays/README
に次のようにRTCの例が書いてあるので、これに従って設定する。今回はds1307をds3231に置き換えるだけで良い。
Using Overlays ============== Overlays are loaded using the "dtoverlay" config.txt setting. As an example, consider I2C Real Time Clock drivers. In the pre-DT world these would be loaded by writing a magic string comprising a device identifier and an I2C address to a special file in /sys/class/i2c-adapter, having first loaded the driver for the I2C interface and the RTC device - something like this: modprobe i2c-bcm2835 modprobe rtc-ds1307 echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device With DT enabled, this becomes a line in config.txt: dtoverlay=i2c-rtc,ds1307 This causes the file /boot/overlays/i2c-rtc.dtbo to be loaded and a "node" describing the DS1307 I2C device to be added to the Device Tree for the Pi. By default it usees address 0x68, but this can be modified with an additional DT parameter: dtoverlay=i2c-rtc,ds1307,addr=0x68 Parameters usually have default values, although certain parameters are mandatory. See the list of overlays below for a description of the parameters and their defaults.
/boot/config.txt
にdtoverlay=i2c-rtc,ds3231
を追加して保存する。
これでOSを起動すると、ログは次のようになる。OSの起動の頭からRTCが効いて正確に日時が扱えているのがわかる。
Dec 7 22:25:47 raspberrypi systemd-modules-load[157]: Inserted module 'i2c_dev' Dec 7 22:25:47 raspberrypi systemd[1]: Starting Flush Journal to Persistent Storage... Dec 7 22:25:47 raspberrypi systemd[1]: Started Apply Kernel Variables. Dec 7 22:25:47 raspberrypi systemd[1]: Started Flush Journal to Persistent Storage. Dec 7 22:25:47 raspberrypi systemd[1]: Started Create Static Device Nodes in /dev. Dec 7 22:25:47 raspberrypi systemd[1]: Starting udev Kernel Device Manager... Dec 7 22:25:47 raspberrypi systemd[1]: Started Set the console keyboard layout. Dec 7 22:25:47 raspberrypi systemd[1]: Reached target Local File Systems (Pre). Dec 7 22:25:47 raspberrypi systemd[1]: Started udev Coldplug all Devices. Dec 7 22:25:47 raspberrypi systemd[1]: Starting Helper to synchronize boot up for ifupdown... Dec 7 22:25:47 raspberrypi systemd[1]: Started Helper to synchronize boot up for ifupdown. Dec 7 22:25:47 raspberrypi systemd[1]: Started udev Kernel Device Manager. ****** あとは省略 ******
/boot/overlays/README
にはI2CのRTCについて次のように記述されている。ここに載っているモジュールであれば同様のやり方で設定できると思う。
Name: i2c-rtc Info: Adds support for a number of I2C Real Time Clock devices Load: dtoverlay=i2c-rtc,<param>=<val> Params: abx80x Select one of the ABx80x family: AB0801, AB0803, AB0804, AB0805, AB1801, AB1803, AB1804, AB1805 ds1307 Select the DS1307 device ds1339 Select the DS1339 device ds3231 Select the DS3231 device m41t62 Select the M41T62 device mcp7940x Select the MCP7940x device mcp7941x Select the MCP7941x device pcf2127 Select the PCF2127 device pcf2129 Select the PCF2129 device pcf8523 Select the PCF8523 device pcf8563 Select the PCF8563 device rv3028 Select the Micro Crystal RV3028 device addr Sets the address for the RTC. Note that the device must be configured to use the specified address. trickle-diode-type Diode type for trickle charge - "standard" or "schottky" (ABx80x only) trickle-resistor-ohms Resistor value for trickle charge (DS1339, ABx80x, RV3028) wakeup-source Specify that the RTC can be used as a wakeup source backup-switchover-mode Backup power supply switch mode. Must be 0 for off or 1 for Vdd < VBackup (RV3028 only)