Garminウォッチ用のデータフィールドを作成する(その2)

前回は、ガーミンウォッチ用のデータフィールドの作成をVisual Studio Codeを使ってプログラムするところまでやりました。
alasixosaka.hatenablog.com
今回は、出来たデータフィールドをウォッチに転送して使えるようにします。
まず、ウォッチフェイスの時と同様に、プロジェクトのフォルダを開いて、binフォルダ内にあるxxxx.prgというファイルを探します(xxxxはプロジェクト名)。
ウォッチをPCに接続して、Appフォルダに先ほどのファイルをコピーします。
PCでの操作はここまで。ウォッチをPCから外して、あとはウォッチ側での操作です。
ウォッチフェイスの場合はここでウォッチフェイスを選択すれば表示されたのですが、データフィールドの場合はもうひと手間必要です。
ウォッチで、Startボタンを押して、データフィールドを追加したいアクティビティを選択します。自分の場合はトレイルランニングに追加したいので、トレイルランニングを選択。Upボタンを長押ししてトレイルラン設定を選択、トレーニングページを選択、そうするとデータフィールドが現れるので、Upボタンを押して追加のページ(画面の中央に緑の+が表示される)を表示させてStartボタンで選択。カスタムデータを選択し、レイアウト選択画面で1項目を選択する。ここで、2項目以上を選択してしまうと、前回シミュレータで表示させたように全項目同じ表示になってしまうので注意。
ここで、一番上に「ConnectIQ 1/xを追加しました」というような表示あるので、これを選択。そうすると、リストにさきほどコピーしたxxxx(プロジェクト名)が表示されるのでこれを選択するとそのアクティビティに自作のデータフィールドが追加される。別のアクティビティ(例えばラン)に追加する場合は同じ操作をランのアクティビティ設定で行う。
追加したら、データフィールドをどの順番に配置するか選択する画面が出てくるので好きなところに配置する。デフォルトだと一番最後に配置される。

別のデータフィールドを作ってみる

もう少し別のデータフィールドも作ってみます。
実は、やりたいことがあったのですが、どうもうまくいかないようです。それは、

distanceToNextPoint、distanceToDestinationは使えない

ということのようです。上の2つのコマンドはAPIのコマンドリストに掲載されていて、ナビゲーション中に次のポイントや目的地(ゴール)までの距離を取得する関数のようです。ところが、FenixやForerunner955などの地図を表示できるデバイスは対応デバイスになっているのですが、Forerunner255は対応デバイスのリストに載っていません。ただ、Instinct2などの地図を表示できないデバイスもリストにあるのでひょっとしたら使えるのではないかと思って試してみました。
結論から言うとForerunner255では使えないようです。Instinct2で使えて、Forerunner255で使えない理由は不明ですが、あると便利な機能なのでちょっとショックでした。一応ナビゲーション中に地図の画面(といっても地形や道路は表示されないですが)からDownボタンでその下の画面に行くとGarminConnectで設定したポイントで次のポイントまでの距離やゴールまでの距離が表示されます。地図画面でもゴールまでの距離は表示されます。
ただ、残り距離を現在時刻やトータルタイムと一緒に表示できればいいなと思ったのですが、どうもForerunner255では無理なようです。
デフォルトの設定でも表示される情報なので、時計の機能自体に対応できないという問題があるわけではなさそうですが、どうもConnectIQのAPIが対応していないようです。
APIにはnameOfNextPointというコマンドもあるので、nameOfNextPointとdistanceToNextPointを表示させるデータフィールドを作ってみました。ナビゲーションを実行しないと機能しない(コースとコース上のポイントを読み込まないと距離の計算ができないはず)なので、実機で、自宅の周りのコースを適当にGarminConnectで作ってウォッチに転送して動かしてみましたが、表示はnameもdistanceもNullとなってデータが表示されませんでした。
ちなみに、ConnectIQストアで次のポイントまでの距離や名前を表示するデータフィールドがあり、対応デバイスにForerunner255が載っていたので、これも試してみましたが、なんか全然変な距離と名前が表示されるだけで全く機能しませんでした。ということで、この機能を実装することは諦めました。

ペースを表示させる

仕方がないので、ペースを表示させるデータフィールドを作ってみました。
表示させるデータは、平均のペースと現在のペース、それに距離と経過時間の4つとしました。データフィールドの区切りは、一番上が距離、真ん中を2つに区切って左が平均ペース、右が現在のペース、一番下が経過時間としました。

