AGL の開発に今年はちょっとだけ関われました

昨日は @helicalgear さんの 「[SailfishOS向けアプリの開発](https://qiita.com/helicalgear/items/e059713e1849fd020709)」という記事でした。今日はマトモな記事を書こうと思っていたんですが、気が変わったので適当に書きます。

# はじめに

[Qt Advent Calendar 2016](https://qiita.com/advent-calendar/2016/qt) の初日に「[Automotive Grade Linux のデモを一緒に開発しませんか？](https://qiita.com/task_jp/items/51efe05374ed05dcd001)」という記事を書いて早２年、今年も [CES2019](https://www.ces.tech/) に向けたデモ開発の真っ最中です。

<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Group photo from this week&#39;s <a href="https://twitter.com/hashtag/AutoLinux?src=hash&amp;ref_src=twsrc%5Etfw">#AutoLinux</a> integration session at the NTT DATA MSE office in Yokohama. Next <a href="https://twitter.com/autogradelinux?ref_src=twsrc%5Etfw">@autogradelinux</a> F2F will be <a href="https://twitter.com/MicrochipTech?ref_src=twsrc%5Etfw">@MicrochipTech</a> office in Karlsruhe starting Nov 27. Details here <a href="https://t.co/xwcezCHuZY">https://t.co/xwcezCHuZY</a> <a href="https://t.co/va8FWTuA3P">pic.twitter.com/va8FWTuA3P</a></p>&mdash; Walt Miner (@VStarWalt) <a href="https://twitter.com/VStarWalt/status/1060523957395296258?ref_src=twsrc%5Etfw">November 8, 2018</a></blockquote>

そんな AGL が開発してきた[Unified Code Base (UCB)](https://www.automotivelinux.org/software/unified-code-base) と呼ばれるディストリビューションが「[CES 2019 Innovation Awards Honoree](https://www.automotivelinux.org/announcements/2018/11/09/automotive-grade-linux-selected-for-ces-2019-innovation-awards-honoree)」というのを受賞したそうで、なんかすごい気がしている今日この頃です。

The Qt Company で働いていた時は、会社として AGL には力を入れていなかったため、仕事としてはほとんど AGL の開発に関わることができませんでした。先月めでたく退職し、今後は AGL の開発を仕事としてもできそうな雰囲気になってきているので、来年は少しやる気を出して貢献してみようと思っています。

今回は AGL の開発における Qt に関する内容を徒然なるままに書きたいと思います。

# AGL の公式デモ

AGL の**公式デモ**は、これまでは１つのセットしかなく、

- 基本的にはほとんどの UI が Qt(QML) で書かれている
- 地図・ナビアプリは Qt じゃなかった
- クラスタメーターのデモはクローズドソースの3Dエンジンで書かれているらしい

という状況でした。

<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Meet the <a href="https://twitter.com/hashtag/autolinux?src=hash&amp;ref_src=twsrc%5Etfw">#autolinux</a> team at <a href="https://twitter.com/hashtag/ew18?src=hash&amp;ref_src=twsrc%5Etfw">#ew18</a> booth 4-171 <a href="https://twitter.com/autogradelinux?ref_src=twsrc%5Etfw">@autogradelinux</a> <a href="https://twitter.com/embedded_world?ref_src=twsrc%5Etfw">@embedded_world</a> <a href="https://t.co/qCcENWLYPy">pic.twitter.com/qCcENWLYPy</a></p>&mdash; Walt Miner (@VStarWalt) <a href="https://twitter.com/VStarWalt/status/968481593680715776?ref_src=twsrc%5Etfw">February 27, 2018</a></blockquote>

CES２０１９では公式のデモが以下の３パターン用意されるそうです。

1. CES2018 を踏襲したもの
2. Alexa を組み込んだもの
3. Toyota 社が主導して開発するもの

このうち、３.では地図アプリが [Qt で書き換えられて](https://gerrit.automotivelinux.org/gerrit/gitweb?p=apps/navigation.git;a=tree;h=refs/heads/sandbox/zheng_wenlong/ces2019_vertical;hb=refs/heads/sandbox/zheng_wenlong/ces2019_vertical) います。これがメインストリーム側にマージされると（されて欲しい） IVI 側で動く公式のアプリがすべて Qt ベースになるわけで、AGL としていいかどうかはともかくとして個人的には快挙なんですよね。

# これ Qt のバグだからすずきさん、直してよ

その地図アプリは、[Qt Location](https://doc.qt.io/qt-5/qtlocation-index.html) の [Map Viewer](https://doc.qt.io/qt-5/qtlocation-mapviewer-example.html) というサンプルを元に作られています。そして Qt 関係で何か問題があった場合、AGL ではだいたい私のせいになります。~~自分で直したらいいのに~~

## 地図のローテーションの問題

AGL の実機環境(R-Car M3 + Yocto + Weston + 独自ウィンドウマネージャー) でそのナビアプリを動かした際に、2本指でローテーションをすると、動作がちらつく（一瞬地図が反転する）という現象があり、「これ Qt のバグだから直してよ」と言われました。 ということで、普段の生活ではまったく使う機会に恵まれないであろう、 Qt Location との戦いが始まりました。

### 現象の再現

はじめに、AGL 界隈でよく使われている [OnLap 1303I](https://www.gechic.com/jp-portable-touch-monitor-onlap1303i-overview.html) は、タッチパネルの表面に埃がついたりすると動作がおかしくなったりするため、「とりあえず画面を綺麗にしてみよう」というところからデバッグがはじまりました。

次に、基本的な動作確認として、[QtQuick のマルチタッチのデモ](http://doc.qt.io/qt-5/qtquick-touchinteraction-example.html#multipoint-flames-example)を動かしてみました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/13867/fa49569c-c59d-3fee-dc5c-0370dbd2f02a.png)

実際はこれをAGLアプリ化したもので確認しました。このデモは [MultiPointTouchArea](http://doc.qt.io/qt-5/qml-qtquick-multipointtoucharea.html) エレメントを使ったもので、AGL 上で問題なく動作しました。

それから、Map Viewer 自体を AGL アプリ化し、動作確認をしたところ、現象が再現できました。Qt Location でマルチタッチを用いたジェスチャー操作を行う際には、[MapGestureArea](https://doc-snapshots.qt.io/qt5-5.9/qml-qtlocation-mapgesturearea.html) というエレメントを使うのですが、その中のマルチタッチ周りに何か問題がありそうということがわかりました。

### 問題を確認

「ローテーション時に一瞬１８０度地図が回転するように見える」という状況から、ジェスチャーを生成する際にマルチタッチの id が入れ代わってるか、順番が変わってるかなんかそんな感じだろうなーという気がしていたので、[MapGestureArea のローテーションのロジック](http://code.qt.io/cgit/qt/qtlocation.git/tree/src/location/declarativemaps/qquickgeomapgesturearea.cpp?h=5.9#n1428)を確認しました。

```cpp:qquickgeomapgesturearea.cpp
void QQuickGeoMapGestureArea::updateRotation()
{
    // Calculate the new bearing
    qreal angle = angleDelta(m_pinch.m_rotation.m_previousTouchAngle, m_twoTouchAngle);
    if (qAbs(angle) < 0.2) // avoiding too many updates
        return;

    m_pinch.m_rotation.m_previousTouchAngle = m_twoTouchAngle;
    m_pinch.m_rotation.m_totalAngle += angle;
    qreal newBearing = m_pinch.m_rotation.m_startBearing - m_pinch.m_rotation.m_totalAngle;
    m_declarativeMap->setBearing(newBearing);

    m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid));
    m_pinch.m_event.setAngle(m_twoTouchAngle);
    m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos());
    m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos());
    m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1);
    m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2);
    m_pinch.m_event.setPointCount(m_allPoints.count());
    m_pinch.m_event.setAccepted(true);

    emit rotationUpdated(&m_pinch.m_event);
}
```

ここで、角度は `m_twoTouchAngle` という変数になっていて、以下の箇所で計算されています。

```cpp
void QQuickGeoMapGestureArea::updateTwoTouchPoints()
{
    QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos());
    QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos());
    m_distanceBetweenTouchPoints = distanceBetweenTouchPoints(p1, p2);
    m_touchPointsCentroid = (p1 + p2) / 2;
    updateFlickParameters(m_touchPointsCentroid);

    m_twoTouchAngle = touchAngle(p1, p2);
}
```

ここで、 `m_allPoints` を確認したところ、**AGL の環境では m_allPoints タッチ位置の順序が入れ替わることがある** ということがわかりました。

### 修正

同じソースファイルの中で `m_allPoints` の内容を更新しているコードの直後でソートをするパッチを書きました。

```diff
void QQuickGeoMapGestureArea::update()
{
    if (!m_map)
        return;
    // First state machine is for the number of touch points

    //combine touch with mouse event
    m_allPoints.clear();
    m_allPoints << m_touchPoints;
    if (m_allPoints.isEmpty() && !m_mousePoint.isNull())
        m_allPoints << *m_mousePoint.data();
+    std::sort(m_allPoints.begin(), m_allPoints.end(), [](const QTouchEvent::TouchPoint &tp1, const QTouchEvent::TouchPoint &tp2) { return tp1.id() < tp2.id(); });
    touchPointStateMachine();
    ...
}
```

このパッチはタイミング的な問題で [5.11 ブランチにマージされ](https://codereview.qt-project.org/244889)、5.12 でも対応される予定です。
5.9 へのバックポートも可能ではありますが、あまりモチベーションがないためどうなるか未定です。

## ルート案内で、Uターンが左方向にUターンになる問題

AGL の JIRA に「[Is there any method to know the Country which is driving on left or right?(QtLocation)](https://jira.automotivelinux.org/browse/SPEC-1916)」というチケットが作られ、アサインされてしまいましたので、~~しょうがないので~~ 対応を考えました。

### 状況の整理

Qt Location には、[ルート案内の機能](https://doc.qt.io/qt-5/location-maps-qml.html#routing-and-navigation) もあり、実装は [OSRM - OpenSource Routing Machine](http://project-osrm.org/) の [Rest API](http://project-osrm.org/docs/v5.15.2/api/) を叩く形になっていました。

で、OSRM 的には ["uturn"は一種類しかない](http://project-osrm.org/docs/v5.15.2/api/#stepmaneuver-object) のですが、Qt 的には　[区別がある](http://code.qt.io/cgit/qt/qtlocation.git/tree/src/location/maps/qgeomaneuver.h?h=5.9#n54) ため、不都合が起こっていました。

```cpp:qgeomaneuver.h
class Q_LOCATION_EXPORT QGeoManeuver
{

public:
    enum InstructionDirection {
        NoDirection,
        DirectionForward,
        DirectionBearRight,
        DirectionLightRight,
        DirectionRight,
        DirectionHardRight,
        DirectionUTurnRight,  // 右方向にUターン
        DirectionUTurnLeft,   // 左方向にUターン
        DirectionHardLeft,
        DirectionLeft,
        DirectionLightLeft,
        DirectionBearLeft
    };
    ....
};
````

で、OSRM の "uturn" をどう割り当てるかというのが問題なのですが、AGL で利用している Qt 5.9 では [`DirectionUTurnRight` 決めうち](http://code.qt.io/cgit/qt/qtlocation.git/tree/src/location/maps/qgeorouteparserosrmv5.cpp?h=5.9#n768) になっていました。

```cpp
    else if (modifier == QLatin1String("uturn"))
        return QGeoManeuver::DirectionUTurnRight;
```

一方、最新の Qt では、これが [`DirectionUTurnLeft` 決めうち](http://code.qt.io/cgit/qt/qtlocation.git/tree/src/location/maps/qgeorouteparserosrmv5.cpp?h=5.11#n788) になっています。

```cpp
    else if (modifier == QLatin1String("uturn"))
        return QGeoManeuver::DirectionUTurnLeft; // This should rather be country-specific. In UK, f.ex. one should rather UTurn Right
```

この変更は「[Return UTurnLeft instead of Right in osrmV5](https://codereview.qt-project.org/212835)」で行われました。
> **Return UTurnLeft instead of Right in osrmV5**
>
> Since Right Hand Traffic is in use in most countries, it makes sense to
return UTurnLeft instead of Right.
This should be possibly made Locale-specific, in the future.

で、この情報を利用してナビアプリはUターンの案内を表示する際に、
![左方向にUターン](https://gerrit.automotivelinux.org/gerrit/gitweb?p=apps/navigation.git;a=blob_plain;f=app/images/9_7_uturn_left.png;hb=refs/heads/sandbox/zheng_wenlong/ces2019_vertical) と ![右方向にUターン](https://gerrit.automotivelinux.org/gerrit/gitweb?p=apps/navigation.git;a=blob_plain;f=app/images/1_uturn.png;h=4414925a6a631a7a0f74100f7bd0916fedb2e2d6;hb=refs/heads/sandbox/zheng_wenlong/ces2019_vertical) を区別したいということです。

### 対応を考える

とりあえず、「[QTBUG-72462: Support U-Turn direction in QtLocation](https://bugreports.qt.io/browse/QTBUG-72462)」というチケットを作りました。

ナビの担当者からは、「右側通行、左側通行は**国によって決まっている**から、国を設定したらUターンの方向を切り替えるようにできませんか？」と言われました。

- 本当は、ルート検索側の機能として持つべきなんだけどね
- 国に寄るとしたら、[QLocale](http://doc.qt.io/qt-5/qlocale.html) あたりに設定を持つべき？
- 「国によって決まる」はだいたい正しそうだけど、例外とかありうるよね
- 結局、下回りで気を利かせてなんとかするのは難しそうだから、アプリ側で制御できるようにしよう

みたいなことを考え、[PluginParameter](https://doc.qt.io/qt-5/qml-qtlocation-pluginparameter.html) で右側通行か左側通行かを設定する（ことで自動的にUターンの方向が決まる)ようにしてみました。

```qml
RouteModel {
    id: routeModel
    plugin : Plugin {
        name: "mapbox"
        PluginParameter { name: "mapbox.access_token";
            value: fileOperation.getMapAccessToken()
        }
        // 日本の場合は左側通行を設定して、Uターンは右方向にする事になる
        PluginParameter { name: 'mapbox.routing.traffic_side'; value: 'left' }
    }
    ...
}
```

### 対応

パッチは「[Introduce a map plugin parameter to decide U-Turn direction](https://codereview.qt-project.org/247835)」になります。

結構やっつけで作ったので（そしてビルドだけ確認をして一度もテストをしていない）、どのくらいダメだしされるかな？と思っていたのですが、細かい指摘だけであっさり Codereview +2 がいただけてしまいました。

本当はドキュメントとか書いたほうがいいんだろうなー。と思うんですけど、多分このまま 5.12 にマージされると思います。

## 地図の更新が１０秒に１回なのに、QtWayland が 60fps で描画をしようとしている件

### (要調査)

## Wayland 関係で Qt のイベントループが固まる件

「[Wayland event dispatcher multi-threading support](https://bugreports.qt.io/browse/QTBUG-66075)」 というバグレポを超頑張って書いたはいいものの、問題が私の手には負えないので、誰かなんとかして欲しいなーと思いつつ、それを実現するための良い方法が思い浮かんでいません。可能性は低いけれど、The Qt Company が AGL をやる気になった暁には最優先で直してもらおう。

# おわりに

ここ２日間、AGL のデモ開発ワークショップに参加して頑張った件についてのご報告でした。

Qt のバグを見つけた際には、

1. 少なくともバグレポは書いてね
2. できれば自分で直してみよう

直す場合に、最初から完璧なパッチを送る必要はまったくないです。
バグレポを書くだけだとなかなか直す気が起きないエンジニアも、なんでもいいからパッチを書くと、コードを見て、問題点を把握してくれて、やる気が起きる可能性が高まるというメリットがあるので、皆さんもぜひ挑戦してみてください。

ということを、AGL の開発コミュニティの中でも声を大にして言いたい。

明日は、@hermit4 さんによる「QtでBluetooth(基礎編）」です。お楽しみにー。

<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>