igpsport: Weather refactoring. Fixed hourly times and icons.

This commit is contained in:
Vitaliy Tomin
2026-05-13 23:33:24 +02:00
committed by José Rebelo
parent 56e67e0761
commit 93c1f78371
2 changed files with 189 additions and 83 deletions
@@ -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<IGPSportDeviceSupport> 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<WeatherSpec> 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<WeatherSpec> 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());
@@ -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<Integer, Integer> 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);
}
}
}