ペースを表示させるデータフィールド

全部白黒なのも味気ないので色を付けてみました。
ペースを直接取得する関数はないので、現在のスピードと平均スピードから計算します。現在のスピードはinfo.currentSpeed、平均スピードはinfo.averageSpeedで取得することができます。どちらも単位はm/sとなっています。ですので、この値からペース(分/km)を計算するには、次の式を実行すればよいことになります。
ペース=1000/60/スピード

プログラム

プログラムのソースです。前回のデータフィールドと基本は大きく変わらないので変わったところだけ説明します。

変数
    hidden var mValue as Numeric;
    hidden var valueFormat = "%d";	
    hidden var label = "距離";  // intial value for the label
    hidden var label2 = "平均ペース";
    hidden var label3 = "タイム";
    hidden var label4 = "ペース";
    hidden var clockTime;
    hidden var eTime;
    hidden const ZERO_TIME = "0:00";
    hidden const ZERO_TIME2 = "00";
    hidden var distanceUnits = System.UNIT_METRIC;
    hidden var distance;
    hidden const ZERO_DISTANCE = "0.0";
    hidden var kmOrMileInMeters = 1000;
    hidden var ave;
    hidden var pace;

変数です。label1からlabel4まではデータフィールドの表示するラベルでそれぞれ、距離、平均ペース、タイム、ペースとなっています。あとは、最後の2つaveとpaceを追加しています。それぞれ平均速度、現在の速度を格納する変数です。

onCompute

    function compute(info as Activity.Info) as Void {
        // See Activity.Info in the documentation for available information.
        //clockTime = System.getClockTime();
        //eTime = info.elapsedTime;
        eTime = info.timerTime;
        distance = info.elapsedDistance;
        ave = info.averageSpeed;
        pace = info.currentSpeed;
    }

onComputeも最後の2行が変わっています。aveに平均速度、paceに現在の速度を格納しています。

