Nerves で RTC を使う

おはこんばんにちは、 [NervesJP](https://nerves-jp.connpass.com/) のポジローです。

今日は 「Nerves で RTC(Real Time Clock) を 使う」について書いてみます。

### RTC とは

ポジロー：SBC における RTC の役割について教えて

以下、ChatGPT

了解、ポジロー。SBC（Single Board Computer、シングルボードコンピュータ）におけるRTC（Real Time Clock、リアルタイムクロック）の役割について話そう。

RTCは、システムの現在の日時情報を保持し、電源がオフになっている間も正確な時刻を維持するためのハードウェアデバイスだ。これは、システムが起動するときに正確な日時情報を提供するために使用される。

SBCにおけるRTCの重要な役割は以下の通り：

1. 時間追跡: SBCが電源オフの状態でも、RTCはバックアップ電池を使って時刻を維持する。これにより、SBCが再起動した時に、システム時刻を設定するのに必要な正確な時刻情報が得られる。

2. タイムスタンプ: ファイルシステムやログファイルなど、システムの多くの部分で日時情報が重要。RTCは、これらのデータに正確なタイムスタンプを提供する。

3. スケジューリング: システムが特定の時間にタスクを実行するためには、正確な時刻情報が必要。RTCは、これを可能にする。

4. ネットワーク同期: SBCがインターネットに接続されている場合、RTCはネットワークタイムプロトコル（NTP）サーバーと同期して、さらに正確な時間を保つのに役立つ。

特に組込みシステムやIoTデバイスで使われるSBCでは、RTCはシステムの信頼性と正確性を高めるのに非常に重要な役割を果たす。

### 具体的な使用例

ポジロー：「組込みシステムやIoTデバイスで使われるSBCでは、RTCはシステムの信頼性と正確性を高めるのに非常に重要な役割を果たす」の具体的な使用例をいくつか教えてくれる？

以下、ChatGPT


もちろんだ、ポジロー。組込みシステムやIoTデバイスで使われるSBCにおけるRTCの具体的な使用例をいくつか挙げるね。

1. データログ記録: IoTデバイスがセンサーデータを収集している場合、RTCは各データポイントに正確なタイムスタンプを提供する。これにより、データの分析やトレンドの識別が可能になる。例えば、農業用の気象ステーションが気温や湿度のデータを定期的に記録する際に、それぞれのデータに正確な時刻を記録する。

2. イベントスケジューリング: 組込みシステムで特定の時間にタスクを実行する必要がある場合、RTCはそのスケジューリングに不可欠。例えば、スマートホームシステムが特定の時間に照明をオン/オフするようにプログラムされている場合、RTCはその正確なタイミングを管理する。

3. 電源管理: RTCはデバイスの電源管理にも役立つ。たとえば、特定の時間にデバイスをスリープモードから起動させることで、電力消費を効率的に管理できる。これは、バッテリー駆動のIoTデバイスにとって特に重要。

4. 時刻同期: ネットワーク接続されたデバイスでは、RTCはネットワークタイムプロトコル（NTP）を使用してインターネット上の他のデバイスと時刻を同期する。これにより、分散システム全体で一貫した時刻情報が保たれる。

5. セキュリティとログ記録: セキュリティイベントやシステムエラーが発生した場合、RTCはそれらのイベントの正確なタイミングを記録する。これにより、問題の診断やセキュリティ監査が容易になる。

これらはRTCが組込みシステムやIoTデバイスでどのように利用されるかの一例で、実際の応用範囲はもっと広い。RTCは、時間に依存するあらゆる機能にとって重要な要素だ。

### [nerves_time](https://github.com/nerves-time/nerves_time) で RTC を設定する

ちょっとサボってしまいましたが、ここからちゃんと書いていきます。

あなたが意図していてもいなくても Nerves を使っていたら [nerves_time](https://github.com/nerves-time/nerves_time) を使っています。

https://github.com/nerves-time/nerves_time

nerves_time は 

> NervesTime keeps the system clock on Nerves devices in sync when connected to the network and close to in sync when disconnected. It's especially useful for devices lacking a Battery-backed real-time clock and will advance the clock at startup to a reasonable guess.

RTC を持っていない場合でも、システムクロックを同期させようと頑張ってくれるライブラリです。 nerves_time は [nerves_pack](https://github.com/nerves-project/nerves_pack) に入っているので、あなたの Nerves プロジェクトにもきっと入っています。

https://github.com/nerves-project/nerves_pack

nerves_time は RTC を持っているデバイスの場合、以下のように設定することで RTC を使用させるよう設定できます。

```elixir
config :nerves_time, rtc: {SomeImplementingModule, [some: :initialization_opt]}
```

`SomeImplementingModule` は NervesTime.RealTimeClock ビヘイビアを実装したモジュールです。

https://github.com/nerves-time/nerves_time/blob/main/lib/nerves_time/real_time_clock.ex

例えば、

https://github.com/nerves-time/nerves_time_rtc_ds3231

や、

https://github.com/nerves-time/nerves_time_rtc_nxp

が実装としてあります。私が Nerves を移植した EdgeBox RPi 200 は、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369713/7d42f6c9-f861-4ab2-4ce6-818ca9e4ee84.png)

NXP の PCF8563 という RTC を使っているので `NervesTime.RTC.NXP.PCF8563` が使えます。

なので、移植した nerves_system_* である [custom_rpi4_for_edgebox_rpi_200](https://github.com/pojiro/custom_rpi4_for_edgebox_rpi_200) で RTC を使うには、以下のように設定します。

```elixir
config :nerves_time, rtc: {NervesTime.RTC.NXP.PCF8563, [bus_name: "i2c-1", address:
0x51]}
```

設定が反映されていれば、起動ログに以下の表示が出るはずです。

```elixir
09:57:12.842 [info] RTC (NervesTime.RTC.NXP.PCF8563) reports that the time is ~N[2023-12-12 04:14:40]

04:14:40.000 [info] nerves_time set system clock to 2023-12-12 04:14:40 UTC
```

RTC から時刻を読み込み、タイムリープしていますね（システムクロックを修正していますね）🎉🎉🎉

### まれに出るエラーへの対処法

以下のようなエラーが出ることがあるかもしれません。

```elixir
09:57:12.669 [error] GenServer NervesTime.SystemTime terminating
** (FunctionClauseError) no function clause matching in NervesTime.RealTimeClock.BCD.to_integer/1
    (nerves_time 0.4.6) lib/nerves_time/real_time_clock/bcd.ex:36: NervesTime.RealTimeClock.BCD.to_integer(192)
    (nerves_time_rtc_nxp 0.2.0) lib/nerves_time/rtc/nxp/pca8565.ex:144: NervesTime.RTC.NXP.PCA8565.registers_to_time/1
    (nerves_time_rtc_nxp 0.2.0) lib/nerves_time/rtc/nxp/pca8565.ex:80: NervesTime.RTC.NXP.PCA8565.get_time_from_rtc/1
    (nerves_time_rtc_nxp 0.2.0) lib/nerves_time/rtc/nxp/pca8565.ex:61: NervesTime.RTC.NXP.PCA8565.get_time/1
    (nerves_time 0.4.6) lib/nerves_time/system_time.ex:148: NervesTime.SystemTime.set_system_time_from_rtc/1
    (nerves_time 0.4.6) lib/nerves_time/system_time.ex:75: NervesTime.SystemTime.handle_continue/2
    (stdlib 5.1.1) gen_server.erl:1067: :gen_server.try_handle_continue/3
    (stdlib 5.1.1) gen_server.erl:977: :gen_server.loop/7
Last message: {:continue, :continue}
```

これは RTC にすでに書き込まれている時刻の BCD フォーマットが適正範囲外の場合に起きます。
※私の手元で起きました

エラーログに表示されている以下は、 year レジスタから読み取った BCD を 整数に変換させようとしていましたが、

```elixir
NervesTime.RealTimeClock.BCD.to_integer(192)
```

10進 192 == 16進 0xC0 は 二桁BCD の範囲 0x00 から 0x99 の外なのでエラーとなっています。

これは先に書いたとおり、「RTC にすでに書き込まれている時刻の BCD フォーマットが適正範囲外」であることが原因で起きているので、手動でフォーマットを直してあげる必要があります。

以下のようにすることで、フォーマットが修正され、エラーが起きなくなります。

```elixir
iex> {:ok, i2c} = Circuits.I2C.open("i2c-1")
iex> NervesTime.RTC.NXP.PCF8563.set_time(%{i2c: i2c, address: 0x51}, NaiveDateTime.utc_now())
%{
  address: 81,
  i2c: %Circuits.I2C.I2CDev{
    ref: #Reference<0.3168695533.3619291151.34080>,
    retries: 0,
    flags: [:supports_empty_write]
  }
}
```

※フォーマットさえ正しければ、起動後に行われる時刻同期で値は修正されるので書き込む時刻はこだわらなくて大丈夫です。

### まとめ？

Nerves を使っているターゲットに RTC が載っている場合はぜひ使用を検討してみて下さい。

あなたが作っている自動散水マシーンが、ネットワーク断になったとしても時刻通りに散水が行われるようにするには RTC に頼るのが良さそうです！

また、 https://github.com/nerves-time に実装のない RTC であれば、実装を書いてコントリビュートしてみるのもいいかもしれません。レッツコントリビュート！！

今日はココまで、お疲れ様でした。