From 93c1f78371c17ec9c5bc640cf17bfbfb9c79b770 Mon Sep 17 00:00:00 2001 From: Vitaliy Tomin Date: Sat, 5 Jul 2025 22:37:31 +0800 Subject: [PATCH] igpsport: Weather refactoring. Fixed hourly times and icons. --- .../igpsport/IGPSportDeviceSupport.java | 87 +------- .../devices/igpsport/IGPSportWeather.java | 185 ++++++++++++++++++ 2 files changed, 189 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportWeather.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java index db894bafff..ad5d65a77c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportDeviceSupport.java @@ -20,29 +20,20 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.Date; -import java.util.List; -import java.util.Random; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; -import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.igpsport.IGPSportRouteInstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.igpsport.IGPSportConstants; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; @@ -53,7 +44,6 @@ import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Ble; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Config; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.CyclingData; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Firmware; -import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.GeneralFileOperation; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Ins; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.PeripheralCommon; import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.RoutePlan; @@ -86,6 +76,7 @@ public class IGPSportDeviceSupport extends AbstractBTLEDeviceSupport { public final BatteryInfoProfile batteryInfoProfile; private IGPSportRoutesManager routeManager; private IGPSportDownloadManager downloadManager; + private IGPSportWeather weatherManager; private int mtuSize=247; //FIXME use actual device mtu @@ -96,6 +87,7 @@ public class IGPSportDeviceSupport extends AbstractBTLEDeviceSupport { routeManager = new IGPSportRoutesManager(this); downloadManager = new IGPSportDownloadManager(this); + weatherManager = new IGPSportWeather(this); addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION); addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE); @@ -660,7 +652,7 @@ public class IGPSportDeviceSupport extends AbstractBTLEDeviceSupport { public void onSendWeather(ArrayList weatherSpecs) { if (!weatherSpecs.isEmpty()) { WeatherSpec weatherSpec = weatherSpecs.get(0); - handleWeather(weatherSpec); + weatherManager.handleWeather(weatherSpec); } } @@ -668,81 +660,10 @@ public class IGPSportDeviceSupport extends AbstractBTLEDeviceSupport { // Send weather final ArrayList specs = new ArrayList<>(nodomain.freeyourgadget.gadgetbridge.model.Weather.getInstance().getWeatherSpecs()); if (!specs.isEmpty()) { - handleWeather(specs.get(0)); + weatherManager.handleWeather(specs.get(0)); } } - private void handleWeather(WeatherSpec weatherSpec) { - //example weather packet - //010302ff02ffff00fe3601ffffffffffffffff04080310021802221508ac021012180b20002a0a323032342d30382d3330221508ac021015180a20002a0a323032342d30382d3331221408651019180c20002a0a323032342d30392d30312a20081310651812200b2a10323032342d30382d33302030333a34373201303a0132322108651011180d2210323032342d30382d33302030353a30302a0331353832023133322108651012180d2210323032342d30382d33302030363a30302a033136373202313332210865101018112210323032342d30382d33302030373a30302a0331373932023133322208ac02100f18332210323032342d30382d33302030383a30302a03323031320231333a0308e807 - - try { - LocalDateTime currentTime = LocalDateTime.now(); - DateTimeFormatter formatterNow = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); - TransactionBuilder builder = performInitialized("set weather"); - Back.back_msg.Builder weatherMsg = Back.back_msg.newBuilder(); - Back.weather_current_data_message.Builder currentWeatherMsg =Back.weather_current_data_message.newBuilder(); - currentWeatherMsg.setCurDayMinTemp(weatherSpec.todayMinTemp-273); - currentWeatherMsg.setCurDayMaxTemp(weatherSpec.todayMaxTemp-273); - currentWeatherMsg.setCurTemperature(weatherSpec.currentTemp-273); - currentWeatherMsg.setCurWeather(weatherSpec.currentConditionCode); - currentWeatherMsg.setWindDeg(String.valueOf(weatherSpec.windDirection)); - currentWeatherMsg.setWindSpd(String.valueOf(Math.round(weatherSpec.windSpeed))); - currentWeatherMsg.setTime(currentTime.format(formatterNow)); - - int currentDay=0; - - for (final WeatherSpec.Daily forecast : weatherSpec.forecasts) { - - LocalDateTime now = LocalDateTime.now(); - currentDay++; - LocalDateTime tomorrow = now.plusDays(currentDay); - DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE; //"yyyy-MM-dd" - weatherMsg.addThreeDaysMsg(Back.weather_three_days_data_message.newBuilder() - .setWeatherIndex(forecast.conditionCode) - .setRainProb(forecast.precipProbability) - .setMaxTemp(forecast.maxTemp-273) - .setMinTemp(forecast.minTemp-273) - .setDate(tomorrow.format(formatter)).build()); - - if (currentDay > 2) //we only need 3 days - break; - } - - int currentHour=0; - for (final WeatherSpec.Hourly hourly : weatherSpec.hourly) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - weatherMsg.addThreeHoursMsg(Back.weather_three_hour_data_memsage.newBuilder() - .setWatherIndex(hourly.conditionCode) - .setRainProb(hourly.precipProbability) - .setTemp(hourly.temp-273) - .setTime(sdf.format(new Date(hourly.timestamp * 1000L))) - .setWindDeg(String.valueOf(hourly.windDirection)) - .setWindSpd(String.valueOf(Math.round(hourly.windSpeed))).build()); - currentHour++; - if (currentHour > 3 ) // its called three hour weather but shows actually 4 entries - break; - } - - - weatherMsg.setCurMsg(currentWeatherMsg); - weatherMsg.setServiceType(Common.service_type_index.enum_SERVICE_TYPE_INDEX_BACK); - weatherMsg.setBackOperateType(Back.BACK_OPERATE_TYPE.enum_BACK_OPERATE_TYPE_SEND); - weatherMsg.setBackServiceType(Back.BACK_SERVICE_TYPE.enum_BACK_SERVICE_TYPE_WEATHER); - - - - byte[] weatherBytes = craftData(weatherMsg.getServiceType().getNumber(), - weatherMsg.getBackServiceType().getNumber(), - weatherMsg.getBackOperateType().getNumber(), - weatherMsg.build().toByteArray()); - builder.writeChunkedData(writeCharacteristicFourth, weatherBytes, getMTU()); - builder.queue(getQueue()); - - } catch (IOException e) { - throw new RuntimeException(e); - } - } public File getWritableExportDirectory() throws IOException { return getDevice().getDeviceCoordinator().getWritableExportDirectory(getDevice()); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportWeather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportWeather.java new file mode 100644 index 0000000000..60c653ac34 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/igpsport/IGPSportWeather.java @@ -0,0 +1,185 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.igpsport; + +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Back; +import nodomain.freeyourgadget.gadgetbridge.proto.igpsport.Common; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; + +public class IGPSportWeather { + IGPSportDeviceSupport support; + public IGPSportWeather(IGPSportDeviceSupport support) { + this.support = support; + } + + // A static map to hold the conversion logic from OWM to QWeather. + private static final Map codeMap = new HashMap<>(); + + // Static initializer block to populate the map with conversion data. + static { + // === OWM Group 2xx: Thunderstorm === + codeMap.put(200, 302); // thunderstorm with light rain -> Thundershower + codeMap.put(201, 302); // thunderstorm with rain -> Thundershower + codeMap.put(202, 303); // thunderstorm with heavy rain -> Heavy Thunderstorm + codeMap.put(210, 302); // light thunderstorm -> Thundershower + codeMap.put(211, 302); // thunderstorm -> Thundershower + codeMap.put(212, 303); // heavy thunderstorm -> Heavy Thunderstorm + codeMap.put(221, 302); // ragged thunderstorm -> Thundershower + codeMap.put(230, 302); // thunderstorm with light drizzle -> Thundershower + codeMap.put(231, 302); // thunderstorm with drizzle -> Thundershower + codeMap.put(232, 302); // thunderstorm with heavy drizzle -> Thundershower + + // === OWM Group 3xx: Drizzle === + codeMap.put(300, 309); // light intensity drizzle -> Drizzle Rain + codeMap.put(301, 309); // drizzle -> Drizzle Rain + codeMap.put(302, 309); // heavy intensity drizzle -> Drizzle Rain + codeMap.put(310, 309); // light intensity drizzle rain -> Drizzle Rain + codeMap.put(311, 309); // drizzle rain -> Drizzle Rain + codeMap.put(312, 309); // heavy intensity drizzle rain -> Drizzle Rain + codeMap.put(313, 309); // shower rain and drizzle -> Drizzle Rain + codeMap.put(314, 309); // heavy shower rain and drizzle -> Drizzle Rain + codeMap.put(321, 309); // shower drizzle -> Drizzle Rain + + // === OWM Group 5xx: Rain === + codeMap.put(500, 305); // light rain -> Light Rain + codeMap.put(501, 306); // moderate rain -> Moderate Rain + codeMap.put(502, 307); // heavy intensity rain -> Heavy Rain + codeMap.put(503, 311); // very heavy rain -> Heavy Rainstorm + codeMap.put(504, 308); // extreme rain -> Extreme Rain + codeMap.put(511, 313); // freezing rain -> Freezing Rain + codeMap.put(520, 300); // light intensity shower rain -> Shower Rain + codeMap.put(521, 300); // shower rain -> Shower Rain + codeMap.put(522, 301); // heavy intensity shower rain -> Heavy Shower Rain + codeMap.put(531, 300); // ragged shower rain -> Shower Rain + + // === OWM Group 6xx: Snow === + codeMap.put(600, 400); // light snow -> Light Snow + codeMap.put(601, 401); // snow -> Moderate Snow + codeMap.put(602, 402); // heavy snow -> Heavy Snow + codeMap.put(611, 404); // Sleet -> Sleet + codeMap.put(612, 404); // Light shower sleet -> Sleet + codeMap.put(613, 404); // Shower sleet -> Sleet + codeMap.put(615, 405); // Light rain and snow -> Rain and Snow + codeMap.put(616, 405); // Rain and snow -> Rain and Snow + codeMap.put(620, 407); // Light shower snow -> Snow Flurry + codeMap.put(621, 406); // Shower snow -> Shower Snow + codeMap.put(622, 403); // Heavy shower snow -> Snowstorm + + // === OWM Group 7xx: Atmosphere === + codeMap.put(701, 500); // mist -> Mist + codeMap.put(711, 502); // Smoke -> Haze + codeMap.put(721, 502); // Haze -> Haze + codeMap.put(731, 507); // sand/ dust whirls -> Duststorm + codeMap.put(741, 501); // fog -> Fog + codeMap.put(751, 503); // sand -> Sand + codeMap.put(761, 504); // dust -> Dust + codeMap.put(762, 999); // volcanic ash -> Unknown + codeMap.put(771, 999); // squalls -> Unknown + codeMap.put(781, 999); // tornado -> Unknown + + // === OWM Group 800: Clear === + codeMap.put(800, 100); // clear sky -> Sunny (day code) + + // === OWM Group 80x: Clouds === + codeMap.put(801, 102); // few clouds: 11-25% -> Few Clouds + codeMap.put(802, 101); // scattered clouds: 25-50% -> Cloudy + codeMap.put(803, 103); // broken clouds: 51-84% -> Partly Cloudy + codeMap.put(804, 104); // overcast clouds: 85-100% -> Overcast + + // === OWM Group 9xx: Extreme === + codeMap.put(900, 999); // tornado -> Unknown + codeMap.put(901, 999); // tropical storm -> Unknown + codeMap.put(902, 999); // hurricane -> Unknown + codeMap.put(903, 901); // cold -> Cold + codeMap.put(904, 900); // hot -> Hot + codeMap.put(905, 999); // windy -> Unknown + codeMap.put(906, 304); // hail -> Hail + } + + + static int convertOWMtoQWeather(int openWeatherMapCode) { + return codeMap.getOrDefault(openWeatherMapCode, 999); // Return 999 for unknown codes + } + + public void handleWeather(WeatherSpec weatherSpec) { + //example weather packet + //010302ff02ffff00fe3601ffffffffffffffff04080310021802221508ac021012180b20002a0a323032342d30382d3330221508ac021015180a20002a0a323032342d30382d3331221408651019180c20002a0a323032342d30392d30312a20081310651812200b2a10323032342d30382d33302030333a34373201303a0132322108651011180d2210323032342d30382d33302030353a30302a0331353832023133322108651012180d2210323032342d30382d33302030363a30302a033136373202313332210865101018112210323032342d30382d33302030373a30302a0331373932023133322208ac02100f18332210323032342d30382d33302030383a30302a03323031320231333a0308e807 + + try { + Instant currentTime = Instant.now(); + DateTimeFormatter formatterHourly = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + .withZone(ZoneOffset.UTC); //device requires UTC time here and in hourly + TransactionBuilder builder = support.performInitialized("set weather"); + Back.back_msg.Builder weatherMsg = Back.back_msg.newBuilder(); + Back.weather_current_data_message.Builder currentWeatherMsg =Back.weather_current_data_message.newBuilder(); + currentWeatherMsg.setCurDayMinTemp(weatherSpec.todayMinTemp-273); + currentWeatherMsg.setCurDayMaxTemp(weatherSpec.todayMaxTemp-273); + currentWeatherMsg.setCurTemperature(weatherSpec.currentTemp-273); + currentWeatherMsg.setCurWeather(IGPSportWeather.convertOWMtoQWeather(weatherSpec.currentConditionCode)); + currentWeatherMsg.setWindDeg(String.valueOf(weatherSpec.windDirection)); + currentWeatherMsg.setWindSpd(String.valueOf(Math.round(weatherSpec.windSpeed))); + currentWeatherMsg.setTime(formatterHourly.format(currentTime)); + + int currentDay=0; + + for (final WeatherSpec.Daily forecast : weatherSpec.forecasts) { + + LocalDateTime now = LocalDateTime.now(); + currentDay++; + LocalDateTime tomorrow = now.plusDays(currentDay); + DateTimeFormatter formatterDaily = DateTimeFormatter.ISO_LOCAL_DATE; //"yyyy-MM-dd" + weatherMsg.addThreeDaysMsg(Back.weather_three_days_data_message.newBuilder() + .setWeatherIndex(IGPSportWeather.convertOWMtoQWeather(forecast.conditionCode)) + .setRainProb(forecast.precipProbability) + .setMaxTemp(forecast.maxTemp-273) + .setMinTemp(forecast.minTemp-273) + .setDate(tomorrow.format(formatterDaily)).build()); + + if (currentDay > 2) //we only need 3 days + break; + } + + int currentHour=0; + for (final WeatherSpec.Hourly hourly : weatherSpec.hourly) { + weatherMsg.addThreeHoursMsg(Back.weather_three_hour_data_memsage.newBuilder() + .setWatherIndex(IGPSportWeather.convertOWMtoQWeather(hourly.conditionCode)) + .setRainProb(hourly.precipProbability) + .setTemp(hourly.temp-273) + .setTime(formatterHourly.format(Instant.ofEpochMilli(hourly.timestamp * 1000L))) + .setWindDeg(String.valueOf(hourly.windDirection)) + .setWindSpd(String.valueOf(Math.round(hourly.windSpeed))).build()); + + currentHour++; + if (currentHour > 3 ) // its called three hour weather but shows actually 4 entries + break; + } + + + weatherMsg.setCurMsg(currentWeatherMsg); + weatherMsg.setServiceType(Common.service_type_index.enum_SERVICE_TYPE_INDEX_BACK); + weatherMsg.setBackOperateType(Back.BACK_OPERATE_TYPE.enum_BACK_OPERATE_TYPE_SEND); + weatherMsg.setBackServiceType(Back.BACK_SERVICE_TYPE.enum_BACK_SERVICE_TYPE_WEATHER); + + + + byte[] weatherBytes = IGPSportDeviceSupport.craftData(weatherMsg.getServiceType().getNumber(), + weatherMsg.getBackServiceType().getNumber(), + weatherMsg.getBackOperateType().getNumber(), + weatherMsg.build().toByteArray()); + builder.writeChunkedData(support.writeCharacteristicFourth, weatherBytes, support.getMTU()); + builder.queue(support.getQueue()); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + +}