mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Zepp OS: Extract services: battery, connection, vibration patterns, weather
This commit is contained in:
-30
@@ -20,13 +20,9 @@ public class Huami2021Service {
|
||||
/**
|
||||
* Endpoints for 2021 chunked protocol
|
||||
*/
|
||||
public static final short CHUNKED2021_ENDPOINT_WEATHER = 0x000e;
|
||||
public static final short CHUNKED2021_ENDPOINT_CONNECTION = 0x0015;
|
||||
public static final short CHUNKED2021_ENDPOINT_STEPS = 0x0016;
|
||||
public static final short CHUNKED2021_ENDPOINT_VIBRATION_PATTERNS = 0x0018;
|
||||
public static final short CHUNKED2021_ENDPOINT_WORKOUT = 0x0019;
|
||||
public static final short CHUNKED2021_ENDPOINT_HEARTRATE = 0x001d;
|
||||
public static final short CHUNKED2021_ENDPOINT_BATTERY = 0x0029;
|
||||
public static final short CHUNKED2021_ENDPOINT_AUTH = 0x0082;
|
||||
public static final short CHUNKED2021_ENDPOINT_COMPAT = 0x0090;
|
||||
|
||||
@@ -39,26 +35,6 @@ public class Huami2021Service {
|
||||
public static final byte STEPS_CMD_ENABLE_REALTIME_ACK = 0x06;
|
||||
public static final byte STEPS_CMD_REALTIME_NOTIFICATION = 0x07;
|
||||
|
||||
/**
|
||||
* Vibration Patterns, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_VIBRATION_PATTERNS}.
|
||||
*/
|
||||
public static final byte VIBRATION_PATTERN_SET = 0x03;
|
||||
public static final byte VIBRATION_PATTERN_ACK = 0x04;
|
||||
|
||||
/**
|
||||
* Battery, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_BATTERY}.
|
||||
*/
|
||||
public static final byte BATTERY_REQUEST = 0x03;
|
||||
public static final byte BATTERY_REPLY = 0x04;
|
||||
|
||||
/**
|
||||
* Connection, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_CONNECTION}.
|
||||
*/
|
||||
public static final byte CONNECTION_CMD_MTU_REQUEST = 0x01;
|
||||
public static final byte CONNECTION_CMD_MTU_RESPONSE = 0x02;
|
||||
public static final byte CONNECTION_CMD_UNKNOWN_3 = 0x03;
|
||||
public static final byte CONNECTION_CMD_UNKNOWN_4 = 0x04;
|
||||
|
||||
/**
|
||||
* Notifications, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_HEARTRATE}.
|
||||
*/
|
||||
@@ -82,12 +58,6 @@ public class Huami2021Service {
|
||||
public static final byte WORKOUT_STATUS_START = 0x01;
|
||||
public static final byte WORKOUT_STATUS_END = 0x04;
|
||||
|
||||
/**
|
||||
* Weather, for {@link Huami2021Service#CHUNKED2021_ENDPOINT_WEATHER}.
|
||||
*/
|
||||
public static final byte WEATHER_CMD_SET_DEFAULT_LOCATION = 0x09;
|
||||
public static final byte WEATHER_CMD_DEFAULT_LOCATION_ACK = 0x0a;
|
||||
|
||||
/**
|
||||
* Raw sensor control.
|
||||
*/
|
||||
|
||||
+6
-43
@@ -55,7 +55,6 @@ import java.time.zone.ZoneRules;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashSet;
|
||||
@@ -590,7 +589,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
private static final byte[] startHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 1};
|
||||
private static final byte[] stopHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 0};
|
||||
|
||||
protected HuamiSupport requestBatteryInfo(TransactionBuilder builder) {
|
||||
private HuamiSupport requestBatteryInfo(TransactionBuilder builder) {
|
||||
LOG.debug("Requesting Battery Info!");
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO);
|
||||
builder.read(characteristic);
|
||||
@@ -1645,7 +1644,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
}
|
||||
|
||||
protected int getFindDeviceInterval() {
|
||||
return HuamiUtils.getFindDeviceInterval(getDevice(), supportsDeviceDefaultVibrationProfiles());
|
||||
return HuamiUtils.getFindDeviceInterval(getDevice(), false);
|
||||
}
|
||||
|
||||
protected void sendFindDeviceCommand(boolean start) {
|
||||
@@ -3010,7 +3009,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
}
|
||||
}
|
||||
|
||||
protected HuamiSupport setVibrationPattern(final TransactionBuilder builder, final String preferenceKey) {
|
||||
private HuamiSupport setVibrationPattern(final TransactionBuilder builder, final String preferenceKey) {
|
||||
// The preference key has one of the 3 prefixes
|
||||
final String notificationTypeName = preferenceKey.replace(PREF_HUAMI_VIBRATION_COUNT_PREFIX, "")
|
||||
.replace(PREF_HUAMI_VIBRATION_PROFILE_PREFIX, "")
|
||||
@@ -3022,7 +3021,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
final VibrationProfile vibrationProfile = HuamiCoordinator.getVibrationProfile(
|
||||
getDevice().getAddress(),
|
||||
notificationType,
|
||||
supportsDeviceDefaultVibrationProfiles()
|
||||
false
|
||||
);
|
||||
|
||||
setVibrationPattern(builder, notificationType, isTry, vibrationProfile);
|
||||
@@ -3030,13 +3029,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the device supports built-in default vibration profiles.
|
||||
*/
|
||||
protected boolean supportsDeviceDefaultVibrationProfiles() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test or set a {@link VibrationProfile}.
|
||||
*
|
||||
@@ -3045,7 +3037,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
* @param test test the pattern (only vibrate the band, do not set it)
|
||||
* @param profile the {@link VibrationProfile}
|
||||
*/
|
||||
protected void setVibrationPattern(final TransactionBuilder builder,
|
||||
private void setVibrationPattern(final TransactionBuilder builder,
|
||||
final HuamiVibrationPatternNotificationType notificationType,
|
||||
final boolean test,
|
||||
final VibrationProfile profile) {
|
||||
@@ -3057,7 +3049,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
final int MAX_TOTAL_LENGTH_MS = 10_000; // 10 seconds, about as long as Mi Fit allows
|
||||
|
||||
// The on-off sequence, until the max total length is reached
|
||||
final List<Short> onOff = truncateVibrationsOnOff(profile, MAX_TOTAL_LENGTH_MS);
|
||||
final List<Short> onOff = HuamiUtils.truncateVibrationsOnOff(profile, MAX_TOTAL_LENGTH_MS);
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.allocate(3 + 2 * onOff.size());
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
@@ -3078,35 +3070,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport implements
|
||||
writeToChunked(builder, 2, buf.array());
|
||||
}
|
||||
|
||||
protected List<Short> truncateVibrationsOnOff(final VibrationProfile profile, final int limitMillis) {
|
||||
if (profile == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
int totalLengthMs = 0;
|
||||
|
||||
// The on-off sequence, until the max total length is reached
|
||||
final List<Short> onOff = new ArrayList<>(profile.getOnOffSequence().length);
|
||||
|
||||
for (int c = 0; c < profile.getRepeat(); c++) {
|
||||
for (int i = 0; i < profile.getOnOffSequence().length; i += 2) {
|
||||
final short on = (short) profile.getOnOffSequence()[i];
|
||||
final short off = (short) profile.getOnOffSequence()[i + 1];
|
||||
|
||||
if (totalLengthMs + on + off > limitMillis) {
|
||||
LOG.warn("VibrationProfile {} too long, truncating to {} ms", profile.getId(), limitMillis);
|
||||
break;
|
||||
}
|
||||
|
||||
onOff.add(on);
|
||||
onOff.add(off);
|
||||
totalLengthMs += on + off;
|
||||
}
|
||||
}
|
||||
|
||||
return onOff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(ArrayList<WeatherSpec> weatherSpecs) {
|
||||
final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
|
||||
|
||||
+55
@@ -1,10 +1,35 @@
|
||||
/* Copyright (C) 2025 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class HuamiUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HuamiUtils.class);
|
||||
|
||||
private HuamiUtils() {
|
||||
// Utility class
|
||||
}
|
||||
@@ -35,4 +60,34 @@ public class HuamiUtils {
|
||||
|
||||
return findDeviceInterval;
|
||||
}
|
||||
|
||||
public static List<Short> truncateVibrationsOnOff(final VibrationProfile profile,
|
||||
final int limitMillis) {
|
||||
if (profile == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
int totalLengthMs = 0;
|
||||
|
||||
// The on-off sequence, until the max total length is reached
|
||||
final List<Short> onOff = new ArrayList<>(profile.getOnOffSequence().length);
|
||||
|
||||
for (int c = 0; c < profile.getRepeat(); c++) {
|
||||
for (int i = 0; i < profile.getOnOffSequence().length; i += 2) {
|
||||
final short on = (short) profile.getOnOffSequence()[i];
|
||||
final short off = (short) profile.getOnOffSequence()[i + 1];
|
||||
|
||||
if (totalLengthMs + on + off > limitMillis) {
|
||||
LOG.warn("VibrationProfile {} too long, truncating to {} ms", profile.getId(), limitMillis);
|
||||
break;
|
||||
}
|
||||
|
||||
onOff.add(on);
|
||||
onOff.add(off);
|
||||
totalLengthMs += on + off;
|
||||
}
|
||||
}
|
||||
|
||||
return onOff;
|
||||
}
|
||||
}
|
||||
|
||||
+21
-159
@@ -50,13 +50,11 @@ import androidx.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
@@ -91,14 +89,12 @@ import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsAgpsInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsGpxRouteInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
@@ -120,11 +116,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021ChunkedDecoder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021ChunkedEncoder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiPhoneGpsStatus;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsFirmwareUpdateOperation;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsAgpsUpdateOperation;
|
||||
@@ -135,8 +129,10 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAssistantService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAppsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAuthenticationService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsBatteryService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCalendarService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCannedMessagesService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConnectionService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsDisplayItemsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFindDeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsHttpService;
|
||||
@@ -156,8 +152,10 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsPhoneService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsSilentModeService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsUserInfoService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsVibrationPatternsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsVoiceMemosService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWatchfaceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWeatherService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWifiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
@@ -206,6 +204,10 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
private final ZeppOsFindDeviceService findDeviceService = new ZeppOsFindDeviceService(this);
|
||||
private final ZeppOsSilentModeService silentModeService = new ZeppOsSilentModeService(this);
|
||||
private final ZeppOsUserInfoService userInfoService = new ZeppOsUserInfoService(this);
|
||||
private final ZeppOsVibrationPatternsService vibrationPatternsService = new ZeppOsVibrationPatternsService(this);
|
||||
private final ZeppOsBatteryService batteryService = new ZeppOsBatteryService(this);
|
||||
private final ZeppOsWeatherService weatherService = new ZeppOsWeatherService(this);
|
||||
private final ZeppOsConnectionService connectionService = new ZeppOsConnectionService(this);
|
||||
|
||||
private final Set<Short> mSupportedServices = new HashSet<>();
|
||||
// FIXME: We need to keep track of which services are encrypted for now, since not all of them were yet migrated to a service
|
||||
@@ -213,6 +215,8 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
private final Map<Short, AbstractZeppOsService> mServiceMap = new LinkedHashMap<Short, AbstractZeppOsService>() {{
|
||||
put(servicesService.getEndpoint(), servicesService);
|
||||
put(authenticationService.getEndpoint(), authenticationService);
|
||||
put(batteryService.getEndpoint(), batteryService);
|
||||
put(connectionService.getEndpoint(), connectionService);
|
||||
put(fileTransferService.getEndpoint(), fileTransferService);
|
||||
put(configService.getEndpoint(), configService);
|
||||
put(agpsService.getEndpoint(), agpsService);
|
||||
@@ -241,6 +245,8 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
put(findDeviceService.getEndpoint(), findDeviceService);
|
||||
put(silentModeService.getEndpoint(), silentModeService);
|
||||
put(userInfoService.getEndpoint(), userInfoService);
|
||||
put(vibrationPatternsService.getEndpoint(), vibrationPatternsService);
|
||||
put(weatherService.getEndpoint(), weatherService);
|
||||
}};
|
||||
|
||||
public ZeppOsSupport() {
|
||||
@@ -455,15 +461,6 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ZeppOsSupport requestBatteryInfo(TransactionBuilder builder) {
|
||||
LOG.debug("Requesting Battery Info");
|
||||
|
||||
writeToChunked2021(builder, CHUNKED2021_ENDPOINT_BATTERY, BATTERY_REQUEST, false);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ZeppOsSupport setFitnessGoal(final TransactionBuilder builder) {
|
||||
final int goalSteps = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
|
||||
@@ -620,6 +617,14 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
throw new UnsupportedOperationException("this method should not be used");
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME: Visibility hack for easier refactoring.
|
||||
*/
|
||||
@Override
|
||||
public void setMtu(final int mtu) {
|
||||
super.setMtu(mtu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstallApp(final Uri uri) {
|
||||
final ZeppOsAgpsInstallHandler agpsHandler = new ZeppOsAgpsInstallHandler(uri, getContext());
|
||||
@@ -853,66 +858,9 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsDeviceDefaultVibrationProfiles() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setVibrationPattern(final TransactionBuilder builder,
|
||||
final HuamiVibrationPatternNotificationType notificationType,
|
||||
final boolean test,
|
||||
final VibrationProfile profile) {
|
||||
final int MAX_TOTAL_LENGTH_MS = 10_000; // 10 seconds, about as long as Mi Fit allows
|
||||
|
||||
// The on-off sequence, until the max total length is reached
|
||||
final List<Short> onOff = truncateVibrationsOnOff(profile, MAX_TOTAL_LENGTH_MS);
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.allocate(5 + 2 * onOff.size());
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
buf.put(VIBRATION_PATTERN_SET);
|
||||
buf.put(notificationType.getCode());
|
||||
buf.put((byte) (profile != null ? 1 : 0)); // 1 for custom, 0 for device default
|
||||
buf.put((byte) (test ? 1 : 0));
|
||||
buf.put((byte) (onOff.size() / 2));
|
||||
|
||||
for (Short time : onOff) {
|
||||
buf.putShort(time);
|
||||
}
|
||||
|
||||
writeToChunked2021(builder, Huami2021Service.CHUNKED2021_ENDPOINT_VIBRATION_PATTERNS, buf.array(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(final ArrayList<WeatherSpec> weatherSpecs) {
|
||||
final WeatherSpec weatherSpec = weatherSpecs.get(0);
|
||||
|
||||
// Weather is not sent directly to the bands, they send HTTP requests for each location.
|
||||
// When we have a weather update, set the default location to that location on the band.
|
||||
// TODO: Support for multiple weather locations
|
||||
|
||||
final String locationKey = "1.234,-5.678,xiaomi_accu:" + System.currentTimeMillis(); // dummy
|
||||
final String locationName = weatherSpec.location;
|
||||
|
||||
try {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
baos.write(Huami2021Service.WEATHER_CMD_SET_DEFAULT_LOCATION);
|
||||
baos.write((byte) 0x02); // ? 2 for current, 4 for default
|
||||
baos.write((byte) 0x00); // ?
|
||||
baos.write((byte) 0x00); // ?
|
||||
baos.write((byte) 0x00); // ?
|
||||
baos.write(locationKey.getBytes(StandardCharsets.UTF_8));
|
||||
baos.write((byte) 0x00); // ?
|
||||
baos.write(locationName.getBytes(StandardCharsets.UTF_8));
|
||||
baos.write((byte) 0x00); // ?
|
||||
|
||||
final TransactionBuilder builder = performInitialized("set weather location");
|
||||
writeToChunked2021(builder, Huami2021Service.CHUNKED2021_ENDPOINT_WEATHER, baos.toByteArray(), false);
|
||||
builder.queue(getQueue());
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to set weather location", e);
|
||||
}
|
||||
weatherService.onSendWeather(weatherSpecs);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1187,21 +1135,10 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
watchfaceService.requestCurrentWatchface(builder);
|
||||
}
|
||||
|
||||
protected void requestMTU(final TransactionBuilder builder) {
|
||||
writeToChunked2021(
|
||||
builder,
|
||||
CHUNKED2021_ENDPOINT_CONNECTION,
|
||||
CONNECTION_CMD_MTU_REQUEST,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void phase2Initialize(final TransactionBuilder builder) {
|
||||
LOG.info("2021 phase2Initialize...");
|
||||
setCurrentTimeWithService(builder);
|
||||
requestMTU(builder);
|
||||
requestBatteryInfo(builder);
|
||||
|
||||
final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences()
|
||||
.withPreference(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STATUS, null)
|
||||
@@ -1251,16 +1188,6 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
|
||||
final ZeppOsCoordinator coordinator = getCoordinator();
|
||||
|
||||
// TODO move this to a service
|
||||
setUserInfo(builder);
|
||||
|
||||
// TODO move this to a service
|
||||
for (final HuamiVibrationPatternNotificationType type : coordinator.getVibrationPatternNotificationTypes(gbDevice)) {
|
||||
// FIXME: Can we read these from the band?
|
||||
final String typeKey = type.name().toLowerCase(Locale.ROOT);
|
||||
setVibrationPattern(builder, HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey);
|
||||
}
|
||||
|
||||
for (AbstractZeppOsService service : mServiceMap.values()) {
|
||||
if (mSupportedServices.contains(service.getEndpoint())) {
|
||||
// Only initialize supported services
|
||||
@@ -1401,27 +1328,15 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
case CHUNKED2021_ENDPOINT_COMPAT:
|
||||
LOG.warn("Unexpected compat payload {}", GB.hexdump(payload));
|
||||
return;
|
||||
case CHUNKED2021_ENDPOINT_WEATHER:
|
||||
handle2021Weather(payload);
|
||||
return;
|
||||
case CHUNKED2021_ENDPOINT_WORKOUT:
|
||||
handle2021Workout(payload);
|
||||
return;
|
||||
case CHUNKED2021_ENDPOINT_HEARTRATE:
|
||||
handle2021HeartRate(payload);
|
||||
return;
|
||||
case CHUNKED2021_ENDPOINT_CONNECTION:
|
||||
handle2021Connection(payload);
|
||||
return;
|
||||
case CHUNKED2021_ENDPOINT_STEPS:
|
||||
handle2021Steps(payload);
|
||||
return;
|
||||
case CHUNKED2021_ENDPOINT_VIBRATION_PATTERNS:
|
||||
handle2021VibrationPatterns(payload);
|
||||
return;
|
||||
case CHUNKED2021_ENDPOINT_BATTERY:
|
||||
handle2021Battery(payload);
|
||||
return;
|
||||
default:
|
||||
LOG.warn("Unhandled 2021 payload {}", String.format("0x%04x", type));
|
||||
}
|
||||
@@ -1496,34 +1411,6 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
}
|
||||
}
|
||||
|
||||
protected void handle2021Weather(final byte[] payload) {
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
switch (payload[0]) {
|
||||
case WEATHER_CMD_DEFAULT_LOCATION_ACK:
|
||||
LOG.info("Weather default location ACK, status = {}", payload[1]);
|
||||
return;
|
||||
default:
|
||||
LOG.warn("Unexpected weather byte {}", String.format("0x%02x", payload[0]));
|
||||
}
|
||||
}
|
||||
|
||||
protected void handle2021Connection(final byte[] payload) {
|
||||
switch (payload[0]) {
|
||||
case CONNECTION_CMD_MTU_RESPONSE:
|
||||
final int mtu = BLETypeConversions.toUint16(payload, 1) + 3;
|
||||
LOG.info("Device announced MTU change: {}", mtu);
|
||||
setMtu(mtu);
|
||||
return;
|
||||
case CONNECTION_CMD_UNKNOWN_3:
|
||||
// Some ping? Band sometimes sends 0x03, phone replies with 0x04
|
||||
LOG.info("Got unknown 3, replying with unknown 4");
|
||||
writeToChunked2021("respond connection unknown 4", CHUNKED2021_ENDPOINT_CONNECTION, CONNECTION_CMD_UNKNOWN_4, false);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.warn("Unexpected connection payload byte {}", String.format("0x%02x", payload[0]));
|
||||
}
|
||||
|
||||
protected void handle2021Steps(final byte[] payload) {
|
||||
switch (payload[0]) {
|
||||
case STEPS_CMD_REPLY:
|
||||
@@ -1550,31 +1437,6 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
|
||||
}
|
||||
}
|
||||
|
||||
protected void handle2021VibrationPatterns(final byte[] payload) {
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
switch (payload[0]) {
|
||||
case VIBRATION_PATTERN_ACK:
|
||||
LOG.info("Vibration Patterns ACK, status = {}", payload[1]);
|
||||
return;
|
||||
default:
|
||||
LOG.warn("Unexpected Vibration Patterns payload byte {}", String.format("0x%02x", payload[0]));
|
||||
}
|
||||
}
|
||||
|
||||
protected void handle2021Battery(final byte[] payload) {
|
||||
if (payload[0] != BATTERY_REPLY) {
|
||||
LOG.warn("Unexpected battery payload byte {}", String.format("0x%02x", payload[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.length != 21) {
|
||||
LOG.warn("Unexpected battery payload length: {}", payload.length);
|
||||
}
|
||||
|
||||
final HuamiBatteryInfo batteryInfo = new HuamiBatteryInfo(subarray(payload, 1, payload.length));
|
||||
handleGBDeviceEvent(batteryInfo.toDeviceEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileDownloadFinish(final String url, final String filename, final byte[] data) {
|
||||
LOG.info("File received: url={} filename={} length={}", url, filename, data.length);
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/* Copyright (C) 2025 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
|
||||
|
||||
import static org.apache.commons.lang3.ArrayUtils.subarray;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||
|
||||
public class ZeppOsBatteryService extends AbstractZeppOsService {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsBatteryService.class);
|
||||
|
||||
private static final short ENDPOINT = 0x0029;
|
||||
|
||||
public static final byte CMD_BATTERY_REQUEST = 0x03;
|
||||
public static final byte CMD_BATTERY_REPLY = 0x04;
|
||||
|
||||
public ZeppOsBatteryService(final ZeppOsSupport support) {
|
||||
super(support, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getEndpoint() {
|
||||
return ENDPOINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(final byte[] payload) {
|
||||
if (payload[0] != CMD_BATTERY_REPLY) {
|
||||
LOG.warn("Unexpected battery payload byte {}", String.format("0x%02x", payload[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.length != 21) {
|
||||
LOG.warn("Unexpected battery payload length: {}", payload.length);
|
||||
}
|
||||
|
||||
final HuamiBatteryInfo batteryInfo = new HuamiBatteryInfo(subarray(payload, 1, payload.length));
|
||||
getSupport().evaluateGBDeviceEvent(batteryInfo.toDeviceEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(final TransactionBuilder builder) {
|
||||
requestBatteryInfo(builder);
|
||||
}
|
||||
|
||||
public void requestBatteryInfo(final TransactionBuilder builder) {
|
||||
LOG.debug("Requesting Battery Info");
|
||||
|
||||
write(builder, CMD_BATTERY_REQUEST);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2025 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||
|
||||
public class ZeppOsConnectionService extends AbstractZeppOsService {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsConnectionService.class);
|
||||
|
||||
private static final short ENDPOINT = 0x0015;
|
||||
|
||||
private static final byte CMD_MTU_REQUEST = 0x01;
|
||||
private static final byte CMD_MTU_RESPONSE = 0x02;
|
||||
private static final byte CMD_UNKNOWN_3 = 0x03;
|
||||
private static final byte CMD_UNKNOWN_4 = 0x04;
|
||||
|
||||
public ZeppOsConnectionService(final ZeppOsSupport support) {
|
||||
super(support, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getEndpoint() {
|
||||
return ENDPOINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(final byte[] payload) {
|
||||
switch (payload[0]) {
|
||||
case CMD_MTU_RESPONSE:
|
||||
final int mtu = BLETypeConversions.toUint16(payload, 1) + 3;
|
||||
LOG.info("Device announced MTU change: {}", mtu);
|
||||
getSupport().setMtu(mtu);
|
||||
return;
|
||||
case CMD_UNKNOWN_3:
|
||||
// Some ping? Band sometimes sends 0x03, phone replies with 0x04
|
||||
LOG.info("Got unknown 3, replying with unknown 4");
|
||||
write("respond connection unknown 4", CMD_UNKNOWN_4);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.warn("Unexpected connection payload byte {}", String.format("0x%02x", payload[0]));
|
||||
}
|
||||
|
||||
public void requestMTU(final TransactionBuilder builder) {
|
||||
write(builder, CMD_MTU_REQUEST);
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/* Copyright (C) 2025 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_HUAMI_VIBRATION_TRY_PREFIX;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class ZeppOsVibrationPatternsService extends AbstractZeppOsService {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsVibrationPatternsService.class);
|
||||
|
||||
private static final short ENDPOINT = 0x0018;
|
||||
|
||||
private static final byte VIBRATION_PATTERN_SET = 0x03;
|
||||
private static final byte VIBRATION_PATTERN_ACK = 0x04;
|
||||
|
||||
public ZeppOsVibrationPatternsService(final ZeppOsSupport support) {
|
||||
super(support, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getEndpoint() {
|
||||
return ENDPOINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(final byte[] payload) {
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
switch (payload[0]) {
|
||||
case VIBRATION_PATTERN_ACK:
|
||||
LOG.info("Vibration Patterns ACK, status = {}", payload[1]);
|
||||
return;
|
||||
default:
|
||||
LOG.warn("Unexpected Vibration Patterns payload byte {}", String.format("0x%02x", payload[0]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(final TransactionBuilder builder) {
|
||||
for (final HuamiVibrationPatternNotificationType type : getCoordinator().getVibrationPatternNotificationTypes(getSupport().getDevice())) {
|
||||
// FIXME: Can we read these from the band?
|
||||
final String typeKey = type.name().toLowerCase(Locale.ROOT);
|
||||
setVibrationPattern(builder, HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX + typeKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSendConfiguration(final String config, final Prefs prefs) {
|
||||
if (config.startsWith(HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX) ||
|
||||
config.startsWith(HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX) ||
|
||||
config.startsWith(HuamiConst.PREF_HUAMI_VIBRATION_TRY_PREFIX)) {
|
||||
final TransactionBuilder builder = new TransactionBuilder("send " + config);
|
||||
setVibrationPattern(builder, config);
|
||||
builder.queue(getSupport().getQueue());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected void setVibrationPattern(final TransactionBuilder builder, final String preferenceKey) {
|
||||
// The preference key has one of the 3 prefixes
|
||||
final String notificationTypeName = preferenceKey.replace(PREF_HUAMI_VIBRATION_COUNT_PREFIX, "")
|
||||
.replace(PREF_HUAMI_VIBRATION_PROFILE_PREFIX, "")
|
||||
.replace(PREF_HUAMI_VIBRATION_TRY_PREFIX, "")
|
||||
.toUpperCase(Locale.ROOT);
|
||||
final HuamiVibrationPatternNotificationType notificationType = HuamiVibrationPatternNotificationType.valueOf(notificationTypeName);
|
||||
final boolean isTry = preferenceKey.startsWith(PREF_HUAMI_VIBRATION_TRY_PREFIX);
|
||||
|
||||
final VibrationProfile vibrationProfile = HuamiCoordinator.getVibrationProfile(
|
||||
getSupport().getDevice().getAddress(),
|
||||
notificationType,
|
||||
true
|
||||
);
|
||||
|
||||
setVibrationPattern(builder, notificationType, isTry, vibrationProfile);
|
||||
}
|
||||
|
||||
private void setVibrationPattern(final TransactionBuilder builder,
|
||||
final HuamiVibrationPatternNotificationType notificationType,
|
||||
final boolean test,
|
||||
final VibrationProfile profile) {
|
||||
final int MAX_TOTAL_LENGTH_MS = 10_000; // 10 seconds, about as long as Mi Fit allows
|
||||
|
||||
// The on-off sequence, until the max total length is reached
|
||||
final List<Short> onOff = HuamiUtils.truncateVibrationsOnOff(profile, MAX_TOTAL_LENGTH_MS);
|
||||
|
||||
final ByteBuffer buf = ByteBuffer.allocate(5 + 2 * onOff.size());
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
buf.put(VIBRATION_PATTERN_SET);
|
||||
buf.put(notificationType.getCode());
|
||||
buf.put((byte) (profile != null ? 1 : 0)); // 1 for custom, 0 for device default
|
||||
buf.put((byte) (test ? 1 : 0));
|
||||
buf.put((byte) (onOff.size() / 2));
|
||||
|
||||
for (Short time : onOff) {
|
||||
buf.putShort(time);
|
||||
}
|
||||
|
||||
write(builder, buf.array());
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/* Copyright (C) 2025 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||
|
||||
public class ZeppOsWeatherService extends AbstractZeppOsService {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsWeatherService.class);
|
||||
|
||||
private static final short ENDPOINT = 0x000e;
|
||||
|
||||
private static final byte CMD_SET_DEFAULT_LOCATION = 0x09;
|
||||
private static final byte CMD_DEFAULT_LOCATION_ACK = 0x0a;
|
||||
|
||||
public ZeppOsWeatherService(final ZeppOsSupport support) {
|
||||
super(support, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getEndpoint() {
|
||||
return ENDPOINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePayload(final byte[] payload) {
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
switch (payload[0]) {
|
||||
case CMD_DEFAULT_LOCATION_ACK:
|
||||
LOG.info("Weather default location ACK, status = {}", payload[1]);
|
||||
return;
|
||||
default:
|
||||
LOG.warn("Unexpected weather byte {}", String.format("0x%02x", payload[0]));
|
||||
}
|
||||
}
|
||||
|
||||
public void onSendWeather(final ArrayList<WeatherSpec> weatherSpecs) {
|
||||
final WeatherSpec weatherSpec = weatherSpecs.get(0);
|
||||
|
||||
// Weather is not sent directly to the bands, they send HTTP requests for each location.
|
||||
// When we have a weather update, set the default location to that location on the band.
|
||||
// TODO: Support for multiple weather locations
|
||||
|
||||
final String locationKey = "1.234,-5.678,xiaomi_accu:" + System.currentTimeMillis(); // dummy
|
||||
final String locationName = weatherSpec.location;
|
||||
|
||||
try {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
baos.write(CMD_SET_DEFAULT_LOCATION);
|
||||
baos.write((byte) 0x02); // ? 2 for current, 4 for default
|
||||
baos.write((byte) 0x00); // ?
|
||||
baos.write((byte) 0x00); // ?
|
||||
baos.write((byte) 0x00); // ?
|
||||
baos.write(locationKey.getBytes(StandardCharsets.UTF_8));
|
||||
baos.write((byte) 0x00); // ?
|
||||
baos.write(locationName.getBytes(StandardCharsets.UTF_8));
|
||||
baos.write((byte) 0x00); // ?
|
||||
|
||||
final TransactionBuilder builder = new TransactionBuilder("set weather location");
|
||||
write(builder, baos.toByteArray());
|
||||
builder.queue(getSupport().getQueue());
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Failed to set weather location", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user