Pebble: add weather support in Tears of the Kingdom watchface

Watchface uses Fahrenheit and its own enumerated weather icons
Watchface source: https://github.com/HarrisonAllen/totk-watchface

Supports icons for night added in v2.1.0
This commit is contained in:
Avery Sterk
2026-01-08 19:21:42 +01:00
committed by José Rebelo
parent efe3fef2cd
commit e47d1c82b8
2 changed files with 118 additions and 0 deletions
@@ -0,0 +1,116 @@
/* Copyright (C) 2017-2025 Andreas Shimokawa, Daniele Gobbetti, Avery Sterk
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
import android.util.Pair;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
class AppMessageHandlerTearsOfTheKingdom extends AppMessageHandler {
private int TEMPERATURE_KEY;
private int CONDITIONS_KEY;
// weather icon numbers
private static final int ICON_CLEAR_DAY = 0;
private static final int ICON_PARTLY_CLOUDY_DAY = 1;
private static final int ICON_CLOUDY = 2;
private static final int ICON_RAINY = 3;
private static final int ICON_SNOWY = 4;
private static final int ICON_STORMY = 5;
private static final int ICON_CLEAR_NIGHT = 6;
private static final int ICON_PARTY_CLOUDY_NIGHT = 7;
AppMessageHandlerTearsOfTheKingdom(UUID uuid, PebbleProtocol pebbleProtocol) {
super(uuid, pebbleProtocol);
try {
JSONObject appKeys = getAppKeys();
TEMPERATURE_KEY = appKeys.getInt("TEMPERATURE");
CONDITIONS_KEY = appKeys.getInt("CONDITIONS");
} catch (JSONException e) {
GB.toast("There was an error accessing the Tears of the Kingdom watchface configuration.", Toast.LENGTH_LONG, GB.ERROR, e);
} catch (IOException ignore) {
}
}
private int getIconFromConditionCode(int conditionCode, boolean isNight) {
if (conditionCode >= 803 ) return ICON_CLOUDY;
if (conditionCode >= 801 ) return (isNight ? ICON_PARTY_CLOUDY_NIGHT : ICON_PARTLY_CLOUDY_DAY);
if (conditionCode == 800 ) return (isNight ? ICON_CLEAR_NIGHT : ICON_CLEAR_DAY);
if (conditionCode >= 700 ) return ICON_CLOUDY;
if (conditionCode >= 600 ) return ICON_SNOWY;
if (conditionCode == 511 ) return ICON_SNOWY; // freezing rain
if (conditionCode >= 300) return ICON_RAINY;
if (conditionCode >= 200) return ICON_STORMY;
return ICON_CLOUDY;
}
private byte[] encodeTotkWeatherMessage(WeatherSpec weatherSpec) {
if (weatherSpec == null) {
return null;
}
int weather_time = weatherSpec.getTimestamp();
// approximate night as the time as any time before sunrise or after sunset
boolean isNight = ( weather_time < weatherSpec.getSunRise() ) || ( weather_time > weatherSpec.getSunSet()) ;
int temperature_f = (weatherSpec.getCurrentTemp() - 255) * 9 / 5; // 255K is 0 degF
int condition = getIconFromConditionCode(weatherSpec.getCurrentConditionCode(), isNight);
ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
pairs.add(new Pair<>(TEMPERATURE_KEY, (Object) temperature_f));
pairs.add(new Pair<>(CONDITIONS_KEY, (Object) condition));
byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null);
ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
buf.put(weatherMessage);
return buf.array();
}
@Override
public GBDeviceEvent[] onAppStart() {
WeatherSpec weatherSpec = Weather.getWeatherSpec();
if (weatherSpec == null) {
return new GBDeviceEvent[]{null};
}
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
sendBytes.encodedBytes = encodeTotkWeatherMessage(weatherSpec);
return new GBDeviceEvent[]{sendBytes};
}
@Override
public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
return encodeTotkWeatherMessage(weatherSpec);
}
}
@@ -431,6 +431,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
private static final UUID UUID_M7S = UUID.fromString("03adc57a-569b-4669-9a80-b505eaea314d");
private static final UUID UUID_YWEATHER = UUID.fromString("35a28a4d-0c9f-408f-9c6d-551e65f03186");
private static final UUID UUID_REALWEATHER = UUID.fromString("1f0b0701-cc8f-47ec-86e7-7181397f9a52");
private static final UUID UUID_TOTK = UUID.fromString("c234bf39-a905-48a4-9972-2931aadf4d9e");
private static final UUID UUID_ZERO = new UUID(0, 0);
@@ -461,6 +462,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
mAppMessageHandlers.put(UUID_M7S, new AppMessageHandlerM7S(UUID_M7S, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_YWEATHER, new AppMessageHandlerRealWeather(UUID_YWEATHER, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_REALWEATHER, new AppMessageHandlerRealWeather(UUID_REALWEATHER, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_TOTK, new AppMessageHandlerTearsOfTheKingdom(UUID_TOTK, PebbleProtocol.this));
}
}