onUpdate
    function onUpdate(dc as Dc) as Void {
        var width = dc.getWidth();
        var height = dc.getHeight();
        var textCenter = Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER;
        var backgroundColor = getBackgroundColor();
        // set background color
        dc.setColor(backgroundColor, Graphics.COLOR_TRANSPARENT);
        dc.fillRectangle (0, 0, width, height);
        // set foreground color
        dc.setColor(Graphics.COLOR_DK_BLUE, Graphics.COLOR_TRANSPARENT);
        dc.drawLine(0,85,width,85);
        dc.drawLine(0,165,width,165);
        dc.drawLine(width/2, 85, width/2, 165);
        
        dc.setColor((backgroundColor == Graphics.COLOR_BLACK) ? Graphics.COLOR_WHITE : Graphics.COLOR_BLACK, Graphics.COLOR_TRANSPARENT);
        
        
        
        //var timeString = Lang.format("$1$:$2$:$3$", [clockTime.hour, clockTime.min.format("%02d"), clockTime.sec.format("%02d")]);
        //var timeString2 = Lang.format("$1$:$2$:$3$", [eTime.hour, eTime.min.format("%02d"), eTime.sec.format("%02d")]);
        var duration;
        var duration2;
        // do layout
        if (isSingleFieldLayout()) {
            dc.drawText(width/2, 30, Graphics.FONT_TINY, label, textCenter);
            dc.drawText(width/2, 180, Graphics.FONT_TINY, label3, textCenter);
            //dc.drawText(width/2, 210, Graphics.FONT_NUMBER_MILD, timeString, textCenter);
            dc.drawText(65, 100, Graphics.FONT_TINY, label2, textCenter);
            dc.drawText(190, 100, Graphics.FONT_TINY, label4, textCenter);
            dc.drawText(190, 70, Graphics.FONT_TINY, distanceUnits == System.UNIT_METRIC ? "km" : "mi", textCenter);
            var distStr;
            var aveStr;
            var paceStr;
            System.println(distance);
            if (distance == null) {
                distStr = ZERO_DISTANCE;
                }
            else {
                var distanceKmOrMiles = distance / kmOrMileInMeters;
                distStr = distanceKmOrMiles.format("%.1f");
            }
            dc.drawText(width/2, 60, Graphics.FONT_NUMBER_MEDIUM, distStr, textCenter);
            if (eTime != null && eTime > 0) {
                var hours = null;
                var minutes = eTime / 1000 / 60;
                var seconds = eTime / 1000 % 60;
                
                if (minutes >= 60) {
                    hours = minutes / 60;
                    minutes = minutes % 60;
                }
            
                if (hours == null) {
                    duration = "0:"+ minutes.format("%02d");
                } else {
                    duration = hours.format("%d") + ":" + minutes.format("%02d");
                }
                duration2 = seconds.format("%02d");
            } else {
                duration = ZERO_TIME;
                duration2 = ZERO_TIME2;
            } 
            dc.drawText(110, 210, Graphics.FONT_NUMBER_HOT, duration, textCenter);
            dc.drawText(190, 215, Graphics.FONT_NUMBER_MILD, duration2, textCenter);
            if (ave==null){
                aveStr = ZERO_DISTANCE;
            }else{
                //var aveSpeed = ave*60/1000;
                var aveSpeed = 1000/60/ave;
                aveStr = aveSpeed.format("%.1f");
            }
            if (pace==null){
                paceStr = ZERO_DISTANCE;
            }else{
                //var paceperkm = pace*60/1000;
                var paceperkm = 1000/60/pace;
                paceStr = paceperkm.format("%.1f");
            }
            dc.setColor(Graphics.COLOR_DK_RED, Graphics.COLOR_TRANSPARENT);
            dc.drawText(60, 130, Graphics.FONT_NUMBER_MEDIUM, aveStr, textCenter);
            dc.setColor(Graphics.COLOR_DK_BLUE, Graphics.COLOR_TRANSPARENT);
            dc.drawText(190, 130, Graphics.FONT_NUMBER_MEDIUM, paceStr, textCenter);
        } else {
            dc.drawText(width / 2, 5 + (height - 55) / 2, Graphics.FONT_TINY, label, textCenter);
            dc.drawText(width / 2, (height - 23) - (height - 55) / 2 - 1, Graphics.FONT_NUMBER_HOT, mValue.format(valueFormat), textCenter);
        }

まず、画面のレイアウトですが、画面を4分割するのでそのための線を3本引いています。最初の2本は前回と同じで、3本目が真ん中を2つに分割する線です。
dc.drawLine(width/2, 85, width/2, 165);
それから、平均ペース、現在のペースを格納する文字列変数を定義しています。
var aveStr;
var paceStr;
経過時間の表示が前回は真ん中だったのを今回は一番下にしたので、表示位置を変更しています。
dc.drawText(110, 210, Graphics.FONT_NUMBER_HOT, duration, textCenter);
dc.drawText(190, 215, Graphics.FONT_NUMBER_MILD, duration2, textCenter);
平均ペースと現在のペースの計算と表示ですが

            if (ave==null){
                aveStr = ZERO_DISTANCE;
            }else{
                //var aveSpeed = ave*60/1000;
                var aveSpeed = 1000/60/ave;
                aveStr = aveSpeed.format("%.1f");
            }
            if (pace==null){
                paceStr = ZERO_DISTANCE;
            }else{
                //var paceperkm = pace*60/1000;
                var paceperkm = 1000/60/pace;
                paceStr = paceperkm.format("%.1f");
            }
            dc.setColor(Graphics.COLOR_DK_RED, Graphics.COLOR_TRANSPARENT);
            dc.drawText(60, 130, Graphics.FONT_NUMBER_MEDIUM, aveStr, textCenter);
            dc.setColor(Graphics.COLOR_DK_BLUE, Graphics.COLOR_TRANSPARENT);
            dc.drawText(190, 130, Graphics.FONT_NUMBER_MEDIUM, paceStr, textCenter);

の部分がそうです。距離の表示と同じように、データがない時はそのままだとNullになってしまうので、データがnullの時をif文で判断して、それ以外の場合は計算して結果を文字列に代入して表示しています。処理の流れは距離の所とほぼ同じです。表示する前にカラーを指定して、平均ペースはダークレッド、ペースはダークブルーにしています。
経過時間の表示がデフォルトでも気にならないのなら、このデータフィールドをわざわざ作る意味はあまりありません。前回も書いたように、デフォルトでの経過時間の表示は何故か、分と秒が大きな文字で、時がとても小さな表示になります。ウォッチ自体の設定でこれを変える方法がないので、オリジナルデータフィールドの価値がそこにあります。
また、マラソン大会専用のデータフィールドを作ったりしている人もいるようなので、データフィールドについてはもう少し研究してみたいと思います。