diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProCoordinator.java index e8d5240ba..d69ccb3c9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/cmfwatchpro/CmfWatchProCoordinator.java @@ -297,7 +297,7 @@ public class CmfWatchProCoordinator extends AbstractBLEDeviceCoordinator { @Override public boolean supportsWeather() { - return false; // TODO weather is not implemented + return true; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java index 5d7b78cf2..cc708ea42 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java @@ -889,6 +889,151 @@ public class Weather { return 5; } } + public static byte mapToCmfCondition(int openWeatherMapCondition) { +/* deducted values: + 1 = sunny + 2 = cloudy + 3 = overcast + 4 = showers + 5 = snow showers + 6 = fog + + 9 = thunder showers + + 14 = sleet + + 19 = hot (extreme) + 20 = cold (extreme) + + 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) + */ + switch (openWeatherMapCondition) { +//Group 2xx: Thunderstorm + case 210: //light thunderstorm:: //11d + case 200: //thunderstorm with light rain: //11d + case 201: //thunderstorm with rain: //11d + case 202: //thunderstorm with heavy rain: //11d + case 230: //thunderstorm with light drizzle: //11d + case 231: //thunderstorm with drizzle: //11d + case 232: //thunderstorm with heavy drizzle: //11d + case 211: //thunderstorm: //11d + case 212: //heavy thunderstorm: //11d + case 221: //ragged thunderstorm: //11d + return 9; + +//Group 90x: Extreme + case 901: //tropical storm +//Group 7xx: Atmosphere + case 781: //tornado: //[[file:50d.png]] +//Group 90x: Extreme + case 900: //tornado +// Group 7xx: Atmosphere + case 771: //squalls: //[[file:50d.png]] +//Group 9xx: Additional + case 960: //storm + case 961: //violent storm + case 902: //hurricane + case 962: //hurricane + return 21; + +//Group 3xx: Drizzle + case 300: //light intensity drizzle: //09d + case 301: //drizzle: //09d + case 302: //heavy intensity drizzle: //09d + case 310: //light intensity drizzle rain: //09d + case 311: //drizzle rain: //09d + case 312: //heavy intensity drizzle rain: //09d + case 313: //shower rain and drizzle: //09d + case 314: //heavy shower rain and drizzle: //09d + case 321: //shower drizzle: //09d +//Group 5xx: Rain + case 500: //light rain: //10d + case 501: //moderate rain: //10d + case 502: //heavy intensity rain: //10d + case 503: //very heavy rain: //10d + case 504: //extreme rain: //10d + case 520: //light intensity shower rain: //09d + case 521: //shower rain: //09d + case 522: //heavy intensity shower rain: //09d + case 531: //ragged shower rain: //09d + return 4; + +//Group 90x: Extreme + case 906: //hail + case 615: //light rain and snow: //[[file:13d.png]] + case 616: //rain and snow: //[[file:13d.png]] + case 511: //freezing rain: //13d + return 14; + +//Group 6xx: Snow + case 611: //sleet: //[[file:13d.png]] + case 612: //shower sleet: //[[file:13d.png]] +//Group 6xx: Snow + case 600: //light snow: //[[file:13d.png]] + case 601: //snow: //[[file:13d.png]] +//Group 6xx: Snow + case 602: //heavy snow: //[[file:13d.png]] +//Group 6xx: Snow + case 620: //light shower snow: //[[file:13d.png]] + case 621: //shower snow: //[[file:13d.png]] + case 622: //heavy shower snow: //[[file:13d.png]] + return 5; + + +//Group 7xx: Atmosphere + case 701: //mist: //[[file:50d.png]] + case 711: //smoke: //[[file:50d.png]] + case 721: //haze: //[[file:50d.png]] + case 731: //sandcase dust whirls: //[[file:50d.png]] + case 741: //fog: //[[file:50d.png]] + case 751: //sand: //[[file:50d.png]] + case 761: //dust: //[[file:50d.png]] + case 762: //volcanic ash: //[[file:50d.png]] + return 6; + +//Group 800: Clear + case 800: //clear sky: //[[file:01d.png]] [[file:01n.png]] + return 1; + +//Group 90x: Extreme + case 904: //hot + return 19; + +//Group 80x: Clouds + case 801: //few clouds: //[[file:02d.png]] [[file:02n.png]] + case 802: //scattered clouds: //[[file:03d.png]] [[file:03d.png]] + return 26; + case 803: //broken clouds: //[[file:04d.png]] [[file:03d.png]] + return 2; + +//Group 80x: Clouds + case 804: //overcast clouds: //[[file:04d.png]] [[file:04d.png]] + return 3; + +//Group 9xx: Additional + case 905: //windy + case 951: //calm + case 952: //light breeze + case 953: //gentle breeze + case 954: //moderate breeze + case 955: //fresh breeze + case 956: //strong breeze + case 957: //high windcase near gale + case 958: //gale + case 959: //severe gale + return 21; + + default: +//Group 90x: Extreme + case 903: //cold + return 20; + } + } public static byte mapToFitProCondition(int openWeatherMapCondition) { switch (openWeatherMapCondition) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java index e93621658..9af2d234f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/cmfwatchpro/CmfWatchProSupport.java @@ -56,6 +56,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Contact; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; @@ -584,7 +585,68 @@ public class CmfWatchProSupport extends AbstractBTLEDeviceSupport implements Cmf @Override public void onSendWeather(final WeatherSpec weatherSpec) { - // TODO onSendWeather + // TODO consider adjusting the condition code for clear/sunny so "clear" at night doesn't show a sunny icon (perhaps 23 decimal)? + // Each weather entry takes up 9 bytes + // There are 7 of those weather entries - 7*9 bytes + // Then there are 24-hour entries of temp and weather condition (2 bytes each) + // Then finally the location name as bytes - allow for 30 bytes, watch auto-scrolls + final ByteBuffer buf = ByteBuffer.allocate((7*9) + (24*2) + 30).order(ByteOrder.BIG_ENDIAN); + // start with the current day's weather + buf.put(Weather.mapToCmfCondition(weatherSpec.currentConditionCode)); + buf.put((byte) (weatherSpec.currentTemp - 273 + 100)); // convert Kelvin to C, add 100 + buf.put((byte) (weatherSpec.todayMaxTemp - 273 + 100)); // convert Kelvin to C, add 100 + buf.put((byte) (weatherSpec.todayMinTemp - 273 + 100)); // convert Kelvin to C, add 100 + buf.put((byte) weatherSpec.currentHumidity); + buf.putShort((short) weatherSpec.airQuality.aqi); + buf.put((byte) weatherSpec.uvIndex); // UV index isn't shown. uvi decimal/100, so 0x07 = 700 UVI. + buf.put((byte) weatherSpec.windSpeed); // isn't shown by watch, unsure of correct units + + // find out how many future days' forecasts are available + int maxForecastsAvailable = weatherSpec.forecasts.size(); + // For each day of the forecast + for (int i=0; i < 6; i++) { + if (i < maxForecastsAvailable) { + WeatherSpec.Daily forecastDay = weatherSpec.forecasts.get(i); + buf.put((byte) (Weather.mapToCmfCondition(forecastDay.conditionCode))); // weather condition flag + buf.put((byte) (forecastDay.maxTemp - 273 + 100)); // temp in C (not shown in future days' forecasts) + buf.put((byte) (forecastDay.maxTemp - 273 + 100)); // max temp in C, + 100 + buf.put((byte) (forecastDay.minTemp - 273 + 100)); // min temp in C, + 100 + buf.put((byte) forecastDay.humidity); // humidity as a % + try { // AQI data might not be available for the full 7 day forecast. + buf.putShort((short) weatherSpec.airQuality.aqi); + } catch (java.lang.NullPointerException ex) { + buf.putShort((short) 0); + } + buf.put((byte) forecastDay.uvIndex); // UV index isn't shown. uvi decimal/100, so 0x07 = 700 UVI. + buf.put((byte) forecastDay.windSpeed); // isn't shown by watch, unsure of correct units + } else { + // we need to provide a dummy forecast as there's no data available + buf.put((byte) 0x00); // NULL weather condition + buf.put((byte) 0x01); // -99 C temp temp + buf.put((byte) 0x01); // -99 C max temp + buf.put((byte) 0x01); // -99 C min temp + buf.put((byte) 0x00); // 0 humidity + buf.putShort((short) 0); // aqi + buf.put((byte) 0x00); // 0 UV index + buf.put((byte) 0x00); // 0 wind speed + } + + } + // now add the hourly data for today - just condition and temperature + int maxHourlyForecastsAvailable = weatherSpec.hourly.size(); + for (int i=0; i < 24; i++) { + if (i < maxHourlyForecastsAvailable) { + WeatherSpec.Hourly forecastHr = weatherSpec.hourly.get(i); + buf.put((byte) (forecastHr.temp - 273 + 100)); // temperature + buf.put((byte) forecastHr.conditionCode); // condition + } else { + buf.put((byte) (weatherSpec.currentTemp - 273 + 100)); // assume current temp + buf.put((byte) (Weather.mapToCmfCondition(weatherSpec.currentConditionCode))); // current condition + } + } + // place name - watch scrolls after ~10 chars + buf.put(StringUtils.truncate(weatherSpec.location, 30).getBytes(StandardCharsets.UTF_8)); + sendCommand("send weather", CmfCommand.WEATHER_SET_1, buf.array()); } @Override