Zepp OS: send real coordinates in v5 place response

The v5 place dataset hard-coded latitude/longitude to "0.0" and used a
constant locationKey "accu:123456", which prevented watches that were
never provisioned via the official Zepp app (Bip 6, Active 2, ...) from
accepting the response as authoritative — they treated the zero coords
as a missing-location signal and silently refused to display weather
(issue #5653).

- ZeppOsWeatherHandlerV5.createPlace: pull lat/lon from WeatherSpec
  when set, else fall back to the user-configured location pref. Format
  with Locale.ROOT and derive a stable per-location locationKey from a
  hash of the coords so the watch can dedupe responses.

- ZeppOsWeatherService.onSendWeather: same fallback chain for the
  CMD_SET_DEFAULT_LOCATION packet so the watch's default-location state
  is bootstrapped with real coordinates instead of "1.234,-5.678".

Refs #5653.

source: https://github.com/PeterXMR/Gadgetbridge

(cherry picked from commit be87ac3364fbf5725f43e3dbac87ef2e103fdd89)
This commit is contained in:
Peter K
2026-06-07 05:32:42 +00:00
committed by Thomas Kuehne
parent ef7a297100
commit 9b7d380257
2 changed files with 42 additions and 9 deletions
@@ -21,7 +21,9 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
@@ -67,7 +69,18 @@ public class ZeppOsWeatherService extends AbstractZeppOsService {
// When we have a weather update, set the default location to that location on the band.
// TODO: Support for multiple weather locations
final String locationKey = "1.234,-5.678,xiaomi_accu:" + System.currentTimeMillis(); // dummy
float lat = weatherSpec.getLatitude();
float lon = weatherSpec.getLongitude();
if (lat == 0f && lon == 0f) {
// Some weather providers don't populate WeatherSpec coords on broadcast.
// Fall back to user-configured location pref so the watch gets a valid
// default location and accepts subsequent v5 HTTP responses (issue #5653).
lat = GBApplication.getPrefs().getFloat("location_latitude", 0f);
lon = GBApplication.getPrefs().getFloat("location_longitude", 0f);
}
final String coordKey = String.format(Locale.ROOT, "%.4f,%.4f", lat, lon);
final long locationKeyId = ((long) coordKey.hashCode()) & 0xFFFFFFFFL;
final String locationKey = String.format(Locale.ROOT, "%.4f,%.4f,xiaomi_accu:%d", lat, lon, locationKeyId);
final String locationName = weatherSpec.getLocation();
try {
@@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.servic
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import nodomain.freeyourgadget.gadgetbridge.GBApplication
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec
import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather.getWeatherSpec
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsWeatherHandler
@@ -18,6 +19,7 @@ import java.time.ZoneId
import java.util.Calendar
import java.util.Date
import java.util.GregorianCalendar
import java.util.Locale
import kotlin.math.roundToInt
@Suppress("unused")
@@ -88,14 +90,32 @@ object ZeppOsWeatherHandlerV5 {
version = 1,
)
private fun createPlace(weatherSpec: WeatherSpec) = Place(
locationKey = "accu:123456",
longitude = weatherSpec.longitude.toString(),
latitude = weatherSpec.latitude.toString(),
affiliation = weatherSpec.location,
name = weatherSpec.location,
countryCode = null,
)
private fun createPlace(weatherSpec: WeatherSpec): Place {
val (lat, lon) = resolveCoordinates(weatherSpec)
// Stable per-location id so the watch can dedupe responses. Hash of rounded
// coords keeps it deterministic across requests for the same location.
val coordKey = String.format(Locale.ROOT, "%.4f,%.4f", lat, lon)
val locationKeyId = coordKey.hashCode().toLong() and 0xFFFFFFFFL
return Place(
locationKey = "accu:$locationKeyId",
longitude = String.format(Locale.ROOT, "%.3f", lon),
latitude = String.format(Locale.ROOT, "%.3f", lat),
affiliation = weatherSpec.location,
name = weatherSpec.location,
countryCode = null,
)
}
// Some weather providers don't populate WeatherSpec.latitude/longitude on broadcast.
// Fall back to the user-configured location pref so the watch gets a non-zero place,
// which it requires to accept the response as authoritative (issue #5653).
private fun resolveCoordinates(weatherSpec: WeatherSpec): Pair<Float, Float> {
if (weatherSpec.latitude != 0f || weatherSpec.longitude != 0f) {
return weatherSpec.latitude to weatherSpec.longitude
}
val prefs = GBApplication.getPrefs()
return prefs.getFloat("location_latitude", 0f) to prefs.getFloat("location_longitude", 0f)
}
private fun createHourlyWeather(weatherSpec: WeatherSpec): HourlyWeather {
return HourlyWeather(