Garmin ウォッチ用ウォッチフェイスを作る(その4)

前回は、日付と曜日、バッテリー容量の表示などを行いました。
alasixosaka.hatenablog.com
だいぶやり方もわかってきたので、残りの表示を一気に片付けてしまいます。

表示したい情報を決める。

表示する情報としては、

  • 日付と曜日
  • 時、分、秒
  • バッテリー残量
  • 心拍数
  • 歩数
  • スマホとのBluetooth接続の有無
  • スマホへのメッセージの有無

の7つにする。そのうち、3つ目までは前回のブログで完成しているので、今回は残りの4つを表示させてみることにする。

情報の取得

表示するための情報の取得については、いつものこちらのサイトを参考にしました。
take4-blue.com
心拍数に関しては

ActivityMonitor.getHeartRateHistory(1, true).next().heartRate

で取得できる。取得できなかった場合は、
ActivityMonitor.INVALID_HR_SAMPLE がTrueになる。
歩数は、

ActivityMonitor.getInfo().steps

Bluetooth接続の有無と、メッセージの数はそれぞれ

System.getDeviceSettings().phoneConnected
System.getDeviceSettings().notificationCount

で取得することができます。

アイコンを決める

前回も紹介したこちらのサイトから適当なアイコンを選んでダウンロードしました。
www.mingcute.com

プログラムの全文

いきなりですが、全文公開です。

layout.xml

まずレイアウトから

<layout id="WatchFace">
    <label id="TimeLabel" x="center" y="60" font="Graphics.FONT_NUMBER_THAI_HOT" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_BLUE" />
    <label id="Label" x="200" y="140" font="Graphics.FONT_LARGE" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_BLUE" />
    <label id ="DayLabel" x="center" y="30" font="Graphics.FONT_LARGE" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_WHITE" />
    <label id ="BatLabel" x="180" y="195" font="Graphics.FONT_TINY" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_WHITE" />
    <label id ="HeartLabel" x="90" y="170" font="Graphics.FONT_TINY" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_WHITE" />
    <label id ="shoeLabel" x="150" y="220" font="Graphics.FONT_TINY" justification="Graphics.TEXT_JUSTIFY_CENTER" color="Graphics.COLOR_GREEN" />
</layout>

TimeLabelというのが時と分を表示する部分で、単なるLabelが秒の表示です。このあたりはまだテストプログラムの名残です。
DayLabelが日付と曜日、BatLabelがバッテリー残量。HeartLabelが心拍数、shoeLabelが歩数です。

drawable.xml

次がdrawable(画像関係)

<drawables>
    <bitmap id="LauncherIcon" filename="launcher_icon.png" />
    <bitmap id="heart" filename="heart_fill.png" />
    <bitmap id="BT" filename="bluetooth_line.png" />
    <bitmap id="mes" filename="message_2_fill.png" />
    <bitmap id="shoe" filename="shoe_line.png" />
</drawables>

LauncherIconはデフォルトのままで弄っていません。heartが心拍数を表すハートのアイコン。BTがbluetoothのアイコン。mesが通知を知らせるアイコン。shoeが歩数表示のための靴のアイコンです。画像ファイル(png形式)はdrawableフォルダに入れておきます。

プログラム本体

プログラム本体のWatchfaceView.mcの部分です。長いですが。

import Toybox.Graphics;
import Toybox.Lang;
import Toybox.System;
import Toybox.WatchUi;
import Toybox.Time;

var BT;
var mes;
var heart;
var shoe;

class watchfaceView extends WatchUi.WatchFace {

    function initialize() {
        WatchFace.initialize();
    }

    // Load your resources here
    function onLayout(dc as Dc) as Void {
        heart = WatchUi.loadResource(Rez.Drawables.heart);
        BT = WatchUi.loadResource(Rez.Drawables.BT);
        mes = WatchUi.loadResource(Rez.Drawables.mes);
        shoe = WatchUi.loadResource(Rez.Drawables.shoe);
        setLayout(Rez.Layouts.WatchFace(dc));
    }

    // Called when this View is brought to the foreground. Restore
    // the state of this View and prepare it to be shown. This includes
    // loading resources into memory.
    function onShow() as Void {
    }

