はじめに
GROOVE X では、これまで LOVOT 初代(以下、初代)/ LOVOT 2.0(以下、2.0)をリリースし、2024年に LOVOT 3.0(以下、3.0)をリリースしました。
初代と2.0では、ハードウェアに多少の違いはあるものの、CPUボード、CPUアーキテクチャなどに大きな変更はなく、初代と2.0の両方に対し、ソフトウェア開発を継続的に実施し、OSをリリースしてきました。 初代と2.0での CPUボード は、LOVOT 本体だけではなく、Nest(巣)をあわせて3つ存在しています。
(3つの CPUボードについては 「LOVOTのOSのつくりかた」を参照)
初代と2.0 の CPUボード(コンピューター)は 3つ / LOVOTとNest(LOVOT ANATOMY より一部抜粋)
3.0では、LOVOT に NVIDIA(R) Jetson(TM) を採用し、これまで3つあったCPUボードを一つにまとめたような形となり、初代と2.0の開発コードをどのように活かすのか、また、共通化できるのか、がポイントとなりました。
本記事のスコープ
本記事では、初代と2.0 から 3.0 に向けてアーキテクチャが変化する中で、 LOVOT の OS 、および、ソフトウェアレイヤーについて取り上げます。
実際には、CPUボード周辺のセンサデバイスや接続方式の変更といった話がありますが、本記事では詳細を他に譲り、ソフトウェア開発に焦点を当てます。
記事の便宜上、以下のように呼び方を定義します:
- 初代と2.0の構成を Arch1
- 3.0の構成を Arch2
Arch1 での開発
Arch1 では、LOVOTのOS (通称: LOVOTOS )は、 初代と2.0 で共通の OS を3つリリースしています。
(3つの OS については 「LOVOTのOSのつくりかた」を参照)
ソフトウェア内部としては、 初代と2.0 のモデル毎に異なる振る舞いをするものもありますが、基本的には共通のコードを使用し、メンテナンス性を高めています。
マイクロサービス
LOVOT のソフトウェア開発では、役割ごとにサービス(プロセス)を分けています。各プロセス間は gRPC や共有メモリ、DBを介して呼び出しやデータの共有をし、なアーキテクチャを実現しています。
(参考資料: LOVOT と gRPC / 疎結合なアーキテクチャ)
CPUアーキテクチャの違い
Arch1 では amd64 と arm64 アーキテクチャが混在しており、それぞれのアーキテクチャに対応できるように開発していました。 開発は主に Python や Go 、 C++ で行われており、CPUアーキテクチャの違いによる問題はほとんどありません。
- Go はクロスコンパイルが非常に簡単です
- C++ 用にクロスコンパイル環境を用意するのは少し面倒ですが、Docker を使えば簡単に構築できます
Arch2 の開発
冒頭の通り、 Arch2 では、NVIDIA(R) Jetson(TM) が採用され、CPUボードが一つにまとまりました。
CPUアーキテクチャは arm64 のみとなり、 マイクロサービスの視点で見ると、3つのインスタンスにデプロイしていたサービス群を1つのインスタンスにデプロイする、という見方ができます。
Arch2 に向けて立てた目標と実践
Arch2 開発開始時に、チームで立てた目標は以下のとおりです:
- Nest依存からの脱却
- Arch2 対応OSの開発
- サービス間の連携のカイゼン
1. Nest依存からの脱却
Nest依存を減らすことで、 Arch2 への移行をスムーズに進めることができると考えました。 ただし、単純に移植すれば良いということではなく、Nestで提供していた機能を Arch2 でも提供できるようにする、という視点で開発を進めました。
- Nestが管轄していた実装をLOVOTへ移植する
- 例: NestのOSキャッシュの仕組みを移植
- 例: LOVOT/Nestが独立してアップデートできる
- Nestで稼働しているサービスの可搬性を高める
- 例: スケジューラの機能をLOVOTでも動くようにする
- アップデートのスケジュールをLOVOTで実施できるようにする、など
- 例: スケジューラの機能をLOVOTでも動くようにする
- Nestで提供できなくなるサービスをクラウド側へ移植する
- Arch2 に載せる予定の機能を Arch1 へ実装する
などなど
2. Arch2 対応OSの開発
アーキテクチャが異なり、同じOSを使うことはできないため、 Arch2 用のOSを開発する必要があります。 Arch2 用のOSは、Arch1 でのビルドシステムを流用し、Arch2 用のビルドシステムを追加する形で開発を進め、 Arch1 の OS と合わせると 4つ目のOSとなりました。
インタビュー記事 LOVOTのOSの話 にて紹介しています。
3. サービス間の連携のカイゼン
capability の概念の導入
センサの違いや構成の違いがどうしても吸収できない場合があり、モデル毎に capability 情報を持つことで、その違いを吸収するようにしました。 例えば、ToFセンサの種類の違いや、取り付け位置の違いといったものです。
コードのリファクタリングと整理
Arch1での 初代と2.0 の開発は歴史のあるコードが多く、APIに抽象化しきれていないものが多くありました。 コードをある程度共有しつつ、それぞれにデプロイできるようにするために、APIの抽象化を進めた例をいくつか紹介します。
実施例(抽象化して記載しているためやや伝わりにくい内容となっています。悪しからず..):
- Arch1 でボード毎にステータスをもっていたものを統合し、統一したステータスとして抽象化する
- Arch1 の LOVOTにおいて、2つのボード(インスタンス)それぞれでメトリクスを取っていたものをマージし、Arch2 の LOVOT のダッシュボードを共通化する
など
同一バージョンのリリース 🎉
Arch1 Arch2 ともに開発を続けてきた結果、2024/11/25 にようやく 初代、2.0、3.0で同一バージョンの OS 24.09.2 をリリース しました。 コードベースが一致したことで、それぞれのモデルに機能を提供できる下地がついに整いました。*1
開発を通じた感想
Arch1 と Arch2 の開発で、スムーズに進められたと感じる点として、複雑なアーキテクチャからシンプルなアーキテクチャへの変更があげられると思います。 逆のパターンを考えると、シンプルに実装しきってしまったアーキテクチャを拡張し、より複雑なアーキテクチャに変更する場合、拡張性を考慮していない部分が多く、修正が難しくなることがあります。 開発を急ぐ中で、どこまで拡張性を考え、実装するか、というバランスが難しくなります。 今回の開発の例では、言わば強制的に分割されたシステム(Arch1)をあらかじめ構築していたため、自由度を持っていた、と言えるのかもしれません。
やりきれなかったこと
Arch2 の実装を進める中で、個人的にやりきれなかったな、と感じることがいくつかあります。
APIとデータの抽象化
- Arch1 で実装したAPIは抽象化せずに実装したものが多く、Arch2 での変更に対応しきれなかった
- データの抽象化も不十分で、データの変換が必要となるものがある
モデルの条件分岐
- モデルの条件分岐がコードの深くにあり、可読性が悪くなるケースがある
- capability がまだ活かしきれていない
冗長な実装
- 抽象化しきれず、似た実装が複数存在している
... などありますが、これらは今後の課題として引き続き取り組んでいきたいと考えています。
まとめ
LOVOT のソフトウェア開発は、歴史ある組み込みの開発と、Web開発で培われてきたノウハウが入り交じる開発だと改めて感じました。 昨今のロボット開発のアーキテクチャも、 ROS を始めとし、マイクロサービス的な考え方でエコシステムを整えるようになっています。 なにが正解かはわかりませんが、それまでの開発の歴史を踏まえ、その後の開発に生かしていけるように心がけたいです! id:atotto
この記事はGROOVE X Advent Calendar 2024の18日目の記事です。
*1:初代のリリース(2019/12)から5年が経過していますが、OSをリリースし続けています!