mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-01-13 03:07:32 +01:00
Garmin: Add support for http weather requests
This commit is contained in:
parent
01d48cde91
commit
d8dcc57813
@ -0,0 +1,5 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.devices.garmin;
|
||||||
|
|
||||||
|
public class GarminPreferences {
|
||||||
|
public static final String PREF_GARMIN_CAPABILITIES = "garmin_capabilities";
|
||||||
|
}
|
@ -15,10 +15,4 @@ public class GarminVenu3Coordinator extends GarminCoordinator {
|
|||||||
public int getDeviceNameResource() {
|
public int getDeviceNameResource() {
|
||||||
return R.string.devicetype_garmin_vivomove_style;
|
return R.string.devicetype_garmin_vivomove_style;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsWeather() {
|
|
||||||
// FIXME: It's not working
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -23,6 +24,8 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||||
@ -191,7 +194,10 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluateGBDeviceEvent(parsedMessage.getGBDeviceEvent());
|
final List<GBDeviceEvent> events = parsedMessage.getGBDeviceEvent();
|
||||||
|
for (final GBDeviceEvent event : events) {
|
||||||
|
evaluateGBDeviceEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
communicator.sendMessage(parsedMessage.getAckBytestream()); //send status message
|
communicator.sendMessage(parsedMessage.getAckBytestream()); //send status message
|
||||||
|
|
||||||
@ -272,7 +278,17 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
|
|||||||
communicator.sendMessage(message.getOutgoingMessage());
|
communicator.sendMessage(message.getOutgoingMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean supports(final GarminCapability capability) {
|
||||||
|
return getDevicePrefs().getStringSet(GarminPreferences.PREF_GARMIN_CAPABILITIES, Collections.emptySet())
|
||||||
|
.contains(capability.name());
|
||||||
|
}
|
||||||
|
|
||||||
private void sendWeatherConditions(WeatherSpec weather) {
|
private void sendWeatherConditions(WeatherSpec weather) {
|
||||||
|
if (!supports(GarminCapability.WEATHER_CONDITIONS)) {
|
||||||
|
// Device does not support sending weather as fit
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<RecordData> weatherData = new ArrayList<>();
|
List<RecordData> weatherData = new ArrayList<>();
|
||||||
|
|
||||||
List<RecordDefinition> weatherDefinitions = new ArrayList<>(3);
|
List<RecordDefinition> weatherDefinitions = new ArrayList<>(3);
|
||||||
|
@ -19,8 +19,10 @@ import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCalendarService;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCore;
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiCore;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiDeviceStatus;
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiDeviceStatus;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiFindMyWatch;
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiFindMyWatch;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiHttpService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiSmartProto;
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiSmartProto;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiSmsNotification;
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiSmsNotification;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http.HttpHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ProtobufMessage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ProtobufMessage;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.ProtobufStatusMessage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.ProtobufStatusMessage;
|
||||||
@ -82,6 +84,13 @@ public class ProtocolBufferHandler implements MessageHandler {
|
|||||||
if (smart.hasSmsNotificationService()) {
|
if (smart.hasSmsNotificationService()) {
|
||||||
return prepareProtobufResponse(processProtobufSmsNotificationMessage(smart.getSmsNotificationService()), message.getRequestId());
|
return prepareProtobufResponse(processProtobufSmsNotificationMessage(smart.getSmsNotificationService()), message.getRequestId());
|
||||||
}
|
}
|
||||||
|
if (smart.hasHttpService()) {
|
||||||
|
final GdiHttpService.HttpService response = HttpHandler.handle(smart.getHttpService());
|
||||||
|
if (response == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return prepareProtobufResponse(GdiSmartProto.Smart.newBuilder().setHttpService(response).build(), message.getRequestId());
|
||||||
|
}
|
||||||
if (smart.hasDeviceStatusService()) {
|
if (smart.hasDeviceStatusService()) {
|
||||||
processed = true;
|
processed = true;
|
||||||
processProtobufDeviceStatusResponse(smart.getDeviceStatusService());
|
processProtobufDeviceStatusResponse(smart.getDeviceStatusService());
|
||||||
|
@ -4,7 +4,7 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
public class CobsCoDec {
|
public class CobsCoDec {
|
||||||
private static final long BUFFER_TIMEOUT = 1500L; // turn this value up while debugging
|
private static final long BUFFER_TIMEOUT = 1500L; // turn this value up while debugging
|
||||||
private final ByteBuffer byteBuffer = ByteBuffer.allocate(1000);
|
private final ByteBuffer byteBuffer = ByteBuffer.allocate(10_000);
|
||||||
private long lastUpdate;
|
private long lastUpdate;
|
||||||
private byte[] cobsDecodedMessage;
|
private byte[] cobsDecodedMessage;
|
||||||
|
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr.GdiHttpService;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.HttpUtils;
|
||||||
|
|
||||||
|
public class HttpHandler {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HttpHandler.class);
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder()
|
||||||
|
//.serializeNulls()
|
||||||
|
.create();
|
||||||
|
|
||||||
|
public static GdiHttpService.HttpService handle(final GdiHttpService.HttpService httpService) {
|
||||||
|
if (httpService.hasRawRequest()) {
|
||||||
|
final GdiHttpService.HttpService.RawResponse rawResponse = handleRawRequest(httpService.getRawRequest());
|
||||||
|
if (rawResponse != null) {
|
||||||
|
return GdiHttpService.HttpService.newBuilder()
|
||||||
|
.setRawResponse(rawResponse)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.warn("Unsupported http service request {}", httpService);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GdiHttpService.HttpService.RawResponse handleRawRequest(final GdiHttpService.HttpService.RawRequest rawRequest) {
|
||||||
|
final String urlString = rawRequest.getUrl();
|
||||||
|
LOG.debug("Got rawRequest: {} - {}", rawRequest.getMethod(), urlString);
|
||||||
|
|
||||||
|
final URL url;
|
||||||
|
try {
|
||||||
|
url = new URL(urlString);
|
||||||
|
} catch (final MalformedURLException e) {
|
||||||
|
LOG.error("Failed to parse url", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String path = url.getPath();
|
||||||
|
final Map<String, String> query = HttpUtils.urlQueryParameters(url);
|
||||||
|
final Map<String, String> requestHeaders = headersToMap(rawRequest.getHeaderList());
|
||||||
|
|
||||||
|
final byte[] responseBody;
|
||||||
|
final List<GdiHttpService.HttpService.Header> responseHeaders = new ArrayList<>();
|
||||||
|
if (path.startsWith("/weather/")) {
|
||||||
|
LOG.debug("Got weather request for {}", path);
|
||||||
|
final Object obj = WeatherHandler.handleWeatherRequest(path, query);
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String json = GSON.toJson(obj);
|
||||||
|
LOG.debug("Weather response: {}", json);
|
||||||
|
|
||||||
|
final byte[] stringBytes = json.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
if ("gzip".equals(requestHeaders.get("accept-encoding"))) {
|
||||||
|
responseHeaders.add(
|
||||||
|
GdiHttpService.HttpService.Header.newBuilder()
|
||||||
|
.setKey("Content-Encoding")
|
||||||
|
.setValue("gzip")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
|
||||||
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
try (GZIPOutputStream gzos = new GZIPOutputStream(baos)) {
|
||||||
|
gzos.write(stringBytes);
|
||||||
|
gzos.finish();
|
||||||
|
gzos.flush();
|
||||||
|
responseBody = baos.toByteArray();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Failed to compress response", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responseBody = stringBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseHeaders.add(
|
||||||
|
GdiHttpService.HttpService.Header.newBuilder()
|
||||||
|
.setKey("Content-Type")
|
||||||
|
.setValue("application/json")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
LOG.warn("Unhandled path {}", urlString);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GdiHttpService.HttpService.RawResponse.newBuilder()
|
||||||
|
.setStatus(GdiHttpService.HttpService.Status.OK)
|
||||||
|
.setHttpStatus(200)
|
||||||
|
.setBody(ByteString.copyFrom(responseBody))
|
||||||
|
.addAllHeader(responseHeaders)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> headersToMap(final List<GdiHttpService.HttpService.Header> headers) {
|
||||||
|
final Map<String, String> ret = new HashMap<>();
|
||||||
|
for (final GdiHttpService.HttpService.Header header : headers) {
|
||||||
|
ret.put(header.getKey().toLowerCase(Locale.ROOT), header.getValue());
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,375 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import net.e175.klaus.solarpositioning.DeltaT;
|
||||||
|
import net.e175.klaus.solarpositioning.SPA;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.CurrentPosition;
|
||||||
|
|
||||||
|
public class WeatherHandler {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(WeatherHandler.class);
|
||||||
|
|
||||||
|
// These get requested on connection at most every 5 minutes
|
||||||
|
public static Object handleWeatherRequest(final String path, final Map<String, String> query) {
|
||||||
|
final WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
|
||||||
|
|
||||||
|
if (weatherSpec == null) {
|
||||||
|
LOG.warn("No weather in weather instance");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (path) {
|
||||||
|
case "/weather/v2/forecast/day": {
|
||||||
|
final int lat = getQueryNum(query, "lat", 0);
|
||||||
|
final int lon = getQueryNum(query, "lon", 0);
|
||||||
|
final int duration = getQueryNum(query, "duration", 5);
|
||||||
|
final String tempUnit = getQueryString(query, "tempUnit", "CELSIUS");
|
||||||
|
final String provider = getQueryString(query, "provider", "dci");
|
||||||
|
final List<WeatherForecastDay> ret = new ArrayList<>(duration);
|
||||||
|
final GregorianCalendar date = new GregorianCalendar();
|
||||||
|
date.setTime(new Date(weatherSpec.timestamp * 1000L));
|
||||||
|
for (int i = 0; i < Math.min(duration, weatherSpec.forecasts.size()); i++) {
|
||||||
|
date.add(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
ret.add(new WeatherForecastDay(date, weatherSpec.forecasts.get(i)));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
case "/weather/v2/forecast/hour": {
|
||||||
|
final int lat = getQueryNum(query, "lat", 0);
|
||||||
|
final int lon = getQueryNum(query, "lon", 0);
|
||||||
|
final int duration = getQueryNum(query, "duration", 13);
|
||||||
|
final String speedUnit = getQueryString(query, "speedUnit", "METERS_PER_SECOND");
|
||||||
|
final String tempUnit = getQueryString(query, "tempUnit", "CELSIUS");
|
||||||
|
final String provider = getQueryString(query, "provider", "dci");
|
||||||
|
final String timesOfInterest = getQueryString(query, "timesOfInterest", "");
|
||||||
|
final List<WeatherForecastHour> ret = new ArrayList<>(duration);
|
||||||
|
for (int i = 0; i < Math.min(duration, weatherSpec.hourly.size()); i++) {
|
||||||
|
ret.add(new WeatherForecastHour(weatherSpec.hourly.get(i)));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
case "/weather/v2/current": {
|
||||||
|
final int lat = getQueryNum(query, "lat", 0);
|
||||||
|
final int lon = getQueryNum(query, "lon", 0);
|
||||||
|
final String tempUnit = getQueryString(query, "tempUnit", "CELSIUS");
|
||||||
|
final String speedUnit = getQueryString(query, "speedUnit", "METERS_PER_SECOND");
|
||||||
|
final String provider = getQueryString(query, "provider", "dci");
|
||||||
|
return new WeatherForecastCurrent(weatherSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.warn("Unknown weather path {}", path);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getQueryNum(final Map<String, String> query, final String key, final int defaultValue) {
|
||||||
|
final String str = query.get(key);
|
||||||
|
if (str != null) {
|
||||||
|
return Integer.parseInt(str);
|
||||||
|
} else {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getQueryString(final Map<String, String> query, final String key, final String defaultValue) {
|
||||||
|
final String str = query.get(key);
|
||||||
|
if (str != null) {
|
||||||
|
return str;
|
||||||
|
} else {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherForecastDay {
|
||||||
|
public int dayOfWeek; // 1 monday .. 7 sunday
|
||||||
|
public String description;
|
||||||
|
public String summary;
|
||||||
|
public WeatherValue high;
|
||||||
|
public WeatherValue low;
|
||||||
|
public Integer precipProb;
|
||||||
|
public Integer icon;
|
||||||
|
public Integer epochSunrise;
|
||||||
|
public Integer epochSunset;
|
||||||
|
public Wind wind;
|
||||||
|
public Integer humidity;
|
||||||
|
|
||||||
|
public WeatherForecastDay(final GregorianCalendar date, final WeatherSpec.Daily dailyForecast) {
|
||||||
|
dayOfWeek = BLETypeConversions.dayOfWeekToRawBytes(date);
|
||||||
|
description = "Unknown"; // TODO from conditionCode
|
||||||
|
summary = "Unknown"; // TODO from conditionCode
|
||||||
|
high = new WeatherValue(dailyForecast.maxTemp - 273f, "CELSIUS");
|
||||||
|
low = new WeatherValue(dailyForecast.minTemp - 273f, "CELSIUS");
|
||||||
|
precipProb = dailyForecast.precipProbability;
|
||||||
|
icon = mapToCmfCondition(dailyForecast.conditionCode);
|
||||||
|
|
||||||
|
if (dailyForecast.sunRise != 0 && dailyForecast.sunSet != 0) {
|
||||||
|
epochSunrise = dailyForecast.sunRise;
|
||||||
|
epochSunset = dailyForecast.sunSet;
|
||||||
|
} else {
|
||||||
|
final Location lastKnownLocation = new CurrentPosition().getLastKnownLocation();
|
||||||
|
|
||||||
|
final GregorianCalendar[] sunriseTransitSet = SPA.calculateSunriseTransitSet(
|
||||||
|
date,
|
||||||
|
lastKnownLocation.getLatitude(),
|
||||||
|
lastKnownLocation.getLongitude(),
|
||||||
|
DeltaT.estimate(date)
|
||||||
|
);
|
||||||
|
|
||||||
|
epochSunrise = (int) (sunriseTransitSet[0].getTime().getTime() / 1000);
|
||||||
|
epochSunset = (int) (sunriseTransitSet[2].getTime().getTime() / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
wind = new Wind(new WeatherValue(dailyForecast.windSpeed * 3.6, "METERS_PER_SECOND"), dailyForecast.windDirection);
|
||||||
|
humidity = dailyForecast.humidity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherForecastHour {
|
||||||
|
public int epochSeconds;
|
||||||
|
public String description;
|
||||||
|
public WeatherValue temp;
|
||||||
|
public Integer precipProb;
|
||||||
|
public Wind wind;
|
||||||
|
public Integer icon;
|
||||||
|
public WeatherValue dewPoint;
|
||||||
|
public Float uvIndex;
|
||||||
|
public Integer relativeHumidity;
|
||||||
|
public WeatherValue feelsLikeTemperature;
|
||||||
|
public WeatherValue visibility;
|
||||||
|
public WeatherValue pressure;
|
||||||
|
public Object airQuality;
|
||||||
|
public Integer cloudCover;
|
||||||
|
|
||||||
|
public WeatherForecastHour(final WeatherSpec.Hourly hourlyForecast) {
|
||||||
|
epochSeconds = hourlyForecast.timestamp;
|
||||||
|
description = "Unknown"; // TODO from conditionCode
|
||||||
|
temp = new WeatherValue(hourlyForecast.temp - 273f, "CELSIUS");
|
||||||
|
precipProb = hourlyForecast.precipProbability;
|
||||||
|
wind = new Wind(new WeatherValue(hourlyForecast.windSpeed * 3.6, "METERS_PER_SECOND"), hourlyForecast.windDirection);
|
||||||
|
icon = mapToCmfCondition(hourlyForecast.conditionCode);
|
||||||
|
//dewPoint = new WeatherValue(hourlyForecast.temp - 273f, "CELSIUS"); // TODO dewPoint
|
||||||
|
uvIndex = hourlyForecast.uvIndex;
|
||||||
|
relativeHumidity = hourlyForecast.humidity;
|
||||||
|
//feelsLikeTemperature = new WeatherValue(hourlyForecast.temp - 273f, "CELSIUS"); // TODO feelsLikeTemperature
|
||||||
|
//visibility = new WeatherValue(0, "METER"); // TODO visibility
|
||||||
|
//pressure = new WeatherValue(0f, "INCHES_OF_MERCURY"); // TODO pressure
|
||||||
|
//airQuality = null; // TODO airQuality
|
||||||
|
//cloudCover = 0; // TODO cloudCover
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherForecastCurrent {
|
||||||
|
public Integer epochSeconds;
|
||||||
|
public WeatherValue temperature;
|
||||||
|
public String description;
|
||||||
|
public Integer icon;
|
||||||
|
public WeatherValue feelsLikeTemperature;
|
||||||
|
public WeatherValue dewPoint;
|
||||||
|
public Integer relativeHumidity;
|
||||||
|
public Wind wind;
|
||||||
|
public String locationName;
|
||||||
|
public WeatherValue visibility;
|
||||||
|
public WeatherValue pressure;
|
||||||
|
public WeatherValue pressureChange;
|
||||||
|
|
||||||
|
public WeatherForecastCurrent(final WeatherSpec weatherSpec) {
|
||||||
|
epochSeconds = weatherSpec.timestamp;
|
||||||
|
temperature = new WeatherValue(weatherSpec.currentTemp - 273f, "CELSIUS");
|
||||||
|
description = weatherSpec.currentCondition;
|
||||||
|
icon = mapToCmfCondition(weatherSpec.currentConditionCode);
|
||||||
|
feelsLikeTemperature = new WeatherValue(weatherSpec.currentTemp - 273f, "CELSIUS");
|
||||||
|
dewPoint = new WeatherValue(weatherSpec.dewPoint - 273f, "CELSIUS");
|
||||||
|
relativeHumidity = weatherSpec.currentHumidity;
|
||||||
|
wind = new Wind(new WeatherValue(weatherSpec.windSpeed * 3.6, "METERS_PER_SECOND"), weatherSpec.windDirection);
|
||||||
|
locationName = weatherSpec.location;
|
||||||
|
visibility = new WeatherValue(weatherSpec.visibility, "METER");
|
||||||
|
pressure = new WeatherValue(weatherSpec.pressure * 0.02953, "INCHES_OF_MERCURY");
|
||||||
|
pressureChange = new WeatherValue(0f, "INCHES_OF_MERCURY");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WeatherValue {
|
||||||
|
public Number value;
|
||||||
|
public String units;
|
||||||
|
|
||||||
|
public WeatherValue(final Number value, final String units) {
|
||||||
|
this.value = value;
|
||||||
|
this.units = units;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Wind {
|
||||||
|
public WeatherValue speed;
|
||||||
|
public String directionString; // NW
|
||||||
|
public Integer direction;
|
||||||
|
|
||||||
|
public Wind(final WeatherValue speed, final int direction) {
|
||||||
|
this.speed = speed;
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int mapToCmfCondition(int openWeatherMapCondition) {
|
||||||
|
// Icons mapped from a Venu 3:
|
||||||
|
// 0 1 2 unk
|
||||||
|
// 3 4 5 6 sunny
|
||||||
|
// 7 8 9 10 sun cloudy
|
||||||
|
// 11 12 cloudy with dashes below
|
||||||
|
// 13 14 sun cloud 2 clouds
|
||||||
|
// 15 16 clouds
|
||||||
|
// 17 rain
|
||||||
|
// 18 19 20 21 rain with sun (or night at night?)
|
||||||
|
// 22 rain
|
||||||
|
// 23 24 unk
|
||||||
|
// 25 26 thunder with rain and sun behind
|
||||||
|
// 27 thunder with rain
|
||||||
|
// 28 29 rain
|
||||||
|
// 30 31 32 33 34 snow with clouds
|
||||||
|
// 35 36 37 snowflake
|
||||||
|
// 38 snow with clouds, with big flake
|
||||||
|
// 39 snow with rain
|
||||||
|
// 40 41 snow with rain
|
||||||
|
// 42 43 44 rain with snow
|
||||||
|
// 45 rain with snow
|
||||||
|
// 46 wind
|
||||||
|
// 47 48 foggy (dashes?)
|
||||||
|
// 49 50 51 unk
|
||||||
|
|
||||||
|
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 27;
|
||||||
|
|
||||||
|
//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 46;
|
||||||
|
|
||||||
|
//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 17;
|
||||||
|
|
||||||
|
//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 40;
|
||||||
|
|
||||||
|
//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 38;
|
||||||
|
|
||||||
|
|
||||||
|
//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 47;
|
||||||
|
|
||||||
|
//Group 800: Clear
|
||||||
|
case 800: //clear sky: //[[file:01d.png]] [[file:01n.png]]
|
||||||
|
return 5;
|
||||||
|
|
||||||
|
//Group 90x: Extreme
|
||||||
|
case 904: //hot
|
||||||
|
return 5;
|
||||||
|
|
||||||
|
//Group 80x: Clouds
|
||||||
|
case 801: //few clouds: //[[file:02d.png]] [[file:02n.png]]
|
||||||
|
case 802: //scattered clouds: //[[file:03d.png]] [[file:03d.png]]
|
||||||
|
return 8;
|
||||||
|
case 803: //broken clouds: //[[file:04d.png]] [[file:03d.png]]
|
||||||
|
return 15;
|
||||||
|
|
||||||
|
//Group 80x: Clouds
|
||||||
|
case 804: //overcast clouds: //[[file:04d.png]] [[file:04d.png]]
|
||||||
|
return 15;
|
||||||
|
|
||||||
|
//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 46;
|
||||||
|
|
||||||
|
default:
|
||||||
|
//Group 90x: Extreme
|
||||||
|
case 903: //cold
|
||||||
|
return 35;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,20 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminPreferences;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
|
import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
|
||||||
|
|
||||||
|
|
||||||
public class ConfigurationMessage extends GFDIMessage {
|
public class ConfigurationMessage extends GFDIMessage {
|
||||||
public final Set<GarminCapability> OUR_CAPABILITIES = GarminCapability.ALL_CAPABILITIES;
|
public final Set<GarminCapability> OUR_CAPABILITIES = GarminCapability.ALL_CAPABILITIES;
|
||||||
private final byte[] incomingConfigurationPayload;
|
private final byte[] incomingConfigurationPayload;
|
||||||
|
private final Set<GarminCapability> capabilities;
|
||||||
private final byte[] ourConfigurationPayload = GarminCapability.setToBinary(OUR_CAPABILITIES);
|
private final byte[] ourConfigurationPayload = GarminCapability.setToBinary(OUR_CAPABILITIES);
|
||||||
|
|
||||||
public ConfigurationMessage(GarminMessage garminMessage, byte[] configurationPayload) {
|
public ConfigurationMessage(GarminMessage garminMessage, byte[] configurationPayload) {
|
||||||
@ -15,8 +22,7 @@ public class ConfigurationMessage extends GFDIMessage {
|
|||||||
if (configurationPayload.length > 255)
|
if (configurationPayload.length > 255)
|
||||||
throw new IllegalArgumentException("Too long payload");
|
throw new IllegalArgumentException("Too long payload");
|
||||||
this.incomingConfigurationPayload = configurationPayload;
|
this.incomingConfigurationPayload = configurationPayload;
|
||||||
|
this.capabilities = GarminCapability.setFromBinary(configurationPayload);
|
||||||
Set<GarminCapability> capabilities = GarminCapability.setFromBinary(configurationPayload);
|
|
||||||
LOG.info("Received configuration message; capabilities: {}", GarminCapability.setToString(capabilities));
|
LOG.info("Received configuration message; capabilities: {}", GarminCapability.setToString(capabilities));
|
||||||
|
|
||||||
this.statusMessage = this.getStatusMessage();
|
this.statusMessage = this.getStatusMessage();
|
||||||
@ -28,6 +34,17 @@ public class ConfigurationMessage extends GFDIMessage {
|
|||||||
return configurationMessage;
|
return configurationMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
|
final Set<Object> capabilitiesPref = new HashSet<>();
|
||||||
|
for (final GarminCapability capability : capabilities) {
|
||||||
|
capabilitiesPref.add(capability.name());
|
||||||
|
}
|
||||||
|
return Collections.singletonList(
|
||||||
|
new GBDeviceEventUpdatePreferences(GarminPreferences.PREF_GARMIN_CAPABILITIES, capabilitiesPref)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean generateOutgoing() {
|
protected boolean generateOutgoing() {
|
||||||
final MessageWriter writer = new MessageWriter(response);
|
final MessageWriter writer = new MessageWriter(response);
|
||||||
|
@ -4,8 +4,11 @@ import android.annotation.SuppressLint;
|
|||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||||
|
|
||||||
public class DeviceInformationMessage extends GFDIMessage {
|
public class DeviceInformationMessage extends GFDIMessage {
|
||||||
@ -82,7 +85,8 @@ public class DeviceInformationMessage extends GFDIMessage {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GBDeviceEventVersionInfo getGBDeviceEvent() {
|
@Override
|
||||||
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Received device information: protocol {}, product {}, unit {}, SW {}, max packet {}, BT name {}, device name {}, device model {}",
|
"Received device information: protocol {}, product {}, unit {}, SW {}, max packet {}, BT name {}, device name {}, device model {}",
|
||||||
incomingProtocolVersion,
|
incomingProtocolVersion,
|
||||||
@ -98,7 +102,7 @@ public class DeviceInformationMessage extends GFDIMessage {
|
|||||||
GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
||||||
versionCmd.fwVersion = getSoftwareVersionStr();
|
versionCmd.fwVersion = getSoftwareVersionStr();
|
||||||
versionCmd.hwVersion = deviceModel;
|
versionCmd.hwVersion = deviceModel;
|
||||||
return versionCmd;
|
return Collections.singletonList(versionCmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSoftwareVersionStr() {
|
private String getSoftwareVersionStr() {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||||
|
|
||||||
@ -16,10 +19,10 @@ public class FindMyPhoneCancelMessage extends GFDIMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GBDeviceEvent getGBDeviceEvent() {
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
|
final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
|
||||||
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
|
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
|
||||||
return findPhoneEvent;
|
return Collections.singletonList(findPhoneEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||||
|
|
||||||
@ -20,10 +23,10 @@ public class FindMyPhoneRequestMessage extends GFDIMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GBDeviceEvent getGBDeviceEvent() {
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
|
final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
|
||||||
findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
|
findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
|
||||||
return findPhoneEvent;
|
return Collections.singletonList(findPhoneEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.ChecksumCalculator;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.ChecksumCalculator;
|
||||||
@ -81,8 +83,8 @@ public abstract class GFDIMessage {
|
|||||||
return new GenericStatusMessage(garminMessage, Status.ACK);
|
return new GenericStatusMessage(garminMessage, Status.ACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GBDeviceEvent getGBDeviceEvent() {
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
return null;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getAckBytestream() {
|
public byte[] getAckBytestream() {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||||
|
|
||||||
public class MusicControlMessage extends GFDIMessage {
|
public class MusicControlMessage extends GFDIMessage {
|
||||||
@ -38,8 +42,8 @@ public class MusicControlMessage extends GFDIMessage {
|
|||||||
return new MusicControlMessage(garminMessage, command);
|
return new MusicControlMessage(garminMessage, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GBDeviceEventMusicControl getGBDeviceEvent() {
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
return event;
|
return Collections.singletonList(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
@ -111,8 +113,8 @@ public class NotificationControlMessage extends GFDIMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GBDeviceEvent getGBDeviceEvent() {
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
return deviceEvent;
|
return Collections.singletonList(deviceEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NotificationsHandler.LegacyNotificationAction getLegacyNotificationAction() {
|
public NotificationsHandler.LegacyNotificationAction getLegacyNotificationAction() {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.NotificationSubscriptionDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.NotificationSubscriptionDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.NotificationSubscriptionStatusMessage;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.status.NotificationSubscriptionStatusMessage;
|
||||||
@ -25,10 +28,10 @@ public class NotificationSubscriptionMessage extends GFDIMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GBDeviceEvent getGBDeviceEvent() {
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
NotificationSubscriptionDeviceEvent notificationSubscriptionDeviceEvent = new NotificationSubscriptionDeviceEvent();
|
NotificationSubscriptionDeviceEvent notificationSubscriptionDeviceEvent = new NotificationSubscriptionDeviceEvent();
|
||||||
notificationSubscriptionDeviceEvent.enable = this.enable;
|
notificationSubscriptionDeviceEvent.enable = this.enable;
|
||||||
return notificationSubscriptionDeviceEvent;
|
return Collections.singletonList(notificationSubscriptionDeviceEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.WeatherRequestDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.WeatherRequestDeviceEvent;
|
||||||
|
|
||||||
public class WeatherMessage extends GFDIMessage {
|
public class WeatherMessage extends GFDIMessage {
|
||||||
@ -21,8 +25,8 @@ public class WeatherMessage extends GFDIMessage {
|
|||||||
return new WeatherMessage(format, latitude, longitude, hoursOfForecast, garminMessage);
|
return new WeatherMessage(format, latitude, longitude, hoursOfForecast, garminMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WeatherRequestDeviceEvent getGBDeviceEvent() {
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
return weatherRequestDeviceEvent;
|
return Collections.singletonList(weatherRequestDeviceEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,8 +2,10 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.sta
|
|||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.SupportedFileTypesDeviceEvent;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.SupportedFileTypesDeviceEvent;
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ public class SupportedFileTypesStatusMessage extends GFDIStatusMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SupportedFileTypesDeviceEvent getGBDeviceEvent() {
|
public List<GBDeviceEvent> getGBDeviceEvent() {
|
||||||
return new SupportedFileTypesDeviceEvent(fileTypeInfoList);
|
return Collections.singletonList(new SupportedFileTypesDeviceEvent(fileTypeInfoList));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import java.util.Map;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsWeather;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsWeather;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.HttpUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||||
|
|
||||||
public class ZeppOsHttpService extends AbstractZeppOsService {
|
public class ZeppOsHttpService extends AbstractZeppOsService {
|
||||||
@ -99,7 +100,7 @@ public class ZeppOsHttpService extends AbstractZeppOsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String path = url.getPath();
|
final String path = url.getPath();
|
||||||
final Map<String, String> query = urlQueryParameters(url);
|
final Map<String, String> query = HttpUtils.urlQueryParameters(url);
|
||||||
|
|
||||||
if (path.startsWith("/weather/")) {
|
if (path.startsWith("/weather/")) {
|
||||||
final ZeppOsWeather.Response response = ZeppOsWeather.handleHttpRequest(path, query);
|
final ZeppOsWeather.Response response = ZeppOsWeather.handleHttpRequest(path, query);
|
||||||
@ -111,25 +112,6 @@ public class ZeppOsHttpService extends AbstractZeppOsService {
|
|||||||
replyHttpNoInternet(requestId);
|
replyHttpNoInternet(requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> urlQueryParameters(final URL url) {
|
|
||||||
final Map<String, String> queryParameters = new HashMap<>();
|
|
||||||
final String[] pairs = url.getQuery().split("&");
|
|
||||||
for (final String pair : pairs) {
|
|
||||||
final String[] parts = pair.split("=", 2);
|
|
||||||
try {
|
|
||||||
final String key = URLDecoder.decode(parts[0], "UTF-8");
|
|
||||||
if (parts.length == 2) {
|
|
||||||
queryParameters.put(key, URLDecoder.decode(parts[1], "UTF-8"));
|
|
||||||
} else {
|
|
||||||
queryParameters.put(key, "");
|
|
||||||
}
|
|
||||||
} catch (final Exception e) {
|
|
||||||
LOG.error("Failed to decode query", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return queryParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void replyHttpNoInternet(final byte requestId) {
|
private void replyHttpNoInternet(final byte requestId) {
|
||||||
LOG.info("Replying with no internet to http request {}", requestId);
|
LOG.info("Replying with no internet to http request {}", requestId);
|
||||||
|
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class HttpUtils {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class);
|
||||||
|
|
||||||
|
private HttpUtils() {
|
||||||
|
// utility class
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> urlQueryParameters(final URL url) {
|
||||||
|
final String query = url.getQuery();
|
||||||
|
if (StringUtils.isBlank(query)) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
final Map<String, String> queryParameters = new HashMap<>();
|
||||||
|
final String[] pairs = query.split("&");
|
||||||
|
for (final String pair : pairs) {
|
||||||
|
final String[] parts = pair.split("=", 2);
|
||||||
|
try {
|
||||||
|
final String key = URLDecoder.decode(parts[0], "UTF-8");
|
||||||
|
if (parts.length == 2) {
|
||||||
|
queryParameters.put(key, URLDecoder.decode(parts[1], "UTF-8"));
|
||||||
|
} else {
|
||||||
|
queryParameters.put(key, "");
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Failed to decode query", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queryParameters;
|
||||||
|
}
|
||||||
|
}
|
46
app/src/main/proto/garmin_vivomovehr/gdi_http_service.proto
Normal file
46
app/src/main/proto/garmin_vivomovehr/gdi_http_service.proto
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package garmin_vivomovehr;
|
||||||
|
|
||||||
|
option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr";
|
||||||
|
|
||||||
|
message HttpService {
|
||||||
|
enum Method {
|
||||||
|
UNKNOWN_METHOD = 0;
|
||||||
|
GET = 1;
|
||||||
|
PUT = 2;
|
||||||
|
POST = 3;
|
||||||
|
DELETE = 4;
|
||||||
|
PATCH = 5;
|
||||||
|
HEAD = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
UNKNOWN_STATUS = 0;
|
||||||
|
OK = 100;
|
||||||
|
NETWORK_REQUEST_TIMED_OUT = 200;
|
||||||
|
FILE_TOO_LARGE = 300;
|
||||||
|
DATA_TRANSFER_ITEM_FAILURE = 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional RawRequest rawRequest = 5;
|
||||||
|
optional RawResponse rawResponse = 6;
|
||||||
|
|
||||||
|
message RawRequest {
|
||||||
|
required string url = 1;
|
||||||
|
optional Method method = 3;
|
||||||
|
repeated Header header = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RawResponse {
|
||||||
|
optional Status status = 1;
|
||||||
|
optional uint32 httpStatus = 2;
|
||||||
|
optional bytes body = 3;
|
||||||
|
repeated Header header = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Header {
|
||||||
|
required string key = 1;
|
||||||
|
required string value = 2;
|
||||||
|
}
|
||||||
|
}
|
@ -7,12 +7,14 @@ option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.vivomovehr";
|
|||||||
import "garmin_vivomovehr/gdi_device_status.proto";
|
import "garmin_vivomovehr/gdi_device_status.proto";
|
||||||
import "garmin_vivomovehr/gdi_find_my_watch.proto";
|
import "garmin_vivomovehr/gdi_find_my_watch.proto";
|
||||||
import "garmin_vivomovehr/gdi_core.proto";
|
import "garmin_vivomovehr/gdi_core.proto";
|
||||||
|
import "garmin_vivomovehr/gdi_http_service.proto";
|
||||||
import "garmin_vivomovehr/gdi_sms_notification.proto";
|
import "garmin_vivomovehr/gdi_sms_notification.proto";
|
||||||
import "garmin_vivomovehr/gdi_calendar_service.proto";
|
import "garmin_vivomovehr/gdi_calendar_service.proto";
|
||||||
import "garmin_vivomovehr/gdi_settings_service.proto";
|
import "garmin_vivomovehr/gdi_settings_service.proto";
|
||||||
|
|
||||||
message Smart {
|
message Smart {
|
||||||
optional CalendarService calendar_service = 1;
|
optional CalendarService calendar_service = 1;
|
||||||
|
optional HttpService http_service = 2;
|
||||||
optional DeviceStatusService device_status_service = 8;
|
optional DeviceStatusService device_status_service = 8;
|
||||||
optional FindMyWatchService find_my_watch_service = 12;
|
optional FindMyWatchService find_my_watch_service = 12;
|
||||||
optional CoreService core_service = 13;
|
optional CoreService core_service = 13;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user