    // Update the view
    function onUpdate(dc as Dc) as Void {
        
        // Get and show the current time
        var clockTime = System.getClockTime();
        var now = Time.now();
        var nowM = Time.Gregorian.info(now, Time.FORMAT_MEDIUM);
        var nowS = Time.Gregorian.info(now, Time.FORMAT_SHORT);
        var batt = System.getSystemStats().battery;
        var hearts = ActivityMonitor.getHeartRateHistory(1, true).next().heartRate;
        var info = System.getDeviceSettings();
        var timeString = Lang.format("$1$:$2$", [clockTime.hour, clockTime.min.format("%02d")]);
        var timeString2 = Lang.format("$1$", [clockTime.sec.format("%02d")]);
        var dayString = Lang.format("$1$/$2$ $3$", [nowS.month, nowS.day, nowM.day_of_week]);
        var battString = Lang.format("$1$%", [batt.format("%02d")]);
        var heartString = Lang.format("$1$", [hearts]);
        var stepstring = Lang.format("$1$", [ActivityMonitor.getInfo().steps.format("%5d")]);
        //System.println (hearts);
        var view = View.findDrawableById("TimeLabel") as Text;
        var view2 = View.findDrawableById("Label") as Text;
        var view3 = View.findDrawableById("DayLabel") as Text;
        var view4 = View.findDrawableById("BatLabel") as Text;
        var view5 = View.findDrawableById("HeartLabel") as Text;
        var view6 = View.findDrawableById("shoeLabel") as Text;
        view.setText(timeString);
        view2.setText(timeString2);
        view3.setText(dayString);
        view4.setText(battString);
        if (hearts != ActivityMonitor.INVALID_HR_SAMPLE){
            view5.setText(heartString);
        }else{
            view5.setText("---");
        }
        view6.setText(stepstring);
        //BAT1.draw(dc);
        // Call the parent onUpdate function to redraw the layout
        View.onUpdate(dc);
        if (info.phoneConnected) {
            dc.drawBitmap(50,195, BT);
        }
        if (info.notificationCount > 0) {
            dc.drawBitmap(80,195, mes);
        }
        if (batt>=50){
            //dc.drawBitmap(140,210, BAT4);
        }else{
            //dc.drawBitmap(140,210, BAT1);
        }
        dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
        dc.drawRectangle(140, 202, 20, 10);
        dc.fillRectangle(140, 202, 20*batt/100, 10);
        dc.drawBitmap(50,170, heart);
        dc.drawBitmap(80, 220, shoe);
    }

    // Called when this View is removed from the screen. Save the
    // state of this View here. This includes freeing resources from
    // memory.
    function onHide() as Void {
    }

    // The user has just looked at their watch. Timers and animations may be started here.
    function onExitSleep() as Void {
    }

    // Terminate any active timers and prepare for slow updates.
    function onEnterSleep() as Void {
    }

}

ここまでその1から読んでいただければそんなに難しくないのでわかると思いますが、ポイントだけ簡単に説明しておきます。
まず、画像を表示するための変数を4つ定義します。

var BT;
var mes;
var heart;
var shoe;

の部分がそうです。
次にfunction onLayoutで

    function onLayout(dc as Dc) as Void {
        heart = WatchUi.loadResource(Rez.Drawables.heart);
        BT = WatchUi.loadResource(Rez.Drawables.BT);
        mes = WatchUi.loadResource(Rez.Drawables.mes);
        shoe = WatchUi.loadResource(Rez.Drawables.shoe);
        setLayout(Rez.Layouts.WatchFace(dc));
    }

の部分で、画像ファイルと紐づけをします。
あとは、画面の更新のfunction onUpdateの部分で表示を制御してやります。前回から追記した部分を中心に説明すると、変数heartsに心拍数が入ります。
Bluetooth接続の有無とメッセージの数は、一旦、変数infoで受けておいて、後で、info.phoneConnectedとinfo.notificationCountからそれぞれの情報を得ています。
心拍数の数値は、文字列のbattStringに代入し、歩数の数値はstepStringに代入します。それぞれ変数view5、view6を使って表示をします。
心拍数は取得できていない時があるので、ActivityMonitor.INVALID_HR_SAMPLEがfalseの時だけ表示し、trueの時は、"---”を表示するようにしています。この辺は参考サイトのやり方を踏襲しています。
また、info.phoneConnectedがtrueならBluetoothのアイコンを表示し、info.notificationCountが1以上であればメッセージアイコンを表示します。

        if (info.phoneConnected) {
            dc.drawBitmap(50,195, BT);
        }
        if (info.notificationCount > 0) {
            dc.drawBitmap(80,195, mes);
        }

の部分がそうです。
こんな感じで表示されます。

一応全部入りで表示してます。

受け取ったメッセージの数を表示することもできますが、今回はしていません。使いながらあった方がいいと思えば追加するかもしれません。
次回は、フォントをオリジナルに変更しようと思っています。