TECH PLAY

アプトポッド

アプトポッド の技術ブログ

252

モビリティを少しの手間で遠隔制御できるようにしたいと思ったことがあるみなさんに話しかけています。 こんにちは。ソリューションアーキテクトの伊勢です。 以前、当ブログで「intdash CONTROL CENTER」をご紹介しました。 tech.aptpod.co.jp tech.aptpod.co.jp intdash CONTROL CENTERはモビリティ管制制御システム向けソリューションです。 今回は適用例として、遠隔地からコマンドを送信するビジュアルパーツなどのコンポーネントを使って市販のロボットを制御してみます。 その前に おことわり これはあくまで"やってみた"記事です。 intdash CONTROL CENTERは鋭意開発中のソリューションです。 正式なインストール手順などは公式ドキュメントをご参照ください。 本題 気軽に見ていただけるように8分ほどの動画にしてみました。どうぞご覧ください。 www.youtube.com ロボットやコンポーネントの説明を飛ばしたい方は 5:30ぐらいから どうぞ。 まとめ 今回はシンプルなモビリティの例としてロボットトイを取りあげました。 同じ仕組みにより、ビル内の巡回ロボットや工場内の搬送ロボット、公道を走るデリバリーロボットなどに適用が可能です。 intdash CONTROL CENTERは、他にも自律運転車、建設機械、ドローンなどのインテグレーションも想定しています。 www.aptpod.co.jp intdash CONTROL CENTERは、今回ご紹介した遠隔指示コマンドパーツをはじめとして、管制制御向けのコンポーネントを複数備えています。 また、より高度なフリート管理のため、モビリティへのタスク配分やルート設定などの機能も追加していきます。また別の機会でご紹介したいと思います。
こんにちは。コーポレートマーケティング室の白崎です。 aptpodのマーケティング全般を担当しています。 2023年1月25日(水)〜1月27日(金)に東京ビッグサイトで開催された第7回ロボデックスに出展し、モビリティ/ロボット群管理・遠隔制御ソリューションフレームワーク「 intdash CONTROL CENTER (以下、CONTROL CENTER)」を展示しました。 連日、多くの方々にご来場いただき、感謝申し上げます。 大盛況のアプトポッドブース 今回の記事では、ご来場いただいた方はもちろん、ご来場できなかった方もいらっしゃると思いますので、改めて展示内容についてご紹介させていただきます。 CONTROL CETNERとは? CONTROL CENTERの構成 CONTROL CENTERが実現する機能 展示内容 おわりに CONTROL CETNERとは? CONTROL CENTER はアプトポッドが2022年12月にベータリリースした、モビリティやロボット向けのリアルタイムかつインタラクティブな管制制御システムを迅速に構築するためのソリューションフレームワークです。 本ソリューションを活用頂くことで、ロボットや自動車、建機・重機といった複数のモビリティを対象に、遠隔からモビリティ群を監視・管理することができ、必要に応じて人の手による遠隔指示・操作の介入を行うことができるようになります。 CONTROL CENTER適用シナリオ例 CONTROL CENTERの構成 CONTROL CENTERはモビリティ/ロボットなどの機体との送受信処理を行うエッジソフトウェアコンポーネントとデータストリーミング中継処理やデータ保存を行うサーバ・ソフトウェア、及びリアルタイムな群管理や個別機体の状態モニタを行うことができるWebアプリケーションコンポーネントで構成されます。 CONTROL CENTERが実現する機能 主な機能を6点ご紹介します。 ①リアルタイムなフリートマップマッピング  機体群の位置情報、映像などをマップマッピングし、Webベースの統合監視環境 ② 機体群の管理UI  機体群のステータス一覧や、SoC・稼働率の管理 ③ 個別機体の様々なリアルタイムデータ確認が可能なダッシュボード  リアルタイムな機体詳細データをダッシュボード表示 ④ 遠隔操作、操縦  遠隔制御コントローラー向けのコンポーネントを提供 ⑤ ロボット、モビリティ、センサーからカメラまで様々なデバイスの接続  様々なデバイスが接続するための接続コンポーネントを提供 ⑥データの収集と活用  運用時のすべてのデータを過去データとしてサーバに永続化 ※より詳しい機能の説明、デモ動画は下記のテックブログ記事をご覧ください。 tech.aptpod.co.jp 展示内容 ロボデックスではCONTROL CENTERを利用した、2つのユースケースにおけるデモを紹介しました。1つ目はロボット群のフリート管理、2つ目はロボットの遠隔操作です。 ①ロボット群フリート管理 CONTROL CENTERのリファレンスアプリケーションであるフリートマップアプリケーションを使用して、各機体の位置や状態をリアルタイムに可視化することができます。 各ロボットの詳細なデータを確認したい場合には、フリートマップアプリケーションからデータ可視化ダッシュボード( Visual M2M Data Visualizer )に遷移して、機体データの詳細をダッシュボード上で確認することができます。 フリートマップアプリケーション上に複数のロボットの位置情報を表示(画面下部には選択しているロボットの名称や状態、カメラ映像を表示) 遷移先のデータ可視化ダッシュボード画面 モビリティ毎の状態(ステータス)、速度、稼働率などの稼働状況や、バッテリー稼働のモビリティのSoC(State Of Charge)やSoH(State of Health)など、モビリティ管理に必要な詳細なデータを遠隔から監視できるようになります。 ダッシュボード上のVisual Partsを変更することで、対象となるデータの値を表示するだけでなく、時間変化に伴う稼働率状況の変化などをグラフ表示することもできます。また、ノンプログラミングでユーザによる自由なダッシュボードの構成変更が可能です。 6台のロボットそれぞれの詳細データ(速度,SoC,SoHなど)をダッシュボード上に表示 稼働率状況の変化などをグラフ表示 ②ロボットの遠隔操作 オープンソースの移動ロボットプラットフォームである「Turtlebot3」(実機のロボット)を使用した遠隔操作のデモです。ロボットに搭載されたカメラ、センサ情報をリアルタイムにVisual M2M Data Visualizerで監視を行いながら、コントローラを使って遠隔操作を行うことが可能です。 ロボデックスでは会場である東京ビッグサイトから監視を行いながら、コントローラを使って弊社の四谷オフィスにあるTurtlebot3を遠隔操作しました 弊社の四谷オフィスにあるTurtlebot3を遠隔操作 弊社四谷オフィスで遠隔操作されたTurtlebot3 実際にお客様がコントローラで遠隔操作を試すことができるデモとなっており、「スムーズに遠隔操作できて驚いた」等の感想をいただき好評でした! おわりに 今回はモビリティやロボットのリアルタイムかつインタラクティブな管制制御システムを迅速に構築するためのソリューションフレームワークであるCONTROL CENTERを紹介しました。 ロボット管制・管理、遠隔操縦、フリート管理などに課題をお持ちの方は、下記リンクより是非お気軽にお問合せください。 aptpodお問合せフォーム 今後も積極的に展示会への出展を予定しており、決定次第、 コーポレートサイト 等で告知します。 様々なデモをご覧いただける機会ですので、ぜひお立ち寄りください! アプトポッドは豊富な実績と高い技術力で、これからもお客様の課題解決に貢献していきます。
intdashは、2022年11月からプロジェクト別のアクセス権限管理機能の提供を開始しました。 これにより、ユーザーが他のユーザー・エッジ・計測を見る権限をコントロールできるようになっています。 手順を確認してみましょう。 こんにちは。ソリューションアーキテクトの伊勢です。 以前のintdashでは、1ドメイン内の全ユーザーがすべてのユーザー、エッジ、計測(以下、リソース)を参照できました。 企業や組織の利用シーンでは、部署やチーム間で見えるリソースを制限したいケースがあります。 その仕組みを提供するのがプロジェクト機能です。 今回はその紹介の前編です。 まだすべてのアプリケーションが対応できているわけではないため *1 、どのような考え方で参照をコントロールするのかを中心に、画面を見ながら機能を確認していきます。 プロジェクト機能 その前に おことわり ドメイン、ユーザー、エッジとは 今までと何が変わったのか 今までとの互換性 確認ポイント 確認用の構成 確認 ユーザー・エッジの作成 ユーザー・エッジの参照 ユーザー・エッジの参照:Global Projectの場合 グループ・プロジェクトの作成 ユーザー・エッジの割り当て ユーザー・エッジの参照:project1、project2の場合 計測 計測の参照:Global Projectの場合 計測の参照:project1の場合 プロジェクト・サブグループへのメンバー追加 プロジェクトへのメンバー追加 サブグループへのメンバー追加 ロールの追加 ロールの追加:プロジェクト ロールの追加:サブグループ 利用中の権限削除 利用中の権限削除:画面表示中のユーザー 利用中の権限削除:ストリーム中のエッジ マルチテナントとの違い まとめ その前に おことわり これはあくまで"やってみた"記事です。 正式な手順はユーザーガイドやデベロッパーガイドをご参照ください。 ドメイン、ユーザー、エッジとは ユーザーガイド によると以下のとおりです。 intdashサーバーを中心とする、1つのintdash使用環境をドメイン(またはintdashドメイン)と呼びます。 intdashサーバーに接続できるのは、そのドメインで発行されたアカウントを持つユーザーとエッジ(データを送受信するデバイス)のみです。 ドメイン、ユーザー、エッジ エッジは、デバイス1台を指します。 ユーザーは、intdashにアクセスする利用者です。1人のユーザーが複数のエッジを所有することができます。 ユーザーには権限としてロールを割り当てます。 他のユーザーを作成できる管理者には admin ロール、一般ユーザーには member ロールを割り当てます。 今までと何が変わったのか プロジェクト機能が導入される前は、1ドメイン内の全ユーザーがそのドメインにあるすべてのリソースを参照できました。 最新のintdashでは、リソースを プロジェクト という単位で管理し、プロジェクトごとに、アクセス可能なユーザーやエッジを設定するこができます。これにより、他部署や他チームからリソースを参照/削除できないようにすることができます。 プロジェクト機能 今までとの互換性 アクセス権を細かく管理する必要がない場合も、今までどおり利用できるように互換性を設けています。 それがGlobal Projectというデフォルトのプロジェクトです。 最新のintdashでは、ユーザー、エッジを作成すると自動的にGlobal Projectに登録されます。 これにより、Global Projectを通じて1ドメイン内のすべてのリソースを参照できるようになっています。 *2 Global Project 確認ポイント 以下では、段階的にプロジェクトやユーザーの設定を行いながら、各ユーザーからのリソースの見え方を確認していきます。 ユーザー・エッジの参照 計測の参照 プロジェクト・サブグループへのメンバー追加 ロールの追加 利用中の権限削除 マルチテナントとの違い 確認用の構成 はじめに設定の全体構成を掲載しておきます。 ユーザーとエッジ グループとプロジェクト プロジェクト機能の導入により、管理リソースは3階層に分かれることになりました。 グローバルレベル:プロジェクトに関わらず共通 ユーザー エッジ グループ プロジェクト グループレベル:複数のプロジェクトに共通 メンバー割り当て プロジェクトレベル:1プロジェクトに固有 メンバー割り当て エッジ割り当て 計測 確認 では上記のリソースを作成しながら、確認していきましょう。 ユーザー・エッジの作成 まずはユーザーとエッジを作成します。 ユーザとエッジの構成はこのようになります。 ユーザーとエッジ グループとプロジェクトはこうなります。 グループとプロジェクト(Global Project) 既存の管理者ユーザー( *3 )を user1 として手順を始めます。 ユーザーガイド 9. (管理者向け)ユーザーとエッジを管理する - Admin Console にしたがい、 member ロールを持つ一般ユーザ user2 と user3 を作成します。 ユーザーの作成 作成したユーザーをユーザー一覧で確認できます。 ユーザー一覧 次にエッジを作成するため、 user2 でサインインしなおします。 ちなみに admin ロールを持っていない user2 はAdmin Consoleを表示できません。 一般ユーザーのAdmin Console ユーザーガイド 4.4. エッジアカウントを作成する にしたがい、My Pageでエッジ edge1 と edge2 を作成します。エッジの所有者は user2 になります。 エッジの作成(My Page) user3 でサインインしなおし、同じく edge3 と edge4 を作成します。エッジの所有者は user3 になります。 続いて user1 でサインインしなおし、所有者なしエッジを作成します。ユーザーガイド 9.9. エッジアカウントを作成する(管理者向け) にしたがい、Admin Consoleでエッジ edge5 を作成します。 エッジの作成(Admin Console) 作成したエッジをエッジ一覧で確認できます。 エッジ一覧 ユーザー・エッジの参照 まず、ユーザとエッジがGlobal Projectでどう見えるか確認します。 ユーザー・エッジの参照:Global Projectの場合 作成したユーザーのいずれかでProject Consoleを開くと Projects に Global Project のみが表示されています。 Project Console Global Project を選択し、メニューのEdges、MembersでGlobal Projectの管理リソースを確認します。 さきほど作成したユーザーがGlobal Projectのメンバーとして自動的に割り当てられています。 メンバーにはユーザーの member ロールが引き継がれています。 Global ProjectのMembers また、さきほど作成したすべてのエッジがGlobal Projectに自動的に割り当てられており、参照できます。 Global ProjectのEdges このように、Global Projectではすべてのユーザーからすべてのリソースを参照できました。 グループ・プロジェクトの作成 では、いよいよプロジェクトを作成していきましょう。 Global Projectとは別にプロジェクトを作成します。このような構成を作成します。 グループとプロジェクトの作成 まずは user1 でサインインし、Admin Consoleで group1 グループを作成します。 グループの作成 続けて group1 の配下に project1 を作成します。 プロジェクトの作成 同じく、 group1 の配下にプロジェクト project2 を作成します。 Project Consoleで group1 を選択すると Projects でプロジェクトの一覧を確認できます。 Projects なお、Admin Consoleのグループ一覧に表示されている Global Group は、Global Projectが属する特別なグループです。削除や新たなプロジェクト作成、メンバー割り当てはできません。 Global Group ユーザー・エッジの割り当て 作成したグループとプロジェクトにユーザーとエッジを割り当てます。 ユーザーとエッジの割り当て user1 でサインインし、Project Consoleでグループ group1 に user2 をメンバーとして追加します。 Admin Consoleでユーザーを作成したときのロールとは関係なく、プロジェクトごとにロールを付与できます。 今回は member ロールを付与します。 group1のメンバー一覧 続いて、エッジを割り当てます。 まずは、エッジの所有者として、エッジの割り当てを行ってみます。 user2 でサインインし、所有エッジ edge1 を project1 に登録します。 エッジの割り当て 次に、プロジェクトの管理者としてエッジの割り当てを行ってみます。所有者なしのエッジを登録するにはそのプロジェクトの admin ロールが必要なため、 user1 としてサインインし、所有者なしエッジ edge5 を登録します。 project1のEdges 同じく project2 にもエッジを割り当てます。 edge3 は user1 としてサインインした状態で登録します。 ユーザー・エッジの参照:project1、project2の場合 では、ユーザーごとに参照できる範囲を確認します。 まず、 user2 が参照できる範囲です。 Project Consoleで user2 が所属するプロジェクト project1 でMembersを確認します。 所属する user1 と user2 を参照できます。所属しない user3 は表示されません。 なお、 user2 は上位グループ group1 のメンバーであるため、ロールが継承されて Inherited Member として表示されます。プロジェクトを作成した user1 は Owner と表示されます。 project1のMembers Edgesで先ほど登録したエッジ edge1 、 edge5 を確認できます。 次に、作成したプロジェクトに属していない user3 でも確認してみます。 Project Consoleの Projects を見ると、 project1 、 project2 が表示されておらず、プロジェクトのリソースを確認することができません。 割り当てられてないとプロジェクトは見えない 割り当てられていないプロジェクトのURLに直接アクセスしても、リソースは表示されません。 URL直指定(自分が割り当てられていないプロジェクト) 以上の通り、プロジェクト機能により、プロジェクトに所属するユーザー・エッジがプロジェクト外から参照できないことを確認できました。 なお、当記事では比較のため、Global Projectにリソースを登録したままにしています。実際の運用ではGlobal Projectからユーザー・エッジの割り当てを削除してください。 計測 続いて、エッジによる計測の作成を確認してみます。 まずはGlobal Project向けにUpstreamします。 エッジをセットアップします。今回はMJPEGとStringを送信します。 *4 edge1 ではMJPEG、 edge3 と edge5 ではStringをUpstreamします。 計測の参照:Global Projectの場合 まずはGlobal Projectでリアルタイム再生を確認します。 Agentで接続先のプロジェクトUUIDとしてGlobal Projectを指定します。 intdash Edge Agent 2 では intdash-agentctl config で、 project_uuid として 00000000-0000-0000-0000-000000000000 を指定します。 intdash Edge Agent では設定ファイルの project_uuid に空を設定します。 Terminal System では 接続先と認証 で プロジェクトUUID に空を設定します。 intdash Edge Agent 2での接続先プロジェクトの指定(Global Project) Upstreamを開始します。 user2 でサインインしてGlobal ProjectでData Visualizerで確認します。すべてのUpstreamデータが確認できます。 Data Visualizer(Global Project) 計測の参照:project1の場合 続いて、各エッジの接続先を project1 に変更してUpstreamします。 AgentのプロジェクトUUID設定を変更します。 プロジェクトUUIDの取得 intdash Edge Agent 2での接続先プロジェクトの指定(project1) 変更した設定でUpstreamを開始します。 なお、 edge3 は project1 に所属していないため、Agentを起動すると認証エラーとなります。 プロジェクトに所属していないエッジは認証エラー Project Consoleで project1 を選択し、さきほどと同じスクリーンを表示します。 project1 に属するエッジ edge1 と edge5 のUpstreamデータだけが表示されています。 edge3 は project1 には所属していないため、パネルにエッジ名が NO EDGE で表示されます。 Data Visualizer(project1) エッジの接続先プロジェクトを変更したため、Global ProjectのスクリーンにはUpstreamデータが表示されません。 Data Visualizer(Global Project)(データはproject1に送信されているためGlobal Projectでは参照できない) 同じく、Agentの接続先を project2 に変更して project2 のData Visualizerを確認すると edge1 と edge3 のUpstreamデータのみが表示されます。 edge5 はAgent起動時に接続エラーとなります。 Data Visualizer(project2) Data Visualizer(project1)(データはproject2に送信されているためproject1では参照できない) プロジェクト機能により、プロジェクト外のユーザーから計測を参照できないこと、プロジェクト外のエッジからプロジェクトに計測を作成できないことを確認できました。 ここまでで、ユーザー・エッジ・計測の参照制御というプロジェクト機能の基本的な効果が確認できました。 プロジェクト・サブグループへのメンバー追加 では、次に少し使い方をレベルアップしていきたいと思います。 ここまでは、プロジェクトのメンバーは、プロジェクトの上位のグループで設定されたロールを継承していました。 運用上は、細かい範囲に限ってメンバーやロールを追加したいケースがあります。 まずはメンバー追加について確認します。 プロジェクトに直接メンバーを追加する方法、上位グループの下にサブグループを作成してメンバーを追加する方法をご紹介します。 user1 としてサインインした状態で、 group2 配下のグループ・プロジェクトを作成し、エッジを割り当てます。 プロジェクト・サブグループへのメンバ追加準備 サブグループの作成 Subgroups 新たに作成した2つのプロジェクトに2通りの方法でメンバーを追加します。 プロジェクト・サブグループへのメンバー追加 プロジェクトへのメンバー追加 まず、プロジェクトにメンバーを追加します。 user3 を project3 に追加します。(操作は user1 として行ってください。) プロジェクトへのメンバー追加 user3 でサインインするとProject Consoleで project3 を参照できます。 プロジェクトに追加されたメンバー プロジェクトに追加されたメンバーから見えるエッジ サブグループへのメンバー追加 続いて、サブグループにメンバーを追加します。 user3 を group2-1 に追加します。(操作は user1 として行ってください。) サブグループへのメンバー追加 user3 でサインインするとProject Consoleで subgroup2-1 、 project4 を参照できます。 サブグループに追加されたメンバー サブグループに追加されたメンバーから見えるエッジ ロールの追加 次に追加したメンバーにロールを追加してみます。 ロールの追加:プロジェクト まずはプロジェクトに追加したメンバーにロールを追加します。 user3 に、 project3 における admin ロールを追加します。(操作は user1 として行ってください。) プロジェクトでのロール追加 プロジェクトでadminロール追加 user3 は project3 でメンバーを追加・削除できるようになったため、Project ConsoleのMembersに Add Member ボタンが表示されます。 user3は、このプロジェクトにおいてadminロールを追加された ロールの追加:サブグループ 続いて、サブグループに追加したメンバーにロールを追加します。 user3 に、 group2-1 における admin ロールを追加します。(操作は user1 として行ってください。) サブグループでのロール追加 サブグループでadminロール追加 user3 は group2-1 でメンバーを追加・削除できるようになったため、 Add Member ボタンが表示されます。 user3は、このサブグループにおいてadminロールを追加された ここまで説明してきた基本的な考え方を応用して、より複雑な権限制御も簡単に実現することができました。 利用中の権限削除 だいぶ長くなってきて読み手の方はお疲れだと思います。 実は書き手も疲れてきているのですが、もう少しだけおつきあいください。 運用中に権限を変更する上でどうしても気になる点を確認したいと思います。 intdashを利用中にユーザーやエッジの権限を削除するとどうなるのでしょうか。 データを送信中の edge1 と、それをリアルタイムにData Visualizerで見ている user2 を、プロジェクトから削除してみます。 利用中のユーザー・エッジの割り当て削除 Data Visualizerでリアルタイム再生します。 再びproject1のData Visualizer 利用中の権限削除:画面表示中のユーザー まず、Data Visualizerを見ているユーザーの削除です。 user1 で group1 からメンバー user2 を削除します。 adminとしてuser2をグループから削除 ユーザーが削除されてもData Visualizerは問題なく表示され続けます。 利用にはすぐに影響が出ないようになっていますね。 削除されたユーザーで参照を継続 利用中の権限削除:ストリーム中のエッジ 続いて、 project1 からエッジ edge1 を削除してみます。 エッジも削除 削除したエッジのUpstreamデータが表示され続けます。 こちらも権限変更が作成中の計測に影響を与えないようになっています。 削除されたエッジの参照を継続 では、削除した権限はいつ反映されるのでしょうか。 ユーザーは次の画面アクセスです。 Data Visualizerの画面をリロードすると、メッセージが表示されて project1 にアクセスできなくなったことがわかります。 403 Forbidden エッジは次の計測開始です。 Upstreamを一度停止し、再度開始すると認証エラーで接続できなくなったことがわかります。 "Unauthorized" いずれも新たな接続でアクセスが拒否されることがわかりました。 マルチテナントとの違い プロジェクト機能と混同しやすい機能としてマルチテナントがあります。 最後にマルチテナントとの違いを説明します。 マルチテナントは複数ドメインを同時利用する場合に、インフラ・プラットフォームを共有して運用コストを低減するオプションです。 主に、intdashを使ったサービスの提供者が複数のお客様に環境を提供する場合を想定しています。 お客様ごとに環境を設けるよりも低コスト化されます。 なお、テナントの管理リソースはグローバルレベルにおいても完全に分離されており、他のテナントからは参照・変更できません。 マルチテナント 一方、プロジェクト機能はここまで見てきたように、1ドメインの中での権限制御機能です。 グローバルレベルの管理リソースはプロジェクト間で共有されます。 また、マルチテナントのドメインでプロジェクト機能を利用する場合は、1テナントの中での権限を制御することになります。 マルチテナントでプロジェクト機能 まとめ お疲れ様でした。これにて前半はおしまいです。 プロジェクト機能により、組織でintdashを利用するときの柔軟な権限制御ができるようになったことがわかりました。 マルチテナントもそうですが、プロジェクト機能はintdashをより大規模にご利用いただくことを想定して追加された機能です。 お客様のビジネスの発展・拡大に貢献すべく、aptpodのプロダクトは進化を繰り返していきます。 後編では、プロジェクト機能によるData Visualizer以外のアプリケーションの変更をご紹介したいと考えています。 *1 : アプリケーションのEdge Finder、Meas Hub、Media Explorerは現時点でGlobal Projectの計測のみ対応しています。今後のアップデートで対応予定です。 *2 : 自動的にGlobal Projectに登録するかどうかはドメインの設定によって異なります。 この設定はサーバー側の構築・運用担当者によって変更が可能です。 設定の詳細については、現在ドキュメントを準備中です。公開までお待ちください。 *3 : 通常、システム管理者から利用者に提供されます。 *4 : セットアップ手順は割愛します。 intdash Edge Agent 2 デベロッパーガイド をご参照ください。
2022年12月に intdash Edge Agent 2 デベロッパーガイド が公開されました。 これまでの intdash Edge Agent (以降、Agent 1)と設定手順を比較してみましょう。 はじめまして。ソリューションアーキテクトの伊勢です。 普段はお客様への提案を担当していて開発作業は行わないので、intdash Edge Agent 2(以降、Agent 2)にまだ直接触れられていません。どうやら、Agent 1の課題だった機能群の関係性のわかりやすさが改善されたことにより、設定方法が変わっているようです。 今回はAgent 2のご紹介がてら、具体的に設定作業がどう変わったのか確認していきたいと思います。 docs.intdash.jp 目次 目次 その前に おことわり Agent 2とは Agent 1と何が変わったのか 確認ポイント 確認用の構成 比較 Upstream:Agent導入とデバイスコネクター設定 Upstream:Agent導入とデバイスコネクター設定 - Agent 1 Upstream:Agent導入とデバイスコネクター設定 - Agent 2 Upstream:フィルタリング設定 Upstream:フィルタリング設定 - Agent 1 Upstream:フィルタリング設定 - Agent 2 Downstreamのセットアップ 遠隔指示:Agent 1 → Agent 1 遠隔指示:Agent 2 → Agent 2 遠隔指示:Agent 2 → Agent 1 遠隔指示:Agent 1 → Agent 2 まとめ Appendix:設定情報 コンテナ agent1up /root/manager.conf /root/logger.conf.mjpeg コンテナ agent2up intdash-agentd config show /etc/dc_conf/jpeg-up.yml /etc/dc_conf/h264-up.yml コンテナ agent1down /root/manager.conf コンテナ agent2down intdash-agentd config show その前に おことわり これはあくまで"やってみた"記事です。 正式な手順は各デベロッパーガイドをご参照ください。 Agent 2とは デベロッパーガイド によれば、以下のとおりです。 intdash Edge Agent 2は、intdashサーバーとの間で時系列データの送受信を行うエージェントソフトウェアです。 エッジデバイス(Linux PC)にインストールして使用します。 intdash Edge Agent 2 エッジデバイスは、カメラやセンサから取得したデータをデバイスコネクター経由でAgentプロセスに流し込みます。 AgentはサーバーのAPIとストリームを張って、データをサーバーにUpstreamします。 また、エッジデバイスへの制御信号をサーバーからDownstreamしてデバイスコネクターに渡します。 Agent 1と何が変わったのか デバイスコネクターとのインタフェースはこれまでの通り、名前付きパイプである FIFOファイル です。 また、サーバーとの通信は iSCP v1 の後継であるiSCP v2を採用していますが、Agentで隠蔽されるため、利用者が違いを意識することはあまりありません。 なお、実装は開発効率のためにRustとGoを採用しています。 利用者目線で変わった点は機能の再編(分割・追加・名称変更)です。 intdash提供開始から増え続けてきた各種機能の位置付けをゼロベースで見直し、定義しなおしています。 intdash Edge Agent 顕著な変更点はこのようなところです。 設定管理はデーモンが行うようになった 設定情報の形式がJSONからYAMLに変わった(オプションでJSONも指定可能) 設定情報の項目立てが一新した デバイスコネクターをパイプラインとして定義するようになった UpstreamとDownstreamの区別が明確になった 設定周りが多いですね。Agent 1の設定ファイルはAgent 2ではそのまま使えません。 では、どのような設定手順になったのか、Agent 1とAgent 2で比較しながら見ていきましょう。 確認ポイント 典型的な利用ケース、またAgent 1→Agent 2の過渡期にあり得そうなパターンを想定して段階的にセットアップしていきます。 Upstreamのセットアップ Agent導入とデバイスコネクター設定 フィルタリング設定 Downstreamのセットアップ 遠隔指示:Agent 1 → Agent 1 遠隔指示:Agent 2 → Agent 2 遠隔指示:Agent 2 → Agent 1 遠隔指示:Agent 1 → Agent 2 確認用の構成 今回はAgent 1/2とUpstream/Downstreamそれぞれの手順を確認したいため、1台のホストコンピュータに4つのDockerコンテナを立てています。 エッジを4つ用意 比較 Upstream:Agent導入とデバイスコネクター設定 外部デバイスのわかりやすい例としてUSBカメラのUpstreamを設定します。 USBカメラはLogicoolのC920s Upstream:Agent導入とデバイスコネクター設定 - Agent 1 まずはAgent 1での設定方法のおさらいです。 構成はこのようになります。 Agent 1で映像をUpstream Dockerコンテナを作成します。 コンテナでデバイスを利用するため --privileged を指定 intdash Edge Agent デベロッパーガイドの 2.1 インストール を参考にインストールしていきます。 rootユーザーなのでsudoなしで エディタとカメラデバイスユーティリティもインストール Admin Console でエッジを作成します。 UUIDとクライアントシークレットをコピー intdash-edge-manager を起動するときの環境変数が多いので起動用シェルスクリプトを作っておきます。 FQDN・UUID・クライアントシークレットを設定 デフォルト設定で起動すると Edge Finder でUpstreamを確認できます。 Status情報がUpstreamされている 一旦Agentを停止し、カメラ映像のUpstream設定を追加していきます。 ユーティリティでカメラデバイスの設定を確認します。 15FPS・640x480になっている 対応しているカメラなら 、プリインストールされたデバイスコネクター(intdash-edge-logger)でAgentへのデータ連携が可能です。 デベロッパーガイドの 3.2.1 マネージャーの設定ファイル例(manager.conf) を参考にmanager.confとmjpeg設定ファイルを編集します。 mjpeg設定ファイルパス・チャンネル・解像度を設定 再度、起動用シェルスクリプトで起動。 引数の設定ファイルパスを変更 Edge FinderでJPEGのUpsteramが確認できました。 1秒に15枚受信 このあと別のコンテナでカメラデバイスを使うため、Agentを停止しておきます。 Upstream:Agent導入とデバイスコネクター設定 - Agent 2 では、いよいよAgent 2を導入してみます。 構成はこのようになります。 Agent 2で映像をUpstream Dockerコンテナを作成し、 intdash Edge Agent 2 デベロッパーガイド を参考に進めていきます。 Agent 1と同じよう流れ Agent 1と違ってデフォルトの設定ファイルがあるわけではありません。 実際にやって覚えるにはデベロッパーガイドの チュートリアル を進めるのがいいのですが、ここは比較のために映像のアップストリームを設定してみましょう。 なお、Dockerではsystemdでデーモンが自動起動しないため、 デーモンを手動で起動 します。 デーモン(intdash-agentd)を起動 Upstreamを設定します。カメラデバイスは設定レシピ集の 7.2. カメラからV4L2経由で取得したJPEGデータを送信する にそのままの手順があります。これを参考にします。 アップストリーム と デバイスコネクター を設定します。 Agent 1の manager.conf の loggers (mjpeg設定ファイルパス・チャンネル・解像度を設定したセクション)にあたる部分ですね。 iSCP v2にはチャンネル channel という概念はありません。しかしここでは、 互換ルール に沿ってiSCP v2のデータをiSCP v1のチャンネル 1 として扱えるようにするため、データ名称プリフィックス data_name_prefix に v1/1/ を指定します。また、Agent 1と同じくカメラデバイスにあわせて解像度・FPSを設定します。 解像度・FPSは環境変数でデバイスコネクターに渡る 続いてデバイスコネクターの パイプライン を設定します。Agent 1のmjpeg設定ファイルの部分にあたります。 Agent1では /opt/vm2m/sbin/intdash-edge-logger の中に隠蔽されていた、デバイス入力〜フォーマット変換〜FIFO出力の流れを定義します。Agent 2の付属デバイスコネクターの特長としてこのパイプラインの仕組みによりタスクの柔軟な定義が可能になっています。 入力→中間処理→出力 環境変数から解像度・FPSを取得 接続設定 を行います。Agent 1では intdash-edge-manager 起動時に環境変数で指定した部分です。このコンテナ用のUUIDとクライアントシークレットをAdmin Consoleで払い出し、設定します。 FQDN・UUID・クライアントシークレットを設定 最後に ストリーマーを起動 します。 パイプラインに含まれる print-log-filter のログが出力されます。 ログレベル は起動時に設定することができます。 デバイスコネクターへの入力データ量がわかる こちらもEdge Finderで確認 Agent 2の intdash-agentctl コマンドだと設定時にフォーマットエラーに気づくことができるため、セットアップ作業の効率が上がりそうです。 Upstream:フィルタリング設定 送信データ量を間引くフィルタリングの代表例として、一定時間に1データポイントに絞るサンプリングの違いを見てみます。 Agent 1/2ともにデバイスコネクターの出力に対して定義します。 Upstream:フィルタリング設定 - Agent 1 Agent 1では sampling フィルター で設定します。 サンプリング間隔の最初のデータポイントをパス manager.conf の manager.filters セクションを編集します。 1000ミリ秒間に1データポイントに絞る 起動用シェルスクリプトを実行すると間引きされているのを確認できます。 1秒に1枚受信 Upstream:フィルタリング設定 - Agent 2 Agent 2では samplingタイプのフィルター を設定します。 考え方は同じ はじめに、デバイスコネクターからのデータの行先を、 recoverable ではなく src_jpeg に変更します。ただし src_jpeg というストリームは存在しせんので、このあとサンプリングフィルターを経ても行先が src_jpeg のままになっているデータは破棄されることになります。 デバイスコネクターの入力元IDを変更 フィルターを定義します。 src_jpeg に対してサンプリングした結果を recoverable に出力します。 targetがフィルターの入力・change_toが出力 ストリーマーを起動すると間引きされているのを確認できます。 1秒に1枚受信 Agent 2のサンプリングはH.264形式に対応しています。確認してみましょう。 H.264は前フレームとの差分データによる動画圧縮規格のため、サンプリングするとリアルタイム再生はできません。JPEG以外にもサンプリングが効くことを確認するために実施しています。(遅延アップロードにより、全データポイントが回収されたあとは動画として再生が可能です。) まずはカメラデバイスの設定を変更します。FPSは15のままです。 解像度・フォーマットを変更 デバイスコネクターの設定を新たに設定します。 カメラデバイス設定を反映 H.264用のパイプライン設定ファイルを作成します。 MJPEGのときに5つだったエレメントが4つになっています。 id: 2 の h264-split-filter の出力は iscp-v2-compat-msg なので、 iscp-v2-compat-filter が不要なためです。 前処理の出力形式により必要なエレメントは異なる デバイスコネクターにあわせて、フィルターの入力元IDとデータ型名も変更します。 jpegをh264に変更 ストリーマーを起動するとEdge Finderで1秒間に1回の受信を確認できます。 H.264は画面下部がバイト表示 Downstreamのセットアップ ダウンストリームの典型例として、別のエッジの送信データをサーバー経由で受信します。 Agent 2同士のほか、Agent 1/2間での送受信の設定方法も確認してみたいと思います。 遠隔指示:Agent 1 → Agent 1 では、まずこれもAgent 1同士の手順を確認しておきます。 構成はこのようになります。 Agent 1からAgent 1にStringを送信 送信側の設定として先ほど作成したコンテナ agent1up にチャンネルを追加します。 デベロッパーガイド 4.1 独自のデバイスコネクターを使用するための設定 を参考に進めます。 デバイスコネクター起動なし 起動用シェルスクリプトでAgentを起動します。 手っ取り早く送信するため、別ターミナルで同コンテナに入り、FIFOファイルにpython3の struct.pack の結果をリダイレクトします。 python3 -c "import struct, sys, time; s, n = divmod(time.clock_gettime(time.CLOCK_MONOTONIC_RAW), 1); sys.stdout.buffer.write(struct.pack('<BBBBLLBBB7s8s', 1, 26, 0, 0, int(s), int(n * 1_000_000_000), 0x1d, 0, 7, b'command', b'message1'))" > /var/run/intdash/logger_002.tx フォーマットは 6.5 Agent とデバイスコネクターの間で使われる FIFO のデータフォーマット 、の6.5.1 共通ヘッダー、6.5.2 データタイプごとの固有部分に従っています。 タイムスタンプ(Time Sec、Time nano)は基準時刻以前だとAgentで受け付けないため、 time.clock_gettime(time.CLOCK_MONOTONIC_RAW) で現在時刻を取得・計算しています。 Time Sec以降のデータサイズであるLength(第3引数 26 )の計算が結構面倒ですね。 任意のメッセージをFIFOにリダイレクト Edge Finderでサーバーでの受信を確認します。 リダイレクトのたびに受信 次に受信側のエッジを構築します。Dockerでコンテナを作成して、送信側と同様にAgentをインストールします。 受信側では --privileged 不要 エディタもインストール 4.3 FIFO からの読み出し を参考にして manager.conf にDownstream設定を追加します。 "clients" の "type": "control" でこれがDownstreamであることを定義しています。 Downstreamの場合、送信元 EdgeのUUIDの設定が必要です。複数のエッジ(2つまで)を設定できる ctlr_ids が利用できます。 clientsとloggersに設定を追加 Admin Consoleで受信側エッジのUUIDを新たに払い出し、起動用シェルスクリプトを作成しておきます。 FQDN・UUID・クライアントシークレットを設定 送信側・受信側それぞれで起動用シェルスクリプトを起動し、別ターミナルで受信側に入り、FIFOファイルを cat コマンドで監視しておきます。送信側でさきほどのリダイレクトを再度実行します。 cat コマンドの結果で、送信側でリダイレクトしたアスキー文字項目(データID、データ)を確認できます。 od コマンドなどでバイトデータとして確認してもいいでしょう。 送信側のリダイレクトとほぼ同時に出力される 遠隔指示:Agent 2 → Agent 2 では、続いてAgent 2で同様の構成を設定してみます。 構成はこうです。 Agent 2からAgent 2にStringを送信 送信側の設定をコンテナ agent2up に追加します。 デベロッパーガイド 4.1. アップストリームの設定を作成/変更する を参考に進めます。 フィルタリングが適用されているカメラ映像とは別にストリームを新規作成します。また、デバイスコネクター設定も作成しますが、Agent 1と同様にリダイレクトでFIFOファイルに書き込むため、パイプラインは作成しません。 新ストリームとデバイスコネクター設定を作成 Agent 1では送信・受信のFIFO( logger_XXX.tx 、 logger_XXX.rx )をセットで定義していましたが、Agent 2では必要なデバイスコネクター設定に対して1つずつFIFOファイルを設定しています。 intdash-agentctl run でストリーマーを起動します。 別ターミナルでコンテナ agent2up に入り、FIFOファイルにpython3の struct.pack の結果をリダイレクトします。 python3 -c "import struct, sys, time; s, n = divmod(time.clock_gettime(time.CLOCK_MONOTONIC_RAW), 1); sys.stdout.buffer.write(struct.pack('<LLHHL6s7s8s', int(s), int(n * 1_000_000_000), 6, 7, 8, b'string', b'command', b'message1'))" > /var/run/intdash/uplink-command.fifo Agent 2ではフォーマットがAgent 1とは異なります。 5. intdash Edge Agent 2とデバイスコネクターの間で使われるFIFO用データフォーマット を参考にしています。複数項目のデータ長の合計項目がなくなったため、Agent 1よりバイト数指定が楽になりました。 リダイレクトのたびに受信 では、Agent 2でも受信側のエッジを構築します。Dockerでコンテナを作成して、送信側と同様にAgentをインストールします。 このくだりはもう慣れたもの Downstreamとデバイスコネクターを設定していきます。 Downstreamとデバイスコネクター設定 受信側エッジ agent2down をAdmin Consoleで払い出し、接続を設定します。 こちらも慣れたもの /etc/init.d/intdash-agentd start でストリーマーを起動します。 起動直後 送信側でもストリーマーを起動します。別ターミナルで受信側に入り、FIFOファイルを cat コマンドで監視しておきます。 送信側でさきほどのリダイレクトを再度実行します。 送信側のリダイレクトとほぼ同時に出力される Agent 2用のフォーマットで出力されているのがわかります。 遠隔指示:Agent 2 → Agent 1 次はAgent 1/2併用を想定した遠隔指示の設定を確認してみます。 Agent 1/2双方のデータを受け取れるように受信側に設定を追加します。まずはAgent 1から。 どこからどこに送信しているのかこんがらがってきますが構成はこちらです。 Agent 2からAgent 1にStringを送信 Agent 2には 4.3. iSCP v1と互換性を持たせるためのデータ名称 が用意されています。実は本記事はこれに則り、データ名称を v1/<チャンネル> で始めていますので、設定は最低限で済みます。 コンテナ agent1down の manager.conf の ctlr_ids に、コンテナ agent2up のUUIDを追加します。チャンネル/データ名称接頭辞、データID、データタイプは今回あわせてあるため、修正不要です。 送信側のUUIDを追加 コンテナ agent2up のストリーマー、コンテナ agent1down の起動用シェルスクリプトを起動し、 agent1down のFIFOを cat しておきます。 agent2up でFIFOにメッセージをリダイレクトします。 Agent 2から送られたデータが、Agent 1→Agent 1のときと同じく、Agent 1形式で出力されているのがわかります。 Agent 2のメッセージをAgent 1形式で出力 遠隔指示:Agent 1 → Agent 2 最後は、Agent 2の受信側にAgent 1の設定を追加します。 構成はこちらです。 Agent 1からAgent 2にStringを送信 コンテナ agent2down にDownstream設定を追加します。 Downstreamの src_edge_uuid として agent1up のUUIDを追加しています。 filters 配下に複数の送信側エッジを設定可能 コンテナ agent1up の起動用シェルスクリプト、コンテナ agent2down のストリーマーを起動し、 agent2down のFIFOを cat しておきます。 agent1up でFIFOにメッセージをリダイレクトします。 Agent 1のメッセージをAgent 2形式で出力 このように、Agent 2はフォーマットが違うAgent 1とも送受信ができるよう、互換性を備えています。 まとめ 長々とお付き合いありがとうございました。 Agent 1では、Agent起動とストリーム開始、UpstreamとDownstream、それにデバイスコネクター内の各処理が、一緒くたになっていて構築・運用しづらい面がありました。 Agent 2では、これらがきちんと意味合いごとに整理・分離され、個別の管理項目として再定義されたことで構造が理解しやすく、取り回ししやすくなりました。 今回はご紹介しきれませんでしたが、Agent 2には遅延アップロード(Agent 1の再送)などの機能があります。ぜひ実際にお使いいただき、intdashの低遅延性・操作性を体感いただければと思います。 また、Agent以外にもaptpodのプロダクトはブラッシュアップしていきますので、今後もお伝えしていきたいと思っています。 Appendix:設定情報 参考までに最終的な設定情報を掲載します。 Agent 2設定はデベロッパーガイド 12. 設定を書き出す/読み込む の出力結果です。 全体像を再掲 コンテナ agent1up /root/manager.conf { "manager": { "meas_root": "$APPDIR/intdash/meas", "meas_root_volatile": "$RUNDIR/intdash/meas", "rawdir": "$APPDIR/intdash/raw", "basetime": "$RUNDIR/intdash/basetime", "stat": "$RUNDIR/intdash/manager.stat", "logger_stat": "$RUNDIR/intdash/logger_%03hhu.stat", "process_stat": "$RUNDIR/intdash/process.stat", "intdash_stat": "$RUNDIR/intdash/intdash.stat", "network_stat": "$RUNDIR/intdash/network.stat", "system_stat": "$RUNDIR/intdash/system.stat", "wwan_stat": "$RUNDIR/intdash/wwan.stat", "workdirs": [ "$APPDIR/intdash/meas", "$RUNDIR/intdash" ], "filters": [ { "name": "sampling", "channel": "1", "target": "realtime", "setting": [ { "key": "rate", "value": "1000" } ] } ] }, "clients": [ { "protocol": "mod_websocket.v2", "type": "realtime", "my_token": "$TOKEN", "my_id": "$UUID", "my_secret": "$SECRET", "auth_path": "$APPDIR/intdash/.auth", "project_uuid": "", "connection": { "host": "$SERVER", "path": "/api/v1/ws/measurements" }, "fifo_rx": "$RUNDIR/intdash/client_%s.rx", "fifo_tx": "$RUNDIR/intdash/client_%s.tx", "path": "$SBINDIR/intdash-edge-client", "stat": "$RUNDIR/intdash/client_%s.stat", "fast_net_check_cmd":"$BINDIR/intdash-edge-networkd.sh -q -t" }, { "protocol": "mod_http", "type": "resend", "my_token": "$TOKEN", "my_id": "$UUID", "my_secret": "$SECRET", "project_uuid": "", "auth_path": "$APPDIR/intdash/.auth", "connection": { "host": "$SERVER", "path": "/api/v1/measurements" }, "fifo_rx": "$RUNDIR/intdash/client_%s.rx", "fifo_tx": "$RUNDIR/intdash/client_%s.tx", "path": "$SBINDIR/intdash-edge-client", "stat": "$RUNDIR/intdash/client_%s.stat", "fast_net_check_cmd":"$BINDIR/intdash-edge-networkd.sh -q -t" } ], "loggers": [ { "path": "", "connections": [ { "channel": 255, "fifo_rx": "$RUNDIR/intdash/logger_XXX.rx", "fifo_tx": "$RUNDIR/intdash/logger_XXX.tx" } ], "details": { "plugin": "status", "plugin_dir": "$LIBDIR/plugins", "plugin_arg": { "stintd": { "meas_root": "$RUNDIR/intdash/meas" }, "stsys": { "storage_dir": "/" } } } }, { "path": "/opt/vm2m/sbin/intdash-edge-logger", "conf": "/root/logger.conf.mjpeg", "connections": [ { "channel": 1, "fifo_rx": "$RUNDIR/intdash/logger_001.rx", "fifo_tx": "$RUNDIR/intdash/logger_001.tx" } ] }, { "connections": [ { "channel": 2, "fifo_rx": "$RUNDIR/intdash/logger_002.rx", "fifo_tx": "$RUNDIR/intdash/logger_002.tx" } ], "details": { "plugin": "fifo" } } ] } /root/logger.conf.mjpeg { "type": "mjpeg", "data_handler": { "path": "/dev/video0", "baudrate": 15, "camera_width": 640, "camera_height": 480, "camera_hwencodedelay_msec": 100 }, "manager_client": { "tx_path": "/var/run/intdash/logger_001.tx", "rx_path": "/var/run/intdash/logger_001.rx" }, "status": "/var/run/intdash/logger_001.stat", "basetime": "/var/run/intdash/basetime" } コンテナ agent2up intdash-agentd config show connection: server_url: https://xxx.intdash.jp project_uuid: 00000000-0000-0000-0000-000000000000 edge_uuid: 84db287d-d763-4b40-a9a2-2d2824a8b803 client_secret: xxx transport: protocol: websocket upstream: - id: recoverable enabled: true recover: true persist: true qos: unreliable flush_policy: interval flush_interval: 5 - id: recoverable2 enabled: true recover: true persist: true qos: unreliable flush_policy: interval flush_interval: 5 downstream: [] device_connectors_upstream: - id: h264 data_name_prefix: v1/1/ dest_ids: - src_h264 enabled: true format: iscp-v2-compat ipc: type: fifo path: /var/run/intdash/h264.fifo launch: cmd: device-connector-intdash args: - --config - /etc/dc_conf/h264-up.yml environment: - DC_V4L2_SRC_CONF_PATH=/dev/video0 - DC_V4L2_SRC_WIDTH=1920 - DC_V4L2_SRC_HEIGHT=1080 - DC_V4L2_SRC_FPS=15 - DC_PRINT_LOG_FILTER_CONF_TAG=h264 - DC_FILE_SINK_CONF_PATH=/var/run/intdash/h264.fifo - id: command data_name_prefix: v1/2 dest_ids: - recoverable2 enabled: true format: iscp-v2-compat ipc: type: fifo path: /var/run/intdash/uplink-command.fifo device_connectors_downstream: [] filters_upstream: - id: sampling enabled: true type: sampling target: dest_ids: - src_h264 interval_ms: 1000 name: "" src_id: "" type: h264 change_to: dest_id: recoverable filters_downstream: [] deferred_upload: priority: same_as_realtime limit_data_storage: true data_storage_capacity: 102400 /etc/dc_conf/jpeg-up.yml tasks: - id: 1 element: v4l2-src conf: path: $(DC_V4L2_SRC_CONF_PATH) type: jpeg width: $(DC_V4L2_SRC_WIDTH) height: $(DC_V4L2_SRC_HEIGHT) fps: $(DC_V4L2_SRC_FPS) - id: 2 element: jpeg-split-filter from: [ [1] ] - id: 3 element: iscp-v2-compat-filter from: [ [2] ] conf: timestamp: stamp: clock_id: CLOCK_MONOTONIC convert_rule: jpeg - id: 4 element: print-log-filter from: [ [3] ] conf: interval_ms: 10000 tag: $(DC_PRINT_LOG_FILTER_CONF_TAG) output: stderr - id: 5 element: file-sink from: [ [4] ] conf: flush_size: 100 path: $(DC_FILE_SINK_CONF_PATH) /etc/dc_conf/h264-up.yml tasks: - id: 1 element: v4l2-src conf: path: $(DC_V4L2_SRC_CONF_PATH) type: h264 width: $(DC_V4L2_SRC_WIDTH) height: $(DC_V4L2_SRC_HEIGHT) fps: $(DC_V4L2_SRC_FPS) - id: 2 element: h264-split-filter from: [ [1] ] conf: clock_id: CLOCK_MONOTONIC - id: 3 element: print-log-filter from: [ [2] ] conf: interval_ms: 10000 tag: $(DC_PRINT_LOG_FILTER_CONF_TAG) output: stderr - id: 4 element: file-sink from: [ [3] ] conf: flush_size: 100 path: $(DC_FILE_SINK_CONF_PATH) コンテナ agent1down /root/manager.conf { "manager": { "meas_root": "$APPDIR/intdash/meas", "meas_root_volatile": "$RUNDIR/intdash/meas", "rawdir": "$APPDIR/intdash/raw", "basetime": "$RUNDIR/intdash/basetime", "stat": "$RUNDIR/intdash/manager.stat", "logger_stat": "$RUNDIR/intdash/logger_%03hhu.stat", "process_stat": "$RUNDIR/intdash/process.stat", "intdash_stat": "$RUNDIR/intdash/intdash.stat", "network_stat": "$RUNDIR/intdash/network.stat", "system_stat": "$RUNDIR/intdash/system.stat", "wwan_stat": "$RUNDIR/intdash/wwan.stat", "workdirs": [ "$APPDIR/intdash/meas", "$RUNDIR/intdash" ], "filters": [] }, "clients": [ { "protocol": "mod_websocket.v2", "type": "realtime", "my_token": "$TOKEN", "my_id": "$UUID", "my_secret": "$SECRET", "auth_path": "$APPDIR/intdash/.auth", "project_uuid": "", "connection": { "host": "$SERVER", "path": "/api/v1/ws/measurements" }, "fifo_rx": "$RUNDIR/intdash/client_%s.rx", "fifo_tx": "$RUNDIR/intdash/client_%s.tx", "path": "$SBINDIR/intdash-edge-client", "stat": "$RUNDIR/intdash/client_%s.stat", "fast_net_check_cmd":"$BINDIR/intdash-edge-networkd.sh -q -t" }, { "protocol": "mod_http", "type": "resend", "my_token": "$TOKEN", "my_id": "$UUID", "my_secret": "$SECRET", "project_uuid": "", "auth_path": "$APPDIR/intdash/.auth", "connection": { "host": "$SERVER", "path": "/api/v1/measurements" }, "fifo_rx": "$RUNDIR/intdash/client_%s.rx", "fifo_tx": "$RUNDIR/intdash/client_%s.tx", "path": "$SBINDIR/intdash-edge-client", "stat": "$RUNDIR/intdash/client_%s.stat", "fast_net_check_cmd":"$BINDIR/intdash-edge-networkd.sh -q -t" }, { "protocol": "mod_websocket.v2", "type": "control", "my_id": "$UUID", "my_secret": "$SECRET", "project_uuid": "", "auth_path": "$APPDIR/intdash/.auth", "connection": { "host": "$SERVER", "path": "/api/v1/ws/measurements" }, "fifo_rx": "$RUNDIR/intdash/client_%s.rx", "fifo_tx": "$RUNDIR/intdash/client_%s.tx", "path": "$SBINDIR/intdash-edge-client", "stat": "$RUNDIR/intdash/client_%s.stat", "down_dst_id": "00000000-0000-0000-0000-000000000000", "ctlr_ids": [ "e5e156b5-35f3-40ec-9993-8d6d812428c9", "84db287d-d763-4b40-a9a2-2d2824a8b803" ], "ctlr_flts":[ { "channel": 2, "dtype": 10, "ids": ["command"] } ] } ], "loggers": [ { "path": "", "connections": [ { "channel": 255, "fifo_rx": "$RUNDIR/intdash/logger_XXX.rx", "fifo_tx": "$RUNDIR/intdash/logger_XXX.tx" } ], "details": { "plugin": "status", "plugin_dir": "$LIBDIR/plugins", "plugin_arg": { "stintd": { "meas_root": "$RUNDIR/intdash/meas" }, "stsys": { "storage_dir": "/" } } } }, { "path": "", "connections": [ { "channel": 2, "channel_rx": -1, "receive_basetime": true, "fifo_rx": "$RUNDIR/intdash/logger_002.rx", "fifo_tx": "$RUNDIR/intdash/logger_002.tx" } ], "details": { "plugin": "fifo" } } ] } コンテナ agent2down intdash-agentd config show connection: server_url: https://xxx.intdash.jp project_uuid: 00000000-0000-0000-0000-000000000000 edge_uuid: 7e340e6a-ad9f-49d7-8e20-0694d906bbd8 client_secret: xxx transport: protocol: websocket upstream: [] downstream: - id: down enabled: true dest_ids: - src_command filters: - src_edge_uuid: 84db287d-d763-4b40-a9a2-2d2824a8b803 data_filters: - type: string name: v1/2/command - src_edge_uuid: e5e156b5-35f3-40ec-9993-8d6d812428c9 data_filters: - type: string name: v1/2/command device_connectors_upstream: [] device_connectors_downstream: - id: src_command enabled: true format: iscp-v2-compat ipc: type: fifo path: /var/run/intdash/downlink-command.fifo filters_upstream: [] filters_downstream: [] deferred_upload: priority: same_as_realtime limit_data_storage: true data_storage_capacity: 102400
Aptpod Advent Calendar 2022 23日目の記事です。(※土日休みにしているので最終日です) CTOの梶田です。 今年もまたあっという間(いつも言ってますね💦)でAdvent Calendar もなんとか走りきれそうです。 (昨年に引き続き継続するのは大変だなーと痛感していますが、なんとかみんな頑張った💪) 今年はAdvent Calendar も5年目となり、なんか新しいカタチにしようかどうか悩んだのですが、今年も例年通り、 1日目の神前の記事 でも触れられていますが。。。 1日目の記事抜粋 12月ですし、年末ネタとして(勝手に)定着させようとしているので2022年も 昨年 と同様に振り返ろうと思います! ではでは始めます。 (ちょっとタイトルの "また" が多いなw) はじめに MVV パートナー活動 Amazon販売 ソリューションパッケージ 2023年に向けて はじめに 2022年も昨年に引き続き、COVID-19は終息することなく、新しい働き方が標準になりつつあります。 (といってもTwitter社のようにリモートワークは終焉し、出社が強制みたいなところも出てきつつありますね💦) 2022年は、ロシアのウクライナ侵攻と関連する材料/資源不足、物価高(まだまだ高くなりそうですが。。)、為替変動、円安。。。と(他にもいろんなことありましたが) 社会の不安定感が増すなか、弊社としてもなかなか難しい局面は続き、攻めというよりはさらなる守り(投資の抑制、財務の健全化等々)へのシフト。。。 となかなか痺れる1年だったのですが、 5月、6月ぐらいにスタートアップに冬の時代がやってきたというのがちらほら出始めていたころ、、 SmartHR創業者の宮田さんの記事 blog.shojimiyata.com には、特に走り続けるチカラをもらいました💪 (社内でも "全員必読" みたいなかたちで少しだけ広まりましたw 😅) 中でも 変化に適応し続ける奴が一番強い というところと スタートアップがキーを打っている最中に死ぬことはめったにないのだ。だからキーを打ち続けよう! というところは、特に自分の中では心に留めています。 といったところで、 今年のトピックをいくつかピックアップして書いていきます。 MVV パートナー活動 Amazon販売 ソリューションパッケージ MVV 会社として、いわゆるMVV(ミッション・ビジョン・バリュー)を今年公開し、HP(ホームページ)を刷新しました。 youtu.be もともと内部的にはうっすらしてたものですが、言語化され公開されたことで意識しやすくなり、 また、バリューの浸透も含めての施策もいくつかされて、意識することも増えたのかなと感じてはいます。 (なかなか策定や公開までも紆余曲折ありつつ、まつわる施策についても試行錯誤の連続で。。。これだけの言葉だけで終わるのもなんともですが😓) パートナー活動 今年は特にこのパートナー各社様との協業、共創活動へのシフトを意識しました。 リリースとしても、 2022.05.30 アプトポッド、様々な分野のパートナーとの協業で産業DX支援を加速 を中心に各社様との活動をリリースさせていただきました。 2022.01.11 ax株式会社のAIソリューション「ailia」がintdashに対応 2022.02.02 アプトポッドとアクロクエストテクノロジー マルチモーダルデータによるAI/IoTソリューション提供で協業 2022.09.07 アプトポッドとオルフェ 動作分析ソリューション提供で協業 2022.10.20 アプトポッド、NECと5Gを活用したユースケースの創出に向けて連携 また、技術面においては、NTTコミュニケーションズ様やマクニカ様とも技術的な共創活動も今年から始めており、来年以降のどこかでお知らせできることを楽しみにしています。 マクニカ様においては、今回のAdvent Calendar2022の岩田の記事 tech.aptpod.co.jp でも触れられています MET2022(Macnica Exponential Technology 2022) というオンラインイベントにおいても弊社との取り組みをご紹介させていただいています。 内容は下記をご覧ください。 met.macnica.co.jp Amazon販売 ちょっと毛色の違うところで軽いものを。 新しい試みとして、こちらのリリースの通り、 2022.05.09 アプトポッド、推論や機械学習の実行環境に最適なエッジコンピュータ EDGEPLANT T1 を、Amazon で販売開始 昨年販売開始をした EDGEPLANT T1 を今年Amazonのプラットフォームで買えるようにしました。 www.amazon.co.jp こういうかたちで見えるとより身近な感じがしてよいですね! 買う敷居が下がり、こちらをきっかけに使っていただく機会が広がってきつつあるのは嬉しい限りです。 ソリューションパッケージ 今年の試みとして大きなもので最近発表しています 2022.12.06 アプトポッド、モビリティやロボットの群管理・遠隔制御の ソリューションフレームワークをベータリリース にありますようにintdashをバックエンドに使用したソリューションとして、最近新しいプロダクトをいくつか発表しました。 よりワークフローやユースケースに寄り添ったソリューションでお客様やパートナー様へ活用しやすくする狙いで進めています。 今回のAdvent Calendar 2022でも記事となっておりますので是非ご覧ください。 13日目:【intdash AUTOMOTIVE PRO REMOTE CAL】ECU遠隔適合システムのご紹介 tech.aptpod.co.jp 15日目:モビリティ管制制御システム向けソリューション「intdash CONTROL CENTER」を紹介します tech.aptpod.co.jp 2023年に向けて 2022年もまた期待の通りというわけではなく、むしろ大きな変化の荒波に対してどう対峙していくかといったところが試されたような気がしています。 やることすべてが必ずしもうまくいかず、パートナー様との共創活動も含め、なかなか難しいことも多かった年ではありましたが、少しづつ芽となっているものもあり、また製品としては進化しているところもたくさんあります。 2023年はさらなる広がりに向けて、お客様およびパートナー様との共創をさらに深く、広げていきたいと思っております。 何か自社の製品と連携できそうとかこんな困りごとがあるといったお客様やパートナー様は以下からお問い合わせいただけると幸いです。 aptpod,Inc. Contact aptpod,Inc. Contact for Partners 再掲になりますが、上記の宮田さんの記事の 変化に適応し続ける奴が一番強い というところと スタートアップがキーを打っている最中に死ぬことはめったにないのだ。だからキーを打ち続けよう! というところを意識しつつ2023年も走り続けていきたいと思っています💪 来年もまたアプトポッドにご期待ください!! メリークリスマス!🎄
aptpod Advent Calendar 2022 も、22日目になりました。本日は、ドキュメント担当の篠崎がお届けします。 私は、社内で1人だけの「ドキュメント担当」 *1 として、社外に公開するマニュアルや技術ドキュメントの制作を行っています。 ドキュメントは新製品・新機能のリリースに合わせて制作しますので、私は、新製品・新機能の開発担当者 *2 と常にコミュニケーションをとりながらリリースまでの作業を進めています。 そこで、この記事では、テクニカルコミュニケーターとしてのドキュメント担当が、製品開発担当者とどのようにコミュニケーションをとりながら仕事を進めているか、書いてみたいと思います。 *3 (なお、ここでご紹介するやり方は、弊社内で「このようなドキュメントだったらこのようなやり方をする」といういくつかのパターンのうちの1つです。すべてのドキュメントを同じ方法で制作しているわけではありません。) この記事で例にするドキュメント 制作の体制と流れ ドキュメントのキックオフミーティング 目次を作成して提案 開発担当者によるレビュー ライターはどこまで自分で悩むべきか 疑問点解決のためのミーティング おわりに この記事で例にするドキュメント この記事では実際の例として、最近公開した「intdash Edge Agent 2 デベロッパーガイド」( PDF版 および HTML版 )の制作を振り返りながら書いてみたいと思います。 「intdash Edge Agent 2 デベロッパーガイド」は、弊社ソフトウェア製品である「intdash Edge Agent 2」を活用いただくための、開発者向けガイドです。 本製品をLinux PCにインストールすると、そのPCはデータ伝送プラットフォーム intdash のクライアント(エッジ)として動作するようになります。 本製品は、従来からある同等の製品「intdash Edge Agent(末尾に2が付かない)」を使いやすくリニューアルしたものであるため、デベロッパーガイドも完全にリニューアルし、一から作ることにしました。 なお、ドキュメント制作に使用しているツールは Sphinx です。また、原稿データは社内GitLabリポジトリで管理しています。 制作の体制と流れ このドキュメントの制作には、主に以下の3名が携わりました。 ライター1名(私) 当該製品の開発担当者1名(ただし、これ以外の開発メンバーにも要所要所で助けてもらっています) プロダクト開発責任者(かつライターの上司でもある) また、スケジュールは、開発日程との関係、また、ライターの他の作業との関係から、以下のようになりました。 7月中旬:ドキュメントのキックオフミーティング 8月上旬:開発担当者の原案をもとにライターが作業開始 8月下旬:原案を整理し、ライターにて新しい目次を提案、OKをもらう 9月下旬:第1回レビュー(開発担当者およびプロダクト開発責任者がレビューする。以降、レビューを重ね、その都度修正する。) 12月上旬:第5回(最終)レビュー 12月中旬:公開 ライターは7月から12月までずっとこの作業をしているわけではなく、他のドキュメントの制作も同時に進めています。 レビュー日程は作業の進捗に応じてその都度設定する形としました。 ドキュメントのキックオフミーティング まず、7月中旬に今回のドキュメント制作に関するキックオフミーティングを行いました。 ここでは、開発担当者から「このようなドキュメントをつくってほしい」という要望をもらい、以下のことについて説明を受けました。 intdash Edge Agent 2とは何か(特に、既存製品との違い、ユーザー像) 開発スケジュールとドキュメント制作スケジュール ユーザーに説明しなければならないことの一覧 社内にある関連資料(設計関連のドキュメント、インストール用パッケージなど)のありか 今回の場合、このキックオフミーティングを受けて、上記の「ユーザーに説明しなければならないこと」は、開発担当者にて原案を書いてもらうよう依頼しました。 「誰が執筆工程を担当するか」については以下のように役割分担をしているのですが *4 、今回は、上から2番目の「開発者向け製品マニュアル」の方法を採用したということになります。 開発担当者とライターの役割分担 目次を作成して提案 8月上旬、予定通り、十分な情報がしっかりとつまった原案を開発担当者からもらいました。 原案を読み込み、以下のように大きく3部に分けて整理することにしました。 まず動かしてみるためのチュートリアル 目的に応じてやり方を参照するためのガイド 網羅的リファレンス(ここに、設定レシピ集も入れる) これは、これまでの別ドキュメントでの反省をふまえ、また、Divio Technologies社の The Documentation System などのベストプラクティスを参考にしたものです。 目次案 新しい目次について開発担当者やプロダクト開発責任者から承認を得られたら、実際にリライトおよび執筆に入っていきます。 開発担当者によるレビュー 8月下旬以降、説明対象のソフトウェアを実際に操作し、調査や実験をしながら、文章を書いていきます。今回は開発担当者が書いた原案があるので、原案を利用できるところは利用しながらリライト、原案がない部分は自分で一から書きます。 ある程度形になったら、開発担当者やプロダクト開発責任者にレビューしてもらいます。 このレビューは、技術的側面から正しい記述になっているかを確認してもらうのが主な目的です。 レビュー結果を書き込んでもらう方法としては以下の2つを使いました。 機能追加等により、まとまった修正が必要な時は社内GitLab上のマージリクエスト 細かい指摘はGoogleドライブ上のPDFにコメント テキストが主体のドキュメントであれば、細かい指摘もマージリクエストでやり取りをしますが、今回のドキュメントは図や表がたくさんあることから、PDFをGoogleドライブで共有し、そこにコメントをつけてもらう方法を使用しました。 基本的に、レビューコメントを付けてくれるのは開発担当者とプロダクト開発責任者ですが、必要に応じて関連する人がメンションされて呼び出され、議論に加わってくれるという形で進みました。 PDFでのレビュー(開発者がメンションされて呼ばれてきたところ) ライターはどこまで自分で悩むべきか 説明を書いていると、たびたび仕様についての疑問が生じるのですが、どのくらい悩んでから開発担当者に質問すべきか迷います。 私はだいたい、「もう行き詰まった」と思うところまで頑張って一人で悩むようにしています。同時に、「行き詰まった」と感じるところまでできるだけ早く行くように心がけています。 これは、社内のライターとして、自分で答えを探すために必要な材料が十分に与えられているためです。 今回の場合は、手元のDocker環境で、説明対象のソフトウェアを動作させ、実験をすることができました。 実験用のintdashサーバーも自由に使えるため、設定を変えて好きなだけデータの送受信を試してみることができます。 また、社内WikiやGoogleドライブに保存されている設計段階の多くの資料も見ることができます。 (逆に言うと、自分で実験できない場合や資料へのアクセスがない場合は、早く聞くしかないということになります。) 実験環境や社内資料など、情報へのアクセスが十分にあるところでしっかり一人で悩んでおくことは、無駄にはなりません。 一人で考えて行き詰まったとしても、開発担当者に質問するときには、最終的に行き詰まった地点を示し、なぜその道筋をたどったのかを添えて質問すると、開発担当者は返答をしやすくなる(はず)。 自分が行き詰まったということは、これからドキュメントを読むユーザーも同様に行き詰まる可能性が高い。その道筋を自分で体験していれば、それを避けるように説明ができる。 例を挙げてみます。以下は、なんらかの設定項目(ここでは設定項目Xとします)の機能について説明を書こうとして疑問が生じたケースです。ここでは、「このまま考えたら矛盾することが分かった」というのが「行き詰まり」であり、それをもとに質問しました: 設定項目Xの機能はAということであると考えました。 しかし、他に設定項目Yがあるので、Xの機能がAとするとXとYの設定が矛盾することになります。 もしかして、Xの機能はBでしょうか。 開発担当者の返答は、だいたい以下のいずれかになります: (例1)Xの機能はAという理解で正しいです。Yの機能は特別なケースのための設定で、... Aという理解で正しかったということで安心です。でも、悩んだ甲斐がありました。Xについて悩むことでXについて正確に理解しているので、「Xとは異なるY」をどのように説明すればよいか、見えてくるからです。 あるいは以下のような返答もありえます。 (例2)そうです、Xの機能は実はBなんです。ちょっと分かりにくいかもしれませんね、そもそもXがなぜ存在するかというと.... うーむ、Xの機能はAだと予想したのは間違いだったようです。でも、悩んだ甲斐がありました。自分がこのように予想したということは、ユーザーもそのように予想する可能性が高いからです。なぜ自分がそう予想したかを念頭に入れて、Xの「そもそも」のところを説明する必要がありそうです。それにより、Xの位置づけがスムーズに説明できるとよいです。 さらには、以下のような返答もあるかもしれません。 (例3)いいえ、Xの機能は、AでもBでもなく、Cです。どういうことかというと... 予想していなかった第3の答えでした。でも、悩んだ甲斐がありました。これまでAとBを念頭に置いて悩んできたわけですが、ここでも、「Aはでなく、Bでもない」ということがCを理解するための支えになります。 例2や例3のように理解や予想がしにくい機能は、説明を書くと長く複雑なものになってしまうことがあります。そんなとき、場合によっては、「こんなに説明が難しくなってしまうということは、この仕様は見直したほうがよいかも」「設定項目の名前を変えたほうがよいかも」という話になり、ドキュメント化がきっかけで仕様の見直しにまで進むことがあります。 こういうことができるのは、製品開発とドキュメントの制作が社内で緊密に連携しながら進んでいるからこそだと思います。 なかなか複雑なやりとりになってきました。PDFやマージリクエスト上のコメント上で会話を続けて解決すればよいですが、例2や例3のようなケースでは、詳細なコミュニケーションが必要になります。 そんなときは、ライターが主催して、疑問点を解決するためのミーティングをします。 疑問点解決のためのミーティング 疑問点解決のためのミーティングでは、ライターは聞きたいこと(時によって1個から20個ぐらい)を書いた資料を用意しておいて、ひとつひとつ開発担当者に聞きながら解決していきます。 今回のドキュメントでは、開発担当者と7~8回のミーティング(各回1時間ぐらい)をおこなったと思います。 通常、もっと小さなドキュメントや、既存ドキュメントの改版の場合はこんなに多くのミーティングはおこないませんが、前述のとおり、今回は一から新しく制作したため、頻繁におこないました。 *5 ちなみに、普段私はリモートワークをしているのですが、たまたまオフィスに出社した日に開発担当者も出社していて、「せっかくなのでマニュアルの疑問点を解決しましょう」ということになって、オフィスの片隅でミーティングをおこなったこともありました。 対面でミーティングをすると、マウスポインターではなく自分の指を使って「ここの説明が...」と指し示したり、手元の紙に簡単な絵を描いたりしながら話ができ、やはりはかどります。 以上のような工程を経て、最終レビューを通過したら、公開することができます。 ひと段落、ではありますが、ここから先も、「このことが書かれていない」「こう書いてほしい」といった要望をもらうことになると思います。終わりはありません。 おわりに どんなふうにドキュメントを作っているのか、今回は社内での開発者とのコミュニケーションに焦点をあてて、一端をご紹介してみました。 ドキュメントを作るテクニカルライターは、ドキュメントをただ「きれいに」してくれる人というわけではなくて *6 、もっといろんなところで役に立つことができるはず、と思っています。 テクニカルライティングはテクニカルコミュニケーションの一分野であり、今回は、社外の方々に対して「コミュニケーション」をするためのドキュメントを作ったわけですが、そのために社内でも、いわば仕込みの段階で、開発担当者と「テクニカルなコミュニケーション」を繰り返しています。 このような社内コミュニケーションの成果が、例えば上記のような「仕様自体を見直そう」という動きになったり、「ここのところは詳しく説明しよう」という話になったりして、社外にお届けする製品およびドキュメントの改善につながります。そのようなことをめざして、働いています。 *1 : 弊社のドキュメント担当の仕事については、以前 「ひとりドキュメント担当」の仕事 でもご紹介しました。 *2 : テクニカルライティング業界だと、ドキュメント制作におけるこの立ち位置はSME (Subject Matter Expert)と呼ばれます。 *3 : 実は、「私と同じようにドキュメントを内製されている方はどうされているのかな、どのようなやり方がよいのかな」といつも気になっています。「知りたいことがあるときは、まず自分のことを書いてみよう」と思ったのでした。 *4 : この表は以前LINE Technical Writing Meetup vol. 11で発表させていただいたときの スライド から流用しました。 *5 : 弊社内のコミュニケーションガイドラインでは、「翌営業日以降なら事前確認なしに空いているところにミーティングを入れてもよい」となっているので、「うーん、これは聞かないといけないな」となったら、翌日のGoogleカレンダーにポンとミーティングスケジュールを入れます。 *6 : テクニカルライターはドキュメントをきれいにしてくれるだけの人と思われてしまう件については こちら に興味深い記事があります。
本記事のタイトルはいわゆる「釣り」です。MQTTは、最近ではMQTT5がリリースされるなど現在でも進化を続けている、とても洗練された使いやすいプロトコルです(本記事にMQTTを貶める意図は一切ありません)。 弊社アプトポッドでは、 MQTTよりもターゲットを絞ったニッチな領域に向けた独自プロトコル “iSCP” を開発しております。本記事では、せっかく釣られてくださった皆様に、その “iSCP” の魅力について少しばかりご紹介できればと思います。 なお、本記事は aptpod Advent Calender 2022 の 21日目としてお送りします。 はじめに お久しぶりです。VPoPの岩田です。昨年1月末に QUIC DATAGRAMに関する記事 を書いてから、忙しさにかまけてテックブログ投稿をサボっていたら、なんと2年弱もの長い年月が過ぎ去っていました。これはイカンということで、重い腰を上げて久しぶりに記事を書いてみることにします。 肝心の記事の内容についてですが、実は先日12月5日、弊社の業務提携先で出資元でもある株式会社マクニカ様が開催された MET2022(Macnica Exponential Technology 2022) というオンラインイベントに、弊社CTOの梶田と一緒に登壇しましたので、そのときにお話しした内容を再構成してご紹介したいと思います。 met2022.macnica.co.jp 内容はズバリ 「弊社の独自開発プロトコル iSCP(intdash Stream Control Protocol)とはナニモノか」 になります。MET2022では、マクニカ様が企画・開発されているFMS(Fleet Management System、遠隔運行管理システム)についてのご紹介がメイントピックで、そのFMSを裏側から支えるテクノロジーという位置づけで弊社のiSCPについてもご紹介させていただきました。本記事ではその登壇のなかから、iSCPに関する部分のみを抜粋してご紹介いたします。 本記事では、以下の内容をご紹介します。 弊社ユースケースにおいてMQTTに不足していたポイント 弊社の独自プロトコルiSCP開発の経緯 弊社の独自プロトコルiSCPの特長 以下のような方には、なにかヒントになるかもしれません。 メッセージ配送ミドルウェアをどう選定したらよいのかわからない 大量・高頻度なデータの伝送がうまくいかなくて困っている MQTTが苦手とするユースケースについて知りたい はじめに そもそもintdashとは iSCPが必要とされる背景 intdash / iSCPの適用先 なぜMQTTを使わなかったのか 高頻度メッセージに対して低遅延性を実現できなかった 伝送効率化のために、結局 X over MQTT が必要になった データの確実な永続化に対応できなかった iSCPの特長 さいごに 2024/01/18 追記 MQTT5に関する連載始めました。ご興味お持ちいただけましたら、こちらも併せて覧ください。 tech.aptpod.co.jp そもそもintdashとは iSCPをご紹介する前に、まずはiSCP(intdash Stream Control Protocol)が頭に冠しているintdashがそもそも何者かについてご説明しなくてはなりません。 intdashは、弊社が開発している産業用IoTプラットフォームです (プラットフォームの名称であり、プラットフォームを構築するためのミドルウェア製品の名称でもあります)。 自動車や自律ロボットなどの高機能マシンを主にターゲットとして 、それらが吐き出す大量かつ高頻度なIoTデータを、インターネットやLTE網などを利用してクラウドに集めたり、遠隔地に中継したりすることができます。intdashを用いることで、様々なIoTデバイスや高機能マシンをコネクテッド化し、相互に通信させることが可能になります。 産業用IoTプラットフォームintdash intdashが持つ主な特徴は以下の3つです。 【データの伝送】IoTデバイス間をリアルタイム伝送でつなぐ 【データの保存】伝送されたデータはクラウドに蓄積する 【データの活用】蓄積されたデータにアクセスするAPIを提供する 1つめのintdashのリアルタイム伝送機能の実現のために、 intdashのサーバーとクライアントの間で使用されるプロトコルが、本記事のメイントピックであるiSCP(intdash Stream Control Protocol) になります。 iSCPが必要とされる背景 前述のとおり intdash / iSCPは自動車や自律ロボットなどの “高機能マシン” のコネクテッド化を主にターゲットとしています 。このようなマシンでは、高機能であるが故に内部のバス上で大量のデータが飛び交っていますが、 大量のデータに対してはデバイス側で集約・抽出処理を行い、サマリデータのみをクラウドに送信するアプローチを取ることが一般的です 。 これは、大量のローデータ(生データ)をそのまま送信する方法では、消費帯域が多く回線費用がかさんだり、そもそもLTE回線では帯域が足りなかったり、デバイスの性能が送信処理に耐えきれなかったりと、様々な問題が発生するためです。 一方で、弊社が長らく支援させていただいている自動車の研究開発分野など、 ニッチではあるものの一部のケースにおいて、このような事前処理のアプローチが取りづらい領域があることも分かっています 。intdashは、試作車両のテストや各種車両からのデータ取りなどでご利用いただくことが多くありますが、 これらのお客様にとっては、ローデータこそが取得したいデータであって、集約されてしまったサマリデータでは意味が無いからです 。 それでもやっぱりローデータがほしい ものづくり大国日本における ”ものをつくる側” の皆様のデジタルトランスフォーメーションをお手伝いするため、ものづくりにおいて不可欠な "計測器"をそのままクラウド上に移動させてしまうもの だと捉えていただくのが最もイメージに近いでしょうか。このようなユースケースにおいて、大量・高頻度なローデータの収集や伝送を実現するため、以下のような目的を持って開発されたプロトコルがiSCPになります。 伝送の低遅延化 : すばやいトライアンドエラーを実現するため、リアルタイムに状態が把握できることは大きなメリットとなります 伝送効率の向上 : 大量のデータからなるべく冗長性を排することで、モバイル回線の細い帯域を有効活用します 保存データの高信頼化 : データ欠損なく保存しきることをプロトコルレベルで保証することで、安心して収集データを活用できます 多様なデータへの対応 : 多様なデータを統一的に扱える仕組みとすることで、様々な試行錯誤に柔軟に対応できます 弊社の主力商品であるWebベースの可視化ダッシュボード 「Visual M2M Data Visualizer」 も、iSCPを使用して実現しているプロダクトのひとつです。 自動車などのマシン側から打ち上げられた大量データをWebアプリケーションで受け取り、ブラウザ上のダッシュボードで可視化するツールですが、 マシンからブラウザまでデータを届ける際のプロトコルとしてiSCPを使用しており、打ち上げられるデータが大量になってもブラウザでしっかり可視化することができます。 www.youtube.com intdash / iSCPの適用先 ちなみに、intdashの適用領域は前述した研究開発領域のみにとどまりません。たとえば最近では、デリバリーロボットをはじめとするAGV/UGVの監視などにおいて、 「普段はサマリデータだけで良いけれども、事故などの問題事象が発生した場合にはすべてのローデータをかき集めてすばやく状況把握や原因解析を行いたい」 といった要望をいただくケースが出てきています。 このように、IoTのコモディティ化に伴って、ニッチな研究開発領域だけでなく一般のIoTサービスにおいても 「いざというときのために」すべてのローデータをリアルタイム伝送できるケイパビリティを備えておきたい、という志向が生まれつつある ように感じています。 また、AGV/UGVなどのユースケースでは、ロボットが自律的に対処できない場面に直面した場合のために遠隔操縦機能を備えておくと安心ですが、 遠隔操縦などを行う場合にも高頻度データの低遅延伝送は効果を発揮します 。現状、遠隔操縦はまだそこまで広く社会実装されている状況ではありませんが、今後自律ロボットの普及に伴って、ロボット管理プラットフォームやそれに付随する遠隔操縦機能の需要も高まってくるのではないかと考えており、intdashはそのような領域にも適用可能です。 その他、intdashをバックエンドに使用したソリューションとして、最近新しいプロダクトをいくつか発表しておりますので、よろしければこちらもご覧ください。 自動車業界向け ECU遠隔適合ソリューション「Automotive Pro Remote CAL」 tech.aptpod.co.jp モビリティ・ロボットの管制制御ソリューション「intdash Control Center」 tech.aptpod.co.jp もちろんintdash自体は、データを伝送・蓄積・活用するための汎用的なプラットフォームになりますので、 これらの活用例に限らず、様々なサービスやソリューションのバックエンドとしてご活用いただけます 。 なぜMQTTを使わなかったのか ここからやっと本題に入ります。ここまで読み進めてくださった皆様がおそらくお持ちであろう 「IoTのリアルタイム伝送ならばMQTTがあるじゃないか、なぜそれを使わなかったんだ?」 という疑問にお答えします。 実は以前、弊社でもMQTTを使用してリアルタイム伝送機能を実現しようとしていたことがありました。しかしながら、MQTTでは、研究開発領域で求められる要件(リアルタイム性と確実なデータ永続化の両立)を実現することができず、泣く泣く使用を諦めたという過去があります。では、MQTTの何がいけなかったのか。 高頻度メッセージに対して低遅延性を実現できなかった MQTTは一般的に、エンドツーエンドで低遅延にデータを転送できるプロトコルとして知られています。これは、PubSub型のメッセージングパターンの採用と、軽量でシンプルなプロトコル仕様により実現されています。MQTTは確かに、 「あるクライアントから送出されたデータが別のクライアントに到達するまでの時間が短い」 という意味で、低遅延伝送を実現可能なプロトコルです。 しかしながら、このプロトコルを "高機能マシン" が吐き出す大量データの伝送に適用するとどうなるでしょうか。一般的に自動車が吐き出す車両データ(CANデータ)は、秒間数千フレーム〜1万フレームにもなります。例えば、この大量フレームをMQTTのメッセージにそれぞれ詰め込んで送信したとすると、 少なくとも開発当時のMQTTブローカーでは、すべてのメッセージを送りきれる程のスループットが出ませんでした 。 メッセージを送りきれないということは、各メッセージはその分滞留することになり、結果としてメッセージ配送に要する時間は増大します。要するに、メッセージ一つ一つの転送自体は低遅延に実現できるものの、大量のメッセージを送りつけるようなケースではメッセージの滞留によりリアルタイム性が損なわれてしまう、ということになります。さらに悲しいことに、 スループットの上限値は、QoS(メッセージの転送品質)の設定を上げるとさらに低下します 。 世の中には、MQTTブローカーのスループットを調査したベンチマーク結果も多くありますが、その多くは、 複数のクライアントから同時にデータ送信を試行することで、クライアント全体として数万メッセージのスループットが出ることを確認するもの です。クライアント数が増えるケースについては、サーバーをクラスタリングして水平スケールにより対応することもできますが、我々が対峙している1台単体で数千〜数万のメッセージを生成してしまうようなケースでは、単にサーバー台数を増やせば解決というわけにはいきません。 このようなケースに耐えうるMQTTブローカーは、少なくとも企画当初は世の中に存在せず、intdash / iSCP開発の大きなきっかけとなりました。 ※ MQTTの名誉のために若干のエクスキューズを入れておくと、 これはMQTTの性能が悪いと言っているわけではありません 。 MQTTは、1クライアントから受け取れるメッセージ数については弊社の要件を満たしませんでしたが、その代わりに水平スケールさせやすい仕様になっていると思っています。 一方で弊社のiSCPでは、1クライアントから受け取る大量メッセージを捌き切るためにステートフルなデータ保持の仕組みを敢えて導入したり、とにかく大量のデータを捌き切ることに仕様を全振りしています。 これは、あくまで対象とするユースケースの違い・プロダクトの思想の違いであって、どちらが良い悪いというものではありません 。 ユースケースによっては、よりMQTTが適している場合もあれば、iSCPが適している場合もあり、適材適所でプロダクトを選択していけばよいだけのことです。 それぞれ向いているユースケースが違う 伝送効率化のために、結局 X over MQTT が必要になった 前のセクションを読まれた皆様の中には、 「1データずつ1メッセージで送信するからメッセージ数が膨大になるのであって、複数メッセージをまとめて送ればよいのではないか」 と気づいた方もいらっしゃると思います。そのとおりです。あくまでメッセージ数が多すぎることが原因なので、複数データをまとめて大きめなメッセージにしてからバルク送信すれば、スループット不足の問題はある程度回避することができます。一見、この方法で問題は解決できるように思えますが、これはまた別の問題を引き起こします。 バルク送信を採用するということは、MQTTのペイロードへのデータの格納方法として所定のフォーマットを定義するということです。これは、 MQTTの上層にさらに別のプロトコルを定義することに相当します(X over MQTT) 。このアプローチを取った場合、 各クライアントは MQTT のプロトコルに準拠しつつ、MQTT上に想定されているフォーマットに合わせた形式でMQTTのペイロードを構築して送信しなければなりません 。すなわち、そのプロトコルは下層にMQTTを採用してはいるものの、MQTTで定義されている仕様に従っただけのクライアントからは通信ができないことになります。 これはたとえば、HTTPは主にTCP上のプロトコル(= HTTP over TCP)ですが、HTTPサーバーとただのTCPクライアントで通信するには、クライアント側にさらにHTTPを解釈するための自前実装が必要になることと同じだと考えていただければ、分かりやすいでしょうか。 MQTTを下層に使用したとして、結局その上層に独自のプロトコルを定義するのであれば、下層はわざわざMQTTに縛られる必要はありません 。幸い、MQTTのプロトコル仕様は極めてシンプルで、MQTTブローカーにより提供される機能性の実装はそこまで複雑なものではないため、 MQTTに依存することによって課せられる制約と、独自にプロトコルを定義するコスト とを天秤にかけた結果、弊社では独自のプロトコルを開発することを選択しました。 どうせ作るするならすべて自前で作ってしまえ データの確実な永続化に対応できなかった 主にデータ収集用途で使用されることが多かったintdashでは、集めたデータの信頼性についても高い要件を課せられていました。 サーバーに送信されたデータは、取りこぼすこと無く確実にデータストアに保存しなければなりません 。しかしながら、この機能性を既存のMQTTブローカーを用いて実現しようとすると、アーキテクチャが複雑化してしまうという問題がありました。 もちろん、MQTTにもQoS(メッセージの転送品質)という考え方があり、 "伝送経路での欠損については" 検出することができます。一般的に言われていることですが、この "伝送経路での欠損については" の部分が注意を要するところで、 MQTTのQoS設定を上げたとしても、エンドツーエンドでの欠損については検出することができません 。 どういうことかというと、例えばパブリッシャーとサブスクライバーが存在した場合、検出ができるのはあくまで パブリッシャー〜ブローカー間 と ブローカー〜サブスクライバー間 それぞれの経路についてのみであって、例えば極端なケースでは、パブリッシャーからブローカーへ転送されたあと何らかのエラーによってデータが消失してしまうと、それをパブリッシャーは検出することができない、ということになります。 既存のMQTTブローカーを用いてリアルタイム機能およびデータの確実な永続化機能を実現しようとすると、データを永続化するクライアントをサブスクライバーとしてブローカーにぶら下げ、永続化サブスクライバーがデータストアへデータを保存することになりますが、このような形式では 送信クライアントからデータストアまでのエンドツーエンド経路におけるデータ欠損の可能性を排除することができず、結局MQTT上で独自のプロトコルを定義しなければならなくなります (前のセクションでも登場した X over MQTT という形になります)。 片道伝送経路での到達確認しかできない 前のセクションでも言及したとおり、MQTTのプロトコル仕様自体は極めてシンプルで、同様のものをイチから考案することもできなくはありません。 MQTT上に独自プロトコルをスタッキングするくらいなら、はじめから独自のプロトコルを作ってしまったほうが柔軟でシンプルなものができあがるだろう ということで、さらに独自プロトコルの開発に踏み切る機運が高まりました。 もちろん世の中には、「プロトコル自体はMQTTを採用し、MQTTの標準仕様には無い機能性を追加した独自ブローカーを作る」というアプローチを取っている商用MQTTブローカーも存在します(このようなブローカーは、intdashの競合ということになります)。このようなアプローチを取るメリットは、一般のMQTTのライブラリやツール群を流用できることにあります。しかし、それと引き換えに、 MQTT標準仕様の範囲から逸脱しない範囲でしか機能を拡張できない、MQTTの仕様改訂への追従など自社でコントロールできない仕様変更リスクを抱える、などのデメリットもプロダクトに内包させる ことになります。 弊社としては、MQTT標準仕様の範囲だけでは実現できない機能性を追加したかったこと、テックベンチャーとしてMQTTに競合するような新しいプロトコルの確立に挑戦してみたかったことなども含め、メリット・デメリットだけでなく開発メンバーの思いやビジネス的な思惑等、様々なことを考慮して総合的に判断した結果、iSCPという独自プロトコルを開発することを選びました。 iSCPの特長 ここまで説明したMQTTを採用するにあたっての課題に対して、X over MQTT から派生して、MQTT部分も含めてすべてオリジナルなプロトコルスタックにより定義したものがiSCPです。 さらに、MQTTのようなPubSub部分も含めて独自で定義したため、MQTTにはない機能性もいくつか追加することができました。 iSCPの特長には様々なものがありますが、特徴的なものだけを拾い上げると、まずは大量・高頻度なデータを効率よく伝送できるように以下の特長を持っています。 これらの特長により、MQTTでは難しかった大量・高頻度なデータの伝送処理を実現しています。 複数データをまとめてバルク送信(もちろんバルクサイズは調整可能) メタ情報をあえてステートフルに保持することで、通信オーバーヘッドを削減 WebSocketを参考にしたメッセージ圧縮方式の採用 また、主に研究開発にて要求されるリアルタイム性と確実な永続化の両立のため、以下の特長も備えています。 ブローカーはデータを受信したら最優先で転送(永続化処理はあと回し) クライアントへのAckは永続化処理が完了してから返却 Ackにより永続化確認ができないデータは別途あとから永続化 クライアントへのAck返却タイミングが永続化処理完了後となっている点がiSCPの特徴的な部分です 。多くのプロトコルではAckは通信経路での到達確認や順序保証に使われるため、ある程度のAckが返却されない場合にはAck待ちにより送信が停止してしまいますが、iSCPにおいてはサーバーへデータが永続化されたかどうかの確認に用いられるものであるため、Ackの返却は必ずしも待つ必要がありません。 大量にデータを送りつけ、欠損があった場合には検出だけしておき、帯域に余裕があるときに改めて欠損部分だけを永続化処理する、というのが iSCP の基本の考え方になります 。 intdash / iSCPでの処理の流れ このような仕様により、 伝送時にはリアルタイム性を最優先に転送を行いつつ、後からではあるものの取りこぼすことなくデータを回収できる ようになっています。 このような機能性は、弊社でも実績の多い研究開発におけるデータ収集のユースケース(データを可視化しながらトライアンドエラーを行い、最終的にはすべてのデータを収集し切りたい)と極めて相性がよいものです 。もちろんそれだけでなく、たとえば遠隔管制・フリート監視のようなユースケースでは監視時にはリアルタイム性が、事故発生時の解析等でにはデータの信頼性が重要なファクターとなるなど、研究開発以外のユースケースにおいても効果を発揮します。 また、最近では遠隔管制に関連して、ロボットに対する遠隔操縦に対する要望もいただくようになってきています。 このようなケースでは、データの詰まりによってリアルタイム性の失われやすいストリームベースの通信方式よりも、データグラムベースの通信方式が好まれる傾向があります。 このような要求にも応えられるようiSCPは現在も日々進化を続けており、iSCPのメジャーバージョンアップに向けて現在準備を進行しております。 メジャーバージョンアップ前の現行バージョンでは、下層に使用するトランスポートプロトコルとしてWebSocketしか選択できませんでしたが、 メジャーバージョンアップ後のバージョンでは、 データグラムベースの通信方式に対するサポートが追加され、 WebSocketに加えて最新のトランスポートプロトコルであるQUICやWebTransportも使用可能となる予定です 。 さいごに いかがでしたでしょうか。 弊社がなぜMQTTを捨て独自プロトコルの開発に踏み切ったのか、また、その結果生まれたiSCPがどのようなプロトコルなのか 、この記事でお伝えできていれば幸いです。また、前述のMET2022にてご一緒させていただいたマクニカ様のFMS(Fleet Management System)のように、intdashやiSCPが持つ特長に少しでも興味や魅力を感じていただけましたら、ぜひとも皆様ご担当のシステムへの導入をご検討ください。 iSCP のクライアントライブラリ はオープンソース化していますので、どなたでも自由にご利用いただけます。また、 プロトコル仕様書 も公開可能なものを用意しておりますので、プロトコル仕様をよく精査して導入をご検討いただけます。 サーバーソフトウェア や弊社が運用する マネージドサービス は有償でのご提供となりますが、 フリートライアルのプラン もご用意しております。その他些細なことでも構いませんので、プロダクトに関するお問い合わせについては、 弊社営業までご連絡ください 。(私自身も、弊社プロダクトの開発責任者として全力でサポートさせていただきます) 結びとなりますが、アプトポッドではintdashやそれを取り巻く周辺プロダクトの販売だけでなく、intdashを活用したシステムインテグレーションや受託開発も行っております。当社が直接お手伝いをするだけでなく、当社が認定した信頼と実績のあるパートナー企業様にて、ご対応させていただくことも可能です。本記事でご紹介できなかった弊社のプロダクトやビジネス全般につきましては、弊社のコーポレートサイトをご覧ください。 「IoTシステムを構築したいがどんなアーキテクチャがよいのかわからない」「MQTTで構築した既存のIoTシステムがあるが限界を感じている」など、IoTシステム構築に関する一般的なご相談につきましても、お問い合わせいただければお力添えいたします。 www.aptpod.co.jp ※ 本記事をお読みいただき、アプトポッドのプロダクトやエンジニアリングに興味を持ってくださったエンジニアの方々からの連絡もお待ちしております。 まずはカジュアルな情報交換からでも構いません。会話をさせていただくなかで、募集中の職種をご紹介させていただきます。( 採用ページはこちら ) www.wantedly.com 最後までお読みいただき、ありがとうございました。 皆様よいクリスマスと年末をお過ごしください! 🎄🎍
aptpod Advent Calendar 2022 の20日目を担当します、intdash グループ フロントエンドエンジニアの佐藤です。 早速ですが、弊社では認可制御にOAuth 2.0 を採用しています。 tech.aptpod.co.jp ブラウザのアプリケーションでこの認可制御をする際、Express 等を使ったバックエンドがある場合が多いかと思います。 弊社でもNext.js を用いて、認証を管理するバックエンドサービス (BFF) のある構成をとっています。 このバックエンドがある場合のパターンは、Node.js のクライアント・ライブラリやExmaple も多く、それに従えばおおよその実装はできるのではないでしょうか。( oauth2-proxy が有名でしょうか。) バックエンドがある場合の方がセキュリティレベルは高く、一般的にこちらが採用されることが多いように思えます。 一方、バックエンドが無いアプリケーション、つまりブラウザ側 のみ で認証管理する方法は調べてもあまり有用な情報が出てこないことが多いです。 業務の中でブラウザ側のみで認証管理をする場合の実装が必要になり、実装方法について色々と調べましたので、その内容をご紹介します。 前提条件 実装方法 実装する処理の流れ 環境構築 code_verifier とstate を保存する 認可リクエスト先のURL を作成する 認証コードをアクセストークンへ交換する アクセストークンを保存する アクセストークンを利用してリソースを取得する 実装してみて 前提条件 Implicit Flow はセキュリティ上の懸念が伴うために使用しない PKCE に対応した Authorization Code Flow で認可リクエストを行う 実装方法 完成したコードの内容は以下のようになりました。 import { v4 as uuidv4 } from "uuid" ; import pkceChallenge , { generateChallenge } from "pkce-challenge" ; import Cookies from "js-cookie" ; import axios from "axios" ; import { AuthMeApi , MeasDataPointsApi , AuthOAuth2Api } from "../../intdash" ; // クライアント ID const CLIENT_ID = "XXXX-XXXX-XXXX-XXXX" ; // 認可先のホスト const AUTHORIZATION_HOST = "https://example.intdash.jp" ; // リダイレクトURI const REDIRECT_URI = "https://localhost:8443" ; // PKCE の設定 const { code_verifier } = pkceChallenge (); // state の作成 const state = uuidv4 (); (async () => { // アクセス時のURL の検証 const code = new URL ( window .location.href ) .searchParams. get( "code" ) ?? "" ; try { const authOAuth2Api = new AuthOAuth2Api ( { isJsonMime: () => true , basePath: ` ${ AUTHORIZATION_HOST } /api` , } ); const { data: { access_token , expires_in } , } = await authOAuth2Api.issueToken ( { grantType: "authorization_code" , code , clientId: CLIENT_ID , redirectUri: REDIRECT_URI , codeVerifier: Cookies. get( "code_verifier" ) ?? "" , } ); // アクセストークンを取得できた場合、PKCE とstate の値を削除する Cookies.remove ( "code_verifier" ); Cookies.remove ( "state" ); if (typeof access_token !== "undefined" ) { Cookies. set( "_bearer_token" , access_token , { ...options , expires: expires_in , } ); } const authMeApi = new AuthMeApi ( { isJsonMime: () => true , basePath: ` ${ AUTHORIZATION_HOST } /api` , } ); const { data: { name } , } = await authMeApi.getMe ( { withCredentials: true , } ); document .querySelector < HTMLDivElement > ( "#app" ) ! .innerHTML = `<p>Hello ${ name } </p>` ; } catch ( error ) { Cookies. set( "code_verifier" , code_verifier , options ); Cookies. set( "state" , state , options ); const requestURL = new URL ( ` ${ AUTHORIZATION_HOST } /api/auth/oauth2/authorization` ); requestURL.searchParams. set( "client_id" , CLIENT_ID ); requestURL.searchParams. set( "response_type" , "code" ); requestURL.searchParams. set( "redirect_uri" , REDIRECT_URI ); requestURL.searchParams. set( "state" , Cookies. get( "state" ) ?? "" ); requestURL.searchParams. set( "code_challenge" , generateChallenge ( Cookies. get( "code_verifier" ) ?? "" ) ); requestURL.searchParams. set( "code_challenge_method" , "S256" ); document .querySelector < HTMLDivElement > ( "#app" ) ! .innerHTML = `<a href= ${ requestURL.href } >Connect Your Account</a>` ; } } )(); 各エンドポイントは OpenAPI Generator でドキュメントから生成したクライアントを通して、リソースを取得しています 実装する処理の流れ 以下のようなフローで認可を行い、エンドポイントから自分の名前を取得するSPA を実装していきます。 なお、薄灰色の箇所は認可サーバーの役割になるので本記事では触れません。 https://localhost:8443 にブラウザからアクセス 「Connect Your Account」のリンクをクリック https://example.intdash.jp へ認可リクエストを行う https://example.intdash.jp 上でユーザー名とパスワードを入れて認証 https://localhost:8443 へcode を発行 (URL のクエリパラメーターにセット) しリダイレクト https://localhost:8443 上でトークン交換をリクエストする 問題なくトークンが発行できたらCookie に_bearer_token として値をセット Axios で自分の名前を取得するエンドポイントからデータを取得 UI フロー図 環境構築 さくっと作りたかったので Vite を使ってみました。 HTTPS でlocalhost を起動したいのと、API のエンドポイントのホスト ( https://example.intdash.jp ) に対してProxy をしたいので vite.config.js を編集します。 import { defineConfig } from "vite" ; import fs from "fs" ; export default defineConfig( { server: { port: 8443, https: { key: fs.readFileSync( "./.key.pem" ), cert: fs.readFileSync( "./.cert.pem" ), } , proxy: { "/api" : { target: "https://example.intdash.jp" , changeOrigin: true , configure: (proxy, options) => {} , } , } , } , } ); 以下のコマンドを入力すると、 https://localhost:8443 でローカルサーバーが立ち上がります。 yarn dev これで、環境準備が整いました。 code_verifier とstate を保存する 認可リクエストをする前に code_verifier と state を適切なブラウザ API を使用して任意の場所に保存します。 以下の例では js-cookie を利用して Cookie に保存しています。 import { v4 as uuidv4 } from "uuid" ; import pkceChallenge , { generateChallenge } from "pkce-challenge" ; import Cookies from "js-cookie" ; // PKCE の設定 const { code_verifier } = pkceChallenge (); // state の作成 const state = uuidv4 (); Cookies. set( "code_verifier" , code_verifier , options ); Cookies. set( "state" , state , options ); 認可リクエスト先のURL を作成する 次に、認可リクエスト先の URL に以下をクエリパラメータとして追加します。 生成したURL をhref 属性に追加したa タグ を実装します。 ユーザーはこのa タグ をクリックすることで認可フローを開始します。 const requestURL = new URL ( ` ${ AUTHORIZATION_HOST } /api/auth/oauth2/authorization` ); requestURL.searchParams. set( "client_id" , CLIENT_ID ); requestURL.searchParams. set( "response_type" , "code" ); requestURL.searchParams. set( "redirect_uri" , REDIRECT_URI ); requestURL.searchParams. set( "state" , Cookies. get( "state" ) ?? "" ); requestURL.searchParams. set( "code_challenge" , generateChallenge ( Cookies. get( "code_verifier" ) ?? "" ) ); requestURL.searchParams. set( "code_challenge_method" , "S256" ); document .querySelector < HTMLDivElement > ( "#app" ) ! .innerHTML = `<a href= ${ requestURL.href } >Connect Your Account</a>` ; 認証コードをアクセストークンへ交換する 認可先で認証が承認されたら認証コードとともに指定されたリダイレクト URL にリダイレクトされます。 https://localhost:8443/?code=UeE7Adf6QxGDiKuKOngG0Gc9Nee3XhnxMnKYsNTqQ60.OJouZMWIrYDm6EYIpt7xbBMSYC6u7UouTCo30Yk-Uyk&remember_me=true&scope=add_any_edge_to_project%20admin%20anonymous%20authz%20config%3Ar%20config%3Arw%20edge%3Ar%20edge%3Arw%20group%3Ac%20group%3Al%20group%3Arw%20me%3Ar%20me%3Arw%20meas%3Ar%20meas%3Arw%20media%3Ar%20media%3Arw%20offline%20passwordpolicy%3Arw%20project%3Al%20project%3Arw%20projectedge%3Arw%20role%3Ar%20role%3Arw%20scope%3Arw%20screen%3Ar%20screen%3Arw%20sig%3Ar%20sig%3Arw%20stats%3Ar%20stream%3Ar%20stream%3Arw%20system%3Ar%20temporary%20user%3Ar%20user%3Arw%20v1_admin&state=0ebdee45-7178-4d86-8978-560bf49fa6e4 アクセストークンの認証コードを交換するために、トークンエンドポイントに対して POST リクエストを行います。 リクエストには次のパラメータが含まれます。 grant_type 値は authorization_code で固定されています code URL のクエリパラメータに含まれている code を使用します client_id redirect_uri 認可フローを開始したときと同じ値を使用します code_verifier 任意の場所に保存した code_verifier を使用します import { AuthOAuth2Api } from "./intdash" ; const authOAuth2Api = new AuthOAuth2Api ( { basePath: `https://example.intdash.jp/api` , } ); const { data: { access_token , expires_in } , } = await authOAuth2Api.issueToken ( { grantType: "authorization_code" , code , clientId: CLIENT_ID , redirectUri: "https://localhost:8443" , codeVerifier: Cookies. get( "code_verifier" ) ?? "" , } ); アクセストークンを取得した場合のサンプルデータです。 { " access_token ": " eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyYjNkNTIwNzY0MDQ3MTAwMmY0ZWY4MjhjZjJlN2YyZmYwNDhkMzc0YzUwNGYyNTJiYmQ0ZTgwZWE4YzJhZGUiLCJ0eXAiOiJKV1QifQ.eyJzY3AiOlsiYWRkX2FueV9lZGdlX3RvX3Byb2plY3QiLCJhZG1pbiIsImFub255bW91cyIsImF1dGh6IiwiY29uZmlnOnIiLCJjb25maWc6cnciLCJlZGdlOnIiLCJlZGdlOnJ3IiwiZ3JvdXA6YyIsImdyb3VwOmwiLCJncm91cDpydyIsIm1lOnIiLCJtZTpydyIsIm1lYXM6ciIsIm1lYXM6cnciLCJtZWRpYTpyIiwibWVkaWE6cnciLCJvZmZsaW5lIiwicGFzc3dvcmRwb2xpY3k6cnciLCJwcm9qZWN0OmwiLCJwcm9qZWN0OnJ3IiwicHJvamVjdGVkZ2U6cnciLCJyb2xlOnIiLCJyb2xlOnJ3Iiwic2NvcGU6cnciLCJzY3JlZW46ciIsInNjcmVlbjpydyIsInNpZzpyIiwic2lnOnJ3Iiwic3RhdHM6ciIsInN0cmVhbTpyIiwic3RyZWFtOnJ3Iiwic3lzdGVtOnIiLCJ0ZW1wb3JhcnkiLCJ1c2VyOnIiLCJ1c2VyOnJ3IiwidjFfYWRtaW4iXSwidGVuIjowLCJleHAiOjE2NjY1OTAwOTAsImp0aSI6ImE3MjQ4ZTBjLTZjYzUtNDkyNS1hZTEyLTU0Mjk3ZDYyNjNiYiIsImlhdCI6MTY2NjU4NjQ5MCwiaXNzIjoiaHR0cHM6Ly9kZXYuaW50ZGFzaC5qcCIsInN1YiI6IjIwMGU0NTBlLTE4YzMtNGI1MS1hMzk2LWM4NmM5NjYxNjYxMiJ9.F6Obt776U0EupdVqKnCSN0fVX1xKzDXZNTOVVpTinWvwF60r4rooKA_X60N7Bu4y3p1V6g95DJFE25BFAsOZBNM0Pjv8yEFEevLP34kgCoydT80l5ODdgYK0M0k5O03-Cdnjd4_yKk2stoSmF2TMNEjWm47ho6XDVDapi41RddfYu7YUn5_NwQVKBUDHL1i5U1a5-wvcOD_42A9rw2_kxoExuYq3UWOg692ZcfaDdaTDNU_OxagjWSZ8_Lx44kQKdNLZu9tLJZI0DUtYSD43eTy1GusVnxVmKASNigYByPuYaXZU6rkEZV9NlHeTjchh73lvpUhOozjpTXfaaoLpcQ ", " expires_in ": 3599 , " refresh_token ": " EIe7ZiGH_USAAs-21En0NzbnxN3WTFPgmMX9UMDSCtE.jTW1O7yEypG_qkmPY1OT3l1mcsSntdZ9c5YwmFZd9RY ", " refresh_token_expires_in ": 2591999 , " scope ": " add_any_edge_to_project admin anonymous authz config:r config:rw edge:r edge:rw group:c group:l group:rw me:r me:rw meas:r meas:rw media:r media:rw offline passwordpolicy:rw project:l project:rw projectedge:rw role:r role:rw scope:rw screen:r screen:rw sig:r sig:rw stats:r stream:r stream:rw system:r temporary user:r user:rw v1_admin ", " token_type ": " bearer " } アクセストークンを保存する 適切なブラウザ API を使用して、アクセストークン(と、ある場合はリフレッシュトークン)を可能な限り安全に保存します。 以下の例では Cookie に保存しています。 Cookies. set( "_bearer_token" , access_token , { ...options , expires: expires_in , } ); アクセストークンを利用してリソースを取得する import { AuthMeApi } from "./intdash" ; const authMeApi = new AuthMeApi ( { basePath: `https://example.intdash.jp/api` , } ); const { data: { name } , } = await authMeApi.getMe ( { withCredentials: true , } ); document .querySelector < HTMLDivElement > ( "#app" ) ! .innerHTML = `<p>Hello ${ name } </p>` ; OAuth 2.0 で認証したのち、発行されたアクセストークンを使用してリソースが取得できました。 実装してみて アクセス時のURL の検証や、Cookie に格納した値の適切なタイミングでの削除などバックエンドがあるときとは異なる点が多くありました。 リクエストURL の生成やトークン交換はNode.js 上で動くクライアントライブラリ (PKCE に対応している client-oauth2 )があるので、そちらを使ったほうがコードがスッキリします。 例えば初回アクセス時やアクセストークンが交換できなかった際、認証先にリダイレクトをしたい場合はバックエンドだと以下のようなコードになります。 // 認証のためのインスタンスの作成 import ClientOAuth2 from "client-oauth2" ; const intdashAuth = new ClientOAuth2 ( { clientId: CLIENT_ID , accessTokenUri: ` ${ AUTHORIZATION_HOST } /api/auth/oauth2/token` , authorizationUri: ` ${ AUTHORIZATION_HOST } /api/auth/oauth2/authorization` , redirectUri: REDIRECT_URI , state , } ); const uri = intdashAuth.code.getUri ( { state , query: { code_challenge , code_challenge_method: "S256" , } , } ); res.redirect ( uri ); 結局、メリットはとくになかったですね。。。 バックエンドがどうしても用意できないときに、最終手段として使うくらいに考えたほうが良さそうです。
aptpod Advent Calendar 2022 の19日目を担当します、ソリューションアーキテクトの渡辺です。 ところで皆さん。 海外旅行に行くために航空機を利用したことがありますか。 その航空機の中でインターネットが当たり前のように利用できます。 それは何故でしょう? 海外旅行に行くために豪華客船を利用したことがありますか。 その豪華客船の中でもインターネットが当たり前のように利用できます。 それは何故でしょう? これらには「宇宙通信」という技術が用いられています。 この宇宙通信を intdash で試す機会がありましたので、宇宙通信の概要説明と共にその様子をレポートします。 宇宙通信とは 2種類の衛星通信 静止衛星を利用する 低軌道衛星を利用する intdash による宇宙通信 宇宙通信とは 宇宙空間を飛行する機体は正式には「宇宙機」と呼ばれます。 人工衛星、探査機、宇宙ステーションこれらすべて宇宙機です。 この宇宙機が行う電波通信のことを宇宙通信と呼び、特に人工衛星に搭載した中継器を用いて地上の2地点間の通信を行うことを「衛星通信」と呼びます。 2種類の衛星通信 衛星通信では人工衛星を通信の中継器として用いますが、利用する衛星によって大きく2種類に分類できます。 静止衛星を利用する 身近なものとしては、BS放送があります。 地上から静止衛星に対してTV映像を送信し、それを受信した静止衛星はそのTV映像を地上の広い範囲に向けて送信します。 静止衛星は高度36,000Kmにあり、地表の広い範囲をカバーします。 また、地上から見ると上空の固定した位置に静止しているため、常に通信を行えます。 【静止衛星のカバー範囲】 静止衛星は1機で地上の30%~40%をカバーできることから、静止衛星を4機使うと理論的には全世界をカバーする通信網を構築することが出来ます。 この方式でTDRS (Tracking and Data Relay Satellite)がNASAにより運用されており、8機の静止衛星が稼働しています。 【静止衛星による全世界カバー】 静止衛星通信のメリット 静止衛星には大出力の中継装置を搭載できるためブロードバンド通信が可能 広範囲に同報配信が出来るため放送用途に向いている 静止衛星通信のデメリット 衛星を赤道上空にしか配置できないため、北極・南極では利用出来ない 地上からの送信に大出力が必要となるため地上の送信装置を小型化できない 低軌道衛星を利用する 近年、本格的に利用出来る様になってきました。 主に大型の航空機や、外航船舶で利用されています。 衛星の高度が静止衛星の36,000Kmに対して800Km前後とかなり低く地上に近いところを飛んでいるため、カバーできる通信範囲が狭いです。 また、地上に対して秒速7Km(音速の20倍)以上という極超音速で移動しているため、1機の衛星は10分足らずで上空を通り過ぎてしまいます。 【低軌道衛星のカバー範囲】 このような低軌道衛星と1対1ではまともな通信は行えないので、多数の衛星を打ち上げて、上空を衛星で一杯にしてしまおう、というのが低軌道衛星を利用する場合の考え方です。 移動中にスマートフォンで通話をしているような場合、一つの基地局のカバー範囲から次の基地局のカバー範囲に人が移動しても通話を続けることが出来ます。 これをローミングと言いますが、低軌道衛星を利用する場合は基地局の方が超高速で移動していて次々とローミングを行っている状態、という訳です。 スペースX社の「スターリンク」サービスは何と2,000機を超える小型衛星を打ち上げて運用しています。 【低軌道衛星による全世界カバー】 低軌道衛星通信のメリット 地上の送受信装置を小型化できるため携帯電話としての利用も出来る 極軌道衛星を用いれば北極・南極も含めて地球上全てのエリアで利用できる 低軌道衛星通信のデメリット 低軌道衛星には大出力の中継装置を搭載出来ないため通信帯域が限られてしまう 多数の衛星が必要で低軌道衛星は運用寿命も短いためコストが高い intdash による宇宙通信 さて、話は変わって intdash による宇宙通信を行ってきた様子のレポートです。 冒頭で航空機で宇宙通信が利用されていることを書きましたが、今回の機会も将来、航空機で intdash を利用して頂くことを想定したものです。 そのためなんと当社製品 「EDGEPLANT T1」 を航空機に搭載して空に飛ばし、上空で宇宙通信(衛星通信)を行ってきました。 利用させて頂いたのは「イリジウム」という低軌道衛星を用いた衛星通信サービスです。 www.n-aviation.com イリジウムは66機の低軌道衛星を運用しています。 このイリジウムが計画された当初は77個の衛星を用いる事になっていたため、原子番号77の元素であるイリジウムがサービスの名称になっています。 このイリジウムという名前にピンときた方はかなりの宇宙マニアですね。 1991年に計画された後 衛星に反射する強い太陽光がUFO騒ぎを起こしたり 人類史上初の人工衛星同士の衝突事故を起こしたり で世を騒がしたあのイリジウムです。 一度は経営破綻して計画がとん挫しましたが、いつの間にか復活して当初計画していたサービスを見事に実現させていたのです。 この度、ナビコムアビエーション様のご協力で、同社が保有する小型飛行機にイリジウム地上局と 「EDGEPLANT T1」 を搭載して飛行試験を行って頂きました。 www.n-aviation.com 今回の飛行試験ではこの小型飛行機が飛びました。 ナビコムアビエーション様が所有している機体です。 二人乗りのシートの後ろに諸々の機材を搭載しました。 もちろん 「EDGEPLANT T1」 もここに搭載します。 そして無事に離陸。 周辺を飛行して地上をカメラで撮影し、撮影データをイリジウム回線で地上に送信する、というのが今回の飛行試験の目的です。 当社営業担当社員のK氏が撮影(カメラ持ち)係として搭乗し、2回飛行したのですが、とっても楽しかったらしいです(笑) こんな感じで無事に宇宙通信を intdash で試すことが出来ました。 このような小型航空機(モーターグライダー)で宇宙通信ができる技術が既にあります。 近い未来「空飛ぶ車」などもっと小型な航空機に intdash が導入されたら、世界中の空を intdash が飛ぶ日も近い???
aptpod Advent Calendar 2022 の16日目の担当は、開発本部ソリューションプロフェショナルGrの影山です。普段は組み込みソフト周辺の開発やインテグレーションを担当しています。 今日は、aptpodで取り組んでいる、ROSをインターネットに接続する技術について、これまでテックブログやデベロッパーズガイドで公開してきた情報だけでは分かりにくかった、導入の大まかなイメージをご紹介したいと思います。 本記事で思ったよりもROSxインターネットが簡単に実現できそう!と思って頂ければと思っています。 想定読者:ROSをインターネット経由で利用したい方、ロボットの遠隔管理、制御に興味のある方 ROSをインターネットにつなぐ技術 TurtleBot3をインターネット経由で制御してみる ステップ1 構成の検討 ステップ2 intdashの環境を導入 ステップ3 Edgeの作成 ステップ4 設定ファイルの書き換え intdash Edge(コントローラ側) intdash Edge(TurtleBot3側) intdash Bridge(コントローラ側) intdash Bridge(TurtleBot3側) ROS launch(コントローラ側、TurtleBot3側共通) ステップ5 動作確認 終わりに 参考情報 ROSをインターネットにつなぐ技術 実際の導入の紹介の前に、前提となる弊社のプロダクトについて簡単にご紹介します。 aptpodではリアルタイム時系列データの可視化、蓄積を実現するintdashをこれまで開発して、様々なお客様に提供してきました。intdashにROSを接続することで、二つのROS空間があたかも同じローカルネットワーク上に存在するように扱えるようにすることを目的として、intdashへの接続のためのintdash Bridgeとノードを開発しました。 intdashを利用したROSのインターネット接続 詳細は以下のエントリを見て頂くのが分かりやすいかと思います。 tech.aptpod.co.jp tech.aptpod.co.jp TurtleBot3をインターネット経由で制御してみる 本記事の細かい部分はそれぞれの弊社の デベロッパーズガイドやマニュアル を参照して頂くとして、作業全体の大きな流れをつかんでいただくことを第一の目標として説明していきたいと思います。 良くチュートリアルやデモで用いられる TurtleBot3 とTurtleBot3が対応しているROS1を例にとって説明していますが、弊社の製品は ROS2にも対応 しております。 ステップ1 構成の検討 TurtleBot3では、Raspberry Piを制御用のコンピュータとして利用しています。TurtleBot3のマニュアルに従ってセットアップすると、開発元で提供されているTurtleBot3制御関連のROSノードがインストールされて、ゲームコントローラなどを接続して走行を制御できるようになります。 コントローラの入力は標準のパッケージのjoyをインストールできていれば、joy_nodeからコントローラの操作像情報を含んだjoyトピックがpublishされ、それをTurtleBot3のノードが受信して動作します。 元となる構成 遠隔制御を実現しようとする場合、上記のような構成を以下のように2つに分解して、遠隔制御を実現するのが一番シンプルかと思います。 intdash bridge nodeとintdash Edgeをセットで対象のデバイスにインストールすることで、ROSをintdash Serverに接続できるようになります。 intdash bridgeではROSのトピックをintdashの通信仕様に合わせて変換して、intdash Edgeを経由して、intdash Serverと送受信できるような機能を提供しています。 intdash bridgeを用いることで、joy_nodeとTurtleBot3関連ノード観点では、2つのROSの空間が透過的に繋がっているように見えるようになります。 目標とする構成 以降のステップではこの構成を実現するために必要なステップを説明していきます。 ステップ2 intdashの環境を導入 intdash Edge *1 とintdash Bridgeはそれぞれパッケージが公開されているのでLinux環境 *2 であれば簡単にインストールできます。 intdash Edge(intdash Edge Agent)のインストール( 詳細 ) sudo apt-get install intdash-edge intdash Bridgeのインストール( 詳細 ) sudo apt-get install ros-noetic-intdash-bridge  (ROS1:Noeticの場合) ステップ3 Edgeの作成 intdashではEdgeの管理や、アップロードされたデータの管理、可視化のために、 各種Webアプリケーション も提供しています。 Edge Admin Console画面のGUI操作で、intdashサーバ上で仮想的なEdgeを簡単に生成できます。 Edgeをサーバ上で作成したときの画面 Edgeの生成時には、各々のEdgeを識別するためのUUIDと、サーバ接続時に利用するシークレットIDのペアが発行されるので、それらのIDを手元に控えます ステップ4 設定ファイルの書き換え 作成したEdge情報を踏まえて、関連する設定ファイルを書き換えていきます。 設定ファイルの全体ではなく、ポイントとなる部分を抜粋して例を記載しています。 設定対象ファイルのパスは代表的なものを示していますが、運用環境に応じて自由に変更可能です。 intdash Edge(コントローラ側) 編集対象: /etc/opt/intdash/manager.conf 空欄の部分に接続先のintdashサーバのアドレス、EdgeのUUIDとシークレットIDを記入します。 以下のようなloggersにintdash Bridgeで使用するFIFOの設定を追記します。 "connections": [ { "fifo_tx": "/var/run/intdash/logger_001.tx", 注:intdash Edgeとintdash Bridgeの間の通信で使うFIFOを指定しています "fifo_rx": "/var/run/intdash/logger_001.rx", "channel": 1 } ], "details": { "plugin":"fifo", "plugin_dir": "$LIBDIR/plugins" } clientsのtype:realtimeに "dst_id": ["<TurtleBot3のEdge UUID>"] を記載する。 intdash Edge(TurtleBot3側) 編集対象: /etc/opt/intdash/manager.conf 空欄の部分に接続先のintdashサーバのアドレス、EdgeのUUIDとシークレットIDを記入します。 loggersにintdash Bridgeで使用するFIFOの設定を追記します(コントローラと同じく)。 clientsのtype:controlを追加して、以下のようなコントローラEdgeからサーバにアップロードされたデータを受信する設定を行います。 "ctlr_id": "<コントローラのEdge UUID>", "ctlr_flt_ids": [ "/joy"    注:joyトピックを受け取るという意味 ], "ctlr_ch": 1, "ctlr_dtype": 14 注:バイナリデータを受け取るという意味の設定 intdash Bridge(コントローラ側) 編集対象: /etc/opt/intdash/params.yaml サーバへ送信して他のEdgeと共有したいトピック名をここで指定します。 incoming: enabled: false # サーバからのデータは受信しない queue_size: 100 suffix: "" outgoing: enabled: true # サーバへデータを送信する max_array_size: 500 topics: - topic_name: "/joy" # アップロードしたいトピック名 send_mode: "raw" queue_size: 500 intdash Bridge(TurtleBot3側) 編集対象: /etc/opt/intdash/params.yaml サーバからの受信を有効にします。 incoming: enabled: true # サーバから来るデータをROSに送り出す queue_size: 100 suffix: "" outgoing: enabled: false # サーバにデータは送らない ROS launch(コントローラ側、TurtleBot3側共通) 既存のノードの起動設定に加えて、intdash Bridge のノードを起動する設定をlaunchファイルに追記します。 launchファイルの編集例 <arg name="paramsfile" default="/etc/opt/intdash/params.yaml" /> 注:前ステップで編集した設定ファイルをここで指定します <node pkg="intdash_bridge" name="intdash_bridge" type="intdash_bridge_node" output="screen" clear_params="true"> <param name="fifo_tx_raw" value="/var/run/intdash/logger_001.tx" /> 注:intdash Edgeの設定と合わせて、intdash Edgeとintdash Bridgeの間の通信で使うFIFOを指定しています <param name="fifo_rx_raw" value="/var/run/intdash/logger_001.rx" /> <rosparam command="load" file="$(arg paramsfile)" /> </node> ステップ5 動作確認 次のようなステップで起動させることで、動作を確認できます。 intdash Edgeの起動 パッケージと共にインストールされている /etc/systemd/system/intdash-edge.service をsystemctlで起動させます *3 。 ROS launchの実行 インターネットに接続した環境で、roslaunchコマンドを使い前のステップで設定した環境を起動させます。設定に問題なければ以下の動画のようにコントローラでの遠隔操作ができようになっているはずです。 youtu.be 終わりに 今日はaptpodのROSに関する取り組みをご紹介しました。いかがだったでしょうか? ネットワーク越えの設定は難しいように思われますが、intdashと組み合わせてステップを踏んで設定していくとで、意外と簡単にROSのネットワーク接続ができることが伝わっていると幸いです。 現在、aptpodでは、今日紹介したROSやその他の環境も想定してリアルタイムでインタラクティブな管制制御システムの迅速な構築を可能にするintdash CONTROL CENTERというソリューションフレームワークを開発しています。 昨日の記事 に詳細が書かれていますので、気になった方はそちらも合わせてご覧ください。 参考情報 aptpod,Inc CONTROL CENTER aptpod,Inc. Docs *1 : intdash Edgeの基本機能はintdash Edge Agentとして提供されています。今回は実績のあるバージョンを紹介していますが、最近新しいintdash Edge Agent2がリリースされたので、今後はそちらを利用していくようになる予定です *2 : AMD64アーキテクチャー上のLinux、Raspberry Pi 上のRaspbianなどをサポートしています *3 : manager.confの配置や、その他のパス設定を変えたい場合はserviceファイルを編集してください。
aptpod Advent Calendar 2022 の15日目を担当します、ソリューションアーキテクトの岩坪です。 今回の記事では当社が2022年12月にベータリリースしました、モビリティ・ロボットのフリート管理や遠隔監視、遠隔制御を実現するための 管制制御システム向けのソリューションフレームワークである「 intdash CONTROL CENTER 」 を紹介させて頂きます。intdash CONTROL CENTERを利用すると 低コスト・短期で、データ利活用が可能な、拡張性のあるシステム を構築することができます。 記事の後半にはintdash CONTROL CENTERを利用した管制制御システムを想定したユースケースのデモ動画も載せていますので、「何はともあれまずはどういうことができるの」という方は先に デモ動画 を見てみて下さい。 はじめに intdash CONTROL CENTER とは何? intdashを利用したデータ伝送プラットフォームの特長 intdash CONTROL CENTERで実現できること intdash CONTROL CENTERを使ったデモ紹介 おわりに はじめに aptpodのソリューションアーキテクトという役割は、お客様のデータ収集やデータ利活用における課題解決や、お客様業務のDX(デジタルトランスフォーメーション)実現に向けて、当社コアプロダクトである IoTデータ伝送プラットフォーム intdash をベースにソリューション提案を行い、お客様と共にプロジェクトを推進、発展させていくことをミッションとしています。 ソリューションアーキテクトってどんな仕事? - aptpod Tech Blog ここ数年は、様々なモビリティ・ロボット向けにintdashを活用頂き、 建設機械・重機・農機、配送ロボットや警備ロボットといったサービスロボット、工場や物流倉庫内のAGV(Automatic Guided Vehicle)・AMR(Autonomous Mobile Robot)等において、 遠隔監視、遠隔制御、フリート管理 を実現したいお客様も増えてきています。 そういった背景もあり、aptpodは様々なモビリティ・ロボットにおける遠隔監視・遠隔管理/フリート管理・遠隔制御向けのプロダクト企画、開発を進めており、 この度、2022年12月に管制制御システム向けのソリューションフレームワーク「intdash CONTROL CENTER」をベータリリースすることができました。 intdash CONTROL CENTER とは何? 今回紹介するintdash CONTROL CENTERは、 モビリティやロボット向けのリアルタイムかつインタラクティブな管制制御システムを迅速に構築 できるようにするための、ソリューションフレームワークです。 本ソリューションを活用頂くことで、ロボットや自動車、建機・重機といった複数のモビリティを対象に、遠隔からモビリティ群を監視・管理することができ、必要に応じて人の手による遠隔指示・操作の介入を行うことができるようになります。 こういった管制制御システムを構築し、運用していく為には、そこに至るまでに 要件定義や機能開発、検証(PoC)のために多大な開発コスト、期間が必要 になるため、システムを導入したいと考えていても、なかなか立ち上げに進められない方々が多いのが現実です。導入検討の初期段階からいきなり本番運用を見据えた全ての要件定義や運用方法を定めるということも現実的ではないため、小さなPoCのフェーズからはじめて徐々に要件を定めつつ、開発、検証のサイクルを回していく必要もあります。 intdash CONTROL CENTERはまさにこういった「管制制御システムを作っていきたいが、何からどう手を付けてよいのか、いきなり膨大な予算を取るのは難しい…。」と考えられている方にも適したソリューションです。本ソリューションの、 データ収集・データ保存・データ管理・データ可視化、といった基本機能をまずは活用頂くことで短期・低コストで初期フェーズのシステム構築を実現 します。検証を繰り返す中で、必要となった機能はあとから拡張開発することも可能なので、段階的に運用に向けたシステム構築を進めることができます。 intdash CONTROL CENTERの適用シナリオとしては、下記のようなものがあげられます。 工場や物流倉庫におけるAGVの運用監視・遠隔制御介入 建設現場における建設機械の運用監視・遠隔指示 スマートシティにおける自動運転モビリティの運用監視・遠隔指示 intdash CONTROL CENTERの適用シナリオ intdash CONTROL CENTERを利用頂くことで、個別開発の要素を減らし、ベースとなる機能の検証も減らすことができるので、 お客様の実現したいことに対して、お客様自身が注力すべき領域のみに集中 してシステム構築を進めることができるようになります。 intdashを利用したデータ伝送プラットフォームの特長 intdash CONTROL CENTERの説明の前に、まずは本ソリューションのベースとなる、当社のコアプロダクト intdash に関して概要を説明します。 intdashに関する詳細は、下記を参照下さい。 aptpod,Inc About intdash intdashでは自動車やロボット・産業機械や制御・可視化アプリケーションなど、短期間に大量のデータを発生させるデバイスをモバイル網やインターネット網を経由して相互にライブ接続することができます。intdashを流れるストリーミングデータはそのまま時系列データストアに保存され、Visual M2Mでの可視化やAnalytics Servicesでの計算処理や機械学習に利用することができます。 各種デバイスからのリアルタイムなデータ伝送と、時系列データとして様々なデータをサーバへ保存し、可視化をするといったデータ伝送プラットフォームの実現のために、当社からは以下のミドルウェアおよびアプリケーションを提供します。 サーバサイドミドルウェア: intdash Server エッジサイドミドルウェア: intdash Edge 時系列データの可視化アプリケーション: Visual M2M Data Visualizer 管理ユーティリティ: Edge Admin Console、Edge Finder、Meas Hub intdashを利用頂くことで、下記の特長を持つIoTデータ伝送プラットフォームを構築することができます。 短期間に大量のデータが発生するような 高頻度のデータ を扱うことができる 高いリアルタイム性 でデータ伝送ができる 伝送時にパケロスした データの完全回収 ができる 多種多様なデータに対して 時刻を同期してデータ管理 が行える サーバに保存された データを後から利活用 できる 上記の通り、intdashはクラウドなどのサーバに構築したintdash Serverにモビリティのデータをリアルタイムに保存し、データを管理・活用することができるものです。モビリティの機体やデータを管理するための、管制制御システムのようなモビリティ管理プラットフォームをサーバをハブとしたデータ伝送システムとして構築するのが望ましい理由としては、いくつかあげられます。 管理対象のモビリティに問題が発生した際に、問題発生時のモビリティのデータをエビデンスとしてあとから確認したい( 過去データの再生 ) 管理対象のモビリティのデータは様々な拠点から、複数の利用者が確認したい( 複数拠点からのアクセス ) 収集したモビリティのデータをただ貯めておく、可視化するだけでなく、データを利活用してDXを推進したい( データの利活用 ) こういったニーズがある場合は、 限定された拠点や環境においてのみ利用可能なデータとして管理するのではなく、intdashのようにクラウドなどのサーバをハブとしたシステムとして、モビリティのデータを伝送し、保存、管理 する必要が出てきます。intdashを利用頂くことで、これらのニーズに応える管制制御システムの構築が可能となり、これが本記事で紹介するintdash CONTROL CENTERで構築したシステムの特長にもなります。 intdash CONTROL CENTERで実現できること 改めて説明すると、intdash CONTROL CENTERはモビリティやロボットのリアルタイムかつインタラクティブな管制制御システムを迅速に構築できるようにするための、ソリューションフレームワークです。 intdash CONTROL CENTERは下記のコンポーネントで構成されます。 モビリティの機体データの送受信処理を行うエッジ側のソフトウェア データストリーミング中継処理やデータ保存を行うサーバ側のソフトウェア リアルタイムな群管理や個別機体の状態モニタを行えるWEBアプリケーション intdash CONTROL CENTERを構成するコンポーネント それぞれのソフトウェアコンポーネントを活用することで実現できるintdash CONTROL CENTERの機能と特長について説明します。 (1)リアルタイムなフリートマップマッピング 複数モビリティの位置情報や、モビリティに搭載されたカメラの映像などのデータをマップマッピングして可視化できる、WEBベースの統合監視環境をリファレンスアプリケーションとして提供します。 本アプリケーションを利用することで、離れたところから現場で稼働する複数のモビリティの稼働位置、稼働状況を確認することができます。 本アプリケーションは、以下のような開発に活用頂くことができます。 屋内シナリオにおける対象となるエリアのマップなどを画像データとして背景に表示したUI開発 屋外シナリオにおけるOpenStreetMapやGoogle Mapsなどを利用したUI開発 モビリティ個別やモビリティ群全体に対するコマンド送信による遠隔指示機能の実装 (2)モビリティの様々なリアルタイムデータを確認可能なダッシュボード モビリティの詳細データをリアルタイムにダッシュボードに表示して確認することができます。 制御データや映像データなど様々なデータをリアルタイムに表示することが可能であり、ノンプログラミングでユーザによる自由なダッシュボードの構成変更が可能です。 モビリティ毎の状態(ステータス)、速度、稼働率などの稼働状況や、バッテリー稼働のモビリティのSoC(State Of Charge)やSoH(State of Health)など、モビリティ管理に必要な詳細なデータを遠隔から監視できるようになります。 ダッシュボード上のVisual Partsを変更することで、対象となるデータの値を表示するだけでなく、時間変化に伴う稼働率状況の変化などをグラフ表示することもできます。 (3)モビリティを遠隔操縦するためのコントローラ接続と操作データ伝送 ゲームコントローラなどの様々な操作デバイスを接続するためのエッジデバイス用のコンポーネントを提供します。本コンポーネントを利用頂くことで、エッジデバイスに接続されたコントローラの操作情報を操作対象であるモビリティへ遠隔からリアルタイムにデータ伝送し、リアルタイムな遠隔操作・操縦を実現します。 倉庫や工場内などのモビリティはAMRのような自律走行やAGVのような磁気テープ上を無人走行するケースが多いですが、予期しない障害物が置かれてしまうなどして動作不能となるケースがあります。その場合に、モビリティの異常状態(動作不能)を検知して、一時的に遠隔からの人の手による操縦を介して復帰可能な位置まで移動させるような場合に、遠隔操縦が活用できます。 (4)ロボット/モビリティ、センサーからカメラまで様々なデバイスの接続 ロボットや自動車などのモビリティ、またモビリティ上に搭載されるセンサーなどの様々なデバイスを接続して、モビリティのデータ収集するための、エッジデバイス用のコンポーネントを提供します。本コンポーネントは、エッジデバイス側のソフトウェアコンポーネントである「Device Connector」として提供します。 Device Connectorはターゲットとなるデータデバイス(モビリティ自体、センサー、カメラなど)からデータを取得して、intdash Serverにデータ送信を行う「intdash Edge Agent」にデータを受け渡す機能を持ちます。 Device Connectorを利用することで、下記のような様々な形式のデータをフュージョンストリーミングでintdash Serverへ送信することが可能です。 ロボットにおけるROS1/ROS2のメッセージ モビリティにおけるCAN(Control Area Network) 汎用センサー、ビデオなどのセンサーデバイス (5)データの収集・保存と利活用 モビリティから収集されたすべてのデータはサーバに永続化されるため、運用時のデータは過去データを再生することで当時の状況を詳細まで確認することが可能です。 これにより、異常発生時には、関係者によるデータレビューもすぐに行うことができます。これは国内外など関係者が様々な拠点にいる場合でも同様で、同じデータに各拠点からアクセスしてデータ内容を確認することができます。 また、サーバに保存されたデータを活用することで、詳細なデータ解析フローを実現することもできます。運用時に複数のモビリティのデータを都度、人の手により確認して、解析することは現実的ではないので自動化された解析フローを構築することで、効率的な管制制御システムの運用を行うことができます。 (6)豊富なSDK・APIを利用した拡張開発 intdash CONTROL CENTERでは、用意されたSDK、APIを利用することでリアルタイムデータ処理アプリケーションの開発が可能です。お客様ごとの、利用されるモビリティや、運用シーンに合わせて下記のような拡張開発を行うことができます。 アラート機能 詳細データを活用した異常検知 リアルタイム分析による異常検知 AIを利用したリアルタイム映像分析 モビリティ個別の稼働率計算 SDKなど拡張開発に必要となる情報は下記リンクにも置いてありますので、ご興味ある方は参照下さい。 aptpod,Inc Development aptpod,Inc. Docs intdash CONTROL CENTERは上記の機能を実現するソフトウェアコンポーネントを提供します。これをベースにユースケースに応じた、拡張性を持ったシステム開発が可能になります。 前述の通り、ゼロから開発を進めると費用、期間ともに大きく掛かってしまう、モビリティ/ロボット向けの管制制御システム、遠隔操縦システムですが、本ソリューションを導入し、全ての機能または一部の機能を活用頂くことで、 低コスト、短期での本番運用に向けたシステム構築 ができるようになります。 上記にあげた機能のいずれかの実現を目ざしている、または、上記のような機能実現に課題があるといったお客様は是非、当社へご連絡頂き、intdash CONTROL CENTERの活用について一緒に検討させて下さい。 intdash CONTROL CENTERを使ったデモ紹介 これまで説明してきたintdash CONTROL CENTERを利用した、2つのユースケースにおけるデモを紹介します。1つ目は 自律走行ロボット群のフリート管理 、2つ目は 移動ロボットの遠隔操縦・コマンド指示 のユースケースです。 (1)自動走行ロボット群のフリート管理 ROS(Robot Operation System)のシミュレータである Gazebo 上で、屋内工場内に6台の自動走行ロボットを稼働させています。ロボット内はROS1で構築しています。 それぞれのロボットにはintdash Serverとデータ送受信を行うための、intdash EdgeとDevice Connectorを搭載しており、各機体の位置情報、カメラ映像などをリアルタイムにintdash Serverに送信しています。 intdash Serverに送信された位置情報をもとに、intdash CONTROL CENTERのリファレンスアプリケーションであるフリートマップアプリケーションを使用して、 各機体の位置や状態、カメラ映像をリアルタイムに可視化 することができます。 各ロボットの詳細なデータを確認したい場合には、フリートマップアプリケーションからVisual M2M Data Visualizerに遷移して、 機体データの詳細をダッシュボード上で確認 することができます。 今回のデモではVisual M2M Data Visualizerで、各ロボットのSoC, SoHや稼働率をダッシュボード上に値表示、グラフ表示を行っています。 本デモのように遠隔監視室から、稼働しているロボットのマップ上の位置や、SoC/SoH、稼働率といった稼働状況を監視することで、 管理対象のロボットが安全かつ効率的な作業が実施できているかを確認 することができます。 システム構成 youtu.be 【動画説明】 3:30頃まではフリートマップアプリケーション上に複数のロボットの位置情報を表示しています。画面下部には選択しているロボットの名称や状態、カメラ映像を表示しています。 3:30頃からVisual M2M Data Visualizerで6台のロボットそれぞれの詳細データをダッシュボード上に表示しています。速度、角速度、SoC/SoH、稼働率などを値で表示しています。 4:30頃からは同じくVisual M2M Data Vizualizerで6台のロボットのSoCや稼働率の推移をダッシュボード上でグラフ表示しています。 本動画では同じディスプレイでそれぞれのアプリケーションを順番に表示していますが、実際の遠隔監視室では複数のディスプレイを配置して、それぞれのアプリケーションを並べて同時に確認することもできます。 (2)移動ロボットの遠隔操縦・コマンド指示 オープンソースの移動ロボットプラットフォームである「 Turtlebot3 」(実機のロボット)とGazebo上のロボット(シミュレータ上のロボット)を使用した遠隔操縦のデモです。 制御対象のロボット(Turtlebot3)はROS1で構築しており、ロボットに搭載したRaspberry Piにintdash Severとデータ送受信するためのintdash EdgeとDevice Connectorを搭載しています。 同じく、Gazebo上のロボットにもintdash EdgeとDevice Connectorを搭載しています。 遠隔操縦用のコントローラとしては「 DualSense 」を使用しています。操作情報をintdash Serverに送信するために、Raspberry Piを用意し、intdash EdgeとDevice Connectorを搭載しています。DualSenseとRaspberry PiはBluetoothで接続して、コントローラのボタン、ジョイスティックの情報をリアルタイムにRaspberry Pi上のintdash Edgeを介して、intdash Serverに送信します。 intdash Serverに送信された操作情報は、ロボット上のRaspberry Piが受信して、操作情報に従い、ロボットの車輪やアームを動作させます。 本デモの構成では、ロボットに搭載されたカメラ、センサ情報をリアルタイムにVisual M2M Data Visualizerで監視を行いながら、 必要な場合には、人の手を介して遠隔操縦を行うことが可能 となります。 また、Visual M2M Data Visualizer上のボタンを操作することで、ロボットに対するコマンド送信を行うこともできるので、 遠隔監視室からロボットへ指示を行うことが可能 です。 システム構成 www.youtube.com 【動画説明】 0:30頃までは、画面右下に表示されているカメラ映像のDualSenseを操作して、Gazebo上のロボットを遠隔操縦しています。Gazebo上のロボットの動きは画面左下のGazebo上のカメラ映像と、LiDAR情報から確認できます。 0:40頃に掛けて、遠隔操縦の制御対象をGazebo上のロボットから、Turtlebot3に切り替えています。 1:15頃までは、DualSenseを操作して、Turtlebot3を遠隔操縦しています。Turtlebot3の動きは、画面右下のカメラ映像と、画面左上のTurtlebot3に搭載したカメラの映像、LiDAR情報から確認できます。 1:25頃からは、Visual M2M Data Visualizer上の、画面右上の青いボタンを押すことで、Turtlebot3に「アーム位置をリセットする」コマンドを送信しています。青いボタンを押すと、画面右下のTurtlebot3のアーム位置が初期位置に移動する様子が確認できます。 おわりに 今回は当社が提供する、 モビリティやロボットのリアルタイムかつインタラクティブな管制制御システムを迅速に構築するためのソリューションフレームワークである「intdash CONTROL CENTER」 を紹介させて頂きました。 intdash CONTROL CENTERを導入して管制制御システムを構築することで、下記の特長を持つシステムが実現できます。 低コスト、短期でシステムを構築 拡張性のあるシステムを実現 クラウドに保存されたデータを利活用可能 ロボット管制・管理、遠隔操縦、フリート管理などに課題をお持ちの方は、是非当社にご連絡下さい。 intdash CONTROL CENTERを活用頂くことで、課題解決を行い、本番運用に向けた管制制御システムを迅速に実現できるようお手伝いできればと思います。 また、当社ではロボティクス分野向けのソリューションの他にも、自動車分野向けのパッケージプロダクトも開発を進めています。 aptpod,Inc REMOTE CAL 【intdash AUTOMOTIVE PRO REMOTE CAL】ECU遠隔適合システムのご紹介 - aptpod Tech Blog intdash CONTROL CENTER、intdash AUTOMOTIVE PRO REMOTE CALともにパイロットユーザーを募集中です! aptpodは今後も、各産業のDXを進めるためのプロダクトを企画、開発を進めていきます。intdash及び、当社プロダクトで解決できる課題があれば、是非、お気軽に下記リンクからご連絡下さい。 aptpod,Inc. Contact
aptpod Advent Calendar 2022の14日目、担当はコーポレート・マーケティング室、デザインチームの「チェン ・ルイ」です。普段は社内製品のアプリケーションのUIデザイン業務を行なっています。 今年8月から弊社デザイン業務が少しずつSketchからFigmaに移行しています。 弊社のデザイナー高森の8月の記事 では俯瞰的にデザインツール変更や導入するときに確認したポイントを紹介しました。 今回は実際にFigmaを使ってデザインファイルを制作、管理するときに実用的なTipsをいくつか紹介します。 グリッドの整理機能 Selection Colorsで色変数の一括適用 Sectionの活用 PageやSectionを利用してコンポーネントをグルーピングする 最後に グリッドの整理機能 複数のオブジェクトを選択して配列したあと、隅にあるグリッドアイコンをクリックすると、配置を整理できます。 オブジェクトはドラッグして再配置したり、直接画面で間隔を調整したり、プロパティパネルで数値を調整することも可能です。 Sketchではグリットを作る場合、1行、1列ずつ整列する必要があります。Figmaのグリッド整理機能があると時短になります。 グリッドレイアウトを制作するときに試してみましょう。 Selection Colorsで色変数の一括適用 デザインコンポーネントを整理するとき、色を一括で色変数に適用したい場合、 Selection Colors の設定を利用すると便利です。 Selection Colors はFills、Strokes、Solid colorsなどを跨いで一括で色を表示しています。 特に複数のレイヤーを含むコンポーネントは、レイヤーを1つ1つ選択しなくても大丈夫なので、かなり時間が節約できます 。 以下の例はボタンのテキストに色変数を適用する例になります。 【 テキストレイヤーを選択して適用する場合 】 コンポーネントのテキストレイヤーを1つ1つ選択するとかなり手間がかかります。 【 Selection Colorsを利用する場合 】 ボタンを一括で選択して、まとめて変換できます。 コンポーネントに色変数の一括適用したいときに、 Selection Colors を使ってみてください。 Sectionの活用 Sectionは最近追加された機能であり、Sketchには無い、上位のレイヤータイプです。複数のFrame(Sketch のart board相当)をまとめて管理することが可能です。 コンポーネントやデザインガイドを整理するとき、グルーピングして管理したい場合もあります。コンテンツの増減によって、全体の背景の大きさを調整する場合もあります。この場合、Sectionをおすすめします。 【 Frame 】 【 Section 】 上記動画の通り、Frameを使用する場合Contraintsの設定はデフォルトで Scale になり、そのまま広げるとUI要素のサイズも変わってしまうため、まずはLeft、Topに設定する必要があります。 一方、Sectionを利用するとContraintsの設定が関係なく自由に広げてもUI要素のサイズが変わりません。細かいところですがかなり作業時間を節約できます。 現状、SectionとFrameの使い分けは主にこのようになります。 Section→ コンポーネントのグルーピング Frame → UI画面のベース UIコンポーネントを整理するときに、Sectionを活用してみてください。 PageやSectionを利用してコンポーネントをグルーピングする Sketchと同じく、コンポーネントの「/」命名規則が適用されます。 過去SketchでComponentの名前をつける際、「/」で分割を多用するとコンポーネント名が長くなりすぎます。グループ名の漏れやタイポが原因でグルーピングできず、後程編集し直すこともたまに発生しました。 Figmaでは、PageやSection、Frameを使用してカテゴリを作成することができます。コンポーネントをフレームに追加すると「/」と同じように整理されます。コンポーネントを新しいフレームにドラッグするだけで、すぐにコンポーネントを再編成できます。 PageとSectionの活用によって、あらかじめSectionやページに配置と最初からグルーピングでき、コンポーネントの名前が短くなり、二度整理する手間をなくすことができます。 現状iconの画面や目次の階層はこのような関係になります。 階層の順番はこのようになります: ページ > Section > Frame > Component名 UIコンポーネントを整理するときに、是非活用してみてください。 最後に Figmaを活用するTipsは他にもたくさんありますが、今後アップデートがあればまた紹介していきたいと思います。
aptpod Advent Calendar 2022 の13日目を担当します、ECUソリューショングループの村松です。 自動車産業は、100年に一度の大変革の時代といわれており、CASE Connected(コネクティッド)、Autonomous/Automated(自動化)、Shared(シェアリング)、Electric(電動化)、クルマを介した様々なサービス化が進んでおります。 排ガス規制をクリアする為に導入されたECU(Engine Control Unit)は、いつのまにかECU( Electronic Control Unit)となり、電気が流れる車載機能のほぼ全てにECUが存在すると言われております。 昨今、安全性や快適性が追求され複数のECUにまたがるロジックの開発、グローバル開発車両に伴う各国での検証は、実走行試験の複雑化や長期化が課題となっております。 aptpodでは、低遅延、ハイスループットのIoTソリューションintdashの利点と汎用計測適合プロトコルXCP(eXtended Calibration Protocol)による遠隔適合ソリューションintdash AUTOMOTIVE PRO REMOTE CAL(CALibration)で、皆様の課題解決へお力になりたいと考えております。 今回は、β版のリリースを予定しております intdash AUTOMOTIVE PRO REMOTE CAL をご紹介したいと思います。 2021年にご紹介しました CCP(CAN Calibration Protocol) DAQ(Data AcQuisition)サービスについてのtech Blog もご参照ください。 tech.aptpod.co.jp REMOTE CAL システム構成 REMOTE CALの機能 遠隔適合 ユースケース 適合試験者 現地作業者 最後に REMOTE CAL システム構成 REMOTE CALは高速で確実な遠隔データパイプライン環境を実現するサーバーシステム、及び車両計測適合用に制定されたマークアップラングエッジA2L(ASAM MCD-2 MC Language)に対応したWebアプリケーションで構成されます。それにより、計測機器メーカー各社のツールと連携し遠隔よりリアルタイムでの計測適合環境を構築します。 REMOTE CAL UI ECU内部情報が記載されたA2Lの登録、リモート先のEdgeの設定、データ可視化 ECU内部パラメータ(定数・変数値)の表示と変更 REMOTE CAL Server EdgeからのECU内部情報を変換、時系列に整列と格納 REMOTE CAL UIからのECUパラメータ変更をEdgeへ伝達 REMOTE CAL Edge 各社ECU計測器・インターフェースと連携し、ECU内部情報をserverへ転送 UIで設定されたECUパラメータを測定器I/F経由にてECUへ書き込み REMOTE CALの機能 ECU内部構造を記載したA2Lファイルの統合的に管理 XCP接続可能なECUに対しRAM値の計測及び、修正や変更など適合が可能 リアルタイムでのECUの計測・適合と合わせ、映像、位置情報、CANなど様々な情報をノンプログラムのダッシュボード上に配置し可視化 遠隔適合 ユースケース 適合試験者 試験車両をリアルタイムでECU内部実測値、画像、位置情報、CANなどを同時にモニタ可能 遠隔にある複数台の試験車両の情報を同時にダッシュボード上に表示 トラブル時の状況をモニタでき、遠隔地にある試験者へ迅速なサポートが可能 現地作業者 ダッシュボードにより、車両の状況の確認可能 適合作業に必要な車両の操作、確認可能 トラブル時の現地対応はもとより、開発拠点からのリモートサポート可能 最後に intdash AUTOMOTIVE PRO REMOTE CALは、製品版リリースへ向けて順次機能を拡充していく予定です。β版入手方法、製品のご質問や追加機能のご希望などございましたら、お気軽に こちらのリンク までご連絡ください。 www.aptpod.co.jp
本日は aptpod Advent Calendar 2022 の12日目、担当は開発本部のやべです。普段は EDGEPLANT 関係の開発業務をやっています。 さて、皆さん、スプラトゥーン3やってますか?12月から新シーズンが始まってますます楽しくなってきましたね。 aptpod には circle_switch なる slack のチャンネルがあるのですが、最近はもっぱらスプラトゥーンの話題です。先日も 8人集めてプライベートマッチやってました。 当初は最近調査していた Multipath TCP に関するきっちりした技術記事を書こうと考えていましたが、業務以外での社内の雰囲気も伝わるかと思い、このネタにしてみました。 概要 環境構築編 入力側の準備 制御(ゲーム操作)側の準備 調整編 実戦編 おわりに 目次はこんな感じです。イカ、よろしく~ ※本記事ではところどころスプラトゥーン3のプレイ画像が挟まりますが、任天堂のガイドラインを考慮してモザイク処理を施しております。見づらいですがご容赦ください。 概要 今回の目標は、キーボード入力を サーバーを経由 して、Switchにコントローラーの情報として送り、 実際に操作する ところまでになります。 まずはおおまかな構成について説明しておきます。 データの流れ 今回は手元にあった機材を流用して、よくある制御のパターンに近い構成にしています。 入力側:開発試作機( こちら で紹介しています)にキーボードを接続 制御側:Raspberry Pi 4B を Bluetoothコントローラーとして動作するように設定し、Switch からHDMI映像を入力する 実際の機材の接続は、こんな感じでした。 入力側環境 制御側環境 当初制御側のエッジでは Raspberry Pi 4B のUSBポートを直接利用していたのですが、それだと Bluetooth 接続時にHDMI入力が切れる問題が発生したため、セルフパワー給電ができるUSBハブを介することで動作を安定させています。写真に写っているのは StarTech 社のハブですが、同社の製品は信頼性が高く、aptpod でもよく採用しています。 www.startech.com 環境構築編 データのやり取りには、aptpod の製品である intdash Edge を利用しています。利用方法などを知りたい方は、 こちら をご覧ください。 入力側の準備 入力側では、以下のデータを取得してサーバーに送ります。 キーボード入力 カメラ映像(キーボードを映すもの) カメラについてはデフォルトで intdash Edge がサポートしているため、取得設定を行うのみです。キーボード入力については、実装スピード重視で、Python のライブラリを利用しました。 github.com キーボードの入力は、それぞれコントローラーの何らかのボタンに割り当てます。入力内容を変換する処理をどこでやるかは全体のシステム設計にもよりますが、データサイズを均一にするため整形を行ってサーバーに送りたいので、今回は入力エッジ側で変換するようにします。 CONTROLS = { "l" : "BA" , "k" : "BB" , "i" : "BX" , "j" : "BY" , ... } このような形で、入力データを2文字のコントローラー用のコードに変換しました(例:BA = Button A)。このデータを、ループして push/release のタイミングで送っていきます。 while True : event = keyboard.read_event() if event.name == "esc" : print ( "escape was pressed" ) break if not event.name in CONTROLS: continue ctrl = CONTROLS[event.name] if event.event_type == keyboard.KEY_DOWN: on_push(ctrl) else : on_release(ctrl) on_push 、 on_release では、FIFO経由で、データ送信処理を管理しているモジュール(Edge Agent)に対してデータを送っています。 制御(ゲーム操作)側の準備 制御側では、以下のことを行います。 キーボード入力をサーバーから受け取って、コントローラーとして動作する HDMI映像をサーバーへ送信する HDMI入力は、安価なキャプチャデバイスを使ってUSB経由で入力します。設定自体は一般的なUSBカメラと同じもので動作します。コントローラーとしての動作については、こちらの Python のライブラリを利用しました。 github.com 入力側からは、コントローラーの入力に対応したコードが送られてくるため、制御側では実際の定義に置き換えるようにします。 BUTTONS = { "BA" : nxbt.Buttons.A, "BB" : nxbt.Buttons.B, "BX" : nxbt.Buttons.X, "BY" : nxbt.Buttons.Y, ... } これらのデータが届いたタイミングで、対応するボタン入力(もしくはスティック入力)が動作するようにします。 while True : recv = fifo.read( 18 ) if len (recv) != 18 : continue d = decode(recv) if d[ "status" ] == 0 : # release button continue ctrl = d[ "ctrl" ] if ctrl in BUTTONS: nx.press_buttons(controller_index, [BUTTONS[ctrl]]) ... 調整編 とりあえず作っては見たものの、入力に対して反応が悪くなイカ? 初期バージョン 操作情報と一緒にCPU負荷を送っているのでそれを見てみると、制御側の負荷がかなり高くなっています。 制御側の負荷状況 おそらく入力を処理しきれていないため、かなり遅延が発生しているようです。そこでライブラリのコードを参考に、使うAPIの変更やキーボード入力の間引き、押し込み時間の調整など、いくつかの対策を行ったところ、かなり改善しました。 負荷軽減バージョン 赤枠のCPU使用率を見てみると、負荷が40%程度軽減されています。 スペシャル(必殺技的なやつ)もしっかり打てるぞ! スペースキーを押して 発動! 精度はさておき、ここまででキーボード入力を受け取って操作を行うというタスクについては完了しました。 実戦編 というわけで、いざ実戦投入! といっても、見知らぬ方々にご迷惑をおかけするわけにはいかないので、冒頭で話した社内プライベートマッチで一戦だけ行いました。 おろおろしているところを撃ち抜かれる図 結果、瞬殺。 映像の遅延やボタン入力の反応はさほど悪くないものの、スティック側の入力反映が微妙で思ったように動きません。また、移動しながらの視点変更など、同時入力を必要とするケースを考慮していなかったのも、けっこう辛いポイント。実際に社内で制御を扱っているプロジェクトでは色々と考慮して設計されているわけで、遠隔制御は1日にしてならず、と痛感いたしました(みんなすごい!)。 おわりに イカがだったでしょうか?intdash を利用した遠隔制御の雰囲気が伝わっていれば幸いです。 実際に自分でやってみた感想としては、単なる疎通まではたいして時間はかからないものの、その後の操作感の向上や安定性などを詰めていく作業が大変でした。ボードゲームのようなものならこれでも十分ですが、今回扱ったスプラトゥーンのように複雑な操作が必要なゲームに実戦レベルで対応するためには、更なる改善検討が必要になります。操作データの送信方法の見直しや処理の非同期化、Python以外の言語への置き換えなど、やれることはまだまだあります。 こんな記事もあるように、世の中的には遠隔制御とゲームの親和性も注目されていたりするので、今回やった取り組みは案外エントリーとしてはいいお題なのかな?とも思いました。 automaton-media.com ほな カイサン!!!
はじめに aptpod Advent Calendar 2022 9日目の記事を担当する、開発本部の加藤と申します。 aptpodでは自社製のハードウェアも提供しております。現在、コンピュータ製品には EDGEPLANT T1 がありますが、異なるニーズやユースケースにも対応できるような、測定用のペリフェラルを内蔵したコンピュータの試作機開発を行っています。この試作機の特長等についてご紹介させていただきます。こちらの試作機にご興味をお持ちいただけましたら、当社までお問い合わせください。製品化に向けたロードマップや、試作機の試用等についてご案内させていただきます。 はじめに 開発の背景 インターフェイスが少なくUSB周辺機器とセットで利用する必要がある 物理的なサイズが大きい 仕様概略 仕様詳細 外観および入出力 SOM 通信機能・GNSS機能 USB CAN 加速度・角速度センサ アナログ入力 電源ON/OFFの仕方 ストレージ 防水機能 社内テスト 動作温度範囲 静電気放電試験 ファスト・トランジェント/バースト試験 放射エミッション試験 自由落下試験 未実施の試験 実際のデータ収集での使用例 横浜ロボットワールド2022 最後に 開発の背景 aptpodでは、 intdash という双方向データ伝送プラットフォームを使用したデータ収集および遠隔制御サービスをご提供しております。 冒頭申し上げました通り、自社製のコンピュータには EDGEPLANT T1 があり多くのケースでご提供しております。このコンピュータはエッジ側でGPUを活用したAI処理を行う意図をもって開発しましたが、自社サービス専用ということではなく汎用的にも使用できるように設計されています。実際に アマゾンで単体販売 もしております。自社のサービスの全てでこの EDGEPLANT T1 を使おうとする場合には、以下の課題があります。 インターフェイスが少なくUSB周辺機器とセットで利用する必要がある 汎用的に使用できるようにUSBのポートを多く持たせ、インターフェイスの種類は少なく設計している為、USB周辺機器とセット利用が必要となります。その結果USB機器の製造も別途必要となります。製造する製品の種類が増えてしまうのは、生産管理が難しい、価格も高くなるなどのデメリットがあります。 物理的なサイズが大きい 処理能力が高くその際の放熱なども考慮されている為、それを必要としないケースではコンピュータ単体のサイズが必要以上に大きくなっていることになります。また、USBポートやそこからの供給電力を多く用意し多様な組み合わせに対応可能な汎用性を持たせており、用途が限定されるCANインターフェースやアナログ/デジタルインターフェースは内蔵しておりません。多くの場合、 EDGEPLANT CAN-USB Interface と組み合わせて構成しているのですが、測定対象が一定数以下の場合には、物量が必要以上に大きくなってしまいます。 課題解決の方法として、下記の条件を満たすエッジコンピュータを準備することを考えました。 弊社のデータ収集サービスでよく使われる測定用の機能を内蔵している。 内蔵している機能は全て同時に使用できる。 小型・軽量 より詳細な条件は、以下の通りとなります。 LTE通信機能を有する。 弊社のデータ収集サービス(データ完全回収)で使用できるCANやデジタル入力、アナログ入力機能を内蔵している。 内蔵している測定機能を全て同時に使用できる性能を有する。 自動車のデータ収集に必要十分な品質を備える。 動作電圧  9V~36V、動作温度範囲 -20℃~65℃。 長期間継続して購入可能であることが期待できる。 上記条件を満たすコンピュータが市販されていないか探してみましたが、全ての条件に合致するものは見つかりませんでした。その為、自社で開発・試作を行いました。 仕様概略 仕様概略につきましては、横浜ロボットワールド2022の参考出展時に配布させていただいたリーフレットをご覧いただければと存じます。 仕様詳細 仕様の意図や工夫した点などご説明させていただきます。 外観および入出力 サイズは幅88mm×奥行き94mm×高さ32mm(取付板を含まない)、質量は250gとなっております。自動車のダッシュボードの収納スペースやオートバイのシート下収納スペースにも収まるようにコンパクトにまとめました。 自動車に搭載する場合のサイズ感 入出力端子の配置は以下のとおりとなっております。 正面 背面 SOM SOMは、Raspberry Pi CM4を採用しました。コストパフォーマンスが高いこと、Raspberry Piは世に多く出ているハードウェアであり既にそこで動作するソフトウェアも多く作られていると考えられること、Linuxが動作すること、動作温度範囲が広いこと等が選定の理由です。Raspberry Piは弊社でも活用事例は多いです。以下は、Raspberry Piを使用した過去の記事のリンクです。 "リアルタイム"デジタルツインデモを展示しました IoTボタンによる回数記録基盤をAWSとRaspberry Piで構築する Bluetooth Low EnergyのclientアプリをBlueZとpythonで作ってみた intdashを活用したシステム開発 5Gのネットワークを計測してみた 通信遅延発生時にTurtlebot3を安全に遠隔制御する技術 ラズパイでCAN通信をして、車両の診断データを送受信してみた 通信機能・GNSS機能 内蔵の通信機能はLTEを採用しました。aptpod のユースケースではLTE通信が欠かせない為、必須要件であるからです。通信モジュールは、SIERRA WIRELESS社のEM7431にしました。これまで弊社で使用していたEM7430と比較して安価であること、GNSS機能も内蔵しておりコストパフォーマンスが高くなることが理由です。有線LANやWi-Fiが必要な場合には、USBから拡張していただく想定です。 USB USBポートは2ポートとしました。想定したのは、 EDGEPLANT USB Camera と CAN-USB Interface で2ポート、あるいは EDGEPLANT USB Camera とマイクで2ポートという使い方です。 データ収集ではカメラは使用頻度の高いデバイスなのですが内蔵してしまうと取付位置の自由度が下がってしまう為、USBによる外付けとしました。 マイクのインターフェースは、3.5㎜ミニジャックのものとUSBのものがあるのですが、データ収集サービスではマイクの需要はそこまで高くない為、汎用的に使えるUSBを使用することを考えました。 USBのコネクタはネジ止めができません。振動・衝撃でコネクタが外れないようにする為の高保持力(15N)のコネクタを採用しました。 CAN 接続したCANバス上のデータを取得する為のインターフェースです。測定用のサービスに使用する場合、CANに流れるデータ全てを記録する必要があります。CANのデータは高頻度で発生する可能性があり取りこぼしを起こさないようにする為に、専用のマイコンで受け取る仕組みにしました。内部回路は弊社の製品のCAN USB-Interfaceと同じにしました。 加速度・角速度センサ 筐体中心部に加速度・角速度センサICを実装しています。移動体の測定に使用することを想定している本製品にはあるべき機能と考えました。フルスケールが加速度は±2/±4/±8/±16g、角速度は±125/±250/±500/±1000/±2000dps、分解能はそれぞれ16bitです。 アナログ入力 下記の様な入力を想定して汎用的なアナログ入力ポートを4ポート設けました。入力回路は、インピーダンスを高くして接続先の動作の影響が少なくなる様意識しました。 各種状態検知:測定対象が出すエラー信号やLED制御信号の検出を行うケース センサ入力:測定対象に内蔵されているセンサーの電圧を測定するケース 各所電圧・電流検知:電圧や電流の測定を行うケース 外部スイッチ:自動車等の測定で運転者が任意のタイミングでスイッチを押し、後で検索しやすくするケース 電源ON/OFFの仕方 自動車に接続する場合を想定して、バッテリーが接続された状態でイグニッションキー(ボタン)の電圧を検出して電源ONする仕組みにしています。専用の入力端子があり、そこに9V以上の電圧印加で電源ONということです。入力がオープンかGNDレベルになると終了処理してからOFFになります。イグニッションキー(ボタン)は監視用のマイコンで常にチェックしています。このマイコンはCANバス信号もチェックすることが可能で、その内容によって電源ON/OFFする仕組み(Wake on CAN)も準備しています。電源OFF時の待機電流は、バッテリー電圧12V・周囲温度25℃の条件で約5mAでした。 ストレージ Raspberry Piシリーズと同様、ストレージはmicroSDカードを採用しました。データの信頼性の高い産業用のSLCモード64GB品を使用しました。また、意図しないバッテリー断でデータ破損しないように電断保護機能有りのものを選びました。 防水機能 防水機能について検討したのですがどうしても小型化と相反する為、外付けの防水ケースを別に設計・試作しました。ケーブルを通す防水ゴムは、ケーブルを先に通してからケーブル端を加工するものが多いのですが、この方式ですとUSBケーブルやアンテナケーブルの接続が困難になる為、コネクタ付のケーブルに取付けできるタイプを採用しました。 防水ケース 社内テスト 試作機で行った社内試験について簡単にご紹介させていただきます。国際規格の試験も行っておりますが、試験を行った試験場は認証試験場ではないことにご注意ください。 動作温度範囲 -20℃および65℃で起動及び24時間の連続動作を確認しました。 静電気放電試験 試験方法はIEC61000-4-2で、接触放電±8KV、気中放電±8KVで、動作停止しない・故障しないことを確認しました。 ファスト・トランジェント/バースト試験 試験方法はIEC61000-4-4で、電源ラインにノイズを印加する試験を行いました。テストレベル1(500V)にて、自己回復不可能な機能障害が無いことを確認しました。接続するディスプレイやUSBデバイスによって結果が大きく異なり、苦労しました。 放射エミッション試験 試験方法は、FCC Part15 Subpart Bで3m法で行いました。これも接続するディスプレイやUSBデバイスによって結果が大きく異なり苦労しました。本試作機単体(接続デバイスを選べば)では、ClassBで合格できそうなことを確認しました。 自由落下試験 試作機単体で1mの高さからコンクリートに自由落下させる試験を行いました。故障・変更・動作異常などなく問題ないことを確認しました。 未実施の試験 製品化を行う場合には最終の仕様にて下記についても試験を行いたいと考えております。 振動・衝撃試験 過渡伝導エミッション/イミュニティ(ISO7637-2) 放射イミュニティ(IEC61000-4-3) 伝導イミュニティ(IEC61000-4-6) 実際のデータ収集での使用例 本試作機を使用したデータ収集の例をご説明させていただきます。 試作機にソフトウェア( intdash Edge )を実装し、実運用に近い自動車での動作を確認しました。 構成図 搭載物一式 下記のデータ全て取得できることを確認できました。 カメラ:1台、解像度はHD、15fps GNSS:1台、位置検出、1秒に1回更新 CAN:内蔵CAN1ch+ EDGEPLANT CAN-USB Interface を1台追加=合計3ch 角速度センサ:3軸、各分解能16bit、104sps 加速度センサ:3軸、各分解能16bit、104sps 測定データは Visual M2M Data Visualizer で確認しました。各種データを様々な方法で可視化可能なツールで、測定中にリアルタイムで確認することも測定後に確認することも可能です。実際の表示画面を添付します。 Visual M2M Data Visualizerでの確認 横浜ロボットワールド2022 横浜ロボットワールド2022の弊社ブースでこの試作機の展示をさせていただきました。私も説明員として立ち会わせていただきました。 展示品 充電式電池(単3×8本)で駆動してワイヤレスにして、回転台の上に載せました。 EDGEPLANT CAN-USB Interface を接続。アナログ入力には測距センサも接続しました。回転させるとカメラ画像、内蔵の角速度センサのデータ、測距センサのデータが、リアルタイムに滑らかに表示される気持ち良い仕上がりになりました。 Visual M2M Data Visualizer画面 動作をみて頂いたお客様にも「センサーの状態がリアルタイムで確認できるのは良い」「電池駆動できるのは面白い」などご意見いただき大変好評でした。ご来場いただきありがとうございました。 最後に 試作したコンピュータを使用した弊社のデータ収集サービスについてご理解いただけたかと思います。もし、 intdash や本試作機にご興味いただけましたら、是非ご連絡をお願いします。また、本記事では記載できませんでしたが試作機(ハードウェア)は遠隔操作または自律移動ロボットのECUとしても利用可能ではないかと考えております。もし、Raspberry Piベースの堅牢なコンピュータにご興味あれば是非お声がけをお願いします。仮に本試作機(ハードウェア)をご評価いただけるということであればすぐにご提供致します。ご連絡をお待ちしております。最後まで読んでいただきありがとうございました。
はじめに こんにちは。 aptpod Advent Calendar 2022 8 日目を担当する、SRE チームの柏崎です。 SRE チームは、 intdash のインフラ関連の業務はもちろんですが、自社のコーポレートサイトやオフィスのネットワーク等の運用など、いわゆる「社内 SE」的な業務も担当しています。 今回は、社内 SE 業務のうちの 1 つで、私がアプトポッドに入社して以来 8 年ほど面倒を見ている、GitLab の運用についてご紹介しようと思います。 はじめに GitLab 運用ノウハウ 利用するバージョンは 1 つ前 ドキュメントをよく読む Ruby と Node.js Ruby のメモリアロケータ CI 環境 実行基盤 Docker Hub のミラーリング おわりに GitLab 運用ノウハウ ここでは、これまで長く運用してきた GitLab 運用の知見の一部をご紹介します。 ※ 現在 GitLab は、Omnibus と呼ばれる deb/rpm パッケージでのインストールや、Helm chart での k8s へのインストールなど、様々なインストール方式が提供されていますが、アプトポッドでは当初からソースインストール方式を使い続けています。 利用するバージョンは 1 つ前 GitLab は、年 1 回のメジャーリリース、月 1 回のマイナーリリースがあり、都度様々な新機能が追加されています。 私は、「1 つ前のマイナーリリース」の最新を追いかけるようにしています。 GitLab は、バグフィックスやセキュリティフィックスが含まれるパッチリリースが頻繁に行われています。 最新リリースでは、不具合やアップグレードドキュメントの不備があることが多く、そういったものは後追いのパッチリリースで修正されていきます。 当初は最新リリースを追いかけていましたが、前述のような不具合等で切り戻す事も多く苦労しました。 1 つ前のマイナーリリースを使うことで、これにアップグレードする頃には大きな不具合は修正されていて、切り戻しはほとんど発生しなくなりました。 ( こんな 1 行修正 MR を出したこともあります。) ドキュメントをよく読む アップグレード時に注意すべき点を把握するため、アップグレード手順だけではなく、インストール手順も含めてドキュメントをよく読むようにしています。 具体的には下記です。 Installation from source Ruby, Go, Git, Node.js, Yarn などの必要バージョンに更新が無いか、インストール手順に更新が無いか、重点的に確認します。 Installation system requirements Redis, PostgreSQL などの必要バージョンに更新が無いか確認します。 Version-specific upgrading instructions バージョン毎に、アップグレード時の注意点が書かれています。 Ruby と Node.js Ruby は rbenv、Node.js は nodenv を利用してインストールしています。 env 系を利用することで複数のバージョンを手軽に同居させられるので、Ruby や Node.js の必要バージョンが変わった際に、アップグレード作業時間外に事前に準備することができるようにしています。 Ruby のメモリアロケータ GitLab のような Ruby on Rails なアプリケーションを長く稼働していると、メモリ断片化によるメモリ使用量の逼迫に悩まされると思います。 当初は定期的に Puma ワーカーや Sidekiq プロセスを立ち上げ直すことで対処していました。 メモリアロケータを jemalloc にした Ruby を利用するようにしてから、メモリ使用量の上昇がゆるやかになり、このような対処が不要になりました。 jemalloc な Ruby に差し替えてからメモリ使用量が改善した様子 CI 環境 ついでに、CI 環境についても触れておきます。 GitLab は早くから CI サービスが統合されていて、私が GitLab 運用を担当するようになってからは、CI 環境の整備に力を入れてきました。 現在では、製品のアプリはもちろんのこと、プロジェクト独自のアプリも含めて、ほとんどのリポジトリで CI が実行されています。 実行基盤 Docker Machine で、GCP でプリエンプティブル VM インスタンスを利用し、低コストで運用しています。 CI は、ジョブ毎に n1-highcpu-4 (4 vCPU, 3.6 GB) のマシン上で実行され、最大 20 並列で動きます。 プリエンプティブル VM インスタンスを利用していることで、このような「つよつよ環境」を利用しても、月額 1 万強で済んでいます。 月額 1 万強 結構な数の CI が実行されている様子 Docker Hub のミラーリング CI は、ジョブごとに新規の環境で実行されるので、Docker Hub からのイメージの取得が都度行われます。 CI 実行基盤と同じネットワーク内に レジストリのキャッシュ を用意し、イメージ取得の高速化・トラフィックの抑制を行っています。 おわりに いかがでしたでしょうか。 GitLab や CI 環境の運用は、「みんなの業務は俺が支えてるんだ」感を味わえるので、実は結構好きな業務です。 だいぶまとまりが無い内容になってしまいましたが、アプトポッドの GitLab・CI についてのご紹介でした。
aptpod Advent Calendar 2022 7日目の記事を担当する、intdashグループの落合です。 いきなりですが、あなたは今年はどんなバグの調査をしましたか? 解決に時間がかかったもの、あっさり解決できたもの色々あったかもしれませんね。 私も問い合わせの度に調査してきました。バグだった場合もそうじゃなかった場合もありますが、調査は早く終わらせたいですよね。 この記事では、問題が出た時に早期解決できるように、ありがちな原因と確認コマンドをまとめてみます。 ただし、 IoTデバイス で Linux が稼働しており ネットワーク や USB で通信するシステム を前提として、そのシステムの状態によって起こるアプリケーションの問題をまとめます。 チートシート 原因ごとの解説 空き容量不足 メモリ不足 ファイルディスクリプタ不足 USB未接続 モバイル回線未接続 おわりに チートシート 現象例 原因 調査方法 ファイル保存でエラー 空き容量不足 df -h <確認したいディレクトリ> プロセスがOSに落とされる メモリ確保でエラー メモリ不足 dmesg -T | grep '[kK]ill process' vmstat 1 -a ファイルオープンでエラー ファイルディスクリプタ不足 ls /proc/$(pidof <プロセス名>)/fd' デバイスと通信できない USB未接続 dmesg -T | grep disconnect ネットワーク通信できない モバイル回線未接続 ping -c 1 <対象サーバー> 原因ごとの解説 空き容量不足 ファイルのフラッシュ時に空き容量が足りない場合エラーになります。 例 以下のコマンドでは C言語(久しぶりに書いた!)でファイルを作成をするコードをmain.cとして保存 gccでコンパイル を行います。 $ cat << EOF >main.c #include <errno.h> #include <stdio.h> #include <string.h> const char* contents = "C lang! It’s been a while."; int main(int argc, const char* argv[]) { // File open FILE* f = fopen(argv[1], "w"); if (f == 0) { fprintf(stderr, "fopen() failed: %s (%d)\n", strerror(errno), errno); return 1; } // File write size_t len = fwrite(contents, 1, strlen(contents), f); if (len != strlen(contents)) { fprintf(stderr, "fwrite() failed: %s (%d)\n", strerror(errno), errno); return 1; } // File close if (fclose(f) != 0) { fprintf(stderr, "fclose() failed: %s (%d)\n", strerror(errno), errno); return 1; } return 0; } EOF $ gcc main.c -o nospace-test コンパイルしたプログラムを空き容量が無いディレクトリ(以下の例では/tmp)に対して実行すると、fclose()のタイミングでエラーになります。 $ ./nospace-test /tmp/test fclose() failed: No space left on device (28) fwrite()のタイミングではなくfclose()でエラーににあるため、fclose()のエラーをハンドルしていないと気がつかないかもしれません。 フラッシュのタイミングでエラーになるので、fflush()を呼ぶ場合はそのタイミングでエラーになります。 調査方法 dfコマンドのAvailで空き容量を確認できます。 df -h <確認したいディレクトリ> $ df -h /tmp/test Filesystem Size Used Avail Use% Mounted on tmpfs 1.9G 1.9G 0 100% /tmp メモリ不足 メモリ不足によりメモリの動的確保がエラーになります。または、 OOM Killerによってプロセスがkillされます。 例 以下のコマンドでは、 C言語でメモリ確保を繰り返すコードをmain.cとして保存 gccでコンパイル を行います。 $ cat << EOF >main.c #include <stdlib.h> int main(int argc, const char* argv[]) { while (1) { void* leaked = malloc(1024*1024); // 1MB } return 0; } EOF $ gcc main.c -o nomem-test コンパイルしたプログラムを実行するとOOM Killerによりkillされます。 $ ./nomem-test Killed 調査方法 OOM Killerが実行されたことはログに残るためdmesgを検索することで確認できます。 dmesg -T | grep '[kK]ill process' $ dmesg -T | grep '[kK]ill process' [Mon Dec 5 07:30:05 2022] Out of memory: Kill process 269571 (nomem-test) score 350 or sacrifice child それとは別に、メモリリークしていることを確認したい場合は vmstat 1 -a で active の増加度合いで確認できます。 $ vmstat 1 -a & ./nomem-test [1] 288948 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free inact active si so bi bo in cs us sy id wa st 1 0 0 1416648 2054456 252476 0 0 17 3 63 96 1 1 98 0 0 2 0 0 741920 2054472 700860 0 0 0 48 594 469 6 11 83 0 0 2 0 0 87432 2042036 1199888 0 0 36 24 640 522 7 11 81 0 0 Killed $ kill 288948 余談ですが弊社の intdash Edge Agent はvmstatを含むメトリクス情報をサーバーに送信するサンプル設定が入っています。以下はそのデータの一部を可視化したものです。 vmstatの可視化 ファイルディスクリプタ不足 1つのプロセスで使用するファイルディスクリプタが上限に達すると、新しいファイルを開くとエラーになります。 例 以下のコマンドでは C言語でファイルを繰り返し作成するコードをmain.cとして保存 gccでコンパイル を行います $ cat << EOF >main.c #include <errno.h> #include <stdio.h> #include <string.h> int main(int argc, const char* argv[]) { char path[256]; for (int i = 0; i < 1024 * 1024; i++) { sprintf(path, "/tmp/%d", i); FILE* f = fopen(path, "w"); if (f == 0) { fprintf(stderr, "fopen() failed: %s (%d)\n", strerror(errno), errno); return 1; } } return 0; } EOF $ gcc main.c -o nodescriptor-test コンパイルしたプログラムを実行すると、ファイルディスクリプタが枯渇しfopen()がエラーになります。 $ ./nodescriptor-test fopen() failed: Too many open files (24) 調査方法 特定のプロセスが開いているディスクリプタ番号が列挙されます。 ls /proc/$(pidof <プロセス名>)/fd' $ ls /proc/$(pidof nodescriptor-test)/fd 0 1 10 11 12 2 3 4 5 6 7 8 9 ファイルディスクリプタ数の上限は ulimit -n で確認できます。 $ ulimit -n 1024 USB未接続 USBデバイスが認識されておらず通信ができない。 物理的な切断も含めLinux OSがUSBデバイスを認識しなくなると(当たり前ですが)デバイスとの通信はできなくなります。 調査方法 USBの接続/切断はログに出力されているため、ログを検索することで確認できます。 dmesg -T | grep disconnect $ dmesg -T | grep disconnect [Mon Dec 5 08:39:10 2022] usb 1-2.4: USB disconnect, device number 3 このままだと、どのデバイスかわかりにくいので、 <bus>-<port[.port[.port]]> を検索すると、そのUSBデバイスが接続した時のUSBディスクリプタの情報を確認できます。 $ dmesg -T | grep 1-2.4 [Mon Dec 5 08:38:02 2022] usb 1-2.4: new high-speed USB device number 3 using tegra-xusb [Mon Dec 5 08:38:02 2022] usb 1-2.4: New USB device found, idVendor=2560, idProduct=c123 [Mon Dec 5 08:38:02 2022] usb 1-2.4: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [Mon Dec 5 08:38:02 2022] usb 1-2.4: Product: e-CAM22_USB [Mon Dec 5 08:38:02 2022] usb 1-2.4: Manufacturer: e-con Systems [Mon Dec 5 08:38:02 2022] uvcvideo 1-2.4:1.0: Entity type for entity Processing 2 was not initialized! [Mon Dec 5 08:38:02 2022] uvcvideo 1-2.4:1.0: Entity type for entity Extension 3 was not initialized! [Mon Dec 5 08:38:02 2022] uvcvideo 1-2.4:1.0: Entity type for entity Camera 1 was not initialized! [Mon Dec 5 08:38:02 2022] input: e-CAM22_USB as /devices/3530000.xhci/usb1/1-2/1-2.4/1-2.4:1.0/input/input4 [Mon Dec 5 08:38:04 2022] usb 1-2.4: usb_suspend_both: status 0 [Mon Dec 5 08:39:10 2022] usb 1-2.4: USB disconnect, device number 3 モバイル回線未接続 モバイル回線が接続していない(電波が悪い)ため、通信ができない、通信遅延が大きい。 これも当たり前ですが、電波が悪ければ通信はできません。 調査方法 pingで対象のサーバーまでの接続と RTT を確認できます。 電波状況を ModemManager や ATコマンド などで確認することもできますが、過去に「電波は十分だけどアプリはネットワークに繋がらない」といったケースがあり、これは名前解決ができていなかったことが原因でした。 そのためアプリケーションと同じ状況を確認するという意味でも、まずpingを確認するのが良いと思っています。 ping -c 1 <対象サーバー> (サーバーはpingに応答する必要があるため、 ICMP に対応している必要があります) $ ping -c 1 google.com PING google.com(nrt12s36-in-x0e.1e100.net (2404:6800:4004:822::200e)) 56 data bytes 64 bytes from nrt12s36-in-x0e.1e100.net (2404:6800:4004:822::200e): icmp_seq=1 ttl=56 time=7.60 ms --- google.com ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 7.595/7.595/7.595/0.000 ms time=7.60 ms がRTTになります。 またまた余談ですが、 intdash Edge Agent がインストールされたIoTデバイスを、車に乗せて走行した時のpingを含むネットワークのメトリクスはを可視化した例です。 pingの可視化 おわりに この記事の冒頭では、「問題が起きる」→「調べる」という流れを書いていますが、効率が良いのは、「状態を監視」→「問題を未然に防ぐ」の方ですね。 記事の途中でも記載したように、 intdash Edge Agent を使用して、サーバーに送信することですることで、状態を可視化することも可能です。ご興味がありましたら是非お試しください! intdash Edge Agent デベロッパーガイド version 1.24.0
aptpod Advent Calendar 2022 6日目の記事を担当する、Visual M2M グループの白金です。 弊社製品の Visual M2M Data Visualizer では、計測データを可視化するための様々なビジュアルパーツを提供しています。 その中の一つに、計測データに含まれる位置情報をもとにGoogleマップに現在位置を表示するビジュアルパーツが含まれています。 今回は、下図のように Googleマップのビジュアルパーツを3Dで表現する機能を試してみたので紹介したいと思います。 ビジュアルパーツで Googleマップを3Dで表示 はじめに マップIDを準備する 実装する Data Visualizer の計測データを使用して可視化する おわりに はじめに 弊社製品の Visual M2M Data Visualizer は Google Chrome のWebブラウザで提供しており、Googleマップのビジュアルパーツは、 Google Maps Platform から提供されている JavasScript API を使用しています。 このGoogleマップを3Dで表現するために WebGL Overlay View で構築する 3D マップ エクスペリエンス を参考に試してみました。 マップIDを準備する Google Maps Platform では、Google Cloud Console で作成したマップのスタイルを マップID に関連づけることができます。 JavaScript API で WebGL の機能を利用するためには、ベクター地図を有効にしたマップIDが必要になります。 設定方法については下記リンク先のページを参照ください。 developers.google.com 実装する 細かな実装方法は、各APIの説明については下記リンク先のステップ4〜8で説明が掲載されています。 developers.google.com ここでは下記2点を表示するための実装を紹介します。 3Dモデルで現在位置の表示する GeoJSON を使用して軌跡を表示する 下記NPMパッケージを使用して、TypeScript、React で実装します。 typescript react react-dom google-map-react three @types/google-map-react @types/google.maps npm i -S typescript react react-dom google-map-react three npm i -D @types/google-map-react @types/google.maps React の Component を実装します。 3DモデルのGLTFデータは こちらのサンプルデータ を拝借しました。 import React , { memo , useEffect , useCallback , useRef , useState } from 'react' import GoogleMapReact from 'google-map-react' import * as THREE from 'three' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' // WebGLOverlayView のサンプルで公開されているGLTFデータを参照します。 import PIN_GLTF from './pin.gltf' // 当コンポーネントで指定する位置情報の型です。 type Coordinate = { lat: number lng: number heading: number } // 当コンポーネントのPropsの型です。 type Props = { /** Googleマップを表示するための Api Key */ mapApiKey: string /** ベクター地図を有効にしたマップIDを指定します。 */ mapId: string /** Googleマップの初期表示位置情報 */ defaultCenter: GoogleMapReact.Coords /** Googleマップの初期表示ズーム値 */ defaultZoom: number /** 3Dモデルの表示位置情報 */ model3dCoordinate: Coordinate | undefined /** 軌跡の位置情報リスト */ trajectoryCoordinates: Coordinate [] } const MODEL_3D_ALTITUDE = 80 const TRAJECTORY_STROKE_COLOR = '#a00' const TRAJECTORY_STROKE_WEIGHT = 6 export const GoogleMaps3dSample: React.FC < Props > = memo (( props ) => { const { mapApiKey , mapId , defaultCenter , defaultZoom , model3dCoordinate , trajectoryCoordinates , } = props // ロードした Google Maps Api のインスタンスを格納します。 const [ mapApi , setMapApi ] = useState < { map: google.maps. Map maps: typeof google.maps } | null >( null ) // WebGLOverlayView で3Dモデルの表示位置を参照するための変数にコピーします。 const refModel3dCoordinate = useRef < Coordinate | undefined >() useEffect (() => { refModel3dCoordinate.current = model3dCoordinate } , [ model3dCoordinate ] ) // Googleマップに表示する軌跡のスタイルを定義します。 useEffect (() => { mapApi?.map.data.setStyle (() => { return { strokeColor: TRAJECTORY_STROKE_COLOR , strokeWeight: TRAJECTORY_STROKE_WEIGHT , } } ) } , [ mapApi ] ) // 軌跡のデータをGeoJSONフォーマットに変換してGoogleマップに表示します。 useEffect (() => { if ( ! mapApi ) { return () => {} } const geoJSON = { type : 'FeatureCollection' , features: [ { type : 'Feature' , properties: {} , geometry: { type : 'MultiLineString' , coordinates: [ trajectoryCoordinates.map (( { lat , lng } ) => [ lng , lat ] ), ] , } , } , ] , } const features = mapApi.map.data.addGeoJson ( geoJSON ) return () => { features.forEach (( feature: any ) => { mapApi.map.data.remove ( feature ) } ) } } , [ mapApi , trajectoryCoordinates ] ) // WebGLOverLayView を使用してGoogle Mapに3Dモデルを表示します。 useEffect (() => { if ( ! mapApi ) { return } const webGLOverlayView = new mapApi.maps.WebGLOverlayView () let scene: THREE.Scene let camera: THREE.PerspectiveCamera let renderer: THREE.WebGLRenderer const model3dGroup: THREE.Group = new THREE.Group () webGLOverlayView.onAdd = () => { // Scene、Camera の情報をセットアップします。 scene = new THREE.Scene () camera = new THREE.PerspectiveCamera () const ambientLight = new THREE.AmbientLight ( 0xffffff , 0.75 ) scene.add ( ambientLight ) const directionalLight = new THREE.DirectionalLight ( 0xffffff , 0.25 ) directionalLight.position. set( 0.5 , -1 , 0.5 ) scene.add ( directionalLight ) // 3Dモデル(GLTF)をロードします。 const loader = new GLTFLoader () const source = PIN_GLTF loader.load ( source , ( gltf ) => { // ロードしたGLTFのスケール、姿勢角の表示調整 gltf.scene.scale. set( 25 , 25 , 25 ) gltf.scene.rotation.x = ( 180 * Math .PI ) / 180 model3dGroup.add ( gltf.scene ) scene.add ( model3dGroup ) } ) } webGLOverlayView.onContextRestored = ( { gl } ) => { // renderer を作成します。 renderer = new THREE.WebGLRenderer ( { canvas: gl.canvas , context: gl , ...gl.getContextAttributes (), } ) renderer.autoClear = false } webGLOverlayView.onDraw = ( { transformer } ) => { // 3Dモデルを表示する位置情報を作成します。 const latLngAltitudeLiteral = { lat: refModel3dCoordinate.current?.lat ?? 0 , lng: refModel3dCoordinate.current?.lng ?? 0 , altitude: MODEL_3D_ALTITUDE , } // 3Dモデルの位置情報が無効な場合は非表示にします。 model3dGroup.visible = Boolean ( refModel3dCoordinate.current ) // 参照する3DモデルのZ軸の回転角度を設定します。 // heading と回転方向が逆のため、反転しています。 model3dGroup.rotation.z = ( -1 * ( refModel3dCoordinate.current?.heading ?? 0 ) * Math .PI ) / 180 // 3Dモデルの表示情報を renderer に反映します。 const matrix = transformer.fromLatLngAltitude ( latLngAltitudeLiteral ) camera.projectionMatrix = new THREE.Matrix4 () .fromArray ( matrix ) webGLOverlayView.requestRedraw () renderer.render ( scene , camera ) renderer.resetState () } webGLOverlayView.setMap ( mapApi.map ) } , [ mapApi ] ) // MouseDown のイベントハンドラを無効にします。 // Data Visualizer 本体でドラッグイベントを使用するためです。 const onMouseDownEventCancel = useCallback ( ( evt: React.MouseEvent < HTMLElement >) => { evt.preventDefault () } , [] , ) return ( < div role = "button" tabIndex = { 0 } style = {{ width: '100%' , height: '100%' }} onMouseDown = { onMouseDownEventCancel } > < GoogleMapReact bootstrapURLKeys = {{ key: mapApiKey , // ベクター地図を有効にしたマップIDを有効にするため、version に beta を指定します。 version: 'beta' , }} options = {{ mapId , tilt: 60 , heading: 0 , }} defaultCenter = { defaultCenter } defaultZoom = { defaultZoom } onGoogleApiLoaded = { setMapApi } / > < /div > ) } ) 上記ソースコードで実装した React Component を呼び出します。 mapId は、事前に準備したベクター地図を有効にしたマップIDを指定します。 mapApiKey は Google Maps Platform で作成したAPIキーを指定します。APIキーの作成手順については こちら をご確認ください。 < GoogleMaps3dSample mapApiKey = "xxxxxx" mapId = "xxxxxx" defaultCenter = {{ lat: 35.68783052263802 , lng: 139.71728196798034 , }} defaultZoom = { 19 } model3dCoordinate = {{ lat: 35.68781633 , lng: 139.7180315 , heading: 80 , }} trajectoryCoordinates = {[ { lat: 35.68778533 , lng: 139.71701067 , heading: 79 } , { lat: 35.68778533 , lng: 139.71782767 , heading: 79 } , { lat: 35.68778533 , lng: 139.71782767 , heading: 80 } , { lat: 35.68778817 , lng: 139.71784633 , heading: 80 } , { lat: 35.68778817 , lng: 139.71784633 , heading: 80 } , { lat: 35.687791 , lng: 139.71786467 , heading: 79 } , { lat: 35.687791 , lng: 139.71786467 , heading: 79 } , { lat: 35.68779417 , lng: 139.717883 , heading: 79 } , { lat: 35.68779417 , lng: 139.717883 , heading: 79 } , { lat: 35.68779767 , lng: 139.71790067 , heading: 79 } , { lat: 35.68779767 , lng: 139.71790067 , heading: 79 } , { lat: 35.687801 , lng: 139.71791883 , heading: 79 } , { lat: 35.687801 , lng: 139.71791883 , heading: 79 } , { lat: 35.68780417 , lng: 139.7179375 , heading: 79 } , { lat: 35.68780417 , lng: 139.7179375 , heading: 79 } , { lat: 35.687807 , lng: 139.71795633 , heading: 79 } , { lat: 35.687807 , lng: 139.71795633 , heading: 79 } , { lat: 35.68780933 , lng: 139.717975 , heading: 79 } , { lat: 35.68780933 , lng: 139.717975 , heading: 79 } , { lat: 35.68781283 , lng: 139.71799333 , heading: 79 } , { lat: 35.68781283 , lng: 139.71799333 , heading: 79 } , { lat: 35.687815 , lng: 139.7180125 , heading: 79 } , { lat: 35.687815 , lng: 139.7180125 , heading: 79 } , { lat: 35.68781633 , lng: 139.7180315 , heading: 80 } , ]} / > 以上で下図のようにGoogleマップ上に3Dモデル、及び軌跡を表示することできました。 実行結果01 実行結果02 Data Visualizer の計測データを使用して可視化する 次に、Visual M2M Data Visualizer のビジュアルパーツに Googleマップ3Dのビジュアルパーツを表示してみました。 位置情報を含む走行データは、弊社製品の Visual Parts SDK を使用して Visual M2M Data Visualizer から取得し、再生時間に沿って可視化しています。 また、他のビジュアルパーツとの比較、位置情報を確認するため、Open Street Map、緯度、軽度の値を表示するビジュアルパーツも表示しました。 youtu.be Visual Parts SDK を含む弊社製品に関するお問い合わせは下記リンク先までお願いします。 www.aptpod.co.jp おわりに 新しい機能を試して実現できた瞬間は、いつになってもテンションが上りますね。 今回は道路に接している移動体の可視化となりましたが、今後は飛行している移動体の可視化、軌跡も表現してみたいと思います。
製品開発グループの大久保です。 aptpod Advent Calendar 2022 の5日目を担当します。 社内ではRustのエッジ製品への適用が本格化し、接続するデバイスに応じたプラグインの デバイスコネクタ やSDK等への広がりを見せています。 個人的にもRustでのゲーム開発についての話題を追いかけているのですが、最近は bevy というゲームエンジンに勢いがあるようです。このbevyはWebAssemblyにビルドし、ブラウザ上で動作させることにも対応しています。というわけで、bevyで作ったアプリケーションをブラウザ上で動作させてみます。 bevyとは 実装 ビルド まとめ bevyとは bevyは ECS に基づいたRust製のゲームエンジンで、本体はシンプルに保ちつつ、プラグインを導入して拡張を容易にする設計になってます。bevy用のプラグインは有志によって多数開発されているようです。 Bevy - Assets bevy自体は頻繁にアップデートが続けられているため、サードパーティ製のプラグインも更新されなければ使えなくなってしまう心配がありますが、有用なものはやはり使っていきたいものです。Rustで人気のあるGUIライブラリである egui をbevyで使用できるようにした bevy_egui もあり、今回はこれでGUIを作ってみます。 実装 Cargo.toml を以下のように記述します。bevyは2022年11月時点で最新のバージョンです。 [package] name = "bevy-test" version = "0.1.0" edition = "2021" [dependencies] bevy = "0.9" bevy_asset_loader = "0.14" bevy_egui = "0.17" rand = "0.8" main.rs は以下のように記述します。 use bevy :: prelude :: * ; use bevy_asset_loader :: prelude :: * ; use bevy_egui :: {egui, EguiContext, EguiPlugin}; use rand :: Rng; fn main () { App :: new () . add_loading_state ( LoadingState :: new ( GameState :: AssetLoading) . continue_to_state ( GameState :: Running) . with_collection :: < MyAssets > (), ) . add_state ( GameState :: AssetLoading) . add_system_set ( SystemSet :: on_update ( GameState :: Running). with_system (ui_system)) . add_system_set ( SystemSet :: on_enter ( GameState :: Running). with_system (setup)) . add_plugins (DefaultPlugins. set (WindowPlugin { window: WindowDescriptor { title: "bevy test" . into (), width: 320.0 , height: 320.0 , .. Default :: default () }, .. default () })) . add_plugin (EguiPlugin) . run (); } #[derive(Resource, AssetCollection)] struct MyAssets { #[asset(path = "rust-logo.png" )] img: Handle < Image > , } fn setup ( mut commands: Commands) { let camera = Camera2dBundle :: default (); commands. spawn (camera); } fn ui_system ( mut commands: Commands, mut egui_context: ResMut < EguiContext > , my_assets: Res < MyAssets > , mut tex_entities: Local < Vec < Entity >> , ) { egui :: Window :: new ( "test" ). show (egui_context. ctx_mut (), | ui | { if ui. button ( "add" ). clicked () { let mut rng = rand :: thread_rng (); let id = commands . spawn (SpriteBundle { texture: my_assets.img. clone (), transform: Transform :: from_xyz ( rng. gen_range ( - 160.0 .. 160.0 ), rng. gen_range ( - 160.0 .. 160.0 ), 0.0 , ), .. default () }) . id (); tex_entities. push (id); } if ui. button ( "clear" ). clicked () { for id in tex_entities. iter () { commands. entity ( * id). despawn (); } tex_entities. clear (); } }); } #[derive( Clone , Eq , PartialEq , Debug , Hash )] enum GameState { AssetLoading, Running, } 少し解説を入れます。 App :: new () . add_loading_state ( LoadingState :: new ( GameState :: AssetLoading) . continue_to_state ( GameState :: Running) . with_collection :: < MyAssets > (), ) . add_state ( GameState :: AssetLoading) . add_system_set ( SystemSet :: on_update ( GameState :: Running). with_system (ui_system)) . add_system_set ( SystemSet :: on_enter ( GameState :: Running). with_system (setup)) bevy単体だとアセットの扱いが結構苦行なので、 bevy_asset_loader を使っています。 MyAssets のロードが完了すると GameState が AssetLoading から Running に移行します。 Running に移行したときに SystemSet::on_enter で指定したシステム setup が動作します。 ui_system は Running 状態でしか呼ばれないこともここで指定しています。 #[derive(Resource, AssetCollection)] struct MyAssets { #[asset(path = "rust-logo.png" )] img: Handle < Image > , } AssetCollection を derive で指定して、ロードしたいアセットを定義します。ここでは、 rust-logo.png というファイルがロードされるようにします。 fn ui_system ( mut commands: Commands, mut egui_context: ResMut < EguiContext > , my_assets: Res < MyAssets > , mut tex_entities: Local < Vec < Entity >> , ) { egui :: Window :: new ( "test" ). show (egui_context. ctx_mut (), | ui | { if ui. button ( "add" ). clicked () { let mut rng = rand :: thread_rng (); let id = commands . spawn (SpriteBundle { texture: my_assets.img. clone (), transform: Transform :: from_xyz ( rng. gen_range ( - 160.0 .. 160.0 ), rng. gen_range ( - 160.0 .. 160.0 ), 0.0 , ), .. default () }) . id (); tex_entities. push (id); } if ui. button ( "clear" ). clicked () { for id in tex_entities. iter () { commands. entity ( * id). despawn (); } tex_entities. clear (); } }); } ui_system はGUIを定義するシステムで、eguiで生成したウィンドウ上にボタンを2つ用意します。addボタンはクリックされたとき、 MyAssets で読み込んだテクスチャを乱数で決めた位置に貼り付けます。このとき spawn したエンティティのIDを記録しておき、clearボタンが押されたときに消去するようにします。 ビルド Unofficial Bevy Cheat Book にはwasm向けビルド方法がいくつか紹介されていますが、今回はwasm-bindgenを用います。 wasm-bindgenは以下でインストールします。 cargo install wasm-bindgen-cli 以下のコマンドでビルドできます。 cargo build --release --target wasm32-unknown-unknown wasm-bindgen --target web --out-dir . --no-typescript target/wasm32-unknown-unknown/release/bevy-test.wasm ビルドが成功すると、 bevy-test_bg.wasm と bevy-test.js という2つのファイルができるので、それを呼び出す index.html ファイルを用意します。 < html > < head > < meta charset = "utf-8" /> </ head > < script type = "module" > import init from './bevy-test.js' init () </ script > </ html > あとは適当なHTTPサーバーを用意し、 python3 -m http.server 8000 ブラウザで開けば動作確認できます。addボタンを押せば画像がランダムに配置され、clearボタンを押せばそれが消えるのを確認できるはずです。 まとめ 今回はRust+bevyで簡単なアプリケーションをwasm向けにビルド、動作確認してみました。eguiによりGUIも比較的簡単に記述することができ、ビルド自体も非常に簡単に行えました。Rustでさくっと書いたものがwebブラウザ上で動くのはなかなか感慨深いものがあります。まだまだRustでのゲーム開発は発展途上であり、bevyもまた開発中ではありますが、webブラウザで動かしたい場合にbevyは有力な選択肢になるのではないでしょうか。
aptpod Advent Calendar 2022 、2日目を担当します、人事グループの照井です。 よろしくお願いします。 自己紹介 制度が迷子、私も迷子 アプトポッドの福利厚生パッケージ制度の検討開始 「Pod's ~ aptpod Employee Benefit ~」ができるまで すごいぞ!デザインチームの腕 今後の「Pod's」 最後に 自己紹介 今年3月にアプトポッドに入社して主に労務業務や制度設計等を行っています。 前職は社員数1,000人ほどの一部上場企業で人事(労務全般~制度設計)をやっていたため、所謂「ベンチャー企業での人事」は私にとって新鮮で未知の世界。 ワクワクしながら入社し、早9カ月が経過しようとしています。 制度が迷子、私も迷子 入社してまもなく、育児介護休業法の改正の対応や給与計算や勤怠処理をするにあたって、アプトポッドにはどんな制度があってどう運用されているかを確認するシーンが増えました。 当時まとまった資料が無く、手あたり次第コンフルエンスを漁っていたのですが、更新日時を見ると数年前だったりすることもあり、それらが正しく運用されているのかもわかりませんでした。 都度マネージャーに確認していましたが、ある時現場のメンバーから制度の問い合わせを受けた際に、 私よりだいぶ社歴が長い方に「そんな制度あるんですね!」と言われ、これは本格的に整理をしなければいけないな…と思ったのでした。 アプトポッドの福利厚生パッケージ制度の検討開始 実は、前職でWork&Lifeバランスに特化した福利厚生パッケージ制度を設計した経験がありました。 今回はその経験を活かして、迷子になっているアプトポッドの制度をきちんと整理して全社員に正しく認識してもらい、有効的に活用できるようにとパッケージの組立を開始しました。 今回のパッケージ化で行ったことは、大きく2点です。 ①現行の制度の洗い出しと整理 ②制度化していないが実態として実施している内容の制度化 今回の目的は、あくまでも現行の制度を表に出して全社の認知度を統一化すること。 そのため、新規施策の検討ではなく「いかに社内に浸透させられるか」に注力しました。 「Pod's ~ aptpod Employee Benefit ~」ができるまで コンフルエンス漁りや先輩社員への確認で、アプトポッドには法定外の福利厚生もいくつかあることがわかり、法定制度も合わせるとボリュームがあったので <Work&Life Design><HealthCare><Communication> の3本柱で制度の組立を行うことにしました。 種別に分けることで、視認性が良くなり必要としている制度が見つけやすくなりました。また、いざとなった時のために「把握しておこう」というケースにも対応ができるようになったのではないでしょうか。 制度の内容は固まりました。 次は「いかに社内に浸透させられるか」において最大の課題ともいえるネーミングです。 正直、私にはネーミングセンスはございません。 そこで、パッケージ制度のロゴをコーポレートマーケティング室のデザインチームにお願いする予定だったのでロゴ制作だけでなく、根本のネーミング考案からお力添えをいただくことにしました。 とても有難いことに、このプロジェクトに手を挙げてくださったデザインチームのメンバーがおり、その方とコーポレートマーケティング室の室長、通りすがりにプロジェクトのスライドを閲覧しているところを捕まえられたCTO、人事マネージャーを巻き込んでのネーミング考案となりました。(羅列すると大所帯w) アプトポッドで今まで制度に名前が付いたことが1度あり、それがコミュニケーション施策に入っているアプトライトです。アプトライトはアプトポッドの「アプト」を取って、縁の下の力持ちにもスポットライトをあてるという趣旨でこの名前になったそうです。 耳障りも良いし、程よい長さだし、なんともスマートなネーミングだなと思っていました。 今回もアプトライトに負けず劣らず素敵なネーミングを!と意気込み、考案する上での方向性/思想を設定し、候補を出し合いました。  <思想/方向性>  ● 安心して活躍できる環境づくり  ● つなぐ(会社⇔社員)  ● 短くて浸透しやすいもの  ● デザインしやすい長さ アプトライトのように、大抵制度の名前をつける際は社名の一部を使用して命名されるシーンが多いように思います。今回もそれに漏れず、「アプトポッド」の名前の一部を使用したものと、全く別物の2パターンの候補案が挙がってきました。 その中でも「apt」の「t」をつなげた造語などもいくつか案として挙がっており、ネーミングセンスに脱帽でした。 候補として挙げられたネーミング案 話し合いの中で、シンプルに「aptpod Employee Benefit」で良いんじゃないか。という意見も出ました。名前をつけたところで対外的にはその名前自体が何を指し示すものなのかがわからないので制度名の後に「〇〇社の福利厚生パッケージ制度」と記載している企業が大多数だったからです。 それであれば、サブタイトルとして制度名の後に付けましょう。ということにしました。 そうなると、必然的に制度名は短い方がいい。 ということで結果、最終的に16案から絞られ選出されたのは「さや状のもの」という意味もある「aptpod」の「pod」を取った「Pod's」と命名することになりました。  ~pod'sに乗せた想い~  ● 安心、包み込む(=安心して活躍できる環境)  ● 皆が含まれる  ● パッケージの中に制度がいくつかあるという意味  ● 短くて浸透しやすい すごいぞ!デザインチームの腕 めでたく命名されたPod'sですが、次はロゴデザイン。 自ら手を挙げて作成してくれたPod’sのロゴがこちら。 そして、各分類のシンボルデザインも、このように考案されました。(ステキすぎる) こうして、Pod'sはアプトポッド初の福利厚生パッケージ制度として2022/11/1にスタートしました。 沢山の方のお力添えあってこそ…!感謝でいっぱいです。 今後の「Pod's」 お蔭様でPod'sができたことによりレポートラインが明確になり、スムーズなエスカレーションが出来るようになりました。 今回は「守り」に徹して目立った新規施策はありませんでしたが、今後は会社の状況、社員のライフイベントの傾向に合わせて、より長く安心してアプトポッドの社員として活躍できるよう、都度バージョンアップを重ねていきたいと思います。 また私自身、入社9カ月経過し、1,000人規模の企業と70人規模のベンチャー企業では求められる制度の質が少し違うのかなと肌で感じています。今度はそれを、カタチにできたらなと個人的に思っています。 最後に アプトポッドは入社初日に有給休暇を付与されたり、かなり自由度の高い働き方ができるので、仕事のやりがい含め、入社してよかったなと心から思う日々です! 2023年も沢山のことに挑戦してアドベントカレンダーに書けるネタが作れるよう、頑張ります!