fix weather CMF Watch

This commit is contained in:
Philip Magyar
2026-01-01 15:12:16 +01:00
committed by José Rebelo
parent 78068ecd07
commit c19ed15dfe
2 changed files with 115 additions and 52 deletions
@@ -335,57 +335,71 @@ object WeatherMapper {
@JvmStatic
fun mapToCmfCondition(openWeatherMapCondition: Int): Byte {
/* deducted values:
1 = sunny
2 = cloudy
3 = overcast
4 = showers
5 = snow showers
6 = fog
1 = sunny - sun with rays
2 = cloudy - cloud
3 = overcast - two clouds
4 = showers - cloud with rain
5 = snow showers - showflake
6 = fog - multiple lines showing "fog"
9 = thunder showers
9 = thunder showers - lightning
14 = sleet
14 = sleet - cloud with dots under
19 = hot (extreme)
20 = cold (extreme)
19 = hot (extreme) - flame
20 = cold (extreme) - snowman
21 = strong wind
22 = (night) sunny - with moon
23 = (night) sunny with stars
24 = (night) cloudy - with moon
25 = sun with haze
26 = cloudy (sun with cloud)
*/
21 = strong wind - wind moving icon
22 = night haze - with moon haze
23 = clear night - moon with star
24 = cloudy night - moon with cloud
25 = sun with haze
26 = cloudy - sun with cloud
*/
return when (openWeatherMapCondition) {
210, 200, 201, 202, 230, 231, 232, 211, 212, 221 -> 9
// Group 2xx: Thunderstorm
200, 201, 202, 210, 211, 212, 221, 230, 231, 232 -> 9 // Thunderstorm - only one thunderstorm icon on watch
// Group 3xx: Drizzle
300, 301, 302, 310, 311, 312, 313, 314, 321 -> 4 // showers
// Group 5xx: Rain
500, 501, 502, 503, 504, 520, 521, 522, 531 -> 4 // showers
511 -> 11 // freezing rain, icon used by openweathermap
// Group 6xx: Snow
600, 601, 602, 620, 621, 622 -> 11 // snow showers
611, 612, 613, 615, 616 -> 14 // Sleet
// Group 7xx: Atmosphere
701, 711, 731, 741, 751, 761, 762 -> 6 // fog
721 -> 25 // haze (day & night)
781, 771 -> 21 // strong wind (tornado, squalls)
// Group 800: Clear
800 -> 1 // clear (day & night)
// Group 80x: Clouds
801, 802 -> 26 // sun/moon with cloud (night & day)
803 -> 2 // cloudy
804 -> 3 // overcast
901, 781, 900, 771, 960, 961, 902, 962 -> 21
// Other codes
900 -> 9 // thunderstorm
901, 902, 962, 905 -> 21 // strong wind (tropical storm, hurricane, windy)
903 -> 20 // cold
904 -> 19 // hot
906 -> 11 // hail, snow showers (or should it be sleet?)
300, 301, 302, 310, 311, 312, 313, 314, 321, 500, 501, 502, 503, 504, 520, 521, 522, 531 -> 4
906, 615, 616, 511 -> 14
611, 612, 600, 601, 602, 620, 621, 622 -> 5
701, 711, 721, 731, 741, 751, 761, 762 -> 6
800 -> 1
904 -> 19
801, 802 -> 26
803 -> 2
804 -> 3
905, 951, 952, 953, 954, 955, 956, 957, 958, 959 -> 21
903 -> 20
else -> 20
// strong wind (calm, light,gentle,moderate,fresh,strong-breeze, high,severe,windcase-gale, (violent) storm)
951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961 -> 21
else -> 0 // no data icon
}
}
@JvmStatic
fun cmfConditionToNight(cmfCondition: Byte): Byte {
if (cmfCondition.toInt() == 1) return 23 // clear --> clear night
if (cmfCondition.toInt() == 26) return 24 // sun with cloud --> moon with cloud
if (cmfCondition.toInt() == 25) return 22 // sun with haze --> moon with haze
return cmfCondition
}
@JvmStatic
fun mapToFitProCondition(openWeatherMapCondition: Int): Byte {
return when (openWeatherMapCondition) {
@@ -44,6 +44,9 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -829,8 +832,19 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
final boolean supportsSunriseSunset = getCoordinator().supportsSunriseSunset();
final int payloadLength = (7 * 9) + (24 * 2) + (supportsSunriseSunset ? 32 : 30) + (supportsSunriseSunset ? 7 * 8 : 0);
final ByteBuffer buf = ByteBuffer.allocate(payloadLength).order(ByteOrder.BIG_ENDIAN);
long currentTime = System.currentTimeMillis() / 1000;
long sunrise = weatherSpec.getSunRise(); // epoch seconds
long sunset = weatherSpec.getSunSet(); // epoch seconds
boolean isDay = currentTime >= sunrise && currentTime < sunset;
// start with the current day's weather
buf.put(WeatherMapper.mapToCmfCondition(weatherSpec.getCurrentConditionCode()));
// Condition
byte cmfCondition = WeatherMapper.mapToCmfCondition(weatherSpec.getCurrentConditionCode());
// If the sun has set, use moon icons where possible
if (!isDay) cmfCondition = WeatherMapper.cmfConditionToNight(cmfCondition);
buf.put(cmfCondition);
// Temperatures, humidity, aqi, uv and wind speed
buf.put((byte) (weatherSpec.getCurrentTemp() - 273 + 100)); // convert Kelvin to C, add 100
buf.put((byte) (weatherSpec.getTodayMaxTemp() - 273 + 100)); // convert Kelvin to C, add 100
buf.put((byte) (weatherSpec.getTodayMinTemp() - 273 + 100)); // convert Kelvin to C, add 100
@@ -845,11 +859,12 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
for (int i = 0; i < 6; i++) {
if (i < maxForecastsAvailable) {
WeatherSpec.Daily forecastDay = weatherSpec.getForecasts().get(i);
// The watch can only show one icon for future days and this is the "day" icon:
buf.put(WeatherMapper.mapToCmfCondition(forecastDay.getConditionCode())); // weather condition flag
buf.put((byte) (forecastDay.getMaxTemp() - 273 + 100)); // temp in C (not shown in future days' forecasts)
buf.put((byte) (forecastDay.getMaxTemp() - 273 + 100)); // max temp in C, + 100
buf.put((byte) (forecastDay.getMinTemp() - 273 + 100)); // min temp in C, + 100
buf.put((byte) forecastDay.getHumidity()); // humidity as a %
buf.put((byte) forecastDay.getHumidity()); // humidity as a %, not shown by watch?
buf.putShort((short) (forecastDay.getAirQuality() != null ? forecastDay.getAirQuality().getAqi() : 0));
buf.put((byte) forecastDay.getUvIndex()); // UV index isn't shown. uvi decimal/100, so 0x07 = 700 UVI.
buf.put((byte) forecastDay.getWindSpeed()); // isn't shown by watch, unsure of correct units
@@ -866,18 +881,52 @@ public class CmfWatchProSupport extends AbstractBTLESingleDeviceSupport implemen
}
}
// now add the hourly data for today - just condition and temperature
// hourly data for next 24 hours, the current hour (or hours before that) should not be included! only condition and temperature
int maxHourlyForecastsAvailable = weatherSpec.getHourly().size();
for (int i = 0; i < 24; i++) {
if (i < maxHourlyForecastsAvailable) {
WeatherSpec.Hourly forecastHr = weatherSpec.getHourly().get(i);
buf.put((byte) (forecastHr.getTemp() - 273 + 100)); // temperature
buf.put((byte) forecastHr.getConditionCode()); // condition
} else {
buf.put((byte) (weatherSpec.getCurrentTemp() - 273 + 100)); // assume current temp
buf.put(WeatherMapper.mapToCmfCondition(weatherSpec.getCurrentConditionCode())); // current condition
int writtenHourlyForecasts = 0;
long nextHour = ((currentTime + 3600 - 1) / 3600) * 3600;
// sunset/sunrise stuff, since we only show 24h, only today and tomorrow is important
WeatherSpec.Daily tomorrow = weatherSpec.getForecasts().get(0); // Weatherspec is today, forecasts is tomorrow and onward
LocalDate tomorrowDate = Instant.ofEpochSecond(tomorrow.getSunRise()).atZone(ZoneOffset.UTC).toLocalDate();
for (int i = 0; i < maxHourlyForecastsAvailable && writtenHourlyForecasts < 24; i++) {
WeatherSpec.Hourly forecastHr = weatherSpec.getHourly().get(i);
// Skip current / past hours
if (forecastHr.getTimestamp() < nextHour) {
continue;
}
// Temperature
buf.put((byte) (forecastHr.getTemp() - 273 + 100));
// If the checked forecast-hour is more the current day sunset, check if the day has ended, if so update the sunset/sunrise to be for tomorrow
if (forecastHr.getTimestamp() > sunset) {
LocalDate forecastHrDate = Instant.ofEpochSecond(forecastHr.getTimestamp()).atZone(ZoneOffset.UTC).toLocalDate();
if (forecastHrDate.equals(tomorrowDate)) {
sunrise = tomorrow.getSunRise();
sunset = tomorrow.getSunSet();
}
}
isDay = forecastHr.getTimestamp() >= sunrise && forecastHr.getTimestamp() < sunset;
// Get condition
cmfCondition = WeatherMapper.mapToCmfCondition(forecastHr.getConditionCode());
// If the sun has set, use moon icons where possible
if (!isDay) cmfCondition = WeatherMapper.cmfConditionToNight(cmfCondition);
buf.put(cmfCondition); // condition
writtenHourlyForecasts++;
}
// Pad if fewer than 24 entries were written
while (writtenHourlyForecasts < 24) {
buf.put((byte) 0x01);
buf.put((byte) 0x00);
writtenHourlyForecasts++;
}
// place name - watch scrolls after ~10 chars. Pad up to 32 bytes.
final byte[] locationNameBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateToBytes(weatherSpec.getLocation(), 30);
buf.put(locationNameBytes);