TECH PLAY

アプトポッド

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

252

はじめに はじめまして。VPoPの岩田です。 弊社は、昨年の8月より開発本部のマネジメント体制を強化し、CTO・VPoE・VPoP から構成される CTO 室体制を敷いております。 aptpod Advent Calendar 2019 では、VPoE 高橋より こんなエモくて素敵な記事 が投稿され、 CTO 梶田からは 1年を総括する記事 が投稿されており、 私も彼らの後に続くべく、遅ればせながら本記事を投稿することにいたしました。 今回の私の記事の題材はというと、ずばり 「アプトポッドってなにをしている会社なの?」 です。 私自身アプトポッドに転職をしてきてから はや3年が経ちますが、実を言うと、今だに身内や友人から 「お前の会社はなにをしている会社なんだ?」 と聞かれることがよくあります。 それもそのはず、アプトポッドの技術領域は、 ハードウェアの自社企画・開発 から、 クラウド環境をフル活用したサーバーアプリ・Webアプリの開発 まで、多岐に渡ります。製品の形態としても、SaaSやPaaS、ミドルウェア製品までラインナップしていますので、一言で なにをしている会社か と説明することは容易なことではありません。 そこで本記事では、アプトポッドに少しでも興味を持ってくださった皆様に、 「なるほどアプトポッドとはこんな会社なんだな」 と納得していただけるよう、 アプトポッドのこれまで、現在、そしてこれからについて 、解説をこころみたいと思います。 (あわよくば、私のように会社説明に難儀している自社社員に、説明の手助けになるような記事にできれば一石二鳥です) 少々長い記事になるかとは思いますが、最後までお付き合いいただけますと幸いです。 はじめに アプトポッドのこれまで 実は、創業から14年も経っています 最初の車両計測案件、そして受託開発へ 受託開発の積み重ねと、自社製品への布石 はじめての自社製品 主力製品の開発開始、そして現在へ アプトポッドの現在 ①車両計測、データ収集/可視化パッケージの提供 ②データ収集/伝送基盤としての IoT プラットフォームの提供 例えば、AI 開発のデータ収集基盤として 例えば、遠隔モニタリングのバックエンドとして その他、プラットフォームの導入コンサルティングも承ります アプトポッドのこれから 車両計測パッケージのさらなる洗練 API/SDK整備によるプラットフォームの独り立ち ロボティクス分野への進出 さいごに アプトポッドのこれまで 実は、創業から14年も経っています アプトポッドの創業は今から14年前、 2006年 に遡ります。 (もちろん当時私はまだ在籍しておりませんので、このセクションのお話はすべてCTOにヒアリングした内容をもとに記載しております) 創業当時は、まだ現在のような自動車計測やIoTプラットフォームといった事業はなく、 プロジェクト支援やマーケティング支援を受注する小さな会社だったそうです。 最初の車両計測案件、そして受託開発へ アプトポッドにとっての転機は、 2010年 に受注したとある案件です。 当時車体改造を手がける他社様と連携して受注した案件のなかで、試しに作製した遠隔モニタリングのデモアプリが全てのきっかけでした。 初めは弊社の技術力を知っていただくためのデモに過ぎませんでしたが、 お客様も弊社自身も、これをきっかけに「遠隔から車両状態が確認できること」の有用性に気づき、 遠隔モニタリング自体が次第に案件の重要な開発項目となってゆきました。 これが、アプトポッドにとっての初の車両計測案件です。 現在の CTO であり、弊社最初のエンジニア社員である梶田 の JOIN により受託開発を受注できるようになったこともあり、 ここから車両計測案件を中心とした受託開発へ事業領域を拡大していきます。 受託開発の積み重ねと、自社製品への布石 とはいえ、受託開発事業を開始してから現在まで、車両計測に関する案件ばかりをこなしてきたわけではありません。 弊社としても、はじめから車両計測が当たると確信して一極集中していたわけではなく、 車両とは全く関係のないUI開発を請け負ったり、メール配信システムの構築からアミューズメント系のアプリ開発までとにかくなんでも受注し、 お客様の要望に全力でお応えしながら、弊社としての生き残る道を模索しました。 幸い、こうした模索を続けるあいだにも、車両計測に関する案件に関しては、何度もご依頼いただくことができました。 受注した案件で、車両情報の遠隔監視やバスの車内サイネージなどをいくつも手がけていくなかで、 自動車関連データに関するノウハウや、データの可視化表現に関するノウハウを蓄積してゆきました。 こうして様々なお客様とともにいくつものサービスを作り上げ、ノウハウを蓄積していくうちに、 複数の案件において共通のワークフローや機能セットがあることに気づきます。 より早く、より高品質にお客様の要望を実現するためには、共通項をまとめたアプリケーションが必要との考えのもと、 現在の intdash (バックエンドシステム) や Visual M2M Data Visualizer (可視化ダッシュボード) の前身となる、 自社製品第一号の企画開発がスタートしました。 はじめての自社製品 それから少し経った 2015年 、当時まだ IoT (Internet of Things) という言葉もそこまでバズっておらず、 M2M (Machine to Machine) という言葉が多少優勢だった時代に、 「自動車を携帯通信網でインターネットに繋げて遠隔監視」 という、当時としてはそれなりに尖った機能性を持った自社製品第一号が正式リリースを迎えます。 (その当時の名称を受け継いで、現在も弊社では Visual "M2M" という名称をブランド名として使用しています) trends.embed.renderExploreWidget("TIMESERIES", {"comparisonItem":[{"keyword":"IoT","geo":"","time":"2010-01-26 2020-02-26"},{"keyword":"M2M","geo":"","time":"2010-01-26 2020-02-26"}],"category":0,"property":""}, {"exploreQuery":"date=2010-01-26%202020-02-26&q=IoT,M2M","guestPath":"https://trends.google.com:443/trends/embed/"}); 主力製品の開発開始、そして現在へ 自社製品第一号をリリースしたことで、大手自動車メーカー様にもご採用いただくなど、自社製品を活用した車両計測案件の受注が本格化しました。 ツールとしてハードに使い倒されていくにつれ、性能や機能、セキュリティなど他方面において強化すべきポイントが見つかるようになります。 これらを改善すべく、 2017年 より企画開発がスタートしたのが intdash と Visual M2M Data Visualizer であり、現在も利用されている弊社の主力製品です。 Visual M2M Data Visualizer(旧名称 Visual M2M)PV映像 intdash の全体像 このように会社の成り立ちをいまいちど振り返ってみると、着実に現場で経験を重ね、その経験をもとに自社製品を育んできた歴史が見えてきます。 (実際、新入社員の口から一番多く言われる言葉は、 「ベンチャーなのに意外と堅実な会社なんですね」 というものだったりします) また、とてもありがたいことに、弊社が現在お手伝いをさせていただいているプロジェクトにおいても、 「開発して納品して終わり」 、というだけではなく、 お客様と一体となって、継続開発をしながらサービスのあるべき姿を模索していくような関係性を構築させていただくことが多くあります。 お客様とのプロジェクトを進めていくなかで、弊社としても経験値やノウハウをためながら、お客様と二人三脚でサービスを進化させていく 、というのは、 会社の創業当時から変わらずアプトポッドの根底に流れる、大切にしていきたい価値観のひとつです。 アプトポッドの現在 こうした経緯を経て、現在のアプトポッドの主な事業領域は、以下の2つから構成されています。 ①車両計測、データ収集/可視化用パッケージの提供 ②データ収集/伝送基盤としての IoT プラットフォームの提供(と、それを利用した受託開発) それぞれの事業について、以下でもう少し詳しく説明をしていきます。 ①車両計測、データ収集/可視化パッケージの提供 この領域では、 自動車や重機、建機などの車両全般を対象とした、データ収集や遠隔監視のためのツール群 を Automotive Pro パッケージ として提供しています。パッケージの中には、車両からデータを取得するための 車載アプライアンス (intdash Terminal System) 、 クラウド上のデータ収集管理プラットフォーム (intdash REST API / Realtime API) 、 可視化用Webダッシュボード (Visual M2M Data Visualizer) などがセットになっており、車両計測で必要となるツール群をワンストップで揃えることができます。 アプトポッドのこれまで でも述べたように、 これまでお客様と積み重ねてきた車両計測案件で蓄積したノウハウを結集して企画開発しておりますので、 車両計測や遠隔監視で問題になりがちな以下のような困りごとを解決するソリューションとして、 大手自動車メーカー様を含め、自動車開発の現場などでご利用いただいております。 データ量が多すぎて詰まってしまい、リアルタイムに可視化/監視できない 電波状況の悪いところで、データが伝送できずに欠損してしまう OSSのダッシュボードツールではリアルタイム性に満足がいかない データが多すぎてOSSのダッシュボードツールではうまく可視化できない ②データ収集/伝送基盤としての IoT プラットフォームの提供 こちらの領域では、もともと車両計測パッケージのバックエンドシステムであった intdash を、 車両計測以外のユースケースでもご活用いただけるよう、 IoT プラットフォーム として提供しています。 例えば、AI 開発のデータ収集基盤として 最近の AI 開発 においては、まず AI モデルの作成のために学習用データの収集が必要となりますが、 データの収集に課題を抱えていらっしゃるお客様にお声がけいただくことが増えてきています。 intdash は、 車両計測で培った高頻度データの伝送や蓄積、管理のノウハウ を生かし、 大量のデータをより早く、より確実に伝送、収集することに長けたプラットフォームとなっておりますので、 このようなユースケースを求められるお客様にも、データの収集基盤としてご好評いただいております。 例えば、遠隔モニタリングのバックエンドとして また、もともと車両情報の遠隔監視から始まった事業でもありますので、 遠隔地に存在する自動車やロボットなどの移動体の リアルタイムモニタリングやフリート管理 のための バックエンドプラットフォームとしてもご活用いただいております。最近では、 低遅延、大容量、双方向性などの機能性を評価いただき 、移動体のみならず、工作機械のモニタリングなどのFA分野でも、だんだんとお声がけをいただく機会ができるようになりました。 ご参考 (最近の事例については掲載が追いついていないものも多く、ご不便をおかけいたしております) 燃料電池小型トラック開発プロジェクト マーシャル諸島共和国ソーラーEV共同実証実験プロジェクト その他、プラットフォームの導入コンサルティングも承ります 様々なお客様のバックエンドシステムとして intdash が活用され始めたとはいえ、 これまで自社製品のバックエンドシステムであったという製品の経緯や、 (ベンチャー企業らしく) 常に機能が追加され、進化を続けているプラットフォームであるという性質上、 2020年現在でも、実はまだプラットフォームがもつ全てのAPIやSDKを開示できているわけではありません。 (みなさまにプラットフォームの全ての機能をご利用いただけるよう鋭意対応中です。本年中には正式にAPIとSDKの正式リリースを予定しておりますので、今しばらくお待ちください) それでは、APIやSDKが揃っていない中で、お客様にどのようにソリューションをご提供しているかといえば、現在は、 製品を熟知した弊社のソリューションアーキテクトがそれぞれのお客様のご要望をお伺いし、最適なアーキテクチャをご提案させていただく 、という形態で、お客様のプロジェクトをお手伝いさせていただいております。また、ご提案したアーキテクチャは自社で開発を請け負い、責任を持ってデプロイ、運用までお付き合いさせていただいております。 アプトポッドのこれから 最後のセクションでは、現在の事業領域を踏まえて、今後の展望を描きたいと思います。 車両計測パッケージのさらなる洗練 現在のアプトポッドでは、過去の経緯から車両計測や自動車関連のPoCプロジェクトが数多くあります。 すでに国内大手自動車メーカー様や建機メーカー様において導入実績を積んできており、 車両計測においてはある程度の認知度を獲得してきていると自負しております。 もちろんこの方向性は、 導入していただいているお客様からの意見を伺いながら、より充実したものへと拡充させてゆきます。 最近では、車両情報と合わせて動画データを取得したいというご要望も多く、ハードウェア部門では自社企画のビデオエンコーダデバイスを鋭意開発中です。 こちらの詳細につきましては、ハードウェア部門より公開されている以下の詳細記事をご参照ください。 tech.aptpod.co.jp さらにこれからは、お客様のワークフローに合わせてより柔軟なシステム構成が取れるよう、 自社企画製品の開発だけでなく、 サードパーティ製品との連携も強化していく方向性も模索していきたいと考えております。 API/SDK整備によるプラットフォームの独り立ち アプトポッドの現在 でも述べましたが、 現在の intdash は、まだ弊社アーキテクトによる導入サポートが必要な段階にあります。 今後、より多くのお客様に弊社のプラットフォームをご利用いただくためには、 弊社からアーキテクチャをご提案させていただくだけでなく、お客様ご自身でアーキテクチャを考え構築できる必要があると考えております。 そのために、APIの開示やSDKの提供、サンプルソース、ドキュメントの拡充を推進していく予定です。 また、他社様とのパートナーシップやOEM展開も視野に入れ、より多くのお客様へソリューションを届けられる体制を確立してまいります。 ロボティクス分野への進出 弊社のお客様のうち、最近自動車開発の延長としてロボット開発に興味を持たれている方々が増えてきているようです。 弊社がお手伝いさせていただいている案件のなかにも、ロボット自体の開発や、自動車開発へロボティクスの知見を活用するPoCが、 見受けられるようになってきました。 自動車とロボットは、 高頻度なデータを発するインテリジェントな移動体 という点において それほど遠くない領域、違いに知見を転用しあえる領域であると言えます。 実際、最近では ROS をベースとした Autoware が自動運転分野で活用されるなど、自動車とロボットの境界が曖昧になりつつあります。 弊社としては、 現在の主戦場である車両計測、自動車分野のみに留まることなく、車両計測で培った知見をロボティクス分野にも応用していきたい と考えており、 現在ロボティクス分野への進出準備を進めています。 最近の展示会において、だんだんとその取り組みは社外公開をし始めており、 今後は実際のお客様事例への適用を目指して、ますます対応を加速させていきます。 tech.aptpod.co.jp さいごに 駆け足にはなりましたが、アプトポッドという会社の成り立ちや、現在の事業領域、今後の展望までを解説してまいりました。 振り返りもかねて、ざっくりと内容を要約しておきます。 車両計測、データ収集用のパッケージ を提供している会社です 自動車やロボットのような 高頻度なデータの収集/伝送/可視化 に強みがあります データ伝送プラットフォームは、 車両計測以外のユースケースでも ご利用いただけます 自社IoTプラットフォームをベースとした 受託開発 も承ります 今後は、API公開を推進し、 PaaSやミドルウェアとして 、ご利用いただける形態を目指します 今後は、自動車分野にとどまらず、 ロボティクス分野 にも挑戦してまいります 最後の最後に少しエモいお話を少々させていただき、締めくくりといたします。 ここ数年で開発組織に関わるメンバーが一気に倍増 し、マネジメント体制が組織拡大に追いつかなくなりつつあるなかで、 CTO室体制が出来上がりやっと半年がすぎた現在、だんだんと組織規模にマネジメントが追いついてきた感があります。 一方で、会社全体の規模をみるとそこそこの大きさに見えるかもしれませんが、冒頭でもお伝えした通り弊社は対象としているレイヤが異様に広く、 ハード、組み込み、サーバー、Web、iOS、インフラ、デザインなど、それぞれのチームに割り振ってしまえば、各チームはまだまだ小さなベンチャー時代とそう変わらない規模のままだったりもします。 何を言いたいのかといえば、 これからさらに事業領域を拡大し、より多くのお客様とともに新しいサービスを作り上げていくには、 現在の規模でもまだまだメンバーが足りていません。 もしこの記事をお読みになって、アプトポッドに興味を持ってくださった方がおられましたら、 ぜひ弊社の人事までお声がけください! もちろん、 車両計測、データ収集やIoTプラットフォームの構築でお困りになられているお客様 も、 是非弊社に一度ご相談ください。 弊社のソリューションアーキテクトが、最適なアーキテクチャをご提案させていただきます! 最後までお読みいただき、誠にありがとうございました。本記事をお読みくださった皆様と、 お客様として、仕事仲間として、お会いできることを心よりお待ちしております!
みなさまこんにちは。先進技術調査グループのキシダです。techブログは3度目の登場です。 今回は小ネタを投下してみたいと思います。 ( 前回の記事 にも登場していますが)2020年のAutomotive Worldにてご紹介した通り、intdashがついに「 音声データ 」にも対応しました! 🎉 そして 音声 といえば、皆様ご存知でしょうか・・・? そうです、AWSの「音声の文字起こし機能」で有名な Amazon Transcribe が、昨年末日本語対応したのです! 🎉 これは組み合わせてみるしかない・・・! ということで、今回は以下のようなシナリオを目指し、文字起こし機能をintdashに組み込んでみました。 iPhoneにむかっておしゃべりしてみる サーバー側で音声データをテキストに変換する 可視化ツールで音声が再生されるのと同時に文字に起こされていく様子を眺める 音声データとテキストデータ双方をintdashに通すことでデータの同期性が担保されるため、音声が再生されるのと同時にテキストに変換されていく様子を確認することができます。こうすることでAmazon Transcribeが音声のどの言葉を拾って変換してくれているのかが一目瞭然でわかる、というところがこのシナリオのGoodポイントです。 使用するサービスとワークフロー 今回のシナリオ自体は至って単純ですが、実は裏側で複数のサービスを使っています。 そのあたりを詳細に説明いたします。 Visual M2M Motion for iOS (以下 Motionとします) iOS向けに開発された、スマートフォンのマイク・カメラ・センサー情報などからデータを収集してくれるアプリになります。今回はiPhoneから音声データを取得し、サーバーに送る箇所で使用します。 Python SDK for intdash Analytics Services  (以下 Python SDKとします) 収集したデータをintdash上で加工・解析するプロセスをはさみ、intdash上でやりとりされるデータの処理を実現するライブラリです。今回は、Amazon Transcribeとの連携で使用します。 Amazon Transcribe 開発者が音声をテキストに変換する機能をアプリケーションに簡単に追加できるようにする、自動音声認識 (ASR、automatic speech recognition) サービスです。 Visual M2M Data Visualizer (以下data-vizとします) intdash上でやりとりされるデータを可視化するアプリケーションです。 今回は解析結果を確認する際に使用します。 弊社製品の詳細は、以下をご参照ください。 https://www.aptpod.co.jp/products/ 上記を踏まえ、全体のワークフローは以下のようなイメージになります。 ワークフローイメージ iPhoneのMotionアプリから音声データをintdashにアップロード 音声ファイルをPython SDK経由でS3にアップロード Amazon Transcribeを用いてテキスト変換 変換後のテキストデータを時系列データに変換し、intdashにアップロード .data-vizで音声とテキストデータを表示 というイメージです。せっかくなので、準備手順も詳細にご紹介したいと思います。 ※ 今回のソースコードは、Python SDKが一般公開されていない点と、今回のコードはそこまで需要がないだろうという独断から、要点のみ公開することにします。全部ほしい!という方がいらっしゃましたらぜひぜひ以下のお問い合わせ窓口へお願いいたします! https://www.aptpod.co.jp/contact/ 1.iPhoneのmotionアプリから音声データをintdashにアップロード iPhoneに向かって以下を朗読します。 ポンコツ滑舌マンにとっては大変な苦行ですが、ここがこらえどころです。 (この記事のショボさをごまかすべく、最初はみんな大好きサンドウィッチマンのコント「ハンバーガー屋」をチョイスしたのですが、著作権侵害が怖くてやめました) intdashは、100ミリ秒∼1ミリ秒間隔程度の高頻度で発生する時系列データを品質保証のないネットワークを経由して、 高速・大容量かつ安定的にストリーミングするための双方向データ伝送プラットフォームです。 intdashは、プラットフォームを構成する製品・サービスの総称を兼ねており、 INTeractive DAta Streaming Hub の頭文字を並べた略称です。 こちらから引用: https://www.aptpod.co.jp/products/intdash/ 2.音声ファイルをPython SDK経由でS3にアップロード 大きな壁を乗り越えた後は、Python SDKを使用してiPhoneからアップロードされた音声データを取得します。 ※ Python SDK自体はまだ一般公開されていませんが、一部コードを貼り付けておきます。 ### Python SDKを使用して時系列データを取得 it = lc.units().list( start=XXX # iPhoneで計測を開始した時間 end=XXX,  # iPhoneで計測を終了した時間 meas_uuid=m.uuid, # 1つ計測に紐づくuuid ) ### 取得した時系列データをファイル化 file = io.BytesIO() for us in it : pcm_list = [u for u in us if u.data.data_type.value == lowlevel.core.DataType.pcm.value] for u in pcm_list: file.write(u.data.data) with wave.Wave_write("20191220_1_test.wav") as w: w.setnchannels(pcm_list[0].data.channels) w.setsampwidth(pcm_list[0].data.bitpersample // 8) w.setframerate(pcm_list[0].data.samplerate) w.writeframes(file.getvalue()) w.close() 3.Amazon Transcribeを用いてテキスト変換 ファイル化したデータをS3にアップロードします。 S3のファイルアップロード画面 アップロードしたファイルを参照し、Amazon Transcribeのジョブを作成します。 session = boto3.Session(profile_name='****', region_name='ap-northeast-1') transcribe = session.client('transcribe') job_name = "audio_test_20200219_2" job_uri = "https://s3-ap-northeast-1.amazonaws.com/transcribe-20191219/audio/20200219_2.wav" transcribe.start_transcription_job( TranscriptionJobName=job_name, Media={'MediaFileUri': job_uri}, MediaFormat='wav', LanguageCode='ja-JP' ) 上記を実行すると以下のようなファイルがjson形式で出力され、S3に保存されます。 { "jobName": "audio_test_20200219_2", "accountId": "263480619405", "results": { "transcripts": [ { "transcript": "インド ダッシュ は 百 ミリ 秒 から 数 ミリ 秒間 程度 の (以下略)" } ], "items": [ { "start_time": "1.44", "end_time": "1.79", "alternatives": [{ "confidence": "0.2469", "content": "インド" }], "type": "pronunciation" }, ======= 省略 ======== ] }, "status": "COMPLETED" } ファイルに対して翻訳した全文と、各単語ごとの変換結果が出力されています。 items の内容を参照すると、変換されたテキストが全体のファイルのうち、どのタイミングで発言されたかが把握できるようになっています。この情報を元に、テキストデータを時系列データに変換していきます。 4.変換後のデータを時系列データに変換し、intdashにアップロード 上記の各itemごとの start_time と end_time を用いて時系列順にテキストデータを並び替え、intdashにアップロードしてみます。 for text in text_list: content = '' for con in text['alternatives']: content += con['content'] if 'start_time' not in text or 'end_time' not in text : text['start_time'] = before_start_time text['end_time'] = before_end_time ## 特定の時間があいたら文章をリセットする if before_end_time != None and float(text['start_time']) - before_end_time > 1: text_joined = '' text_joined = text_joined + content before_end_time = float(text['end_time']) before_start_time = float(text['start_time']) ## 時系列のデータ形式でアップロードする(Python SDKを使用) units.append( lowlevel.core.Unit( elapsed_time = pd.Timedelta(str(text['end_time']) + 's'), channel = 1, data = protocol.data.String(label='text', value=text_joined) ) ) 5.data-vizでテキスト表示してみる 変換したテキストデータをintdash上にアップロードすることに成功したので、もう一度朗読内容を振り返りつつ、data-vizで確認してみます。 intdashは、100ミリ秒∼1ミリ秒間隔程度の高頻度で発生する時系列データを品質保証のないネットワークを経由して、 高速・大容量かつ安定的にストリーミングするための双方向データ伝送プラットフォームです。 intdashは、プラットフォームを構成する製品・サービスの総称を兼ねており、 INTeractive DAta Streaming Hub の頭文字を並べた略称です。 さすがAmazon Transcribe、いい感じに音を拾って変換出来ている様子が伺えますね 👌 今回は音声以外にも、音声データを分析する際に用いられる音の波形情報と、スペクトログラムも同時に表示しています。1つのデータを数種類の可視データに加工し、1つの時系列で同時に確認できる点は、弊社製品の大きなアピールポイントとなります。 まとめ 今回は音声機能をテーマに、intdashとAmazon Transcribeで簡単に同時文字起こし機能が作れました。内容的には非常にあっさりしていましたが、より文字起こしの変換機能の強化を目指し、案件での使用をめざす予定です。今後も引き続き 音声データ をテーマに解析手法を検討してまいります!
ソリューションアーキテクトの榮枝です。 今回は1/15(水)〜17(金)に開催された オートモーティブワールド の展示のお話を。 自動車関連の見本市であるオートモーティブワールドにコネクテッド・カーに大きな関わりを持つ製品・サービスを提供する弊社も出展させていただきました。 (今回の展示の柱の一つ、ビデオエンコーダについては おおひらさんが記事にしています 。) 弊社としてはこのイベントに昨年も出展しているのですが、入社時期的に私にとっては初めてのイベントでした。今回は準備全体の取りまとめ役として、好奇心からその役を受けさせてもらいました。 老舗企業ならこういう出展のやり方や担当者/部署は決まっていたりするのかもですが、 私自身はこの手の取りまとめは初めてで、何をすれば良いんだろう?というところからでした。 案の定色々苦労したり得られたものも色々あったので、その話を書かせてもらいます。 当初想定した進め方 当初展示までの流れを以下のイメージに沿って進めてみることにしました。 現在の状況整理 複数の展示アイテムとそのテーマの整理 各展示アイテムの担当者決めと進捗状況の管理 (必要に応じて)展示用のデータ取得 展示アイテムから展示会場の設置/配置を決め 設営 展示当日へ 「1. 状況整理」~「3. 進捗管理」 この1~3は順々に完了してスムーズに話が進みました!。。。とは行かず、行ったり来たりしながらになりました。 なかなか思ったように話は進まないデス。 私が取りまとめ役を引き受ける前からある程度話は進んでいたようで、 その関係者に現状の聞き取りからはじめました。 .... が、 ○○という展示をしたいという話があったけれど、その実現可能性はまだ良くわかってなくて実現可能性を確認するには相応の工数が必要。その上で展示に向けてリソースを割いてもらうべきかをどう判断(調整)すべきか悩む △△という展示をしたい話があって、実現可能なのはわかっているが、(他の展示項目と並べたときに)その展示のコンセプトが自分として腑に落ちて無くて、何のためにそれを展示したいという話が上がって来たのかが分からない 以前まで使っていた展示アイテムをやめて、新しいものをやりたいらしいけどその理由が自分自身どうにも腑に落ちないから、開発陣に新しいものを用意してもらうにしてもうまく説明ができない。 といった感じでハッキリ分からないことが多く、具体的に「これを展示する」と決めるまでにズルズルと時間が過ぎていき、焦り(汗)、、、 要はコミュニケーションの問題なんですが、私自身社歴も浅く、ハッキリ理解するために相談する先のキーパーソンを見つけ出すのに時間がかかったというところが焦りポイントでした。 そんな状況だったのですが、昨年のオートモーティブワールド展示担当がうまく打ち合わせの場を用意してくれたことでなんとか話がまとまり、「何をどういうテーマのもと展示するか」が決まりました。 感謝🙏 反省の一つとして、 「忙しいキーパーソンの捕まえ方」といったテクニック/創意工夫が私に不足していたように思います。 「カレンダーが埋まっていても/slackへの反応が悪くても、捕まえる方法は色々ある。」 「3.データ取り」 何を展示するかが決まり、展示用のデータ取りをはじめました。 展示内容に向けて実験するためのデータや、 展示会場で見栄えのするデータや動画を撮ったりといった感じです。 社内の風景や社用車で街中を走ってデータ取りをできますが、 展示会場でインパクトある、もといサービスを 正しく&わかりやすく 理解してもらえるデモにするために、 なるべく良い環境でデータ取りしました。 フェイスマスク 動画に映った顔にマスキングする技術の展示です。それ自体はAWSのサービスを利用しているのですが、弊社システムに組み込んで使える一例として見せています。 これは、こちらを向いた顔が沢山映っている絵が欲しくて渋谷のスクランブル交差点を選んで撮影しました。 スクランブル交差点にて 年末の慌ただしい中、時間を見つけて撮影しに行きました。 思っていた以上にいい画がとれて、フェイスマスクを掛けてもらった結果も良くてホっ。 歩きながら撮ってみた画もあるのですが、手ブレがひどくてこちらはボツに。 外国からの観光客も同じように撮影していたりしたのですが、みんな手ブレ防止用のジンバルやスタビライザーを使っていて、、 動画撮影って奥が深いですね。 ※実際には動画で展示させていただきました。 ※フェイスマスクをかけてない元動画は公開していません。 ビデオエンコーダ用データのサーキットでの撮影 こちらは、 弊社のビデオエンコーダーの開発を担当したおおひらさんの記事 のほうが詳しいですね。 トヨタの86を借りて千葉県の茂原サーキットという場所で実車走行してデータ取りをしました。 余談ですが、86って豆腐店のイメージしかなかったのですが、最新のモデルはあれとはだいぶ違ってまたいいクルマなんですね。 自動車業界と馴染みの深い弊社なんですが、私自身は車より自転車派であんまり車に詳しくないという。。。 年始の時間が無い中サーキットの天気が悪くて一日延期したりと慌ただしかったですが、 おおひらさんをはじめ開発のハードウェアチームが取り仕切ってくれたので、 自分としては安心して見ていました。 オーディオデータ取得 弊社のシステム、今までは音に関する計測・可視化システムがなかったのですが、開発陣が頑張って年末に掛けて色々と揃ってきたこともあり、その展示も用意しました。 自動車関連の音 ということでドライバーの発声を録っても良かったのですが、エンジン音を録ってみようという視点で、ハードウェアチームが選定してくれた このギターとかの振動を拾うためのマイク をエンジン付近に固定して録ってみました。 ※こちらの動画も茂原サーキット上で撮影したものです。 (IT系(IoT系?)の弊社ですが、自動車関連の計測話が多いのでこの件に限らずこういった サーキットや変わった場所でのお仕事がちょくちょくあるのが面白い です。) 「4.展示会場の設置/配置」 展示には展示アイテムを用意するだけでなく、 会場のレイアウトを決めたり設営業者さんを手配したりといったことも必要なんですが、 当然ながらこの具体的なところを自力で行うのは難しいのでここは業者さんにおまかせです。 そうは言っても色々やり取りする必要があるのですが、 この辺りは社長や弊社のデザイナー、昨年の担当に進めてもらい、自分はなるほどこういう感じに進めるのか〜と思いながら見ていた感じです。 実際にaptpodのデザイナーさんが設計した展示レイアウト 「5. 設営」 会場設営は業者さんがしてくれるわけですが、 実際展示するデータはもちろんこちらで用意しないとなりません。 展示データ自体は上に書いたように用意できましたが、 いざ説明用に展示するとなると、さらなる準備が必要でした。。。 データを見やすく切り取ったり、 インターネット環境が良くなかったときのため(※)に動画にしておいたり、 説明の際に手間取らないように展示用PCのアカウント整理したり、 古くなってたOSを更新したり、 久しぶりに動かしたデモ用PCが壊れて動かなくなってたり...... (※Web経由で計測データを確認出来る可視化ツールがあるのですが、たくさんの人が訪れる展示開場の通信環境は悪い可能性があるため) 細かな作業が意外と多く地味に時間がかかり、ギリギリまで作業していたのですが なんとか展示会前日の1月14日(火)に間に合いました。 業者さんによるブース設営の風景を初めて見ました。 弊社だけでなく、出展社全社が同じように設営されてるので、会場はおおわらわ。 設営風景 「6.展示当日」 ようやく展示当日。 無事スタートできようやく一息、 とはいえここからが本番! 1月15日(水)〜17日(金)の3日間、私も説明員として立って、立ち寄ってくださった方々へ弊社の製品・サービスの説明や プレゼンブースでちょっとしたプレゼン形式での説明などを行いました。 プレゼンは、人が行き交う中急に始めるスタイルでした。いきなり(マイクで)大きな声で話だすのは勇気が入りますね。。 喋り出しはめっちゃ緊張しましたが、初めてしまうとなんとかなるし、なんなら少し楽しかったぐらいです。 説明員としての説明も最初は単純に自社紹介説明を繰り返していて、 興味は持ってもらえて名刺をいただくことまではできるのですが、 どうにも先につながる感じがせず、、、 ですが、以前弊社営業の方にその辺りのことのアドバイスもらったことを思い出し、 自社の話をするよりもお客さんのお困りごとを聞き出すように気をつけていくと 色々と話が弾み先の話が見えてきた、   かな? 総括 そんなこんなで無事展示会を終え、多くのお仕事のお話にも繋がりました。 大変でしたがとてもやりがいのあるイベントでした。 以下に諸々振り返りと反省点を記載しておきます。 - 1〜10まで自分でやらなきゃ!と思い込んで無駄な心労になっていた。状況共有とある程度の方向指針さえすれば、みんなどんどん動いてくれる。早い段階でもっと信頼して協力してもらえばよかった。 - 今回はっきり役割が決まってないイレギュラーな業務を経験したからこそ、役割分担の重要性とそのやり方を意識するようになった。普段の業務でも活かせるようにしたい。 - 普段関わりの少ない人とも、こういった機会があると色々話せるのでいいですね。 - あとになってからこの記事を書くことになったので、写真をあんまり撮ってなかった。記事用に色々写真撮っておけばよかったです。「うらがわ」というタイトルにしたのに、展示ブースの裏の写真を撮っていなくって後悔。 諸々反省もあるのですが、次またあれば反省を活かしてチャレンジ、、するかも。 (なかなかに忙しく、他の業務への影響も無くはなかった(汗)ので、状況次第で!) 内容としては以上ですが、ぜひ展示の詳細などに興味が出た方は弊社までご連絡ください!お待ちしています。
はじめに ハードウェアGpのおおひらです。 弊社は1/15(水)〜17(金)に開催された オートモーティブ・ワールド に出展させて頂きました。 お忙しいなか足をとめてブースに立寄って頂いた皆様、大変ありがとうございました。 car.watch.impress.co.jp www.nikkan.co.jp 本記事ではブースにて初お披露目となったビデオエンコーダ試作品の開発経緯を紹介させて頂きます。 はじめに 開発の背景 課題1:イメージセンサ撮像〜打刻のタイミング管理 課題2:複数のカメラを使用してデータ分析する際の不正確さ 課題解決にむけて フェーズ1 : 仕様検討 & 原理試作 (3ヶ月) フェーズ2 : 設計試作 (2019/6月〜12月) 基板/筐体設計 ヘテロコアのSystem on a chip (SoC)のソフト設計 FPGA設計 事前走行テスト おわりに 開発の背景 従来、弊社では移動体の動画計測が求められるユースケースに対して 民生品のWebカメラ を利用していました。特にビデオエンコーダ内蔵のWebカメラであればサーバーに送信する際の処理リソースも末端の機器側に分散できますし、そもそも民生品なので圧倒的に低コストで調達性もよいというメリットがあります。 一方、動画に加えて、車両の制御バスであるCAN(Controller Area Network)や運転者の挙動を解析するためのアナログセンサなどを含め、移動体のシステム全体のセンサデータを統合して計測するという観点では、その時系列の正確性に課題が生じます。 課題1:イメージセンサ撮像〜打刻のタイミング管理 一般に民生品のWebカメラは自動の露出モードを有する製品が多く、暗いシーンでは露出時間を伸ばしてフレームレートを落とす処理がされます。弊社が提供する産業IoTのプラットフォームである intdash では末端の機器群におけるターミナルとして車載コンピュータ上のソフトウェアが複数センサの打刻を一元的に管理しますが、カメラから出力される映像ストリームに対してはそれを受信したタイミングで打刻しますので、受信までの間に生じる遅延を管理することはできません。もちろんカメラの撮影設定パラメータを試行錯誤したり、実験的に種々の撮影環境における遅延時間を求めることは可能ですが、カメラ内のエンコード処理に起因する遅延時間も含めると完全に保証をすることは難しいと言えます。 この結果として、車両から計測したCANデータを可視化した結果と、同タイミングで計測した動画との間で数百ミリ秒の時間のずれが生じ、計測結果を統合的に見た際に違和感をおぼえるという問題が生じます。 課題2:複数のカメラを使用してデータ分析する際の不正確さ 一般にカメラのデバイスの源振には特性のばらつきがありますので、複数のカメラ間におけるフレームレートは完全に一致しません。仮に設定上は各自のカメラが30 fpsになっていたとしても、カメラ#1は29.7 fps, カメラ#2は30.4 fps …というように誤差が生じ、その結果、例えば1時間の計測を行ったとしてカメラ#1は106,920フレーム、カメラ#2は109,440フレーム…という具合にフレームの総数が異なる結果となります。 動画は結局のところ複数の静止画の集合体ですので、複数カメラの映像を統合的にデータ解析をしようとすると、まずこのフレームのばらつきの前処理に手間がとられます。 また、ひとつの事象とそれに紐づくデータを解析する際の正確性も課題となります。例えば道路上に飛び出してきた歩行者を前方カメラが捉えた瞬間、運転者の視線がどこに向いていたか、その後どのタイミングでどれぐらいまでブレーキを踏み込んだか、ハンドルをどう切ったか…など、特定のシーンに着目したデータ解析をする場合、30 fpsの動画の1フレーム周期は約33ミリ秒で他のセンサデータの周期に対して大きく、たった1フレームずれるだけで事象分析の正確さが失われるケースが生じることも想定されます。このように、 データの利用価値を高める という点でも解決が必要な課題が多くあります。 課題解決にむけて 下記の方針を立てました。 撮像のシャッタータイミングを管理できるよう、トリガ入力をもつ産業用カメラモジュールを利用する カメラモジュールの価格はピンキリなので極力低コストで導入できるものを選定する 複数のカメラの撮像タイミングを管理するためにリアルタイム性に優れた制御デバイスを用意する ビデオエンコード部の遅延時間を管理するため、リアルタイムエンコード処理も可能なデバイスを用意する 撮像タイミング信号を使ってスナップショットした時刻情報を、ビデオエンコード時にメタデータとして埋め込む 時刻情報は既存の弊社内製CANインターフェース機器と同様、源振となるクロックを機器間で共有して生成する フェーズ1 : 仕様検討 & 原理試作 (3ヶ月) このフェーズでは技術的な解決方針が機能することの見極めとして、カメラモジュールの機器選定やH.264のビデオエンコードが可能なデバイスの選定を行い、実際に評価基板を組合せて原理に問題がないか確認を行いました。技術的な実証・試行錯誤をチームメンバー2名に担当してもらい、並行して私は製品に対する要求のヒアリング・分析・対策立案→技術選定という仕様検討のサイクルをまわすことに注力しました。要求の一例をご紹介すると… カメラは複数とりつけたい、最低4つほしい バスやトラックなど商用車にとりつけたいケースもあるのでケーブル長は長くしたい、5mは必要 車両のペダルの撮影など、狭い場所にカメラを潜り込ませることもあるので筐体は極力小さくしたい カメラのレンズは広角〜望遠など、お客様のユースケースに合わせて選択肢を設けたい LTEの上り回線の限られた帯域で複数のカメラ映像を伝送したい 映像はサーバーに送信すると同時に、高画質でローカルストレージにも保存しておきたい (圧縮なしのRAWデータを解析用に保持したい) などなど。 こだわるところと割り切るところを見極めながら落とし所を探りました(こういう地道な製品開発ループまわしは大好物😇) ある程度の実現可能性が見えた段階で社内で開発計画を共有し、商品のコンセプトや想定顧客といったマーケティング観点と、コスト(試作/量産時の材料費・開発委託費)やスケジュールといった実際の設計観点で承認を頂きました。時期的には2019年の5月末頃で、この時点で 2020年1月のオートモーティブワールドでの試作機の動展示がひとつの大きなマイルストーンである ということをマネジメントと開発チームメンバーとの間で認識合わせしました。 フェーズ2 : 設計試作 (2019/6月〜12月) このフェーズでは実際に試作基板をつくり、デバイス選定や仕様に問題が無いか機能の検証を行います。特に電源部品や主要な半導体部品が確定できること、ソフト含めたアーキテクチャに問題がないことが重要です。開発メンバーの内訳は、電気兼メカ(私)とソフト・ロジック(FPGA)設計それぞれ1名ずつの3名体制。 基板/筐体設計 回路設計を弊社で行い、基板のアートワーク〜製造と筐体の選定(熱設計含)・部品調達を協力会社様にお願いいたしました。今回は設計スケジュールの短縮のために、主要な半導体に周辺部品が既に実装されているSystem on Module(SoM)を利用する方針にし、回路設計に約1.5ヶ月、AW〜製造で約1.5ヶ月で、試作品の納品までトータル約3ヶ月の設計期間となりました。 基板が納品されてからは基本的な電源性能の確認やペリフェラルの電気的な検証を行い、必要に応じて一部回路や部品の改修を実施してソフト・FPGAとの結合を進めました。 ヘテロコアのSystem on a chip (SoC)のソフト設計 評価ボードベースの検討から、試作機で使用するSoMを利用した検討に移行しました。まずはSoMのメーカーが提供しているキャリア基板を利用して映像入力や周辺ペリフェラル(USB, Ethernet, HDMI etc)の機能確認をしつつ、試作基板むけのピン設定ファイルなどを作成し、試作基板のハードウェアの検証がひと段落した段階で実際に結合してソフトを立ち上げるという手順です。 ソフトウェアの設計観点では、Linuxの映像ストリーム処理(GStreamer)でタイムスタンプの扱いを修正したり、リアルタイム処理に利用する小規模のARMコア上のReal-Time OS(RTOS)のソフト設計が必要だったり。 過去の記事 を見て頂くと分かるとおり、ひとつのチップに複数のARMコアやハードウェアアクセラレータが混在するSoCを採用しています。担当メンバーはこういったSoCを扱うのは初めてだったにも関わらず、奮闘してくれて大変ありがたく頼もしい限りでした。 FPGA設計 今回の試作品では、カメラのシャッタータイミング制御や映像の縮小・合成処理のために前段に FPGA を利用しています。こちらも評価基板ベースの検討からはじめて、主に下記の機能を実装していきました。 カメラとの映像伝送I/F (1.8Gbpsの高速シリアル通信を4ch) SoCとの映像伝送I/F (パラレル変換して後段に送信) 映像の縮小 (簡易的なピクセルの間引き) フレームメモリを利用した4画面合成 組込みCPU (XilinxのMicroBlazeでFreeRTOSを動作させ、HostとなるSoCやカメラと制御通信する) FPGAのメモリインターフェースを扱うのが初めてだったり、 高位合成で映像処理を作ってみたり と、開発メンバーにとっては初めてづくしの業務が多かったと思いますが着実に進めて頂き大変助かりました。 事前走行テスト これまで挙げてきた機能のインテグレーションが概ね完了してカメラからサーバーへの映像の疎通ができたのが2019年の12/20頃で、年末休みに入るまでの1週間で実際に車に積んで市街地走行をするという検証を進めました。なかなか痺れるスケジュール…ですがこれも計画通り🙃 カメラの露出タイミング制御や画質調整、4画面合成機能のバグ修正、サーバー側のソフトウェアとの疎通の問題などを順番に潰しつつ、新年明けてからは展示会用のデモ計測のために千葉県の 茂原ツインサーキット をお借りして実車計測を行いました。 86にからみつく計測器たち 4-camera demo 上のデモ映像は、サーキットにおける実車計測の結果を VisualM2M という可視化ダッシュボードで再生したものです。本試作機で計測した動画(計5ch)や、オーディオ(エンジン音を録音するマイク)、車両のCANデータから可視化したハンドルやスピードメータを統合してWebブラウザ上で可視化しています。 特に4画面合成された左下のハンドル俯瞰映像と、ダッシュボード上のハンドルのイラストの動きに注目して頂くと完全に同期していることがわかります。これは本記事で紹介したビデオエンコーダと弊社製のCANインターフェス機器が同一のタイムスタンプを生成して打刻しているからこそ可能な計測と言えます。 おわりに 展示会で頂いたフィードバックをもとに、一旦仕様の見直しをしつつ、確実に商品化を進めてまいります。 具体的には、 カメラの接続を取り回ししやすいケーブルに (汎用のインターフェースを採用) 全体の材料費低減 (一部SoMをやめてICを基板実装したり筐体を小型化したり) カメラモジュールの再選定 (今回は自作したものの、量産コスト観点でOEM/ODMも検討) 製造性の改善 画質(ダイナミックレンジやエンコードの画質) システム全体の遅延低減 (SoCのHWアクセラレータはまだまだ使いこなしの余地あり) 各種信頼性試験やEMC試験 などなど、まだまだやることは山積みですが、弊社メンバー皆で協力して良いモノを作っていきたいと思います。 動画と各種センサデータの時系列解析に課題をお持ちの方はぜひ弊社までご連絡下さい。よろしくお願い致します。
先進技術調査グループのリサーチエンジニアの酒井 ( @ neko_suki )です。 先進技術調査グループでは、新しいトランスポートプロトコルのQUICの製品への適用を検討しています。今回の記事では、自社が主に扱う高頻度なデータの伝送における課題のひとつをQUICを適用したらどうなるかを評価してみました。 目次は以下の通りです。 QUICとは Head of Line (HoL) Blocking とは 実験内容 実験結果 まとめ 参考文献 QUICとは QUICとは、Googleが提案・実装をし、現在IETFで標準化が行われているプロトコルです。(区別のため、前者はgQUIC、後者はiQUICと呼ばれています)。2020年の中頃にはRFCが発行される予定です。 QUICはUDPベースの信頼性のある接続を提供するトランスポートプロトコルです。QUICではコネクション上に仮想的なストリームを生成して通信を行います。 今回は高頻度なデータの伝送における課題のひとつであるHoL Blocking という課題について、QUICを適用して課題の解決が可能かどうかを確認します。 Head of Line (HoL) Blocking とは TCPは順序を保証します。そのため、以下の図のようにパケットロスが発生すると、サーバアプリケーションが後続のデータを受信するタイミングに遅延が発生するという課題がありました。 TCPにおけるHoL QUICでは異なるストリーム間の順序保証を行わずにデータを送信することができます。(ただし、同一ストリーム内では順序保証がされます)。下の図のようにUDPのパケットがロスしても、サーバアプリケーションは別のストリームで送信されているデータをHoL Blockingによる遅延の影響なく受信することが期待できます。 実験内容 実験は、高頻度なデータの塊をクライアントからサーバに伝送するケースを想定します。 送信するデータは仮に1unit (データの単位)を8byteとします。これを1000 unit/secで送信します。そのために1msec毎に1unitのデータをクライアント側で生成します。 クライアント-サーバ間の送信遅延を評価するために、データがクライアントで生成された時刻とサーバ側で受信した時刻の差分を遅延時間として定義します。 intdashでは、IPやTCPヘッダのオーバーヘッドを低減するために、flushという一定期間のユニットをバッファリングして送信する仕組みを導入しています 1 。 flush 構造 なので、10個の連続したデータを一つの塊として送信します。以降、このバッファリングしたデータ単位を flush と呼びます。 以下の図のように、10個の連続したデータを1個のflushとして、一つのQUICパケットおよびUDPパケットに格納します。 一つの塊に含まれるデータの到達遅延は、生成時刻が古いデータから順に、片道遅延+9 msec+α (αはタイミングに依存), 片道遅延 + 8msec + α, ... 片道遅延 + 0msec + αで到達することが予想されます。 今回の実験ではHoL Blockingの影響が発生しないことを確認するために、1flush毎に一つのQUICストリームを作成してデータを送信します。 これによって、特定のUDPパケットがロスしても他のパケットの受信には影響が出ないことが期待されます。 評価はネットワークのエミュレーション環境で行います。HoL Blockingの影響を受けずにデータを送信できることを確認するためにパケットロス率は2%としています。また、モバイル環境を想定して往復遅延50msec(片道25msec) を設定しました。 参考までに、QUICの実装は quic-go を用いています。 実験結果 以下の図では、送信したデータそれぞれに対して時系列に遅延時間として「ユニットの受信時刻 - ユニット生成時刻」を時系列にプロットしています。縦軸が遅延時間 (msec) 横軸は、時系列に10秒間送信した10,000個のデータを表しています。 片道の遅延が25msecかつ1flushで10msec分のデータを溜め込むため、パケットロスが発生していない場合は、25msec~35msecの範囲におおむねデータが収まります。図を見ると多くの場合にそうなっていることが確認できます。また、パケットロスが発生しているときのデータが、その範囲から飛び出ていることが見て取れます。 定量的な評価のため、パーセンタイルを確認してみます。ここではベースラインとしてロス率が0%で実験をした時の値も載せています。 パーセンタイル ロス率2%の時の遅延 (msec) ロス率0%の時の遅延 (msec) 2%の時の遅延 - 0% の時の遅延 (msec) 98% 85.81 35.83 49.98 97% 49.50 35.66 13.84 95% 43.10 35.53 7.57 90% 38.31 35.10 3.21 50% 32.63 31.11 1.52 98パーセンタイルの時には、パケットロスがない時と比較して、49.98 msecの遅延が発生しています。一方で、97パーセンタイルになると13.84 msecまで縮まります。95, 90, ... 50と値を小さくしていくと、パケットロスがない時との差分はほぼ無視できるレベルまで縮まることが確認できます。 さらに、生データを詳細に分析してみます。 1つのUDPパケットが、パケットロスせずに1flush分のデータを送った場合の遅延時間「ユニットの受信時刻 - ユニット生成時刻」は以下のようになります。 flush内に含まれるデータのインデックス ユニットの受信時刻 - ユニット生成時刻 1 35.81 2 34.87 3 33.79 4 32.79 5 31.79 6 30.86 7 29.74 8 28.76 9 27.76 10 26.95 10msec分のデータがバッファされるので、最もインデックスが古いデータが片道遅延 25msec + 11msec、最も新しいデータがほぼ片道遅延25msec+2msec 程度になっていることがわかります。 データを詳細に分析した結果、パケットロスしたと思われる1flush分のデータは遅延時間が100msec近い値になることを確認しました。遅延が増加した状況は1flush=10個のデータ分続きます。そして、その次のflushのデータは正常な値に回復します。 相対的なデータのインデックス ユニットの受信時刻 - ユニット生成時刻 前のflushの最後のデータ 27.27 1 103.74 2 102.76 3 101.79 4 100.68 5 99.77 6 98.72 7 97.74 8 96.58 9 95.73 10 94.75 次のflushの最初のデータ 36.25 遅延が80msec 以上だったデータをこのパターンに含まれているとすると、10000個あるデータの中で、210個がこのパターンに該当しました。これはおおむね全体の2%でありパケットロスを設定した値と同じになります。 なので 期待通りにHoL blockingの影響を回避してデータを受信できていることが確認できた と言えそうです。 まとめ 本記事では、自社が扱う高頻度なデータの伝送における課題のひとつであるHoL Blocking という課題について、QUICを適用したらどうなるかを評価してみました。HoL Blocking の回避の観点だとQUICの適用は有効であると言えそうです。 先進技術調査グループでは今後もQUICの検討を進めていきます。 QUICなどの最先端の通信・ネットワークプロトコル技術を用いた研究開発・製品開発に興味がある方は、ぜひ以下の採用ページへのアクセスをお願いします! 採用情報: https://www.aptpod.co.jp/recruit/ 参考文献 White Paper: iSCP (intdash Stream Control Protocol) https://www.aptpod.co.jp/basetech/05.iscp.pdf ↩
あけましておめでとうございます 。 2020年最初のTech Blogはデザイン室の上野が送らせていただきます。 aptpod Advent Calendar 2019 はご覧になっていただけましたでしょうか? まだの方はぜひご一読ください。 今回ですがこのAdvent Calendarで作成した OGP画像 について書かせていただきます。 OGP画像とは 今回作るに至ったきっかけ ワークフロー 素材作成 テキスト 背景画像 調整 / レビュー 書き出し / 設定 掲載 Advent Calendarを終えて [後日談] やらかしてました OGP画像とは OGP(Open Graph Protocol)とは、Webページの内容を伝えるための情報のことで、OGP画像とはその中でも投稿した記事をSNSなどで展開する際に表示されるサムネイルのような画像を指します。 これですね。 記事の具体的なイメージを伝えることで読み手に何が書いてあるかを想像させ、クリックしてもらう動線になるためとても大切なものとなります。 今回作るに至ったきっかけ 弊社ではここ数年で社内のエンジニアも増えてきまして、2018年からAdvent Calendarに取り組むようになりました。 2018年のAdvent CalendarはQiitaに載せるくらいの規模感だったのですが、2019年からTech BlogやTwitterアカウントを立ち上げ、より積極的に活動を知ってもらえるよう動き始めました。 blogやTwitterでの展開は初の試みといった部分もあり投稿初期の記事ではOGP画像を設定しておらず、SNS上で表示を確認してみると記事内で最初に使われている画像やテキストが画像化され自動的にOGP画像に設定されてしまいました。 「ナンカモッタイナイナー」 エンジニアがとてもいい記事を書いているのに、ぱっと見たときに記事の内容をイメージできるようなものにすればもっとアクセス数伸びるのでは?と思いSlackで改善を提案してみました。 結果たくさんの反応をいただきまして、そこからデザイン室でOGP画像を展開していこうということになりました。 ワークフロー ワークフローと言いましても成果物は一枚の画像なので早い人は数分で終わってしまうような作業です。また作り方に正解もありません。 今回は自分が作る上で考えたことを織り交ぜながら紹介できればと思います。 まず下準備としてデザイナーは担当する記事を読み内容を把握した上でコンテキストに沿ったOGP画像の作成に入ります。 そもそも内容と全く違うイメージを作ってしまっては元も子もありません。 内容をしっかりと把握したら、以下のようなイメージで展開していきます。 画像サイズは こちらの記事 を参考に 横1200px×縦630px で展開していきます。 素材作成 テキスト OGP画像といえば目を引くテキスト+背景画像の組み合わせをよく見ると思います。 今回のAdvent Calendarではテキストは基本的に記事のタイトルと同じ文言で作っていきました。 冗長なものは読み手を想定してどんなワードに惹かれるのかピックアップし組み直したりします。 仮にレイアウトしてから フォントの種類 サイズ 色 ウェイト(文字の太さ) 段組 などを読みやすく目を引くようにアレンジしていきます。 サイズやウェイトで抑揚をつけてみたり、色を加えたり下線を引くことでピックアップしたワードを強調したりしました。 背景画像 背景画像はAdobeStock他フリーの素材や記事内の画像を使用・加工したりと内容に沿ったイメージで展開します。 (素材を使う際は商用可・個人利用のみなど使用できる範囲がライセンスごとに細かく分かれているので気をつけましょう) コントラストが強かったりテキストを載せるスペースがないといった複雑な画像には ぼかす シェイプで座布団を敷く 塗りのオーバーレイを加える ような工夫をすることでテキストの可読性を上げることができます。 調整 / レビュー テキストと背景画像を合わせて調整していきます。 比重を考えてレイアウトや色味を調整し、全体的にバランスがよくなるような画像作りを心がけています。 aptpodデザインテイストと記事とのバランス品質を担保できるよう、デザイン室内でレビューを行います。 フィードバックを元に調整し、完成まで持っていきます。 書き出し / 設定 完成した画像を書き出し、 ImageOptim などを使いファイルサイズを軽量化します。 最後にTech Blogの管理画面からでOGP画像を設定するまでがデザイナーのワークフローになります。 掲載 OGP画像を設定し投稿された記事は掲載されるとこのように反映されます。 #はてなブログ 激動の2019年を振り返る - aptpod Tech Blog https://t.co/daS7HOFKSS — aptpod_tech (@aptpod_tech) 2019年12月25日 Advent Calendarを終えて 今回Advent Calendarで連日投稿される記事のOGP画像をつくるのはなかなか大変でした。 ですが作っていく内にグラフィック作成の知見もたまり、(途中からですが)最終日までやり遂げてとても達成感がありました! 今後も継続してOGP画像作っていきたいと思います! [後日談] やらかしてました この記事を書いているときに恥ずかしながら投稿済みのOGP画像にTypoを見つけてしまいました。 (いやもう本当にすみません) そんな僕のようにやらかしてしまって修正をしたけど、肝心のSNS側はキャッシュが残っててチェックできない! といった方のためにキャッシュのクリア方法をまとめてくださった方がいたのでリンクおいておきます! OGP情報を更新した時のTwitter・facebookのキャッシュクリア方法 | 野良人(のらんど)|大阪府堺市のWEB制作屋さん aptpod Tech Blog Twitter デザイン室がどんなことをやっているのか興味がある方はこちらの記事を是非! 高速データ可視化におけるフォントの重要性 - aptpod Tech Blog ありがとうございました!
Aptpod Advent Calendar 2019 25日目=最終日の記事です。 CTOの梶田です。 あっという間に最終日となり、小さなトラブル😷もありましたがみんなの頑張りで今年も無事に完走といったところです。 素晴らしい! 今年は新しい試みでTechブログでの挑戦になったわけですが、昨年に比べ、記事を投稿した人も増え、バリエーションも広がり来年への布石ができたかなーと個人的には思っています。 さて、本題に。 2019年も終わりということでちょうどよいので月並みですが、2019年振り返りと2020年に向けて書こうと思います。 はじめに 2019年タイムライン 1月 〜3月 〜6月 次世代への向けての整理 〜9月 組織変更 iSCP特許登録 〜12月 次世代向けの開発 さいごに おまけ 参考リンク(書籍) はじめに 昨年末に シリーズBの資金調達 を発表し、あっという間に2019年も終わりに近づいています。 あー早かった。なんかここ何年かずっと早いけど。。😅 人も増え、組織も変わり、整備することもどんどん増えていき、向き直りすることも。 技術的負債も見えてきて、このまま行くのか一旦立ち止まるのか。。。 そんな判断もありました💦 ありがたいことに売上規模は拡大の傾向にあり、来年もさらなる拡大を見込めるような状況にあります。 いろいろ試行錯誤はありましたが、結果的にはいい方向に向かっていると思っています。 さてさて、そんな試行錯誤含めた2019年の振り返りを書いていきます。 2019年タイムライン 2019年のタイムラインをざっと。 イベント出展もキーポイントなので開発のトピックと並列で記載。 ちなみにこちらは主要なイベントのみでこれ以外のイベントにも出展しています。 1月 オートモーティブワールドという比較的大きな展示会に合わせて、以下の 対外リリース を発表しました。 Visual M2M Motion for iOS Synchronized CAN Transceiver Python SDK for intdash Analytics Services このリリースの中でも Python SDK for intdash Analytics Services は、Python SDKでデータを加工したり、データ分析や機械学習環境として広く使われている Jupyter Notebook を使って処理ロジックの開発や試行錯誤したり等々、新たな利用幅拡大への礎となっています。 また、イベントも盛況でここから案件につながったものも多くありました。 〜3月 期末ということもあり、例年通り忙しく対応していたなーと。 あまり記憶がないw この中でも新しい機械学習でのユースケースのPoCを遂行できたのがポイントで今でも続いており、製品の対応の幅としても広がった出来事でした。 2日目の記事 Amazon SageMakerとintdashでお菓子の高速検出システムをサクッと構築してみた はこの派生ですね。 〜6月 Automotiveへ注力し、機能追加や周辺のドキュメント等々整備し強化を図りました。 いろいろありますが、成果物のひとつとしてこんなのがあります。 オートモーティブプロ評価ガイド 19日目の記事 エンジニアによるユーザーマニュアルの作り方 も成果物のひとつです。 また、並行してAWS Summitに向けて発表されたばっかりのAWS RoboMakerとの連携を検討し、デモの実装をしました。 この試行錯誤は 1日目 AWS RoboMakerとintdashでTurtlebot3を遠隔制御できるようにしました! の記事をご覧ください。汗と涙の結晶です! 次世代への向けての整理 時を同じくして、広がってきた製品と内部的な負債(技術的負債/組織的負債/思想的負債...)というものが見えてきました。 繁忙期を過ぎて落ち着いて見えてきたことも多く、 内部的にはスクラムの実践やプロダクトオーナーの配置、フロー策定、タスク管理の一新、インセプションデッキの作成...等々いろんなチャレンジをして模索していた時期でした。 ここでの判断は、 このまま突っ走るのではなく、先を見据えて継続的に開発できるよう整える ことを優先し、技術的もそうですが、組織的にも思想的にも整理してから前を進むこととしました。 いろんな課題も見えてきて、まだまだ今でも発展途上ではあるにせよここで立ち止まることが重要でした。 〜9月 イベントとしては、初出展となる 5G/IoT展 や 次世代自動運転・コネクテッドカーカンファレンス に出ましたが、やはり 時流としてHOTなこともあり、新しい様々な引き合いがありました。 中でも5Gは注目されているなと感じ、来年はさらに盛り上がるのでは!という期待もあります。 組織変更 8月には、 次世代への向けての整理 の整理の延長でもあるのですが、将来(スケール)を意識した体制へ向かうために組織変更を行いました。 大きなポイントとしては、 ミドルウェア/基幹技術開発に完全専任するチームを編成 CTO、VPoP、VPoEによるチーム体制(CTO室) となります。 自分としても組織が成長する中で、なかなか追いつかない部分もあり、いろいろ溢れていく中で 20日目の記事 に投稿している高橋を新しくVPoEとして迎え入れ、内部からVPoPを抜擢し、CTO室というチームとして開発組織の課題解決に取り組むことにしました。 整理していたGoogle スプレッドシートのキャプチャ(一部) その体制の準備のために多岐にわたる自分の業務を整理(キャプチャの下も続く。。。)してどう役割を分けるか等々検討しました。 まだまだできてないこともありますが、良くなってきていると感じています! iSCP特許登録 また、8月にはもうひとつ、2016年から進めていて、2017年に出願した 独自に開発したプロトコルである iSCP(intdash Stream Control Protocol) のベース特許がついに登録されました! こちらについては、別の機会のTechブログにて。 〜12月 11月には4回目となりますが、社内イベントとして千葉県の茂原のサーキットを貸し切って 自社製品に触れる(新しく入った人向け) デモ用データ取得 プロダクトの検証(新しい製品も含む) といった目的で走行しに行きました。 準備も大変ですが、製品に実際に触れて体験できるいい機会で 大人の遠足 といった感じです♪ ここでは、 モノへつながる感動 もあるのでこれを忘れずに製品開発に活かしていきたいという想いがあります。 次世代向けの開発 次世代向けの開発も進行し、継続的に開発できるような土壌固めを中心にアドベントカレンダーの記事内でもあった 従来のモノリス型のサービスから、徐々にマイクロサービスへ移行 : 8日目 スケールに向けたインフラアーキテクチャの実現 : 22日目 映像関連のハードウェア製品開発 : 12日目 、 13日目 、 23日目 の開発もありますが、そのほかにも様々な製品プロジェクトが進行中です。 12月に出展した AWS re:Inventについては、こちらの 15日目の記事 AWS re:Invent 2019 で AWS RoboMakerとintdash によるTurtlebot3の遠隔制御の展示を行いました! を見てください。 さいごに 駆け足になってしまいましたが、主な出来事を中心に2019年をざっと振り返りました。 そのほかにもいろんなことがありすぎて、書ききれないですが2019年 も 激動であったなと。 <そのほかのネタ> 通常のQAや市街地走行では問題ないけど、箱根を走行すると問題が出る → 魔物がいる!とか。。。 大変なこともありましたが、2019年も様々な知見や経験を得ることができました。 知見/経験の蓄積はテクノロジー企業の源泉であり、IoTの事業をやる上で重要だと考えています。 これは来年以降につなげていきたいと思っています。 2020年はオリンピックもあり、5GのPoCや自動運転の実証等々で様々なチャンスがありそうな期待感もあります。 来年も 激動 かなー。。。 泥臭いことも多いですが、 モノからモノへつながる感動 を忘れずに来年もさらなる進化を遂げて飛躍できればと思っています。 長くなりましたが、メリークリスマス!🎄 来年のアプトポッドにご期待ください‼ おまけ aptpodの採用ページ 参考リンク(書籍) プロダクトが進捗していないと感じた時の戦い方 カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで CTO・VPoE・VPoPの分立とCTO Delegation Pokerで権限移譲について学ぶ
TL;DR Raspberry PiでCAN通信を動かして、車両の診断データ(OBD-II)を見てみた話です。 SocketCANのISO-TPの機能が便利だったので、その紹介がメインになります。 はじめに この記事は aptpod Advent Calendar 2019 の24日目の記事です。 お送りするのは、組み込みソフトチームの 松下 です。 TL;DR はじめに 背景と目的 車両診断とは? 準備 確認環境 Raspberry Pi CAN通信用モジュール CAN通信の環境構築 下準備 can-utilsをインストール CAN Interfaceの有効化 SocketCAN interfaceの設定 車両に接続する OBD-II通信の環境構築 OBD-II通信とは? ISO 15765-2 ISO-TP 実際に使ってみると… can-isotpのカーネルモジュールをビルド&インストールする OBD-II通信してみる リクエスト時のCAN-IDについて リクエスト時のSID,PIDについて ISO-TPを使った、コマンドの送受信方法 VINの取得を試してみる 拡張CAN-IDで再トライ 注意と自己責任(免責事項) あとがき 弊社ソリューションのご紹介 参考リンク(書籍) 背景と目的 弊社の製品である intdash Automotive Pro は、車両CAN(Controller Area Network)データ等のデータロギング、可視化・解析などのワークフローをクラウドシステムで実現するソリューションです。このAutomotive Proをベースに、車両診断(ダイアグ通信)のワークフローをクラウド化できないか?という要望があり、車両診断に関する機能も開発中です。 この開発の過程で、実際にRaspberry Piを使って車両診断(OBD-II)通信を試したので、そのやり方を紹介します。 車両診断とは? 診断機能の概略については、 Vector社のはじめての診断 をご一読ください。 本記事では、CANを使ったOBD-IIによるダイアグ通信を紹介します。 準備 車両とCAN通信するために、Raspberry Piを用いた方法を簡単に説明します。 Raspberry PiでCAN通信する方法は、MCP2515を接続する方法が数多く紹介されています。 本記事でも、このMCP2515を使用します。 (詳細は、参考リンクをご覧ください) 確認環境 以下、筆者の動作確認環境です。 Raspberry Pi 3B+ Aptpod社製 ラズパイ拡張ボード 車両は弊社所有のHonda FIT Raspberry Pi Raspberry Pi 3B+ OS: Raspbian buster $ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 10 (buster) Release: 10 Codename: buster CAN通信用モジュール 今回は弊社で開発したラズパイ用の拡張ボードを使用します。 Aptpod社製 ラズパイ拡張ボード といっても、拡張ボード上はMCP2515をラズパイのSPIとGPIO25に接続する、一般的な方法で実装しています 1 。 データシートや、「 MCP2515 回路 」等で検索すると出てくる回路図を参考にしてください( こちら の記事がわかりやすいです)。 もしくは、 Amazonで売ってるMCP2515モジュール でも大丈夫だと思います。 宣伝 紹介ですが、弊社のラズパイ向け拡張ボードは、CAN通信以外にも、以下の機能を提供しています。 RTC(ハードウェアクロック) 電源スイッチ機能 電源管理機能(5V〜19Vの入力が可能) 状態通知用のLED(アプリケーションから制御可能) また、弊社ではCANバスに接続しUSBによってデータを取り出すCAN-USBインターフェイスもハードウェア製品として販売しております。 もしご興味がございましたら、 弊社のコーポレートサイトからお気軽にお問い合わせください 。 www.aptpod.co.jp CAN通信の環境構築 下準備 can-utilsをビルドするので、git等をインストールしておきます。 $ sudo apt update $ sudo apt install git build-essential can-utilsをインストール SocketCAN通信を使うには can-utils が便利なので、 ソースコードからbuild & installしておきます。 $ git clone https://github.com/linux-can/can-utils.git $ cd can-utils $ make $ sudo make install 以下のコマンドがインストールされます。 candump cansend isotpsend isotprecv ... CAN Interfaceの有効化 /boot/config.txt に、以下の設定を追記します 2 。 # Enable MCP2515 dtoverlay=spi-bcm2835 dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25 dtoverlay=spi-dma 再起動後、以下のようにSocketCAN interfaceの can0 が見えるようになりました。 $ ip link 4: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN mode DEFAULT group default qlen 10 link/can SocketCAN interfaceの設定 CANのビットレートは500kbpsに設定します。 $ ip link set can0 type can bitrate 500000 $ ip link set can0 up can0 のNICをUp後に、 ifconfig で can0 が認識できている事を確認できました。 $ ifconfig can0: flags=193<UP,RUNNING,NOARP> mtu 16 unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 10 (UNSPEC) RX packets 1992 bytes 15936 (15.5 KiB) RX errors 0 dropped 1992 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 車両に接続する 車両のDLC(データリンクコネクタ) 3 と接続して、 candump コマンドでCAN通信をダンプしてみます。 $ candump can0 can0 140 [8] 1F 39 00 00 00 1F 15 00 can0 141 [8] 89 2A 3F A8 00 C0 38 08 can0 144 [8] 00 01 00 00 00 A8 38 00 can0 140 [8] 1F 3A 00 00 00 1F 15 00 can0 141 [8] 89 2A 3F A8 00 C0 38 08 can0 360 [8] 00 00 28 FF 49 40 20 00 can0 361 [8] 00 1A 00 DC 20 00 00 00 can0 362 [8] 12 7A 82 96 00 00 00 01 can0 140 [8] 1F 3D 00 00 00 1F 15 80 ... CAN通信を確認できました! 4 OBD-II通信の環境構築 CAN通信の環境準備ができたので、OBD-IIのダイアグ通信を試してみます。 OBD-II通信とは? ものすごく簡単に説明すると、OBD-IIは多くの車両に搭載されている車両診断を行うための規格です。 5 OBD-IIでは以下の機能が定義されています 6 。 故障コード(DTC:Diagnostic Trouble Code)の取得要求 フリーズ・フレームデータの取得 エンジン系のECUのパラメーター取得 アクティブテスト機能 車両情報等 etc.. ここで、CAN通信上でOBD-IIのデータを送受信するには、ISO 15765-2の規格に対応する必要があります。 ISO 15765-2 一般的なCAN通信 7 の場合、1つのCANメッセージに載せられるデータは最大で8byteです。 しかし診断データをやりとりする上で8byte以上のデータをやり取りしたいケースも出てきます。 そこで、長い診断メッセージを分割し、複数のCANメッセージを使って伝送する(以降、マルチフレーム)方式としてISO 15765-2の規格が採用されています。 こちらの記事 ではデータの1byte目にデータ長さを設定していますが、実はこれもISO 15765-2の通信規格で定められたフォーマットだったりします。 ISO-TP ISO-TPはlinuxのsoketcan上でISO 15765-2に準拠した通信機能を提供するプロトコルドライバーです。 SocketCAN ISOTP このISO-TPの機能を使う事で、上述の面倒なCANの分割/結合/フロー制御の処理を実装する事無く、ダイアグ通信が可能となります。 ISO-TPの機能は、can-utilの isotpsend 、 isotprecv 等のコマンドで簡単に使用できます。 また、下記のように、 CAN_ISOTP のプロトコルでsocketを作成して通信もできます 8 。 int s; struct sockaddr_can addr; static struct can_isotp_options opts; addr.can_addr.tp.tx_id = 0x07e0 ; addr.can_addr.tp.rx_id = 0x07e8 ; s = socket (PF_CAN, SOCK_DGRAM, CAN_ISOTP); setsockopt (s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof (opts)); addr.can_family = AF_CAN; addr.can_ifindex = if_nametoindex ( "can0" ); bind (s, ( struct sockaddr *)&addr, sizeof (addr)): write (s, buf, buflen); 実際に使ってみると… 実際にISO-TPのコマンド使うと、 Protocol not supported と表示されてしまいました。 OTL $ echo 09 02 | isotpsend -s 7e0 -d 7e8 can0 -p 0:0 socket: Protocol not supported can-isotpのカーネルモジュールをビルド&インストールする こちら のページにしたがって、カーネルモジュール(can-isotp.ko)をビルドします。 $ git clone https://github.com/hartkopp/can-isotp.git $ cd can-isotp $ make kernel moduleをbuildするためのヘッダーがない!と怒られる場合、 make[1]: *** /lib/modules/4.19.75-v7+/build: No such file or directory. Stop. ラズパイのkernelのヘッダーファイルをダウンロードします 9 。 $ sudo apt install raspberrypi-kernel-headers 無事、ヘッダーファイルがインストールされました。使用しているkernelとヘッダーのVersionも揃っています。 $ ls /lib/modules/4.19.75-v7+/ build ... $ uname -r 4.19.75-v7+ ビルドができたら、モジュールをインストールします 10 。 $ sudo make modules_install $ sudo insmod ./net/can/can-isotp.ko can-isotp モジュールがインストールされました 11 。 $ lsmod | grep can can_raw 20480 0 can_isotp 24576 0 can 28672 2 can_isotp,can_raw can_dev 28672 1 mcp251x OBD-II通信してみる リクエスト時のCAN-IDについて 送信先のCAN-IDの指定方法ですが、11bit(通常)と29bit(拡張)の2種類があります 12 。 どちらの種別を受け付けるかは、筆者が確認する限りでは車種(メーカー)で異なる模様です 13 。 以下に、リクエスト時のCAN-IDの定義について抜粋しておきます。 種別 CAN-ID 説明 11bit (通常) 7DF リクエストを理解できる全てのECUへのリクエスト 29bit (拡張) 18 DB 33 F1 リクエストを理解できる全てのECUへのリクエスト リクエスト時のSID,PIDについて リクエスト時のデータセクションの最初のバイトはモードを指定します。OBD-IIではSID(Service ID)と呼ばれてます。 そしてデータセクションの2バイト目には、各モードに応じてPID(Parameter ID)を指定します。 SIDとPIDのリストは wikipedia にまとめられています。 以下、SIDの例を示します 14 。 SID 命令内容 0x01 現在のデータを表示する 0x02 フリーズ・フレームデータを表示する 0x04 DTCを消去し診断履歴を消去する 0x09 車の情報を要求する さらに、SIDとPIDの組み合わせの例を以下に示します。 SID & PID Description 01 05 冷却水の温度 01 0C RPM(エンジン回転数) 01 0D 車速 01 1F エンジン始動時からの稼働時間 09 02 VIN 09 04 キャリブレーションID 09 20 ECUの名前 たとえば、車の情報 (SID=0x09) からVIN (PID=02) をを取得したい場合は 09 02 を送信します。 また、現在のデータ (SID=0x01) からエンジン回転数 (PID=0x0C) を取得したい場合は、 01 0C という具合です。 ISO-TPを使った、コマンドの送受信方法 ISO-TPのコマンドを使うには、送信元( -s )と実際の受信相手( -d )をCAN-IDで事前に指定する必要があります。 しかし、どのECUが返答してくれるかは今はわからないので、以下の手順でCAN-IDを特定します。 すべてのECUコマンドへリクエストを投げてみる ( isotpsend ) 応答してくれるECUのCAN-IDを見る( candump ) 受信元のCAN-IDを指定して、マルチフレームのCANデータを受信( isotprecv )してデータを見る VINの取得を試してみる 実際に、弊社の所有するHonda FITのVINを取得してみます。 VIN(Vehicle Identification Number:車両識別番号)は最大17バイトの文字列なので、マルチフレーム通信しないと取得できません。 ラズパイでターミナルを(送信と受信用)2個開きます 受信側は candump can0 で待機しておきます 以下のVIN取得の要求コマンドで、応答があるか確認します $ echo "09 02" | isotpsend -s 7df -d 7e8 can0 -p 0:0 CAN-IDの 7DF は リクエストを理解できる、すべてのECUへリクエスト です どのECUが応答してくれるかわからないので、 7E8 は適当です 念の為、padding( -p 0:0 )も設定しておきます $ candump can0 むむ、応答がありません。CAN-IDが 7E* あたりから返答を期待したのですが。。。 拡張CAN-IDで再トライ ここで諦めず、CAN-IDを拡張(29bit)に変えてみます。 $ echo "09 02" | isotpsend -s 18DB33F1 -d 18DA33F1 can0 -p 0:0 CAN-IDの 18DB33F1 は リクエストを理解できる、すべてのECUへリクエスト です -d のCAN-IDは適当です 実際に送信してみると、CAN-ID= 18DAF10E から応答がありました! $ candump can0 can0 18DB33F1 [8] 02 09 02 00 00 00 00 00 can0 18DAF10E [8] 10 14 49 02 01 47 50 35 しかし、以降のマルチフレームのCANデータが受信できていません。 これは、ECUからの受信に対して、ラズパイ側がフロー制御の応答を返していないからです。 そこで、以下のコマンドでマルチフレームの受信処理を有効にします。 -d には、さきほど応答があったCAN-IDの 18DAF10E を指定します。 $ isotprecv -s 18DB33F1 -d 18DAF10E -l can0 この状態で、送信用のターミナルから要求を投げると... $ echo "09 02" | isotpsend -s 18DB33F1 -d 18DAF10E can0 -p 0:0 無事、応答が来ました!↓ ^15 $ isotprecv -s 18DB33F1 -d 18DAF10E can0 49 02 01 47 50 35 2D 31 32 30 31 ** ** ** 00 00 00 00 00 00 実際のcandumpの結果は以下の通り。 $ candump can0 can0 18DB33F1 [8] 02 09 02 00 00 00 00 00 can0 18DAF10E [8] 10 14 49 02 01 47 50 35 can0 18DB33F1 [8] 30 00 00 00 00 00 00 00 can0 18DAF10E [8] 21 2D 31 32 30 31 ** ** can0 18DAF10E [8] 22 ** 00 00 00 00 00 00 併せて、isotpdumpの結果も示します。マルチフレームのフロー制御が動作していますね。 $ isotpdump -s 18DB33F1 -d 18DAF10E can0 can0 18DB33F1 [8] [SF] ln: 2 data: 09 02 can0 18DAF10E [8] [FF] ln: 20 data: 49 02 01 47 50 35 can0 18DB33F1 [8] [FC] FC: 0 = CTS # BS: 0 = off # STmin: 0x00 = 0 ms can0 18DAF10E [8] [CF] sn: 1 data: 2D 31 32 30 31 ** ** can0 18DAF10E [8] [CF] sn: 2 data: ** 00 00 00 00 00 00 応答のデータフォーマットの詳細は割愛しますが、最後の17バイトのASCIIコードが、そのままVINの情報です。 47 50 35 2D 31 32 30 31 ** ** ** 00 00 00 00 00 00 変換すると GP5-1201*** となります。 実際に、市販されている故障診断機の JDiag JD101 でも取得してみましたが、結果は一致していました。 注意と自己責任(免責事項) 本記事を参考にして、機器や車両の故障、事故等の被害が起きたとしても、弊社・個人としては責任は負いかねますので、ご了承願います。 あとがき 本当はデータモニターや故障コードの解説もしたかったのですが、分量が多くなってしまいましたので今回はここまでです。 最後も駆け足になってしまいました。orz 記事を書き終わった後に気がついたのですが、書籍の カーハッカーズ・ハンドブック に今回紹介した内容が記載されていました!詳しく知りたい方・興味を持った方は、この本を読んでください! それでは、よいクリスマスをお過ごしください。 弊社ソリューションのご紹介 弊社では、自動車からのCANデータの収集や、遠隔適合のためのソリューションとして、以下のプロダクトをご提供しております。 自動車向け遠隔計測ソリューション(CAN/CAN-FD対応) www.aptpod.co.jp 自動車ECU向け遠隔適合ソリューション www.aptpod.co.jp 今回の記事では、Raspberry Piで簡易的に実現する方法をご紹介しましたが、実際にシステムとして長期に安定稼働させるにはその他にも様々な検討・開発が必要になります。 上記のような弊社のソリューションを採用いただければ、そういった個別開発の工数を省略しオールインワンパッケージとして遠隔計測・遠隔適合システムを導入いただけます 。 個別の要件に応じたカスタマイズについても可能な範囲で対応させていただきます。 まずはお気軽に、 弊社サイトのお問い合わせフォームよりご相談ください 。 www.aptpod.co.jp 参考リンク(書籍) Raspberry PiでOBD-II (CAN)の情報を取得するための基板を自作する (Qiita) Raspberry Pi で CAN通信(準備) MCP2515を使った自作基板とRaspberry Piで自動車のECUにOBDリクエストを送る (Qiita) Raspberry Pi と MCP2515 で CAN 通信 はじめての診断 by Vector (pdf) OBD (wikipedia) OBD-II PIDs (wikipedia) SocketCan (wikipedia) SocketCAN ISOTP (GitHub) カーハッカーズ・ハンドブック 弊社ボードは、更にESD保護やコモンモードチョークが入っています ↩ 最近のラズパイは -overlay をつけないので、参考リンク先の古い記事を読む方は注意してください! ↩ OBD2コネクタという表現が一般的かも ↩ 最近の車両はセキュリティの関係でCANバスの通信が見えない場合が多いです。ただし、ダイアグ通信に対してはゲートウェイを通してDLCから通信が見える模様です ↩ サポートしていない車両もあるはず ↩ https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%B3%E3%83%BB%E3%83%9C%E3%83%BC%E3%83%89%E3%83%BB%E3%83%80%E3%82%A4%E3%82%A2%E3%82%B0%E3%83%8E%E3%83%BC%E3%82%B7%E3%82%B9 ↩ データサイズを8バイト以上に拡張するCAN-FDの仕様があります。ぜひ、当ブログの CAN FDことはじめ もご覧ください。 ↩ 実際のsocketの作成方法は、isotpsend.c isotprecv.c を参考にするといいです。ソースの リンク ↩ https://www.raspberrypi.org/documentation/linux/kernel/headers.md ↩ 事前にCANのモジュールがloadされている必要があります。ロードできない時は、 modprobe can も試してください。 When the PF_CAN core module is loaded ('modprobe can') the ISO-TP module can be loaded into the kernel with ↩ modprobe等によるモジュールの自動ロードの設定は割愛します。 ↩ 詳細はISO15765-4の規格を参照。 この記事 がわかりやすいです。 ↩ 弊社の所有するホンダFitは29bit、スバルXVは11bitでリクエストを受け付けました。 ↩ カーハッカーズ・ハンドブック より抜粋 ↩
aptpod Advent Calendar 2019 の23日目を担当するハードウェアGpの おおひら です。 いきなりアレな感じのタイトルでなんだか申し訳ないのですが、株式会社アプトポッド(以下aptpod)に入社してちょうど1年になることもあり、このタイミングでしか書けないだろうなぁということで書きました。 (本当は イケてるFPGA高位合成の記事 へのRTLおじさんからのアンサーとして TclスクリプトではじめるFPGAのバージョン管理 なる記事を投下しようと思っていたんですが、これはまたの機会に…) 昨日に引き続き弊社若手エースの記事です。RTLおじさんの思い上がった鼻をへし折りにくる高位合成の破壊力☺️ご笑納下さい。 高位合成でFPGA開発!最短 1日で映像リサイズ機能を実装する - aptpod Tech Blog https://t.co/hPORcfEpW8 — Ryuichiro Ohira (@ryu_ohira) 2019年12月13日 はじめに 自己紹介 転職に至る経緯 aptpodに入社して 組織のこと 意思決定の速さ 質実剛健さ 異なる経験を持つメンバーがお互いを尊敬し合う雰囲気 メンバーの能力の高さ 働く環境のこと Slackコミュニケーションで快適 G-SuiteやOffice 365やSmartHRやMFクラウドが Gitが普通に使えて最高 家族を大切にし、それを公言できる雰囲気 真の裁量労働 バックオフィスの手厚いサポート おまけ めっちゃ褒めてるけどホンマかいなと思ったあなたへ ハードウェア(電気)設計者として、移籍して思ったこと 協力会社・商社の皆様が優しい件 調達コストはやっぱり大変 設計だけが仕事じゃないんやで問題 ハードウェア製品の性質の宣伝 おわりに はじめに 本稿を読むのにかかる時間は約8分です あくまで一個人としてのポエムです Japanese Traditional Big Company (JTBC) などと言って日本の大企業を揶揄する風潮がありますが、本記事はそのような類のものではありません 令和元年12月23日(月)は平日😇ということで、ちょっと気落ちした心をリフレッシュできるよう前向きな文体を心がけます 自己紹介 2011年4月、電気設計エンジニアとして東日本大震災でてんやわんやしていた電機メーカーに入社 B2Bをやりたい(むしろB2Cは興味ない)、という奇特さを買われて業務用映像機器の設計部門に配属 画質評価やちょっとした小基板・FPGA・組込ソフト設計を3年ほど 映像処理基板・中規模のFPGA設計を2年ほど Windowsアプリのプロマネを1年ほど 商品設計 / 電気設計 / FPGA設計の各署リーダーを2年ほど 2019年1月から現職 転職に至る経緯 リーマンショック後のリストラ期に電機メーカーに入社したため 痛みを伴う改革 を多く目の当たりにした 切るほうも切られるほうも、どっちもツライ、けど儲かってないからどうしようもない 利益を出せないことの惨めさや、企業というものの効率追求システムの原則が身にしみた そんなこんなで醸成された価値観に加えて、 業務用機器という、安定したB2B市場の中で技術者としてチャレンジする機会が減っていった(とくに自分がマネジメントする立場に近づくにつれて、 自分も当然のごとく安全策をとるようになった と自覚していた) 技術的におもしろい部品や新しい仕組み・ツールの情報を得ても、なかなか活用する機会が作れない なんとか根回しして新しいことを始めようとしても、本流の既存事業の重要な案件が飛び込んでくる(しゃあない) 「あの計画してくれた案の試作費、やっぱりバジェットに入れられなかったわ…すまん!」 「そうですか😇まぁしょうがないですよね😇またどこか良きタイミングで提案します😇」 ということもあったりで、 やっぱり技術者は成長市場で勝負せねばいかんのか… とおぼろげながら感じていました。 また、勤めていたメーカーは比較的若手への権限移譲が進んだ組織でしたが、それでも私が所属していた部署は 課長は早くて35歳ぐらいから。しかし優秀な30,40代の人がゴロゴロいて椅子取りゲームの様相 部長は45歳ぐらいから。そもそも事業が儲かってないとポジションがあるかどうか…(黒字部門からの落下傘もある 部門長までいけるか、なんて完全に運 (個人が優秀かどうかなんて瑣末なこと という現実もあり、 仮に運良くマネジメント職につけたとして、自分のやりたいことと組織の方向性をすり合わせる影響力を行使できる(かもしれない)のは50歳代…20年先か🤔… ということで大きな組織に勤めるリスクをひしひしと感じていたのでした。 こんな背景から、2017年ごろから転職を見据えていくつかの会社に話を伺いに行っていたりしたのですが、2018年の夏に現職の人事の方に声を掛けて頂いてaptpodを知る機会があり、B2Bをやりたい私の志向と会社の向いている方向が合っていると感じたうえに、ハードウェア設計を含めて垂直統合でソリューションを提供するという、なかなか類を見ない(=難易度の高い)事業に挑戦する姿勢に魅力を感じて入社を決めた次第です。 aptpodに入社して 組織のこと 意思決定の速さ 経営陣までの階層が2つぐらいしかないですから当然のごとく速いです。メーカー換算すると、ちょっとデスクに行って部課長をつかまえて議論して、製品の方向性を決めて試作Go、ぐらいの体感速度。大変にストレスフリーです。 質実剛健さ 本稿のタイトルに こんにちはスタートアップ とありますが、 ごめんなさい、盛りました。 aptpodは2006年創業で、スタートアップというよりはベンチャーと呼んだほうがしっくりくる組織だと感じます。 スタートアップ キラキラ などと検索すると下記みたいな記事が出てきますが… thestartup.jp 弊社はそれよりも、目の前のお客さまに実体のある価値を届けることを重要とする空気を感じます。これはメーカーも似ているかも。ユーザーファーストの質実剛健さはとても大事で好きなところです。 異なる経験を持つメンバーがお互いを尊敬し合う雰囲気 メーカーにおられる方は分かって頂けると思いますが「XX屋を説得してこい!」「これだからXX屋さんは話が通じねぇ!」みたいな部署間の折衝、ありますよね。あるいは「君さ、これやっといて」みたいな、年齢や立場を使った上から下への謎のタスクぶん投げ。That's ディスコミュニケーション。 現職は垂直統合型の組織でそれぞれのメンバーのバックグラウンドや技術領域が多種多様なこともあり、お互いの違いを尊重して丁寧なコミュニケーションを取ってくれる方が多いと感じています (ちなみに私が入社して一番初めに書いた社内ブログは半導体の解説記事でした) お互いの持つ技術領域が大きく異なるがために、理解し合おうとする空気感は本当にありがたいものだと感じます。 メンバーの能力の高さ NETFLIXの最強人事戦略 自由と責任の文化を築く 作者: パティ・マッコード 発売日: 2018/08/17 メディア: 単行本(ソフトカバー) 仕事の満足度は、グルメサラダや寝袋やテーブルサッカーの台とは何の関係もない。仕事に対する真のゆるぎない満足感は、優れた同僚たちと真剣に問題解決にとりくむときや、懸命に生み出した製品・サービスを顧客が気に入ってくれたときにこそ得られる。 書籍の内容は賛否あれど、1万回ぐらい噛み締めたい言葉ですね。転職してよかったと思いますし、チームとして結果を出していきたいと思います。 働く環境のこと Slackコミュニケーションで快適 言わずもがなのため割愛。 G-SuiteやOffice 365やSmartHRやMFクラウドが 同上。割愛。 Gitが普通に使えて最高 メーカーで古参のHWエンジニアだとSubversionでも手取り足取り教えてもできない人は多いんですよね。そういうリテラシーが普通にあるということに感動しました (基準がおかしいとか言わないで… 家族を大切にし、それを公言できる雰囲気 前職だと40代、50代ですでに子育てを一段落した男性がマジョリティということもあり、どうしても仕事優先という空気感が出ることが多いのです。社内の食堂で晩御飯を食べて、さぁもうひと頑張りするぞ!みたいな。 aptpodに入社してからは小さいお子さんがいるメンバーが多く、家族を優先して当然という空気感が大変ありがたいです。Slackで「子どもが熱を出したので早退します」に対して【お大事に】のスタンプが付く速さよ😂 入社して数ヶ月経った頃、当初反対(そりゃそうだ)していた妻が 転職してよかったね と言ってくれたことが印象に残っています。 真の裁量労働 メンバーを信頼して任せてくれる大人の雰囲気。前職でも最後の数年は裁量労働していたはずなんですが…なぜなんでしょうかね…。 私はとくに仕事とプライベートの区切りが曖昧で ワークアズライフ を地で行く人間のため、早くに帰宅して子どもを寝かしつけてから仕事を再開したり、お風呂に入りながらスマホで新製品の部品データシートを読んだり、自分の隙間時間を効率よく活用できて自由になったと感じます。 バックオフィスの手厚いサポート いつもありがとうございます!!! おまけ コーヒー飲み放題 お菓子も食べ放題 (太る 社内自動販売機は一律30円 関東IT健保ええやん 社内サークルたくさん オフィスは四谷三丁目、気分転換に新宿御苑へGo めっちゃ褒めてるけどホンマかいなと思ったあなたへ 当然ながら上に書いたことはすべて、捉えようによってはネガティブにもなり得ます。 自由と責任は表裏一体 ということ 。 ハードウェア(電気)設計者として、移籍して思ったこと ここからは少し業務に関連して、実際にハードウェア設計をする立場として感じたことを。 協力会社・商社の皆様が優しい件 「量産の数が出ないんだから大変だろうな…メールしても返事がないとか、『ごめんなさい売れません』とかは覚悟しておこう」と思っていたんですが、ありがたいことに意外となんとかなるものだなというのが正直なところです。打合せのたびに 年間xxx個なんですが…大変申し訳ありません… という気持ちで臨むのですが、皆様とても優しくサポートしてくださり大変感謝しております🙇‍♂️ でもたまにはDigi-Keyで買います 調達コストはやっぱり大変 メーカー時代には10円で調達できた部品が簡単に100円以上に化けます。そりゃ当然だわという感じなんですが、いざ部品表から材料費をはじき出してみるとゾッとします。メーカーとガチンコ勝負したら絶対勝てない。あと市場で逼迫している部品は当然ながら手に入りません (特定スペックのMLCCとか) 設計だけが仕事じゃないんやで問題 メーカー時代には当然のごとく社内のプロの方々に協力してもらえた業務が自力になります。これも覚悟していましたが、やはり大きな組織は強い。長年蓄えられた圧倒的な資産が輝いてみえます。小規模組織 (現状、弊社ハードウェアGp 4名) に於いては製造に近い部分で協力会社の皆様にお力をお借りするのですが、それでもなかなか下記すべてに手が回らないのが正直なところで、昔は恵まれた環境にいたのだなと思い返すことも多くあります。 製品の市場調査・企画・投資回収計画の立案 信頼性試験 安全規格・EMC認証などの各種法規対応 部材調達 (見積依頼・発注・在庫管理・EMSへの部品支給・ディスコン対応) 量産工程の改善・QC ハードウェア製品の性質の宣伝 ソフトウェアのバックグラウンドを持つメンバーに説明する必要と責任があります。 いまから試作に取り掛かって、デモは半年後、出荷は1年後です と勇気を持って宣言しましょう。このあたりはメーカーで鍛えられた政治手腕が( おわりに 弊社ハードウェアGpは2020年1月15日から東京ビッグサイトで開催される オートモーティブワールド にむけて、試作品のデモ展示のために鋭意設計中です。ここ半年ほど取り組んできた映像関連のハードウェア製品開発の一端をお見せできるかと思いますので、車載計測にお悩みの方をはじめ、移動体全般のデータ計測・可視化・活用にご興味がある方はぜひブースにお立ち寄りください。 またハードウェアエンジニアに限らず全組織で絶賛メンバー募集中ですので、 aptpodの採用ページ に足を運んで頂けるとありがたく思います。 明日12/24(火)の記事は、弊社エンベデッドチームリードの @ffmatsu さんによるラズパイとCAN計測の質実剛健な(本人いわくチャラい)記事です!
アドベントカレンダー 22日目を担当します。ソリューションアーキテクトの sataro です。 前職ではホスティング専門のインフラエンジニアとして、穴蔵に籠って運用保守をやっていました。 それはそれで楽しかったのですが、自社プロダクトへの興味が膨らみ転職、心機一転aptpodではSAとして働いています💪 さて、本稿ではスクラムについてちょっとお話ししてみたいと思います。 aptpodでは自社プロダクト開発の一部にスクラムを取り入れています。私はその中でも スケーラビリティ をテーマに掲げたチームで、プロダクトオーナー(PO)として活動しています。 とはいっても、タイトルの通りスクラムに取り組んでようやく半年になろうかというところです。何か驚きのテクニックをご紹介!というわけにもいきませんが、これからスクラム始めてみようという方や、POってなんぞ?という方の参考になれば幸いです。 はじめに まずは予習してみた で、POってなに? 実践してみたこと Who Why What When Where How まとめ はじめに スケーラビリティ と書きましたが、まずは簡単にプロダクトの背景説明を。 チームのミッションは、自社プラットフォームのスケール向けたインフラアーキテクチャの実現です。 弊社の基幹プロダクトの1つである intdash ですが、大変有り難いことに研究開発分野において多方面で活用頂いています。 そして当然、研究開発の先には、どこかのタイミングで 製品化 & 大規模運用 というフェーズが待っています。 クライアントが大規模運用に打って出ようとした時に、弊社のプラットフォームが足を引っ張っては元も子もありません。 そこで、 intdashのスケーラビリティは急務である 。というわけです。 まずは予習してみた 半年前、スクラムを実施するにあたり、いくつか書籍をあたりました。 SCRUM BOOT CAMP THE BOOK(西村 直人 永瀬 美穂 吉羽 龍太郎)|翔泳社の本 カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで(市谷 聡啓 新井 剛)|翔泳社の本 どちらも言わずと知れた名著です。 基礎知識から実践のイメージまで、はじめるために必要なことは全て書いてあります。 迷うなら読んだ方が良いです。 スクラムの基礎については こちら のブログなどでも学べます。 ある程度概要が掴めたら、とにかくやってみるのが良いと思いました。 説明を読んでもなかなか頭に残らないので・・・ で、POってなに? 一度原点である スクラムガイド に立ち返りたいと思います。 スクラムガイドによると、 プロダクトオーナーは開発チームから生み出されるプロダクトの価値の最大化に責任を持つ 。とされています。 実務的な動きとしては以下のようなものです。 プロダクトバックログアイテムを明確に表現する。 ゴールとミッションを達成できるようにプロダクトバックログアイテムを並び替える。 開発チームが行う作業の価値を最適化する。 プロダクトバックログを全員に見える化・透明化・明確化し、スクラムチームが次に行う作業を示す。 必要とされるレベルでプロダクトバックログアイテムを開発チームに理解してもらう。 プロダクトオーナーがこれらを実践できればスクラムは回ります。ですが、実際に取り組んでみると、 開発チームが行う作業の価値を最適化する がものすごく深い。一筋縄ではいかないので、あれこれ考えてみました。 実践してみたこと 価値を最適化する について考え始めると沼にはまりそうだったので、少々乱暴ではありますが一度自分の中で単純化してみました。 5,6個くらいの指針なら意識して行動できるのでは?と思い、いわゆる5W1Hを意識すると良いんじゃないかと考えています。 ビジネスや受験勉強などでもよく聞くワードですし、スクラムにおいてはユーザーストーリーを考える際にも登場します。 価値の最適化を見失わないために、ここでも活躍してもらいましょう。 Who ここでの「 誰 」はチーム外にいるステークホルダーの明確化です。 チーム内で開発アイテムに対するステークホルダーは基本的にPOになると思います。 受け入れ条件を定義して各アイテムを検査する、というのはチーム内で行われます。 一方で、プロダクトの大枠においてのステークホルダーというのも別にいるかと思います。 今回の場合、「○○という案件が来年度以降で同時接続デバイス数XX台になる見込みがある」という具体的な数値目標がありましたので、機能要件のステークホルダーはその案件を担当するソリューションアーキテクトとなりました。 プラットフォームがスケールしていく中で満たすべきセキュリティやコストバランスなどの非機能要件は、経営層や管理部門に別のステークホルダーが存在するはずです。 各側面で「 誰 」と要件を握っていけば良いのか意識すると、無駄なものを生み出す危険が減ると感じています。 Why ここでの「 何故 」はゴール設定における目的意識です。 本プロダクトの場合、スケールするアーキテクチャの実現という大テーマはゆらぎませんが、一方で具体的な数値目標はふわふわしているとも言えます。 前述の通り「同時接続デバイス数XX台」という目標があり、まずはそこをゴールとして走り始めました。 ただ、案件の状況も会社の状況も随時変化しますので、ゴールが状況と合わなくなることもあります。 スクラムはゴールが変化することを前提としたフレームワークですので、ゴール設定を再確認する向き直りの機会さえ持てれば、プロダクトを継続して開発することはできるはずです。私たちのチームも一度スプリントを止めて向き直るタイミングがありました。 そのタイミングを見誤らないために、自分たちは「 何故 」それを作っているのか、という視点は常に持っている方が良いと感じています。 What ここでの「 何 」は開発アイテムへの理解です。 次々にチームから生み出される開発アイテムについて、POはチーム外へ正しく説明できる必要があると思います。 チーム内では開発アイテムの優先度について合意をとった上で進めていきます。しかし、チーム外からは、優先度の高いアイテムの意義が見えにくいこともあるかもしれません。 何となく停滞しているんじゃないか、など要らぬ心配や横槍が発生しないようにしたいところです。 外部のステークホルダーにはどういった目的で「 何 」を開発しているのか、情報共有していくことが必要だと感じています。 When ここでの「 何時 」は期限です。 最終的に開発アイテムは求められるリリース時期にアジャストしていく必要があります。 ここでは案件との兼ね合い以外にも、製品のリリースサイクルも絡んできます。せっかく作ったものを世に出すタイミングを逃しては大変です。 「 何時 」リリースに乗せるか、というのもプロダクトの大事な要素です。 Where ここでの「 何処 」は・・・5W1Hとか言ってしまいましたが、あまり関係ありませんでした😅 強いて言えば、チームとの会話の機会は「 何処 」でもつか。 face to face でもった方が良い。ということです。 極端な話POはスプリントの最初と最後、プランニングと振り返りさえ顔を出していればスクラムは回ります。 あとはタスクボードで会話すればいいじゃん。となってしまってはもったいないと思っています。 POは自分で実装はしません。だからこそ、開発チームがどんな考えで、どんなこだわりをもって開発を進めているか。肌で感じて進めていくのが、その熱量をチーム外に伝える助けになると感じています。 How 最後に「 どうやって 」です。スクラムにおいて、実現方法はPOではなく開発チームに委ねられます。 もちろん私たちのプロダクトも同様です。 弊社にはアプリケーションからハードウェアまで、イカしたエンジニアが揃っていますので、ここでのPOの役目は信じて任せるだけです。 常時各レイヤーで仲間を求めていますので、興味があれば是非 こちら をご覧ください。ダイレクトマーケティング🤣 まとめ PO視点で、チームがうまく機能するためにはどうしたら良いか・・・この半年考えていたことを語ってみました。もちろんこれで正解というものではないので、常に カイゼン を目指していきたいと思います。 何か参考になれば嬉しいです。ありがとうございました。
Aptpod Advent Calendar 2019 21日目担当のハードウェアグループの織江です。 この記事では電子工作を趣味とする界隈でも人気のM5stackの小技を紹介したいと思います。 M5Stackとは M5Stackは中国深セン発の液晶付き汎用開発プラットフォームです。 世界的に有名なArduinoと互換性が高く人気を博しています。 一般的に電子部品の開発ボードは基板剥きだしで、製品としてのケース が付いていなかったりするため製品化には大きなハードルがあります。 この製品は液晶画面ボタン、スピーカー、WiFi、BT、バッテリー等の汎用 に使える機能が(評価機としては)しっかりとしたケースに入っていて エンジニア以外の方が触っても自然に使えるようになっているのが 特徴だと言えると思います。 日本ではスイッチサイエンスから購入することが可能です。 M5Stack Basic - スイッチサイエンス 弊社では液晶等の表示装置の無い組み込みPCをよく使うのですが、簡易的な表示器が欲しい際にこうした製品を利用します。 M5Stackのリセット回路の問題点 大変便利なM5Stackなのですが、不安定な動作をするときがたまにあります。 弊社で直面した大きな課題の一つは 電源投入時に正しくリセットされないことがある というものです。(画面が真っ暗なまま何も起きない) 弊社のユースケースは下記の通りで、自動車のエンジンがかかるのに連動して起動できる 車載PC(Linux)のUSBポートの電源に連動してM5Stackの電源を入れたいという条件でした。 車載PCの状態を定期的にM5Stackに送信して液晶画面に表示する機能を実装しています。 M5Stack内臓のLipoバッテリーは自動車の中で使うことを想定しているため、 取り除いた状態で使用しています。 使用例 調査の結果M5Stack内のESP32というメインのマイコンのリセット信号が電源電圧が立ち上がる前にぐねぐねと立ち上がっており、正しくリセットできていないため後段の周辺回路も 正しく動作していないということが分かりました。 ただ、電源・充電管理とUSBシリアル変換IC(CP2104)だけは正しく 動作していることが分かりました。 M5Stack簡易ブロック図 リセットの配線を公式の回路図で確認すると下記の赤い丸の部分になります。 M5Stack回路図抜粋 この回路図でENと書かれたリセット信号はDTR・RTS信号を経由して、さらにUSBシリアル変換IC(CP2104)経由でリセットできることが分かります。 Pythonを使ったM5Stackのリセット方法 では実際にこのUSBシリアル変換IC経由のリセットを試してみたいと思います。 まず適当なLinuxマシンを用意します。 今回はVirtualbox上のUbuntu16.04LTSを用いました。 続いてドライバのインストールを行います。詳しくは説明しませんが こちらのサイトが参考になります。 usbserial とcp210xのドライバをインストールします。 drivers - Ubuntu 16.04.1 usbserial missing - Ask Ubuntu 特に何も設定しなければM5Stackが/dev/ttyUSB0として認識されるようになるかと思います。 後はシリアル通信を開始する際にDTRとRTSピンを下記のように操作するとリセットをかけることが出来ます。 ピンを操作した後rtsctsをFalseにしないとその後の通信ができなくなるのでその点が注意点です。 import serial import time ser = serial.Serial('/dev/ttyUSB0',115200) ser.setDTR(False) time.sleep(0.1) ser.setRTS(False) ser.rtscts = False time.sleep(1) # wait esp32 wakeup #your code here ser.close() 起動画面 リセット失敗した状態(画面真っ暗)からのリセット あとがき 通常リセット回路はどのような電源が繋がれても確実にリセットされるようになっていると嬉しいのですが、必ずしもそうなっているとは限らないです。 M5Stackの場合はたまたまUSBシリアル変換ICだけは 正しくリセットされるようでしたのでそこを起点にメインのマイコンを復帰させることができました。 参考URL M5Stack Basic - スイッチサイエンス pySerial API — pySerial 3.4 documentation Arduino UNOでpyserialを使ったら再起動してしまった - Qiita
aptpod Advent Calendar 2019 の20日目担当をします。高橋です。 CTO室でVPoEとして 組織マネジメント を担当させてもらっています。 弊社はハードウェアや組込みソフトの低レイヤから、ネットワークを介しクラウドに至るまでの広範囲な技術を一貫して内製で開発し提供するため、多種多様なエンジニアがおり ダイバーシティ(多様性)が尊重される会社 です。 私からは、普段の業務で心がけていることについてお話します。 人と向き合う 会話のオンとオフ Goodの発掘 弱みは隠すより見せる 任せるのではなく支援する 見過ぎるのではなく間違えずに見る 組織と向き合う 目標はたすき掛けで設計する チームの価値は面で捉える 挑戦にはリミットとリカバリープランを おっさんが輝くから若手が輝く おわりに 人と向き合う 会話のオンとオフ 私が担っている業務として、2週間に1度、エンジニア全員との 1on1ミーティング を実施させてもらっています。 オープンな場で言いにくいこと・言葉を選びにくい内容も伝えてくれるので 組織改善のヒント になっています。 ポイントとしては、オフィシャルな場での オンな会話 (言葉を選び悪目立ちしない発言)だけではなく オフな会話 (心の奥底で感じている率直な発言)をしてもOKな場にしています。 プライバシーは守り、かつ評価の参考などには絶対用いないと約束 するからこそ成立しています。 Goodの発掘 1on1では、 Good と Bad を教えてもらっており Badは一緒に解決しGoodを共に喜ぼう と伝えており 本人が当たり前だと思っていることも改めてGoodに数える ようにしています。重要なのは 自分自身で状況が発展/進展していると気づくこと です。ときとして周囲からの感謝がこのキッカケになったりもしますので、slackのスタンプでポジティブな発言に👍することを意識しています。ジワジワと効果が出ます。 弱みは隠すより見せる 失敗が許されない、パーフェクトであることを求められる職場って息が詰まりませんか?また、失敗や弱みを必死に隠そうとする相手と、さらけ出してくれる相手ってどっちが見ていて気分がイイですか? 私は自分の苦手なことやダメな部分をオープンに開示するようにしています。例えば、ダイエットするとか言っておきながら ワザと その日のうちに ライス大盛 を頼んだり、そそっかしくて会議室に忘れ物を結構したり(これはワザとではない)、許容範囲はありますが。 相手も人間なんだな、と思ってもらうこと から許し合いや協調が増えるイメージです。 任せるのではなく支援する 日本語は似た単語でもニュアンスが変わるので使うのが難しい言語だと思います。マネジメントしていて最近よく思うのですが 任せる と言う言葉が古くなってきていると感じます。 任せる という言葉には、上意下達な前提で「(俺がやりたいことをお前に)任せる」というニュアンスが入る気がします。それよりも「(あなたがやりたいことを私は)支援する」というニュアンスの方がしっくりきます。 成長する組織の多くは 、この サーバント型リーダーシップ が存在し 個々が自己組織化して動くみたいなのを ニュアンスで理解 している組織ではないかなと思います。 見過ぎるのではなく間違えずに見る マネジメントや育成というテーマにおいて、メンバー/プロジェクトのことを”ちゃんと見れてるのか”みたいな話はよく出る会話かと思います。( うん、聞き飽きてる ) 頻度高く細かくみること(マイクロマネジメント)も大事だとは思いますし、近視眼的には早く決断がなされて良いかもしれません。ただ、長く時間が経てば経つほどに私情や私見による判断のブレを招いたり 承認文化による組織スピードの劣化を招く恐れ があります。 実際にそれ(承認文化)が染みつき、意思決定に超絶根回しの時間が掛かりメンバーが疲弊する組織や、無意識のうちにそこに加担してしまった経験もあります。 そのため、間違えた見方をしないこと(エッセンシャルマネジメント?)が重要と考えています。 自身で突破する成功体験が多いほうが成長する ので、できるだけ 我慢してタッチしすぎないことも大切 ですね。 組織と向き合う 目標はたすき掛けで設計する 目標を立てるときに気をつけていることとして 個人に組織目標を依存させすぎない ことがあります。 当社でも 同じテーマや課題を持つ同士が組むと単独で目指すよりも強固な実行力を持つ ケースが多いです。 私は、目標設定において たすき掛け というキーワードを意識するようにしており、共通の課題感を持った人同士で 協力して達成する目標を設計 し提案・促すなどの工夫をしています。 チームの価値は面で捉える 近年のIT業界は、少ない専門性のみで成果を積み上げることが困難な時代にあるとも言えます。 ナンバー1品質は クラウド や サブスクリプション で 外から安価に買えば済む時代 になってきたためです。 タレントや強みを一個に限定しない 面で価値貢献ができるチーム 、言い換えると 強みのコンボが多く決まるチーム が良いチームなのではないかと考えています。 挑戦にはリミットとリカバリープランを 挑戦には期限を設けることが大切です。これはコスパを意識しろということではなく 他人から時間切れを言い渡されるより 自分でハラオチをすることが大切 だという意味です。 一方で、組織としては唯一の挑戦に賭けること/依存する判断はリスクになり得ます。うまくいかない場合は 失敗も想定し準備する ことで被害を最小にし、結果的な成功確度も上がります。バスケやサッカーで味方がシュート打った後も他の選手がゴール前に詰めてる みたいなことと同じです。 おっさんが輝くから若手が輝く 変化=成長という前提で雑に書きますが、おっさんが変化することは組織の成長にとても重要です。現時点でのキーマンが優秀でも、変化対応できず硬直しきった組織では継続成長は望めません。そのため、 おっさんから先陣をきって変化を奨励する組織作り をしないとならないと自戒を繰り返しています。もちろん吸収力/柔軟性で若い人材にアドバンテージがあるのは間違いないです。ただ、おっさんができる事って 自分の能力や活躍に固執するだけではない はずです。多くの機会を周囲に回し 組織の中で大きなパスをつないでいくこと も務めですよね。 と、まぁこんなことを考えつつ少しでも多くのエンジニアが活躍する会社にしようと思って日々仕事をしています。 今回はこの辺で! おわりに アプトポッドは エンジニアを絶賛募集中 です! 応募意思がなく興味本意でのオフィス見学も歓迎! ぜひお気軽にご連絡ください!
aptpod Advent Calendar 2019 の19日目担当、Webチームの蔵下です。普段は、自社プロダクトのUIをReactでゴリゴリ書きつつ、社内のお酒好きを集めて不定期で飲み会を開いています🍶(飲みのお誘いお待ちしています) みなさんの会社では誰がユーザーマニュアルを作成していますか? 数百人規模の大きな会社であれば専属のマニュアル作成チームがあるかもしれません。aptpodでも専属のチームがいてくれたら心強いのですが、絶賛成長中のスタートアップということもありまだ専属チームはありません。 そのような中でも、手塩にかけて開発したプロダクトを多くのユーザーに使っていただくためには、ユーザーマニュアルは欠かせません。 我らaptpodのエンジニアが、エンジニアならではのアプローチでユーザーマニュアルを作った方法を紹介します。 自動でできるところは自動で! バージョン管理でデグレなんてしない! をモットーに💪 前段: aptpodでのプロダクト開発の進め方 エンジニアによるユーザーマニュアル作成の流れ 1. 目次作成 2. 担当者をアサイン 3. 校正ツールの導入 4. 文章作成 5. GitLab上で相互レビュー 6. レビュー後の文章をWord化 7. PDF生成 おわりに 前段: aptpodでのプロダクト開発の進め方 ユーザーマニュアル作成の話に入る前に、aptpodでプロダクト開発をするときの進め方を紹介します。aptpodでは、ハードウェア・サーバー・インフラ・UI・デザインと、一つのプロダクトにさまざまなスキルを持ったメンバーがアサインされます。プロダクトの提供する技術的範囲が広いため、ユーザーマニュアルもそれぞれの領域の知識が必要になります。 そのような背景もあり、プロダクトの開発に関わった複数人が同時に、デグレが無く安全に、楽して、クオリティの高いユーザーマニュアルを作成することが求められました(aptpodではユーザーマニュアルも一つの製品として力を抜かない😎)。 エンジニアによるユーザーマニュアル作成の流れ いよいよ本題に入ります。大まかには、仕様を把握しているエンジニアが文章やざっくりとした図版のイメージ(UIのキャプチャやパワポでちょっとお絵かきしたり)を作成し、そのデータを元にデザイナーがページレイアウトや図版を起こしていく流れになります。 下記が実際の作成フローです。 目次作成 担当者をアサイン 校正ツールの導入 文章作成 GitLab上で相互レビュー レビュー後の文章をWord化 PDF生成 各Stepでどのように進めたのか解説していきます。 1. 目次作成 全体の文章構成、ボリュームをつかむべく目次を作成します。最初から完璧な目次は作れないので、書いてる中でのアップデートする前提で大丈夫です。ここでは共同編集できるようにGoogle スプレッドシートで管理しました。 実際に使用していたGoogle スプレッドシートのキャプチャです。モザイク多め... 2. 担当者をアサイン 目次をまとめたGoogle スプレッドシートに 担当者 という項目を設け、章・節ごとにメインで実装していた開発者を担当者としてアサインしました。仕様に詳しい人が書いたほうが確実で手戻りも少なくなります。中には担当者がいない(開発とは直接関係のない)章も出てくるので、そこは私が担当しました。 3. 校正ツールの導入 人によって文章の書き方にばらつきが出てくる可能性があるため、下記の 校正ツール を導入します。校正ツールとは、文章を一定ルールに沿って自動で校正してくれるツールです。 Visual Studio Code テキスト校正くん テキスト校正くんはVisual Studio CodeにインストールするPluginです。 JTF日本語標準スタイルガイド のルールに沿って校正してくれます。 自動でできるところは自動で! 人で担保が難しいところはツールに任せちゃいましょう。 4. 文章作成 いよいよ文章を書き始めます! 文章はエンジニアライクな Markdown で書き進めます。文章だけ書くのであれば通常のMarkdown記法のみでいいですが、文章の性質によっては強調して目を引くデザインにしたい部分もあり(取り扱いでの注意など)別途ルールを設けました。そのルールに沿って、後ほど説明する「6. レビュー後の文章をWord化」で自動変換します。 追加したルールの一部。誰でも読めるようにQiitaTeamで管理しました。 文章が書き上がったら、バージョン管理のためにGitLabへPushします。 Gitで管理することによってデグレという最悪の事故を未然に防げることはもちろん、レビューの内容や編集の履歴を残すことによって、文章の修正意図を知れたり、その後の文章作成に役立てることができます。 GitLab上でのディレクトリ構成は下記のような構成にしました。Markdownファイルを節ごとに分けることで、各担当者間で作業が干渉しないようにしました。 GitLabでのディレクトリ構成。章ごとにディレクトリを分け、節ごとにMarkdownファイルを分けました。 バージョン管理でデグレなんてしない! マニュアル作成でバージョン管理しないなんて... もうそんな世界では生きられない... 5. GitLab上で相互レビュー 文章をPushできたら、レビュー担当者にレビューを依頼するために Merge Request を作成します。Pushされた文章をレビュー担当者が確認し、指摘ポイントはMerge Requestのコメントに記載していきます。文章作成者はその指摘を対応して再度Push、レビュー担当者が再度確認して問題なければ Approval(承認) とし、developブランチへマージできるようになります。 レビューの様子。指摘ポイントごとに管理できるのはよかった。 マージされたら、この節は作成完了です!!👏 6. レビュー後の文章をWord化 文章が一通り揃ったら、デザイナーへ文章とざっくりした図版を渡します。デザイナーの力は絶大ですね。ざっくり図版が、かっこよく、さらにわかりやすい状態に仕上げていただけました。 文章と図版が揃ったら、いよいよPDFへの書き出しのフローに入ります。今回は、出力後のページデザインを担保するために Markdownファイル → Wordファイル → PDFファイル という段階で書き出しました。 Markdownファイル → Wordファイル の変換は、 Pandoc で自動化しました。章ごとに分かれていたMarkdownファイルを一つに結合し、「4. 文章作成」で紹介したルールに沿ってデザインを反映させながらWordファイルが生成されます。 サンプルMarkdownファイルをWordファイルへ変換した様子。デザインが反映されていいかんじ! 7. PDF生成 いよいよ最後の工程、 Wordファイル → PDFファイル です。生成されたWordファイルをWordで開き、PDF出力をポチ! と、ここでアクシデント発生... 文章内で指定していたページ内リンクが切れちゃっていました...😱 macOSのWord特有の不具合のようで、macOSではなくWindowsのWordで開き、AcrobatからPDF出力することでページ内リンクも効いた状態のPDFが出力されました!!! これにてユーザーマニュアル完成!!🎉 完成したユーザーマニュアル おわりに 最後にちょっとしたハプニングがありましたが、無事にエンジニアとデザイナーでユーザーマニュアルを作成することができました! 自動化とGitを導入できたことで、デグレもなく、レビューもスムーズにでき、ユーザーマニュアルの中身に時間を割くことができました。 今回は私が代表で記事にしましたが、他にも関わったメンバーはたくさんいます。メンバー全員で試行錯誤しながら進めてきました。みなさん本当にお疲れさまでした!!! 打ち上げしたい!!! ウェーイ🍻
aptpod Advent Calendar 2019 18日目を担当させていただきます 上野 と申します。 昨年も ARKit2.0が凄い。あなたの見ている方向を記録、可視化するデモ という記事で参加させていただきまして、 今年もiOS系で記事を書かせていただこうと思います。iOSアプリエンジニアのみなさんよろしくお願いします。 さて、今回のフォーカスする内容ですが、、、 皆さん、、Metalって使ってる、、、、? 昨年の記事では ARKit 、 SceneKit といったフレームワークを使用していますが、あれももちろん 、 UIKit などに含まれるビューコンテンツやアニメーション、イメージなどのほとんどは Metal をコアに作られています。 Metalは、 Apple製品に搭載されたGPUへアクセスを提供するAPI で基本的にUIに関わる部分ほとんどに使われているようです。 今回はそのMetalにフォーカスし、Metalの実装コストを下げた MetalKit を利用してデモアプリを作りたいと思います。 ※Metalの事前知識が欲しいと言う方はよくまとめられた記事がありましたので こちら をご覧ください。 今回作る物 実際「 よし、Metal(だけ)で何か作ってみるか、、、 」となった場合に皆さんはパッと何か思いつくでしょうか? 試しにAppleで サンプル を見てみましたが、 Creating and Sampling Textures Using a Render Pipeline to Render Primitives Reflections with Layer Selection 単純に画像を MetalView 上に描画する物だったり、 グラフィクスAPI開発の入門ではありがちですが単純に三角形の描画のサンプル、 何か 凄い3Dのモデルを描画する方法だったり等ありますが、イマイチ何を作ろうか、、、と私はなりました。 いろいろ調べていく中で、MetalKit では実装方法さえ知っていれば Texture上にピクセル単位で簡単に色を塗ることができると分かりましたので、 タイトルにあるように簡易的な ペイントツール を作ろうと思います!今回は MTKView( MetalKitに含まれるView ) が Canvas となります。 描画までの流れ MTKViewのフレームサイズに応じて解像度(縦横のピクセル数)を決める。 ピクセル数に応じた配列(テクスチャへ渡す用のバッファ)を作成。 画面がタッチされたらその座標と同等の配列のインデックスに選択されている色情報を挿入。 定期的に MetalKit から draw のリクエスト( MTKViewDelegate のコールバック)が呼ばれるので、そのタイミングで用意したバッファと書き込む対象のテクスチャを渡す。 シェーダにて描画する色情報を適切にテクスチャに渡す。 といった流れとなります。 実装開始 Metalをセットアップ ではいつも通りの感じでプロジェクトを作ります。 MetalKit Viewを設置しましょう。 ViewController.swift import UIKit import MetalKit class ViewController : UIViewController , MTKViewDelegate { override func viewDidLoad () { super .viewDidLoad() // Do any additional setup after loading the view. self .setupMetalView() } // MARK:- Metal View @IBOutlet weak var metalView : MTKView ! var mDevice = MTLCreateSystemDefaultDevice() var mCommandQueue : MTLCommandQueue ! var mComputePiplineState : MTLComputePipelineState ! var metalViewDrawableSize : CGSize ? = nil var targetMetalTextureSize : CGSize = CGSize.zero var bufferWidth : Int = - 1 var mTextureBuffer : MTLBuffer ? func setupMetalView () { guard let library = self .mDevice?.makeDefaultLibrary() else { return } // Register Texture Shader guard let kernel = library.makeFunction(name : "computeTexture2d" ) else { return } guard let computePipeline = try? self .mDevice?.makeComputePipelineState(function : kernel ) else { return } self .mComputePiplineState = computePipeline self .metalView.device = self .mDevice self .metalView.delegate = self self .metalView.framebufferOnly = false // ← これがないとXcode11以降では落ちます self .mCommandQueue = self .mDevice?.makeCommandQueue() } //MARK:- MTKViewDelegate func mtkView (_ view : MTKView , drawableSizeWillChange size : CGSize ) {} func draw ( in view : MTKView ) {} } 一旦こんな感じでViewControllerを書いてみました。 IBOutlet で定義している metalView はViewControllerに設置したもので、この辺りで出てきている mDevice だったり、 libary 、 computePipeline あたりはお作法のようなものなので 先ほどのリンク だったり こちら をご参照ください。 ※かなり初歩的な所は深くは語りません。 ここで設定したシェーダの設定 computeTexture2d は後ほど解説します。 基本的には MTKViewDelegateに含まれる draw のコールバックにて GPUへ必要なテクスチャやバッファ情報を渡す事になります。 それでは、ここからは先ほど示した描画までの流れにそって進めていきます。 1.MTKViewのフレームサイズに応じて解像度(縦横のピクセル数)を決める ViewController.swift // MARK:- viewDidAppear override func viewDidAppear (_ animated : Bool ) { self .updateMetalViewDrawableSize() } // MARK:- viewWillTransition override func viewWillTransition (to size : CGSize , with coordinator : UIViewControllerTransitionCoordinator ) { coordinator.animate(alongsideTransition : nil ) { (_) in self .updateMetalViewDrawableSize() } } オートレイアウトで指定したViewのフレームサイズが決まる(変わる)タイミングって大体この2つですよね、 このタイミングで設置した MTKView の実際のフレームサイズからピクセル数を決めましょう。 ViewController.swift let kMetalTextureHeightDotSize : Int = 512 ... func updateMetalViewDrawableSize () { // Viewの実際のサイズから縦横比を参考に高さのドットサイズから幅のドットサイズを求める var width = Int(ceil(( self .metalView.frame.width /self .metalView.frame.height) * CGFloat(kMetalTextureHeightDotSize))) // // ここには後ほど書き換えがあります(1) // self .targetMetalTextureSize = CGSize(width : width , height : kMetalTextureHeightDotSize ) self .log( "drawableSize: \(self.metalView.drawableSize) , frame: \(self.metalView.frame) => targetMetalTextureSize: \(self.targetMetalTextureSize) - MetalView" ) self .metalView.drawableSize = self .targetMetalTextureSize } 今回は高さのピクセル数を 512 に固定にし、そこから縦横比で求める事にしました。 ピクセル横 = フレーム幅 / フレーム高さ * ピクセル縦 そして、求めた解像度を MTKView の drawableSize に 描画するサイズとして指定してあげましょう。 ですがこのままでは不十分で、後ほど (1) の内容を説明します。 2. ピクセル数に応じた配列(テクスチャへ渡す用のバッファ)を作成 ViewController.swift //MARK:- MTKViewDelegate func mtkView (_ view : MTKView , drawableSizeWillChange size : CGSize ) { self .log( "drawableSizeWillChange \(view.drawableSize) => size: \(size) , frame: \(self.metalView.frame) , targetSize: \(self.targetMetalTextureSize) - MTKViewDelegate" ) guard !self .targetMetalTextureSize.equalTo(CGSize.zero) else { return } if self .metalViewDrawableSize != nil { guard !self .metalViewDrawableSize ! .equalTo( self .targetMetalTextureSize) else { // 前回と同じ値だった場合は更新しない return } } self .metalViewDrawableSize = self .targetMetalTextureSize self .bufferWidth = Int( self .metalViewDrawableSize ! .width) self .setupMetalBuffer() } 先ほど MTKView に解像度を指定しましたがあちらをセットすると MTKViewDelegate 内の drawableSizeWillChange がコールされます。 このタイミングで、指定した解像度が MTKView に反映されますので、バッファの作成を行いましょう。 ViewController.swift let kMetalTextureClearColor : simd_float4 = [ 255 / 255.0 , 255 / 255.0 , 255 / 255.0 , 1.0 ] ... func setupMetalBuffer () { guard let device = self .mDevice else { return } let colors = [simd_float4]. init (repeating : kMetalTextureClearColor , count : self.bufferWidth * kMetalTextureHeightDotSize) let bufferLength = colors.count * MemoryLayout < simd_float4 > .stride // <- ここ要チェック self .mTextureBuffer = device.makeBuffer(bytes : UnsafeRawPointer (colors), length : bufferLength , options : .cpuCacheModeWriteCombined) } ここ要チェック とコメントしている箇所に注目してください。配列の数でバッファサイズを求めている処理で、今回はあまり関係ないですが、Metalのバッファ管理では かなり重要 です。 こちら で詳しく解説されていますがMelta側のバッファサイズとCPU側のバッファサイズが違ってしまう要因になりますので理解は必須です。 初期値(何も書かれていない色情報(白))と縦横の長さで配列を宣言、 MTLDevice でバッファを作成します。 3. 画面がタッチされたらその座標と同等の配列のインデックスに選択されている色情報を挿入 ViewController.swift // タッチイベントが開始された override func touchesBegan (_ touches : Set <UITouch> , with event : UIEvent ?) { guard let touch = event?.touches( for : self.metalView )?.first else { return } let point = touch.location( in : self.metalView ) self .lastPoint = nil self .drawCanvas(point : point ) } // タッチ位置が移動した override func touchesMoved (_ touches : Set <UITouch> , with event : UIEvent ?) { guard let touch = event?.touches( for : self.metalView )?.first else { return } let point = touch.location( in : self.metalView ) self .drawCanvas(point : point ) } // タッチが終了した override func touchesEnded (_ touches : Set <UITouch> , with event : UIEvent ?) { guard let touch = event?.touches( for : self.metalView )?.first else { return } let point = touch.location( in : self.metalView ) self .drawCanvas(point : point ) } // タッチがキャンセルされた override func touchesCancelled (_ touches : Set <UITouch> , with event : UIEvent ?) {} タッチイベントを取得します。 UIViewController は標準で override でタッチイベントを取得できて簡単ですね。 タッチされた位置が MetalView に対するタッチイベントのみ処理しています。 ViewController.swift func drawCanvas (point : CGPoint ) { let x : Int = Int(ceil((point.x /self .metalView.frame.width) * CGFloat( self .bufferWidth))) let y : Int = Int(ceil((point.y /self .metalView.frame.height) * CGFloat(kMetalTextureHeightDotSize))) var color : simd_float4 = [ self .isRed ? 1.0 : 0.0 , self .isGreen ? 1.0 : 0.0 , self .isBlue ? 1.0 : 0.0 , 1.0 ] let dataSize = MemoryLayout < simd_float4 > .stride if let ptr = self .mTextureBuffer?.contents() { if 0 <= x, x < self .bufferWidth, 0 <= y, y < kMetalTextureHeightDotSize { let index = (x * kMetalTextureHeightDotSize) + y memcpy(ptr.advanced(by : index * dataSize), & color, dataSize) } } } タッチされた座標を元に、用意したバッファに色情報を入れていきます。 isRed 、 isGreen 、 isBlue という項目が出てきましたが色情報を変更できる様にフラグを宣言しています。 先ほど作成したMetal用のバッファの contents() と言うファンクションでバッファの先頭のポインタ、アドレス情報を取得します。この処理はC言語っぽいですが memcpy で色情報をアドレスを指定してバッファにコピーしましょう。 これでバッファの準備は完了です。 4. MetalKit から定期的に呼ばれるdrawのリクエスト(MTKViewDelegateのコールバック)のタイミングで、用意したバッファと書き込む対象のテクスチャを渡す ViewController.swift let kMetalThreadGroupCount : Int = 16 ... func draw ( in view : MTKView ) { guard let drawable = view.currentDrawable else { return } guard let commandBuffer = self .mCommandQueue.makeCommandBuffer() else { return } guard let textureBuffer = self .mTextureBuffer else { return } let computeEncoder = commandBuffer.makeComputeCommandEncoder() computeEncoder?.setComputePipelineState( self .mComputePiplineState) let texture = drawable.texture // 書き込む対象のテクスチャをセット computeEncoder?.setTexture(texture, index : 0 ) // GPUに渡すテクスチャ用バッファをセット computeEncoder?.setBuffer(textureBuffer, offset : 0 , index : 1 ) let threadGroupCount = MTLSizeMake(kMetalThreadGroupCount, kMetalThreadGroupCount, 1 ) let threadGroups = MTLSizeMake(Int( self .targetMetalTextureSize.width) / threadGroupCount.width, Int( self .targetMetalTextureSize.height) / threadGroupCount.height, 1 ) computeEncoder?.dispatchThreadgroups(threadGroups, threadsPerThreadgroup : threadGroupCount ) computeEncoder?.endEncoding() commandBuffer.present(drawable) commandBuffer.commit() commandBuffer.waitUntilCompleted() } 今回の肝となる処理ですね、先ほど用意したバッファをMetal側に渡します。 Metalに限らず GPUプログラミング では一般的な処理フローを1つ説明します。 引用元(※NVIDIAブログ) GPU は CPU とは違い基本的には 並列 で処理が実行されます。 上の図の例で言うと、処理したい関数 saxpy を再起的に呼び出し、同じスレッド内で連続して処理しているのに対し Cuda の saxpy は非同期に処理され cuda 内のメモリに計算結果を反映しています。 今回 kMetalThreadGroupCount で指定している数値が並列処理が行われるスレッドの数になります。 commandBuffer( MTLCommandBuffer ) と computeEncoder( MTLComputeCommandEncoder ) を使って、 テクスチャとバッファをMetal( GPU側 )に渡してあげましょう。 5. シェーダにて描画する色情報を適切にテクスチャに渡す シェーダのセットアップ シェーダは 別ファイル となります。拡張子は .metal で言語は C++ ですね。 PaintShader.metal #include <metal_stdlib> using namespace metal; /* * |--|--|--|--| * | 0| 4| 8|12| * | 1| 5| 9|13| * | 2| 6|10|14| * | 3| 7|11|15| * |--|--|--|--| */ kernel void computeTexture2d(texture2d<half, access::write> output [[texture( 0 )]], device float4 *color_buffer [[buffer( 1 )]], uint2 gid [[thread_position_in_grid]]) { int h = output.get_height(); int index = (gid.x * h) + gid.y; half r = color_buffer[index].x; half g = color_buffer[index].y; half b = color_buffer[index].z; half a = color_buffer[index].w; output.write(half4(r, g, b, a), gid); } はい、ここでMetalのセットアップで出てきた computeTexture2d が出てきましたね。 引数の [[texture(0)]] や [[buffer(1)]] の数値は先ほど computeEncoder でセットした際に指定した インデックス です。 gid [[thread_position_in_grid]] は指定したスレッドの数によって並列化した際の現在の インデックス(配列のアドレスのような物)情報 が設定されています。 この gid の位置が書き込むキャンバスのピクセル位置情報となります。 X座標とY座標情報でインデックス情報を算出し、渡したバッファから色情報を抽出します。 そして、先ほどのテクスチャに色情報を渡してあげれば MTKView上に絵が描画されます!(パチパチ) ん? 謎の隙間がある?と思った方鋭いですね。 このままではキャンバス全体に対して色を塗りつぶすことができない場合があります。(※できる場合もある) ViewController.swift func updateMetalViewDrawableSize () { // Viewの実際のフレームサイズから縦横比を参考に高さのドットサイズから幅のドットサイズを求める var width = Int(ceil(( self .metalView.frame.width /self .metalView.frame.height) * CGFloat(kMetalTextureHeightDotSize))) // 書き換えた内容(1) ////// // 指定するThreadGroupCountで割り切れなければならない為調整をする let v : Int = width % kMetalThreadGroupCount if v > 0 { width -= v } // 書き換えた内容(1) /////// self .targetMetalTextureSize = CGSize(width : width , height : kMetalTextureHeightDotSize ) self .log( "drawableSize: \(self.metalView.drawableSize) , frame: \(self.metalView.frame) => targetMetalTextureSize: \(self.targetMetalTextureSize) - MetalView" ) self .metalView.drawableSize = self .targetMetalTextureSize そこで先ほど 1. で 後ほど書き換えがあります(1) と記述した内容になります。 kMetalThreadGroupCount で割った場合の余りを幅から引くことで改善できます。 つまり、 GPU側のスレッド数で割り切れない物は描画しきれない という注意すべきポイントがあります。 Appleのドキュメント にて ThreadGroup と GirdSize についての記述はありますが、今回はなるべくバッファは余分に取らずあまりを出さないようにした方が管理が楽だったのでこの方法を取っています。 +αその1、これでは線が引けないので引ける様にする このままではタッチイベントが発生したタイミングでキャンバスに色を1点塗るだけ似合ってしまうので線を引けるように対応します。 ViewController.swift // 2点間の線を引く為のポイント一覧を取得する // 参考(プレゼンハムのアルゴリズム): https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%AC%E3%82%BC%E3%83%B3%E3%83%8F%E3%83%A0%E3%81%AE%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0 func getLinePoints (p0 : CGPoint , p1 : CGPoint ) -> [CGPoint] { var points = [CGPoint]() var x0 : Int = Int(p0.x) var y0 : Int = Int(p0.y) let x1 : Int = Int(p1.x) let y1 : Int = Int(p1.y) let dx : Int = Int(abs(p1.x - p0.x)) // DeltaX let dy : Int = Int(abs(p1.y - p0.y)) // DeltaY let sx : Int = (p1.x > p0.x) ? 1 : - 1 // StepX let sy : Int = (p1.y > p0.y) ? 1 : - 1 // StepT var err = dx - dy while true { if x0 >= 0 , y0 >= 0 { points.append(CGPoint(x : x0 , y : y0 )) } if x0 == x1, y0 == y1 { break } let e2 = 2 * err if e2 > - dy { err -= dy x0 += sx } if e2 < dx { err += dx y0 += sy } } return points } 2点間の線を引くアルゴリズムは プレゼンハム のアルゴリズム が有名ですね、 ウィキペディア にあった内容を利用させていだだきました。 ブレゼンハムのアルゴリズム(Bresenham's line algorithm)は、 与えられた始点と終点の間に連続した点を置き、近似的な直線を引くためのアルゴリズム。 ブレゼンハムの線分描画アルゴリズム、ブレゼンハムアルゴリズムとも。 コンピュータのディスプレイに直線を描画するのによく使われ、 整数の加減算とビットシフトのみで実装できるので多くのコンピュータで使用可能である。 コンピュータグラフィックスの分野の最初期のアルゴリズムの1つである。 これを若干拡張すると、円を描くことができる これで簡単なメモ書きぐらいには使えそうですね。 +αその2、描ける線の太さを変えられるようにする ペイントツールといえば線の太さを変えれますよね、なのでそちらを再現したいと思います。 先ほどのウィキペディアの記述にあったように プレゼンハムのアルゴリズム を利用すれば円を描くこともできます。 こちら にあったプログラムを参考にさせていただきました。 参考にしたものから円の中を塗りつぶせるように改良しましたが特にパフォーマンスは意識していないのでそちらはご容赦を。 ViewController.swift // 中心点と半径から縁を描く為のポイント一覧を取得する // 参考(ブレゼンハム円描画のアルゴリズム): http://dencha.ojaru.jp/programs_07/pg_graphic_09a1.html func getCircleFillPoints (center : CGPoint , radius : Int ) -> [CGPoint] { var points = [CGPoint]() let centerX : Int = Int(center.x) let centerY : Int = Int(center.y) var cx : Int = 0 var cy : Int = radius var d : Int = 2 - 2 * radius // Left Top var ltx : Int = 0 var lty : Int = 0 // Right Top var rtx : Int = 0 var rty : Int = 0 // Left Bottom var lbx : Int = 0 var lby : Int = 0 // Right Bottom var rbx : Int = 0 var rby : Int = 0 // Top(0, R) var vx : Int = cx + centerX var vy : Int = cy + centerY if vx >= 0 , vy >= 0 { points.append(CGPoint(x : vx , y : vy )) } // Bottom(0, -R) vx = cx + centerX vy = - cy + centerY if vx >= 0 , vy >= 0 { points.append(CGPoint(x : vx , y : vy )) } // Right(R, 0) vx = cy + centerX vy = cx + centerY if vx >= 0 , vy >= 0 { points.append(CGPoint(x : vx , y : vy )) } // Left(-R, 0) vx = - cy + centerX vy = cx + centerY if vx >= 0 , vy >= 0 { points.append(CGPoint(x : vx , y : vy )) } while true { if d > - cy { cy -= 1 d += 1 - 2 * cy } if d <= cx { cx += 1 d += 1 + 2 * cx } guard cy > 0 else { break } // Right Bottom (Bottom To Right) rbx = cx + centerX rby = cy + centerY if rbx >= 0 , rby >= 0 { points.append(CGPoint(x : rbx , y : rby )) // 0 ~ 90 } // Left Bottom (Bottom To Left) lbx = - cx + centerX lby = cy + centerY if lbx >= 0 , lby >= 0 { points.append(CGPoint(x : lbx , y : lby )) // 90 ~ 180 } // Left Top (Top To Left) ltx = - cx + centerX lty = - cy + centerY if ltx >= 0 , lty >= 0 { points.append(CGPoint(x : ltx , y : lty )) // 180 ~ 270 } // Right Top (Top To Right) rtx = cx + centerX rty = - cy + centerY if rtx >= 0 , rty >= 0 { points.append(CGPoint(x : rtx , y : rty )) // 270 ~ 360 } // 上半分は上部分から左右に、下半分はした部分から左右に伸びている //print("[\(ltx), \(lty)], [\(rtx), \(rty)], [\(lbx), \(lby)], [\(rbx), \(rby)]") // Y軸は左右同じ地点を指している事から上版分、下半分でX軸の左端から右端にポイントを追加する事で円を塗り潰します。 for i in lbx ... rbx { if i >= 0 , rby >= 0 { points.append(CGPoint(x : i , y : rby )) } } for i in ltx ... rtx { if i >= 0 , lty >= 0 { points.append(CGPoint(x : i , y : lty )) } } } // 中心線 for i in centerX - radius ... centerX + radius { if i >= 0 , centerY >= 0 { points.append(CGPoint(x : i , y : centerY )) } } return points } 円の描画は左右の端から上もしくは下方向にループで描画していくので、左端から右端にループを伸ばして中の色情報を挿入しています。 バッファに色情報を入れる関数の全容 ViewController.swift func drawCanvas (point : CGPoint ) { let x : Int = Int(ceil((point.x /self .metalView.frame.width) * CGFloat( self .bufferWidth))) let y : Int = Int(ceil((point.y /self .metalView.frame.height) * CGFloat(kMetalTextureHeightDotSize))) var color : simd_float4 = [ self .isRed ? 1.0 : 0.0 , self .isGreen ? 1.0 : 0.0 , self .isBlue ? 1.0 : 0.0 , 1.0 ] let dataSize = MemoryLayout < simd_float4 > .stride if let ptr = self .mTextureBuffer?.contents() { if 0 <= x, x < self .bufferWidth, 0 <= y, y < kMetalTextureHeightDotSize { let index = (x * kMetalTextureHeightDotSize) + y self .log( "draw color[[ \(x) , \(y) ] => \(index) ]: \(color) " ) memcpy(ptr.advanced(by : index * dataSize), & color, dataSize) } } // 現在地点と前回地点の間に線を入れます var linePoints : [CGPoint] = [CGPoint]() let newPoint = CGPoint. init (x : x , y : y ) defer { self .lastPoint = newPoint } // 前回値を保持しておく if let last = self .lastPoint, ! last.equalTo(newPoint) { // 2点間の線を引く為のポイント一覧を取得し追加する linePoints.append(contentsOf : self.getLinePoints (p0 : last , p1 : newPoint )) } else { // ※同じ点を描画する事になるのであまり処理効率は良く無いですが説明上描画した値を入れます。 linePoints.append(newPoint) } if self .pointSize > 1 { // 円描画 var cirlePoints : [CGPoint] = [CGPoint]() for p in linePoints { cirlePoints.append(contentsOf : self.getCircleFillPoints (center : p , radius : self.pointSize / 2 )) } linePoints.append(contentsOf : cirlePoints ) } if linePoints.count > 0 , let ptr = self .mTextureBuffer?.contents() { for p in linePoints { let x = Int(p.x) let y = Int(p.y) guard x < self .bufferWidth, y < kMetalTextureHeightDotSize else { continue } let index = (x * kMetalTextureHeightDotSize) + y memcpy(ptr.advanced(by : index * dataSize), & color, dataSize) } } } できたもの Metalで簡単なペイントツールを作ってみました #iOS #Metal #MetalKit #Texture #2D #Demo pic.twitter.com/yGmzw647Up — aptueno (@aptueno) December 16, 2019 まとめ MetalKitをふんだんに使ったアプリケーションって結構実装コストと学習コストも相まってなかなか使えないことが多い気がします。 この記事をみてMetalKitを使ったアプリケーション、機能の案が浮かんだ方、ラッキーでしたね。 正直私はハイパフォーマンスで絵を描ければなんでもできる気がしているので少しでも参考になったら嬉しいです。 今回のデモは前回同様githubで公開しますのでよかったら動かしたり、ご覧ください。 少しボリューミーな記事になったかと思いますがご覧いただきありがとうございました。 ・iOS-MetalPaintDemo
デザイン室の仕事 データ表示の際のフォントの選択肢 フォント制作 7セグメントフォント apt7seg(自作7セグフォント) aptQ(既存フォントの改造) ライセンス周り 所感 デザイン室の求人 aptpod Advent Calendar 2019 17日目は、デザイン室 @tetsuがお送りいたします。 今回フォントについてのお話ですが、 apt7seg(自作7セグフォント) に自作している7セグフォントの公開先をリンクしておりますので、もし使っていただけたら Twitter などにフィードバックいただけると嬉しいです! デザイン室の仕事 まずデザイン室ではどのような業務を行っているか、簡単に紹介させていただきます。 製品及びカスタム案件のWeb/モバイル/ネイティブのアプリケーション開発におけるUIデザインを主としています。 その他にコーポレートのブランディング、マーケティングデザイン、カタログ等の印刷物制作、マニュアル制作、Webサイトのデザイン及び運用、製品/案件/プロモーションに伴うPV動画制作などもデザイン室にて、試行錯誤を重ねながら内製しています。 このTech Blogも運用し始めて、記事を書くエンジニアにOGP設定してもらうのも負担だと思い、途中からデザイン室でまとめて作り出しました。最初からやっとけよ…ということも多々ありますが、UI開発もマーケ施策も、やってみてスグ改善できるのが内製の強みだと思っています。 この辺の話も、また別の機会に記事にできればと思います! 今回は、その中でもUIデザインの業務について、特に 高速なデータを表示する際のフォントについてどのように取組んでいるか を紹介させていただきます。 データ表示の際のフォントの選択肢 UIデザインにおいてフォントの重要性は言わずもがなのことですが、数字データを表示する上での選択肢には条件がいくつかあります。 リアルタイムに高速に変化する数字を可読性をもって美しく表示するには、プロポーショナルフォント 1 は適していません。 数字が変化する度にカクカクと揺れてしまうと、読み取りづらく、美しいものではありません。 高速に変化する数字箇所には等幅フォントを使用します。文字通り、文字の横幅が等幅なので、数字が変化しても、間が詰まったり開いたりしません。 メーターパーツなどの場合、等幅であっても桁数の変化にも考慮が必要で、桁数が固定でない場合は右揃えにします。 中央揃えだと、桁数が増えた時に左右に広がり、カクつきます。 しかしUIの全てを等幅フォントで統一するのは現実的ではありません。 ラベルや、テキストなどはプロポーショナルフォントで組みます。文章において等幅では無駄にスペースを使いますし、なにより美しくデザインされたプロポーショナルフォントで表示されることにより、ストレスなく文章が頭に入ってきます。 世にあるフォントの殆どはプロポーショナルフォントです。 コーディングされる方は等幅フォントに馴染みがあるかと思いますが、等幅フォントの選択肢はそう多くはありません。 UIの世界観で見ると、デザイン的に他の要素と馴染む等幅フォントは少ないというのが現実です。 フォント制作 7セグメントフォント 等幅フォントに近いもので、セグメントフォントがあります。あらかじめ予約されたセグメントの表示非表示を切り替える形なので、高速に変化する数字がカクついて表示されることはありません。 一般的には 7セグメントディスプレイ がよく知られています。電光掲示板とか古いタイプのデジタル目覚まし時計に使われているアレですね。 こちらのフォントは等幅フォント以上に選択肢が少なく、既存のものでUI全体の世界観に合うものは見つけれらませんでした。 そこで、素人仕事ですが、7セグフォントを作ってみることにしました。 apt7seg(自作7セグフォント) Visual M2M Data Visualizer(以後VM2M) をデザインするにあたり、未来感のあるUIを実現しつつ可読性を保つメインのフォントとして AXIS を採用しています。このAXISとの親和性を考慮して、スクエアでありつつ若干丸みを保つようセグメントを切ったフォントを作りました。Illustratorでデザインして、Glyphsでフォントパッケージして書き出し。アプリ開発チームに用途ごとに適した形式で共有しています。 素人仕事で大変お恥ずかしいのですが、使ってみようという奇特な方がいらっしゃれば、以下にアップしていますので、どうぞご利用ください。それぞれOTFとWOFF2形式です。フィードバックなどいただけたら大変ありがたいです! ライセンスは SIL OFL(SIL Open Font License) とします。 ダウンロード:apt7seg_sq.zip aptQ(既存フォントの改造) 前項で7セグフォントについて紹介しましたが、数字以外の文字がうまく表現できない(可読性が低くなってしまう)というデメリットがあります。 そのためCAN 2 データなど16進数で表示するものには適していません。 VM2MのUIではこの役割を果たすフォントととして、 Quantico をベースに作成したaptQを使用し、数字、記号、アルファベットを併記する箇所に使用しています。 ライセンス周り フォントの改変時はライセンスに注意が必要です。 上述のQuanticoは SIL OFL(SIL Open Font License) ライセンスです。単独での配布はNGなので、こちらのフォントファイルは公開しませんが、改変してアプリケーションに組み込むことで配布が認められています。 所感 フォントを作ろうとしてみて、あらためて文字を作る職人の皆様の仕事の凄さの一端に触れ、この道を追求しすぎるのはやめようと、打ちひしがれました。。 今回はWEBアプリケーション向けの文脈でフォントについて書きましたが、iOSやAndroid、また他のデバイスネイティブ向けとなると他にも考慮すべき点があります。 組み込みでの有料フォント利用のライセンス料は桁が1つ2つ変わってくるので、なかなか現実的ではありません。 自作してしまえば、その問題もクリアできると考えたこともありましたが、本格的に自作に取り組むのは相当難しいと学びました。 そのようなライセンス事情も相まってか、昨今海外でもデザインシステムと同時にコーポレートフォントを外部依頼で制作する企業が増えていますね。 正直弊社のようなtoB向け事業では実現性はなさそうですが、 メルカリさんの独自フォントのニュース などは本当に羨ましいなぁと眺めております。 デザイン室の求人 最後に宣伝を。 冒頭の デザイン室の仕事 で触れた業務に興味のあるデザイナー募集中です! 一時期は世間の流れに押されて「UI/ UX デザイナー」募集としておりましたが、UIデザイナーもしくはグラフィックデザイナーを募集しております。 UX とは職業ではなくて、みんなが意識するものだと思っています。 弊社における最大のUXポイントとは、データが確実に取得できて(エッジ→サーバ)速く処理して届く(サーバ→制御/UI)こと。 UXの中でタッチポイントのひとつとしてのUIなので、もちろんどこも大事ですが、全体見ようね、その上でUIデザイン/グラフィックデザインで垣根越えたい!そんなデザイナーを募集しております。 産業IoTという分野の中で、デザインに対して意識高くやっていける環境です。僕がジョインするまで、デザインワークは代表の坂元がやっておりましたので、デザインに理解のある環境です! ぜひ、以下からご応募ください。 aptpod Recruit 文字毎に文字幅が異なるフォントのこと。日本語では可変幅フォントとも。 ↩ Controller Area Network 自動車での使用を前提に開発されたシリアル通信プロトコル。 ↩
aptpod Advent Calendar 2019 16日目担当の榮枝です。 aptpod で ソリューションアーキテクト という職種でSEのような仕事をしています。 システムの全体設計をしたり、見積書を書いたり、プロジェクト管理?みたいなことをしたり、 出張経費精算で領収書をなくして怒られたりしてます。。 さて、そんな中で社内イベントでモバイル通信環境を計測する機会がありましたのでその話をします。 0.前置き 弊社ではテレメトリという領域を軸に様々なプロジェクトを行っています。 モバイル回線を通して 車両のCANデータ・各種センサーデータ・動画データ等を遠隔から高いリアルタイム性を保ちつつ監視・計測したり、 車両の操作といった遠隔制御したり、 といったプロジェクトが多く、その中で、周回コース※などの特定の環境下を車を走らせながらデータを上げたい、といった要望があがってくることがあります。 ※そういったコースは、通信環境が悪い街外れにあることが多いです。 その際、 「(そのプロジェクトで入手/利用できる範囲で)最適構成の通信機器やキャリア」を見つけつつ、 「その環境下で一定時間におおよそどれだけのデータをアップロードできるのか」という調査データがシステム設計のために必要になって来ます。 ただ、たまにかつ急に調査需要が出てくることもあり、調査の際にあたふたしていました。。。 そんな折、先日千葉県茂原のサーキットを貸し切って自社製品を触ってみよう、という社内イベントがありちょうどよい機会だったので、このモバイル通信環境計測の作業手順を整理する意味で、準備~計測~評価までを行ってみました。 また、せっかくの機会だったので、 「通信にどれだけのレイテンシが発生しているのか」 の準備〜評価も並行して行いました。 1.用意するもの プロジェクトによって比較対象とする通信機器は異なってくるのですが、今回は計測手法自体が目的のため特別な通信機器は用意せず、弊社の標準構成 + 3キャリアで計測してみました 車載コンピュータ(通信モジュール内蔵) 写真のもの、通信計測のためだけには大仰な装置ですが apdpodでよく使う装置のためこれを利用しています。 Anker PowerHouse 車載コンピュータ用電力源。今回は6台の車載コンピュータを並行して利用するために用意しました。構成によっては車のシガーソケット供給でも良いです。 計測時間x消費電力x台数を満たせる容量と出力があることは要確認。(今回のケースでは問題ないことを事前に確認してもらっています。) 車載器6台に電力供給できるように、シガーソケットからの電力分岐ケーブルをHWチームに突貫で作成してもらいました。 sim x 3社 x 各2枚(通信帯域幅測定用 & レイテンシ測定用) 通信帯域幅測定用simはデータ容量に注意、計測時間にもよりますが、数十GBは欲しいところです。 実際計測した際は写真を撮ってなかったので、この記事用に改めて録りました。当時の状況と若干異なります。 2.事前設定 おおまかな構成 「帯域幅測定」 iperfコマンドを利用して計測します。 iperfでは、対応するiperfサーバが必要になります。 公開サーバ もありますが、どこかの誰かが同時に使っていたりする可能性もあり、 安定的に利用するには不向きなため、自前で用意する必要があります。 iperfコマンド: iperf3 -t 5 -i 2 -c <自社で用意したiperfサーバドメイン> -p <ポート> の結果をパースして、GPSデータと合わせて(厳密にはGPSとは別のパケットで送るのですが、詳細は省略・・)弊社のintdashサーバに送ります。 「レイテンシ測定」 pingコマンドを使うこと以外は、帯域幅測定とほぼ同じです。 ping受け側サーバは、iperfと違ってサービスを用意したりは不要です。 pingコマンド: ping -c 1 <サーバ側IPアドレス> 2-補足 intdashについて intdashとはaptpod社製の 「⾼速・⼤容量かつ安定的にストリーミングするための双⽅向データ伝送プラットフォーム」 で、高いリアルタイム性を維持しつつ、データの完全回収性(リアルタイムで送れなかったデータを後回収する)システムです。 今回のような計測を行う場合、 「計測完了してみたらデータが取れていなかった」 といったことが起きがちなのですが、intdashを使うことで、データの計測状況をモニタリングすることができます。 また、通信回線が弱くリアルタイムに送れなかった分の計測データも、後から自動でサーバ側に回収されるため、データの取り扱いも非常に楽に行えます。 更に、データを見比べる際、取得したCSVデータをエクセルでグラフ化して、、、といった作業をよくやりますが、intdashを使うとVM2M( Visual M2M Data Visualizer )という可視化ツールも揃っているため、グラフ化作業や、データ比較も非常に簡単に行えます。 intdash便利です! (以上、intdashの宣伝でした) 3.計測 前述の車載器に電源を入れると自動的に計測開始されるように設定しているため、 計測時は電源を入れる以外に特にやることはありません。 (可視化ツールのVM2Mで動作状況を確認するぐらい) 弊社オフィスそばの駐車場から茂原サーキットへ向かう道中と、茂原サーキットで数周走行して計測しました。 弊社オフィスからアクアライン出口まで (アクアライン出口の料金所で、一旦車を止めて計測に区切り) アクアライン出口から茂原サーキット 茂原サーキット ※茂原サーキットでは、あるキャリアは全く通信ができなかったため、グラフ上にもデータが表示されていません 4.評価 計測結果をどういった軸で評価するか(評価指標)は、プロジェクトによって変わるのですが、 よくある/今後欲しくなりそうな指標として以下2種を考え、それぞれについて計算してみました。 (なお、VM2MからはAPIやCSVとしてダウンロードするなどしてデータを取り出すことができ、今回はCSV出力したデータを元に計算しました) 「帯域幅」 1.平均送受信量 この評価指標が求められるケース: 計測機器から発生するデータをおおよそリアルタイムに上げきることができるか?を知りたい時。 通信帯域の計測結果と、計測機器が発生させる秒間データ量の見積もりなどを比較を、設計の参考にします。 計算方法: 帯域幅の平均値 (一部-1と出ているところは0として計算) 注意:千葉県茂原サーキットやその周辺は、非常に電波状況が悪いため、この結果も一般的な通信帯域幅よりだいぶ低い値になっています。 キャリアA UP平均: 2.3Mbit/sec Down平均: 2.1Mbit/sec キャリアB UP平均: 11.8Mbit/sec Down平均: 10.7Mbit/sec キャリアC UP平均: 10.4Mbit/sec Down平均: 9.2Mbit/sec 2.帯域安定度 この評価指標が求められるケース: 恒常的にリアルタイム通信ができるか?を求められるケース 平均送受信量では、通信が悪い時にデータがエッジに溜まり、良い時に回収され、後からデータが流れてくる、といった形になりますが、 リアルタイムにデータ送受信できる必要性が高い場合はこの安定度が必要になります。 計算方法: 標準偏差(ケースによっては最小値) キャリアA UP偏差: 4.8Mbit/sec Down偏差: 4.4Mbit/sec キャリアB UP偏差: 10.1Mbit/sec Down偏差: 9.3Mbit/sec キャリアC UP偏差: 10.7Mbit/sec Down偏差: 9.7Mbit/sec →正直平均値に対して、標準偏差が大きすぎて、、、通信状況の良い東京と、アクアラインの地下や、ほとんど通信できなかった茂原付近等をまとめて計算するとだめですね。。。。 茂原サーキットのみのデータで、安定して測定できたキャリアCだと UP平均: 6.2Mbit/sec Down平均: 5.4Mbit/sec UP偏差: 1.4Mbit/sec Down偏差:1.2Mbit/sec だったので、特定の範囲無いだけを評価するのは良さそうです。 「レイテンシ」 レイテンシも平均と標準偏差を出してみました。 - 計測できなかった時に丸め値として出る0値を除いて計算 - 10sec近く掛かることもあり、平均値を大きく引っ張るため、100msec以上のものを除外した平均も計算しています キャリアA 全平均:73.1msec 除外平均:48.8msec 標準偏差:263.4msec キャリアB 全平均:45.3msec 除外平均:41.2msec 標準偏差:101.1msec キャリアC 全平均:57.3msec 除外平均:46.8msec 標準偏差:178.3msec 異常値を除いた平均レイテンシは各キャリアあまり変わらなそうですね。 若干キャリアBがよいかな?ぐらい。 異常値が少ない、という点でも今回の計測の範囲内ではキャリアBが良さそうです。 まとめと感想 分析はもっと時間をかけて、じっくりやれば色々見えてくるものはありそうです。 せっかくとったデータを活かしきれてない感覚が残り、データアナリティクス的な知識をもうちょっとつけておけば、、と反省です。 ただ、計測手法や、大まかな分析方法は見えたので、今後同様の事例があった時は慌てずに済みそうです! 今回、aptpodのHWチーム、サーバチーム、エンべ(組み込みソフト)チーム各所にご協力頂いて計測実験ができました。 部署横断的に、急遽お願いして協力して頂いたのですが、即座に対応してくれて大変助かりました。 中の人間の自画自賛ですが、この様に広い分野の課題に対して部署横断的にすぐ動けるのもaptpodの強いところです!!
この記事は、 Aptpod Advent Calendar 2019 の15日目の記事です。 先進技術調査グループの酒井です。 つい先日の12/2〜6にラスベガスで開催されたAWS re:Invent2019でブース展示をしてきました! ブースでは、 Amazon SageMakerとintdashでお菓子の高速検出システムをサクッと構築してみた と 、 AWS RoboMakerとintdashでTurtlebot3を遠隔制御 をパワーアップさせたものを展示しました。 今回の記事は、このパワーアップさせたRoboMakerの展示について紹介します。 前回 は Turtlebot3 に装着した OpenManipulator (アーム) の操縦やセンサー情報の吸い上げができませんでした。しかし、今回はアームの制御とLiDARデータの吸い上げが可能になりました! また、今回はアメリカから日本のTurtlebot3の遠隔制御と、現地に持ち込んだTurtlebot3 のクラウド経由での遠隔制御の2つを展示しました。 遠隔制御では、aptpodの本社がある四谷三丁目にあるオフィスにあるTurtlebot3 with OpenManipulator を アメリカ ラスベガスから遠隔制御しています。 操作感を撮影した動画をYouTubeにアップロードしたのでご覧ください。 www.youtube.com 大きな操作遅延を感じることなく操作できているのが見て取れると思います。 本記事では、デモの構成について少し掘り下げて説明します。 デモの構成 日米間の遠隔制御の構成 まずは日米間の遠隔制御デモについて説明します。日本側ではaptpodのオフィスに、Turtlebot3 を設置しています。 US側では、制御データを送るラズパイと接続したPS3コントローラー、LiDARデータをモニタリングするためのWebアプリケーションを用意します。 US側にあるコントローラーはAWS 東京リージョンに設置されているintdashサーバーを経由して、aptpodのオフィスにあるTurtlebot3に制御コマンドを送信します。 aptpodのオフィスにあるTurtlebot3は、LiDARのデータをAWS 東京リージョンのintdashサーバーを経由して、US側にあるモニタリングアプリケーションにデータを配信します。 図では省略されていますが、四谷三丁目のオフィスの様子を映すために、 C920 というWebカメラとRaspberry Piを接続したものと、Turtlebot3のRaspberry Pi Camera Module に本体の操作とは別のRaspberry Piを取り付けたものも使用しています。 これらのRaspberry Piにもintdash Edge Moduleを搭載しています。これによって、C920からはH.264の動画データ、Raspberry Pi Camera ModuleからはMotion JPEGの画像データの配信を実現しています。 AWS RoboMakerのシミュレーション (Gazebo) と実機を同期させて動かすデモの構成 次に、AWS RoboMakerのシミュレーション (Gazebo)と実行を同期させて動かすデモの構成について説明します。 こちらのデモでは、Turtlebot3、コントローラー、LiDARデータをモニタリングするためのWebアプリケーションはすべてUS側で動かしました。 intdash サーバー、AWS RoboMakerはAWS 東京リージョンのものを使っています。 こちらのデモでは、コントローラーから送信した制御データを、実機とシミュレーション環境上のTurtlebot3がそれぞれ受信し同じ動作をします。 感想 USからの遠隔制御はぶっつけ本番で行ったので遅延がどうなるかが不安でした。しかし、実際に触っていただいたお客様からは、「操作の遅延がほとんど感じられなくてすごい」というコメントを頂けたのでよかったです。 OpenManipulatorを使って、ノベルティーとして配っていたガムを積み上げるデモを行っていたので、ブースを通りかかった人から「Cool !」、「Awesome! 」という声も頂きました。 取材記事のご紹介 クラスメソッド様にブースの様子をご紹介いただきました! dev.classmethod.jp gihyo.jp 様にも取材記事を載せていただきました gihyo.jp おまけ aptpod ブース
先端技術調査グループの南波です。 aptpod Advent Calendar 2019 の14日目は、チームビルディングのために半年間続けた1on1の振り返りです。 背景 大学で信号処理/機械学習系の研究をしていた経験を活かしたく2018年にアプトポッドに入社し、技術調査を中心に製品開発や機械学習を利用したいお客さまとの共同プロジェクトなどにも参加していました。(今年は AWS Certified Machine Learning - Specialty も取得することができました 🎉 ) 2019年に入ってからは先端技術調査グループのチームメンバーが順調に増え、8月には元々のチームリードがVPoPに就任するために自分がチームリードとなり、現在は5名(うち4名がそれぞれ2019年の4,5,8,10月入社)の新規メンバーが多数を占めるチームと変化してきました。 新規メンバーのオンボーディングは生産性に直結します。そのため、会社・チームごとにいろいろな工夫をされていると思います。 弊グループでは、データサイエンス、ネットワークプロトコル、ロボティクスなどの広い技術領域からメンバーのバックグラウンドに沿ったテーマを任されます。 結果として、 チーム内に同じ領域を専門・担当としている人が少ない という特徴があります。 そのため、他の人の型をなぞることが難しく、 参加初期の仕事の進め方に不安を覚えやすい という課題があるのかなと思っていました。 これに対応するための弊グループのオンボーディング施策として、「 一刻も早く自立して動けるメンバーになってもらう(=自信を持って仕事をしている状態になってもらう) 」ことを目指し、 週1〜月2回の1on1 に取り組んでみました。 勉強方法 施策を始める前からも、実際に始めてからも、ひたすら参考となりそうな資料を漁りました。 以下は覚えている限りの例です。 ヤフーの1on1 1on1マネジメント エンジニアのためのマネジメントキャリアパス エンジニアリング組織論への招待 EM.FM ソフトウェア・ファースト HIGH OUTPUT MANAGEMENT まず、ルールを破れ やってみて 実施してみた所感として やり方の正解がわからない難しさ 成果があったのかどうか明確にはわからない難しさ がずっと付いてまわる感覚がありました。 やり方の正解がわからない難しさ 実はタイトルの「65回」は、 60回:自分が主に聴く側(チームメンバーとの1on1) 5回:自分が主に話す側(VPoEとの1on1) でした。 1on1の目的がそれぞれ異なるために 話題 ログの残し方 お互いの話す量 など、細かい方法が異なってくるのも当然ありうることとは理解しているものの、「自分のやり方は本当に適しているのか?」という疑問は頻繁に浮かびました。 特に、お互いの話す量の観点で、勉強した資料の中では 聴く側は話しすぎない ティーチングよりコーチング という内容が頻出していたため、 「自分が話し過ぎてしまった」 「自分が解決策について考え過ぎてしまった」 という反省をすることがよくありました。 一方で、1on1の主目的をオンボーディングの円滑化においているので、社内技術のキャッチアップ補助などのティーチングの割合が多くなってしまうのも、ある程度は仕方ないのかなと思ったりもしていました。 難しいです 🙃 成果があったのかどうか明確にはわからない難しさ データサイエンスやエンジニアリングの世界で過ごす時間が長かったので、「自分の仮説・施策は正しかったか・効果があったか」を計測できる(ようにしなければならない)という考えが自分の中にある一方、今回の取り組みはA/Bテストもできず、この取り組み以外の環境要因も大きいため、「 本当に月に5時間以上使ってる意義があるのか 」という不安はありました。 幸いチームの活動は順調で、過去に行なった振り返りでも好意的な感想をもらえたので「すぐにやめた方がいい」という判断をするほどではないかな?と思えています。 またチームメンバーのほうから「今までは週1だったけど、今後は月2くらいでいいと思う」といった進め方の意見をもらえていることも、取り組みの価値が浸透していっている感覚としていいなと思っています。 一方で、取り組みが形骸化しないようにする・形骸化してきたらやめられるようにする努力・判断は自分のほうで持つ必要があるなというプレッシャーも感じています。 難しいです 🙃 まとめ 半年間続けた1on1を振り返りました。一言でまとめると「 難しい 」です。 同時に、思い込み・運の良さなだけかもしれませんが、 チームがうまくまわってる感じ は少なからずあります。 「もし月に5時間でこの効果を得られてるのだとしたら レバレッジ効いてるな 」という印象は、エンジニアリングの中で感じられる「少しの最適化で何倍にもコードが速くなったとき」と同じ楽しさ・やってやった感なのかなと思えています。 会社として、チームとして、個人として、おそらく来年も2019年と同じくらい変化のある1年になると思いますが、引き続き 難しさの中に楽しさを見つけていきたい と思います 💪 一緒に楽しさを見つけていける人を探しています ので、ピンッときた方はお気軽にコンタクトお願いします 🙏
aptpodでは複数のカメラをフレーム単位で同期させて映像を取得できるカメラデバイスの開発を行なっています。 前日の記事 では、このカメラデバイスのエンコードを担当するSoCの話でしたが、 aptpod Advent Calendar 2019 13日目の今回は映像のフロントエンドに使用しているFPGAについての話題です。 カメラデバイスを開発する上で、FPGAでイメージセンサから取得した画像データをリサイズする機能を実装する必要が出てきたのですが、RTL設計経験のない私でも流行りの高位合成でサクッと実装できた話をまとめます。 前日に続き塩出が担当します。 話の流れ まずは高位合成の説明 高位合成での実装手順 アルゴリズムのC++ソース記述方法 C++でのテストベンチ記述方法 シミュレーション結果の確認 まとめ 高位合成とは? 高位合成の詳しい話は色々記事が出ておりますので、そちらを参照してください。 論理回路の高位合成について 高位合成おぼえがき ざっくり言えば、C/C++ベースのアルゴリズムをRTL(レジスタ転送レベル)に変換してくれるツールになります。HDL(ハードウェア記述言語)であるverilogやVHDLなどで記述するよりも複雑な処理を記述しやすく、うまく使えば実装時間が短縮できると思います。ただし、動作はRTLなのでRTLも少し知っていた方が思い通りの動作をさせやすくなるのかなと思います。 今回使用した環境 FPGA :Xilinx社製のFPGA,Artix-7 speed grade 1 ツール:Xilinx社製 Vivado HLS 2018.3,Vivado 2018.3,SDK 2018.3 これらのツールはXilinxの ダウンロードページ からダウンロードできます。 インストールの方法や基本的なツールの使用方法は省略します。 高位合成で実現したいこと 今回のシステムでは、YUV422の画像のデータが Axi Stream として流れています(xilinxでは画像データは基本的にこのAxi Streamで扱うようです)。このストリームデータを受け取って、フルサイズから1/4サイズにリサイズして、同じくAxi Streamで流したいという要望がありました。また、リサイズするかしないかはAxi Liteのアクセスで変えられるようにしたい,画像のサイズも同じく変えられるようにしたいという要望がありました。 また、BRAMがカツカツだったのでラインバッファを使わない、単純な間引きで実装したいという思いがありました。 まとめますと、以下のような要望になります。 YUV422のデータを1/4にリサイズできること リサイズにはラインバッファを使わず,単純な間引きをすること Axi Stream入力,出力できること 画像のサイズ,リサイズ適用制御はAxi Liteから設定可能であること Axi Streamの入出力、Axi Liteでの制御と考えると、RTLでどうかけばいいのか全く検討がつきませんでしたが、色々調べると高位合成だったらサクッと実装できそうだったので挑戦してみました。 高位合成での実装手順 実装の前にアルゴリズムの説明 実装に入る前に,YUV422について触れて間引きのアルゴリズムを説明します。 YUV422とは? YUV422の説明は この記事 を参考にしてください。Y(輝度成分)は毎ピクセルごと、u,v(色成分)は毎ピクセル交互に格納されています。2つで1つなので、隣り合うY2つに対してuvが1セットです。その仕組み上、幅サイズは偶数になります。 ピクセル数 1 2 ・・・ n-1 n データ Y1U1 Y2V1 Yn-1Un/2 YnVn/2 YUV422の間引き方法 2ピクセルで1セットなので、以下の図のように幅方向を間引く時は4ピクセルから2セットを抽出します。なので、4の倍数ピクセル目から1つ、その1つ前のピクセルから1つを採用しあとは間引きます。 高さ方向は単純に偶数行を丸っと間引くようにします。 本当なら2ライン分バッファに入れて、上下左右のピクセルから補間するようにするのが一般的だと思いますが、ラインバッファ(BRAM)削減のためこのような単純間引きをしています。 余談ですが、実際にXilinx が提供しているライブラリに Risizer があり、こちらはRGBのみ対応ですがlinerの補間をしてくれるものになっております。 アルゴリズムのC++ソース記述方法 上記のアルゴリズムをC++で記述します。いきなりですが、コードを公開します。 メインの関数は F2Q() です。 #pragma がいっぱい入っていて見にくいですが、高位合成では重要な文言ですので我慢して見てください。 各関数をざっくり説明しますと F2Q() いわゆるメイン関数.関数の引数をpragma宣言によってAxi Streamだったり、Axi Liteだったり、関数そのものの実行制御をAxi-Liteから可能にしています。 また、メインの処理順序を記述しています。 処理の流れはAxi Streamからデータを読んでフィルタを通してAxi Streamで出すといった感じです。 YUV2Full2Quarter_local() この関数では、flagによってリサイズするかしないかを条件分岐させています。 YUV2Full2Quarter() この関数が先ほど説明したリサイズのアルゴリズムです。 mat_copy() これはリサイズしない時に入力をそのまま出力する関数です。 自分で型宣言していますので、その説明です。 typedef hls::stream<ap_axiu<24,1,1,1> >AXI_STREAM これがAxi Streamの変数型で今回は将来的にRGBも通せるようにデータ幅24bitにしています。 typedef hls::Mat<MAX_HEIGHT, MAX_WIDTH, HLS_8UC3> RGB_IMAGE これは画像を扱う変数型です。テンプレートでMAXサイズを指定した方がメモリのサイズを抑えられるようです。1pixelあたり8bit、3要素で作成しています。 F2Q.cpp #include "F2Q.h" #include <stdint.h> template < int SRC_T, int ROWS, int COLS, int DROWS, int DCOLS> void mat_copy(hls::Mat<ROWS, COLS, SRC_T> & src, hls::Mat<DROWS, DCOLS, SRC_T>& dst) { HLS_SIZE_T scols = src.cols; HLS_SIZE_T srows = src.rows; loop_height : for (HLS_SIZE_T h = 0 ; h < srows; h++){ #pragma HLS LOOP_TRIPCOUNT min= 1 max= 1 loop_width : for (HLS_SIZE_T w = 0 ; w < scols; w++){ #pragma HLS loop_flatten off #pragma HLS pipeline II= 1 #pragma HLS LOOP_TRIPCOUNT min= 1 max= 1 hls::Scalar<HLS_MAT_CN(SRC_T), HLS_TNAME(SRC_T)> s; src >> s; dst << s; } } } template < int SRC_T, int ROWS, int COLS, int DROWS, int DCOLS> void YUV2Full2Quarter(hls::Mat<ROWS, COLS, SRC_T>& src, hls::Mat<DROWS, DCOLS, SRC_T>& dst) { HLS_SIZE_T scols = src.cols; HLS_SIZE_T srows = src.rows; HLS_SIZE_T dcols = dst.cols; HLS_SIZE_T drows = dst.rows; loop_height : for (HLS_SIZE_T h = 0 ; h < srows; h++){ #pragma HLS LOOP_TRIPCOUNT min= 1 max= 1 loop_width : for (HLS_SIZE_T w = 0 ; w < scols; w++){ #pragma HLS LOOP_FLATTEN off #pragma HLS PIPELINE II= 1 #pragma HLS LOOP_TRIPCOUNT min= 1 max= 1 hls::Scalar<HLS_MAT_CN(SRC_T), HLS_TNAME(SRC_T)> s; src >> s; if ( (h% 2 == 0 ) && ( (w% 4 == 0 ) || ((w+ 1 )% 4 == 0 ) ) ){ dst << s; } } } } template < int SRC_T, int ROWS, int COLS, int DROWS, int DCOLS> void YUV2Full2Quarter_local(hls::Mat<ROWS, COLS, SRC_T>& src, hls::Mat<DROWS, DCOLS, SRC_T>& dst, bool flag) { if (flag){ YUV2Full2Quarter(src, dst); } else { mat_copy(src, dst); } } void F2Q(AXI_STREAM& input, AXI_STREAM& output, uint16_t height, uint16_t width, uint16_t rheight, uint16_t rwidth, bool flag) { #pragma HLS_INTERFACE axis register both port=input #pragma HLS_INTERFACE axis register both port=output #pragma HLS_INTERFACE s_axilite port=height #pragma HLS_INTERFACE s_axilite port=width #pragma HLS_INTERFACE s_axilite port=rheight #pragma HLS_INTERFACE s_axilite port=rwidth #pragma HLS_INTERFACE s_axilite port=flag #pragma HLS_INTERFACE s_axilite port= return RGB_IMAGE in_image(height, width); RGB_IMAGE out_image(rheight, rwidth); #pragma HLS dataflow hls::AXIvideo2Mat(input, in_image); YUV2Full2Quarter_local(in_image, out_image, flag); hls::Mat2AXIvideo(out_image, output); } F2Q.h #ifndef F2Q_H #define F2Q_H #define MAX_WIDTH 1920 #define MAX_HEIGHT 1080 #include "hls_stream.h" #include "ap_int.h" #include "hls_video.h" typedef hls::stream<ap_axiu< 24 , 1 , 1 , 1 > >AXI_STREAM; typedef hls::Mat<MAX_HEIGHT, MAX_WIDTH, HLS_8UC3> RGB_IMAGE; typedef hls::Scalar< 3 , unsigned char > RGB_PIXEL; void F2Q(AXI_STREAM& input, AXI_STREAM& output, uint16_t height, uint16_t width, uint16_t rheight, uint16_t rwidth, bool flag); #endif /* F2Q_H */ 関数のポイント Axi Streamについて 通常、Axi Streamはバッファを噛ませない限り要素をさかのぼってアクセスすることはできません。なので処理を書くときも要素をさかのぼるような記述をしてはいけません。 さかのぼるとは、例えばライン1とライン2の比較とかがそれに当たります。したければバッファ( LineBuffer や Window )にその要素を入れてやる必要があります。ここら辺は実際のハードを意識した書き方をしなければならないようです。 #pragma HLS dataflow この宣言によって、関数の終了を待たずに次の関数の処理に移れるようになります。イメージは下図を参照してください。 これはAxiStreamのようなデータフローの場合に最適な処理になります。自分のプログラムでは、streamの入力が来たらすぐにそのデータをフィルタに通し、通し終わったものはすぐにstream出力したいので、このpragmaを宣言しています。 この宣言には1つ注意が必要で、図から明らかなのですが処理は関数ごとに書かないと構文エラーになります。なので、 YUV2Full2Quarter_local() ではflagによって実行する関数を分けるだけの処理をさせています。例えばmainの関数の中に条件分岐は書いてはいけません。(#pragma dataflowの前での関数振り分けもできませんでした。) 詳しい説明はXilixの 解説ページ を見てください。 #pragma HLS LOOP_FLATTEN off これはonにすると、入れ子になっているループを平滑化して、内側のループ<->外側のループ間の移動に1クロックかかるのをかからなくしてくれるものなのですが、videoの処理ではoffにするようです。おそらくtlastをアサートするからだと思いますが、詳しくはわかっておりません。 詳しい説明は Xilinxのページ を見てください。 #pragma HLS pipeline これはループ内処理の終了を待たずに次の処理を行うようにさせ、レイテンシを改善するための宣言です。 詳しい説明は Xilinxのページ を見てください。 テストベンチ 先ほどの関数が思った通りの動作をしているかを確認するためのテストベンチになります。 画像サイズ8x6のデータを4x3にリサイズさせています。 元データのpixelデータは要素1に幅成分、要素2に高さ成分、要素3は要素1と要素2を足した値を格納しています。 return 0 で成功したとみなされるため、本当は期待値と比較して正しければ0を返すといったように書く方が望ましいですが、期待値を書くのが面倒だったため、printして期待値と同じかをチェックしています。 F2Q_tb.cpp #include <stdio.h> #include "../passthru.h" #define HEIGHT 6 #define WIDTH 8 #define RESIZE #ifdef RESIZE #define RHEIGHT HEIGHT/ 2 #define RWIDTH WIDTH/ 2 #define FLAG 1 #else #define RHEIGHT HEIGHT #define RWIDTH WIDTH #define FLAG 0 #endif int main( int argc, char ** argv) { AXI_STREAM input; AXI_STREAM output; RGB_IMAGE in_image(HEIGHT, WIDTH); RGB_IMAGE out_image(RHEIGHT, RWIDTH); for ( int h = 0 ; h < HEIGHT; h++) { for ( int w = 0 ; w < WIDTH; w++){ RGB_PIXEL pixel; pixel.val[ 0 ] = w; pixel.val[ 1 ] = h; pixel.val[ 2 ] = w+h; in_image << pixel; printf( "input: %d , %d , %d\n " , pixel.val[ 0 ], pixel.val[ 1 ], pixel.val[ 2 ]); } } hls::Mat2AXIvideo(in_image, input); F2Q(input, output, HEIGHT, WIDTH, RHEIGHT, RWIDTH, FLAG); hls::AXIvideo2Mat(output, out_image); for ( int h = 0 ; h < RHEIGHT; h++) { for ( int w = 0 ; w < RWIDTH; w++){ RGB_PIXEL pixel; out_image >> pixel; printf( "output: %d , %d , %d\n " , pixel.val[ 0 ], pixel.val[ 1 ], pixel.val[ 2 ]); } } return 0 ; } こちらを実行した結果になります。幅、高さ共に狙った間引きになっているのが確認できます。 input:0, 0, 0 input:1, 0, 1 input:2, 0, 2 input:3, 0, 3 input:4, 0, 4 input:5, 0, 5 input:6, 0, 6 input:7, 0, 7 input:0, 1, 1 input:1, 1, 2 input:2, 1, 3 input:3, 1, 4 input:4, 1, 5 input:5, 1, 6 input:6, 1, 7 input:7, 1, 8 input:0, 2, 2 input:1, 2, 3 input:2, 2, 4 input:3, 2, 5 input:4, 2, 6 input:5, 2, 7 input:6, 2, 8 input:7, 2, 9 input:0, 3, 3 input:1, 3, 4 input:2, 3, 5 input:3, 3, 6 input:4, 3, 7 input:5, 3, 8 input:6, 3, 9 input:7, 3, 10 input:0, 4, 4 input:1, 4, 5 input:2, 4, 6 input:3, 4, 7 input:4, 4, 8 input:5, 4, 9 input:6, 4, 10 input:7, 4, 11 input:0, 5, 5 input:1, 5, 6 input:2, 5, 7 input:3, 5, 8 input:4, 5, 9 input:5, 5, 10 input:6, 5, 11 input:7, 5, 12 output:0, 0, 0 output:3, 0, 3 output:4, 0, 4 output:7, 0, 7 output:0, 2, 2 output:3, 2, 5 output:4, 2, 6 output:7, 2, 9 output:0, 4, 4 output:3, 4, 7 output:4, 4, 8 output:7, 4, 11 こちらの結果は波形でも確認できます。stream のoutができていることが確認できました。 まとめ Axi Streamで流れてくる画像のサイズを変更する機能を高位合成で作成した話をまとめました。今回は省いてしまいましたが、高位合成で作成したIPをMicroblazeから制御して画像サイズの変更やresizeするしないなども制御できます。 RTL設計経験がない私でも、Microblazeから制御するのを含めて実働1日程度で実装することができましたので、RTLで書くよりかは早く実装できたのではないかな?と思います。 今回は単純な間引きだけでしたが、バッファを使ってもう少し高度なこともできるので時間があればそのこともまとめてみたいと思います。
aptpodでは、複数のCANバスのデータを時刻同期して取得できる Synchronized CAN Transceiver に続き、このデバイスと時刻同期し、かつ複数のカメラ映像をフレーム単位で同期させて録画できるカメラデバイスの開発を行なっています。 このデバイスには、TI社製のAM5728というSoC(System on a Chip)の採用を検討しています。このSoCには様々なコアが搭載されており、その中でもマルチメディア処理を担当するIVA(ハードウェアアクセラレータ)は1080p60の映像を処理できる優れものです。 aptpod Advent Calendar 2019 の12日目は、このSoCを使って1920x1080 30fpsのH.264エンコードをしてみた話をハードウェアエンジニアの塩出がお届けします。 AM5728について AM5728はTI社が提供している Sitara™ プロセッサシリーズ の1つです。Sitara™ プロセッサは産業用途向けに開発されたチップで、AM5728はマルチメディア処理用のハードウェアアクセラレータが乗ったシリーズになります。 AM5728の搭載機能を把握するには、以下のブロック図を見るのが一番早いと思います。 特徴として以下のようにかなり機能モリモリなSoCです。 Cortex-A15 Cortex-M4が2つ DSP 産業通信用にRPUも使える GPU搭載 ペリフェラルも充実 映像出力にも強い IVAで1080p60の映像処理性能 ビデオ信号を受けるVIP(Video Input Port)が複数あり,複数の映像入力に対応 VPE(Video Processing Engine)と呼ばれる色空間変換 などなど AM5728商品ページより引用 今回使用したボード 機能評価を行うにあたり、TI社が提供している AM572x 評価モジュール とこれに接続できる カメラモジュール を使用しました。合わせて8万円程度でした。このパッケージにlinuxイメージが入ったSDカードが同封されており、開封後すぐに機能評価を始められます。 カメラモジュールはリビジョンによって使用されているイメージセンサが異なり、リビジョンA1は MT9T11 、リビジョンC1は OV10635CSP となっています。今回手元に届いたのは MT9T11 でした。 このカメラモジュールでは解像度によって30fpsまでの出力が可能です。データシートは こちら から参照ください。 TI社AM572x 評価モジュールの商品ページより どうやってIVAを使うのか? TIの wikiページ に説明があります。ここではかいつまんで説明したいと思います。 前提 SMPモード動作しているA15コアでlinuxが動いています。また、2つあるM4の片方は IPUMM と呼ばれるTI社が提供しているマルチメディア処理用のファームウェアが動いています。このファームウェアはTI社製のRTOSをベースとしています。linuxとRTOS間の通信は remotoproc と RPMsg を使用しています。 IVAにはTI社製のコーデックが乗っているようですが、ソースコードは公開されていないので詳細はわかりません。 IVAとM4間の通信は Codec Engine というTI社が提供しているRTOSパッケージを介して行なっています。 Codec Engine はXDAIS準拠のアルゴリズムを実行するためのAPI群で、おそらくIVAに乗っているコーデックがXDAIS準拠のアルゴリズムになっていると思われます。そのコーデックアルゴリズムを Codec Engine を通じて実行しているものと考えられます。XDAISに関しては詳しく調査しておりませんので詳細は触れません。 下の図は先ほどの wikiページから抜粋 した、マルチメディア処理のソフトウェアスタック説明図です。青色がTI社が提供しているソフトウェアですが、色分けする意味がほとんどないくらいほぼ青色です。マルチメディア処理のフレームワークには,Gstreamerが採用されています。 流れ まずGstreamerのプラグインである、 GST-Ducati Plugin で上流から画像データを受け取る この画像を処理するためのエンコード指示が発行される この時に、inputする画像のメモリとエンコード後のデータ格納先メモリも一緒に指示する この指示は libdce を使用してRPMsg経由で,M4へ指示を送る M4はlinuxから送られてきたエンコード指示を受け取り、 Codec Engine に登録されているIVAのエンコードアルゴリズムを呼ぶ IVAがエンコードを開始する エンコードが終了したらlinuxの処理へ戻り、あらかじめ指示したエンコード後のデータ格納先を参照する Gstreamerなので、エンコードされたデータを下流のエレメントにpushする 1へ戻る Gstreamerのパイプラインの例 Gstreamerのプラグインを使えばIVAを使えるということがわかりましたので,1920x1080 30fpsのH.264エンコードをとりあえず動かしてみます。 GST_DEBUG =ducati*:LOG gst-launch-1. 0 -e \ v4l2src device =/dev/video1 num-buffers = 300 io-mode = 4 ! \ ' video/x-raw,format=(string)YUY2,width=(int)1280,height=(int)720,framerate=(fraction)30/1 ' ! \ queue ! vpe num-input-buffers = 8 ! ' video/x-raw,format=(string)NV12,width=(int)1920,height=(int)1080 ' ! queue! \ ducatih264enc level =level-51 profile =high intra-interval = 10 inter-interval = 5 ! queue ! h264parse ! filesink location = test .h264 エンコードされたデータは ffplay で再生できますが、am5728には入っていなかったのでffplayが使えるPCにコピーして再生します。 $ ffplay test .h264 実行結果は以下のような感じで、とりあえずのエンコードはできました。 VPEで1280x720 -> 1920x1080に変換しているので引き伸ばされている感があります。 コマンドの説明 v4l2src まずはカメラモジュールからデータを取得するために、v4l2srcを使っています。 /dev/video1 はこのカメラモジュールのことを直接意味してる訳ではなく、 VIP と呼ばれるパラレルなビデオ信号を受け取るためのペリフェラルのディスクリプタです。このVIPは結構優れもので、色空間変換やスケーリングなども行えます。 video/x-rawで指定しているフォーマット、サイズがカメラモジュール側で対応していなければ、それに合うようにカメラモジュールからの画像を変換した状態で受け取ってくれます。(もちろん限界がありますが) 重要なのは io-mode です。ここでは4を指定しています。この4はdmabufを意味しています。これにより、VIPで受け取った画像データをユーザ空間のメモリにコピーすることなく下流にpushするようにしてくれます。 vpe vpe はM2Mデバイスで、メモリにある画像データを色空間変換、スケーリングしてくれるペリフェラルです。ここでは、YUY2からNV12へのフォーマット変換とサイズの変換を行なっています。 なお、フォーマット変換をしているのは、IVAのエンコーダがNV12のみに対応しているからです。 ducatih264enc これがH.264エンコードするプラグインです。profileやlevel、IDRフレームの間隔などを指示しています。1920x1080のフルHD画質をエンコードするために、profileはhigh、levelは51を指定しています。 ここら辺はH.264のフォーマットの話なので、詳しいことは割愛します。 下流は filesink でH.264データをそのまま保存するという形です。 本当に1920x1080 60fpsを処理できるのか? 上記のコードを実行すると以下のようなログが得られ、エンコード時間がわかります。 60fpsだとすると16.6msec以内にエンコードが終わっていないといけないことになりますが、1920x1080のエンコード時間はだいたい14~15msec程度で,1フレーム以内に処理が終わっており、1920x1080 60fpsの処理を達成できると思われます。 0:00:00. 646824120 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 661454910 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 14541811ns ( 14 ms ) : 13 bytes 0:00:00. 661699724 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb4f0ba10 0:00:00. 669780062 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 684446964 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 14629488ns ( 14 ms ) : 13 bytes 0:00:00. 684697797 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb5b1bef8 0:00:00. 702904178 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 718422318 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 15482353ns ( 15 ms ) : 13 bytes 0:00:00. 718666319 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb4f0b830 0:00:00. 723180013 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 738440976 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 15225990ns ( 15 ms ) : 13 bytes 0:00:00. 738673591 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb4f0b8d0 0:00:00. 746872188 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 761427988 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 14521315ns ( 14 ms ) : 13 bytes 0:00:00. 761634738 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb4f0b970 0:00:00. 783937083 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 798444083 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 14469098ns ( 14 ms ) : 41589 bytes 0:00:00. 799538671 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb4f0b8d0 0:00:00. 807385094 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 821465092 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 14044862ns ( 14 ms ) : 21440 bytes 0:00:00. 822195631 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb5b1be58 0:00:00. 830828876 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 845438681 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 14575971ns ( 14 ms ) : 13287 bytes 0:00:00. 845915134 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb4f0ba10 0:00:00. 864899390 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 879473246 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 14534166ns ( 14 ms ) : 27112 bytes 0:00:00. 880307242 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb5b1bef8 0:00:00. 888348052 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 902456842 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 14073979ns ( 14 ms ) : 27080 bytes 0:00:00. 903251797 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb4f0b830 0:00:00. 921819950 1532 0x168690 DEBUG ducati gstducatividenc.c:928:gst_ducati_videnc_handle_frame: Calling VIDENC2_process 0:00:00. 936442281 1532 0x168690 DEBUG ducati gstducatividenc.c:934:gst_ducati_videnc_handle_frame: < ducatih264enc 0> VIDENC2_process took 14588171ns ( 14 ms ) : 14010 bytes 0:00:00. 936930121 1532 0x168690 LOG ducati gstducatividenc.c:1006:gst_ducati_videnc_handle_frame: < ducatih264enc 0> free buffer: 0xb5b1bef8 終わりに TI社のAM5728というSoCを使って1920x1080 30fpsの映像を H.264エンコードをしてみました。使用したカメラモジュールが30fpsしか出ないものでしたが、エンコード時間から60fpsでも問題なくエンコードできそうなことがわかりました。 今後はこのSoCを使って取得したデータを弊社フレームワークの intdash で扱えるようにしたり、並行して製作しているFPGAを搭載したカメラモジュール側と結合したりしていきます。 今回はエンコードしてみたというゆるい内容になってしまいましたが、メモリの動きやTI製のRTOSの使い方、H.264エンコードにメタデータを含める方法、M4とA15の通信など書けそうなことは多々あるので、時間を見つけて書いていきたいと思います。 読んでいただきありがとうございました。 aptpod Advent Calendar 2019 の12日目担当の塩出でした。