1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-26 02:25:50 +01:00

Casio GW-B5600: initial support

This commit is contained in:
Johannes Krude 2023-07-20 15:15:05 +02:00 committed by José Rebelo
parent 91415e2500
commit 7cb060c9fe
12 changed files with 779 additions and 6 deletions

View File

@ -8,6 +8,7 @@
* Zepp OS: Add PAI charts * Zepp OS: Add PAI charts
* Bump target SDK version to 31 * Bump target SDK version to 31
* Fix media button control for some applications * Fix media button control for some applications
* Initial support for Casio GW-B5600
#### 0.75.1 #### 0.75.1
* Fix Weather Notification integration * Fix Weather Notification integration

View File

@ -51,6 +51,8 @@ vendor's servers.
- Casio GB-X6900B - Casio GB-X6900B
- Casio GB-6900B - Casio GB-6900B
- Casio GB-5600B - Casio GB-5600B
- Casio GW-B5600
- Casio GMW-B5000 (untested)
- Casio STB-1000 - Casio STB-1000
- Casio GBX-100 - Casio GBX-100
- Casio GBD-100 - Casio GBD-100

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2018-2021 Andreas Böhler /* Copyright (C) 2018-2023 Andreas Böhler, Johannes Krude
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -118,6 +118,7 @@ public final class CasioConstants {
MODEL_CASIO_5600B, MODEL_CASIO_5600B,
MODEL_CASIO_GBX100, MODEL_CASIO_GBX100,
MODEL_CASIO_STB1000, MODEL_CASIO_STB1000,
MODEL_CASIO_GWB5600,
} }
public enum ConfigurationOption { public enum ConfigurationOption {
@ -157,6 +158,7 @@ public final class CasioConstants {
put("CASIO_VERSION_INFORMATION", (byte) 0x20); put("CASIO_VERSION_INFORMATION", (byte) 0x20);
put("CASIO_DST_WATCH_STATE", (byte) 0x1d); put("CASIO_DST_WATCH_STATE", (byte) 0x1d);
put("CASIO_DST_SETTING", (byte) 0x1e); put("CASIO_DST_SETTING", (byte) 0x1e);
put("CASIO_WORLD_CITY", (byte) 0x1f);
put("CASIO_SERVICE_DISCOVERY_MANAGER", (byte) 0x47); put("CASIO_SERVICE_DISCOVERY_MANAGER", (byte) 0x47);
put("CASIO_CURRENT_TIME", (byte) 0x09); put("CASIO_CURRENT_TIME", (byte) 0x09);
put("CASIO_SETTING_FOR_USER_PROFILE", (byte) 0x45); put("CASIO_SETTING_FOR_USER_PROFILE", (byte) 0x45);

View File

@ -0,0 +1,139 @@
/* Copyright (C) 2023 Johannes Krude
based on code from BlueWatcher, https://github.com/masterjc/bluewatcher
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.casio.gwb5600;
import java.util.Collection;
import java.util.Collections;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.bluetooth.le.ScanFilter;
import android.os.ParcelUuid;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioDeviceCoordinator;
public class CasioGWB5600DeviceCoordinator extends CasioDeviceCoordinator {
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
String name = candidate.getDevice().getName();
if (name != null) {
if (name.equals("CASIO GW-B5600") ||
name.equals("CASIO GMW-B5000")) {
return DeviceType.CASIOGWB5600;
}
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.CASIOGWB5600;
}
@Override
public int getBondingStyle(){
return BONDING_STYLE_BOND;
}
@Override
public Class<? extends Activity> getPairingActivity() {
return null;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
}
@Override
public int getAlarmSlotCount(GBDevice device) {
return 0;
}
@Override
public boolean supportsCalendarEvents() {
return false;
}
@Override
public boolean supportsRealtimeData() {
return false;
}
@Override
public boolean supportsFindDevice() {
return false;
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public boolean supportsActivityDataFetching() {
return false;
}
@Override
public boolean supportsActivityTracking() {
return false;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null;
}
@Override
public boolean supportsScreenshots() {
return false;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return false;
}
@Override
public boolean supportsAppsManagement(final GBDevice device) {
return false;
}
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return null;
}
}

View File

@ -1,8 +1,9 @@
/* Copyright (C) 2015-2020 Andreas Böhler, Andreas Shimokawa, Carsten /* Copyright (C) 2015-2023 Andreas Böhler, Andreas Shimokawa, Carsten
Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Gordon Williams, Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Gordon Williams,
Jean-François Greffier, João Paulo Barraca, José Rebelo, Kranz, ladbsoft, Jean-François Greffier, João Paulo Barraca, José Rebelo, Kranz, ladbsoft,
Manuel Ruß, maxirnilian, Pavel, Pavel Elagin, protomors, Quallenauge, Manuel Ruß, maxirnilian, Pavel, Pavel Elagin, protomors, Quallenauge,
Sami Alaoui, Sebastian Kranz, Sophanimus, tiparega, Vadim Kaushan Sami Alaoui, Sebastian Kranz, Sophanimus, tiparega, Vadim Kaushan,
Johannes Krude
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -92,6 +93,7 @@ public enum DeviceType {
ROIDMI3(112, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi3), ROIDMI3(112, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi3),
CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900), CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900),
CASIOGBX100(121, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogbx100), CASIOGBX100(121, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogbx100),
CASIOGWB5600(122, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogbx100),
MISCALE2(131, R.drawable.ic_device_miscale2, R.drawable.ic_device_miscale2_disabled, R.string.devicetype_miscale2), MISCALE2(131, R.drawable.ic_device_miscale2, R.drawable.ic_device_miscale2_disabled, R.string.devicetype_miscale2),
BFH16(140, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_bfh16), BFH16(140, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_bfh16),
MAKIBESHR3(150, R.drawable.ic_device_default, R.drawable.ic_device_hplus_disabled, R.string.devicetype_makibes_hr3), MAKIBESHR3(150, R.drawable.ic_device_default, R.drawable.ic_device_hplus_disabled, R.string.devicetype_makibes_hr3),

View File

@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDev
import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.BinarySensorSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.binary_sensor.BinarySensorSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gb6900.CasioGB6900DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gb6900.CasioGB6900DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100.CasioGBX100DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gbx100.CasioGBX100DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gwb5600.CasioGWB5600DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.domyos.DomyosT540Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.domyos.DomyosT540Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.fitpro.FitProDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.fitpro.FitProDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support.FlipperZeroSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support.FlipperZeroSupport;
@ -293,6 +294,8 @@ public class DeviceSupportFactory {
return new ServiceDeviceSupport(new CasioGB6900DeviceSupport()); return new ServiceDeviceSupport(new CasioGB6900DeviceSupport());
case CASIOGBX100: case CASIOGBX100:
return new ServiceDeviceSupport(new CasioGBX100DeviceSupport()); return new ServiceDeviceSupport(new CasioGBX100DeviceSupport());
case CASIOGWB5600:
return new ServiceDeviceSupport(new CasioGWB5600DeviceSupport());
case MISCALE2: case MISCALE2:
return new ServiceDeviceSupport(new MiScale2DeviceSupport()); return new ServiceDeviceSupport(new MiScale2DeviceSupport());
case BFH16: case BFH16:

View File

@ -1,5 +1,6 @@
/* Copyright (C) 2015-2021 Andreas Böhler, Andreas Shimokawa, Carsten /* Copyright (C) 2015-2023 Andreas Böhler, Andreas Shimokawa, Carsten
Pfeiffer, Daniel Dakhno, Daniele Gobbetti, JohnnySun, José Rebelo Pfeiffer, Daniel Dakhno, Daniele Gobbetti, JohnnySun, José Rebelo,
Johannes Krude
This file is part of Gadgetbridge. This file is part of Gadgetbridge.
@ -82,6 +83,12 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
return mQueue.connect(); return mQueue.connect();
} }
public void disconnect() {
if (mQueue != null) {
mQueue.disconnect();
}
}
@Override @Override
public void setAutoReconnect(boolean enable) { public void setAutoReconnect(boolean enable) {
super.setAutoReconnect(enable); super.setAutoReconnect(enable);

View File

@ -0,0 +1,64 @@
/* Copyright (C) 2023 Johannes Krude
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gwb5600;
import java.util.UUID;
import java.io.IOException;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.Casio2C2DSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gwb5600.InitOperation;
public class CasioGWB5600DeviceSupport extends Casio2C2DSupport {
private static final Logger LOG = LoggerFactory.getLogger(CasioGWB5600DeviceSupport.class);
public CasioGWB5600DeviceSupport() {
super(LOG);
}
@Override
public boolean useAutoConnect() {
return false;
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
// remove this workaround once Gadgetbridge does discovery on initial pairing
if (getCharacteristic(CasioConstants.CASIO_READ_REQUEST_FOR_ALL_FEATURES_CHARACTERISTIC_UUID) == null ||
getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID) == null) {
LOG.info("Reconnecting to discover characteristics");
disconnect();
connect();
return builder;
}
try {
new InitOperation(this, builder).perform();
} catch (IOException e) {
GB.toast(getContext(), "Initializing watch failed", Toast.LENGTH_SHORT, GB.ERROR, e);
}
return builder;
}
}

View File

@ -0,0 +1,421 @@
/* Copyright (C) 2023 Johannes Krude
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gwb5600;
import java.util.List;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalTime;
import org.threeten.bp.Month;
import org.threeten.bp.DayOfWeek;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.zone.ZoneRules;
import org.threeten.bp.zone.ZoneOffsetTransition;
import org.threeten.bp.zone.ZoneOffsetTransitionRule;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
public class CasioGWB5600TimeZone {
/*
There are six clocks on the Casio GW-B5600
0 is the main clock
1-5 are the world clocks
0x1d 00 01 DST0 DST1 TZ0A TZ0B TZ1A TZ1B ff ff ff ff ff
0x1d 02 03 DST2 DST3 TZ2A TZ2B TZ3A TZ3B ff ff ff ff ff
0x1d 04 05 DST4 DST5 TZ4A TZ4B TZ5A TZ5B ff ff ff ff ff
DST: bitwise flags; bit0: DST on, bit1: DST auto
0x1e 0-5 TZ_A TZ_B TZ_OFF TZ_DSTOFF TZ_DSTRULES
A/B seem to be ignored by the watch
OFF & DSTOFF in 15 minute intervals
0x1f 0-5 (18 bytes ASCII TZ name)
Timezones selectable on the watch:
A B OFF DSTOFF DSTRULES
BAKER ISLAND 39 01 D0 04 00
PAGO PAGO D7 00 D4 04 00
HONOLULU 7B 00 D8 04 00
MARQUESAS ISLANDS 3A 01 DA 04 00
ANCHORAGE 0C 00 DC 04 01
LOS ANGELES A1 00 E0 04 01
DENVER 54 00 E4 04 01
CHICAGO 42 00 E8 04 01
NEW YORK CA 00 EC 04 01
HALIFAX 71 00 F0 04 01
ST.JOHN'S 0C 01 F2 04 01
RIO DE JANEIRO F1 00 F4 04 00
F.DE NORONHA 62 00 F8 04 00
PRAIA E9 00 FC 04 00
UTC 00 00 00 00 00
LONDON A0 00 00 04 02
PARIS DC 00 04 04 02
ATHENS 13 00 08 04 02
JEDDAH 85 00 0C 04 00
TEHRAN 16 01 0E 04 2B
DUBAI 5B 00 10 04 00
KABUL 88 00 12 04 00
KARACHI 8B 00 14 04 00
DELHI 52 00 16 04 00
KATHMANDU 8C 00 17 04 00
DHAKA 56 00 18 04 00
YANGON 2F 01 1A 04 00
BANGKOK 1C 00 1C 04 00
HONG KONG 7A 00 20 04 00
PYONGYANG EA 00 24 04 00
EUCLA 36 01 23 04 00
TOKYO 19 01 24 04 00
ADELAIDE 05 00 26 04 04
SYDNEY 0F 01 28 04 04
LORD HOWE ISLAND 37 01 2A 02 12
NOUMEA CD 00 2C 04 00
WELLINGTON 2B 01 30 04 05
CHATHAM ISLANDS 3F 00 33 04 17
NUKUALOFA D0 00 34 04 00
KIRITIMATI 93 00 38 04 00
Timezones NOT selectable on the watch:
A B OFF DSTOFF DSTRULES
CASABLANCA 3A 00 00 04 0F
BEIRUT 22 00 08 04 0C
JERUSALEM 86 00 08 04 2A
NORFOLK ISLAND 38 01 2C 04 04
EASTER ISLAND 5E 00 E8 04 1C
HAVANA 75 00 EC 04 15
SANTIAGO 02 01 F0 04 1B
ASUNCION 12 00 F0 04 09
PONTA DELGADA E4 00 FC 04 02
*/
private byte[] name;
private byte[] number;
private byte offset;
private byte dstOffset;
private byte dstRules;
private byte dstSetting;
// bitwise flags
final static byte DST_SETTING_ON = 1;
final static byte DST_SETTING_AUTO = 2;
private CasioGWB5600TimeZone(byte[] name, byte[] number, byte offset, byte dstOffset, byte dstRules, byte dstSetting) {
this.name = name;
this.number = number;
this.offset = offset;
this.dstOffset = dstOffset;
this.dstRules = dstRules;
this.dstSetting = dstSetting;
}
static public byte[] dstWatchStateRequest(int slot) {
// request only even slots, the response will also contain the next odd slot
return new byte[] {
CasioConstants.characteristicToByte.get("CASIO_DST_WATCH_STATE"),
(byte) slot};
}
static public byte[] dstWatchStateBytes(int slotA, CasioGWB5600TimeZone zoneA, int slotB, CasioGWB5600TimeZone zoneB) {
return new byte[] {
CasioConstants.characteristicToByte.get("CASIO_DST_WATCH_STATE"),
(byte) slotA,
(byte) slotB,
zoneA.dstSetting,
zoneB.dstSetting,
zoneA.number[0],
zoneA.number[1],
zoneB.number[0],
zoneB.number[1],
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
}
static public byte[] dstSettingRequest(int slot) {
return new byte[] {
CasioConstants.characteristicToByte.get("CASIO_DST_SETTING"),
(byte) slot};
}
public byte[] dstSettingBytes(int slot) {
return new byte[] {
CasioConstants.characteristicToByte.get("CASIO_DST_SETTING"),
(byte) slot,
number[0],
number[1],
offset,
dstOffset,
dstRules};
}
static public byte[] worldCityRequest(int slot) {
return new byte[] {
CasioConstants.characteristicToByte.get("CASIO_WORLD_CITY"),
(byte) slot};
}
public byte[] worldCityBytes(int slot) {
byte[] bytes = {
CasioConstants.characteristicToByte.get("CASIO_WORLD_CITY"),
(byte) slot,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
System.arraycopy(name, 0, bytes, 2, Math.min(name.length, 18));
return bytes;
}
static CasioGWB5600TimeZone fromWatchResponses(List<byte[]> responses, int slot) {
byte[] name = "unknown".getBytes(StandardCharsets.US_ASCII);
byte[] number = {0,0};
byte offset = 0;
byte dstOffset = 0;
byte dstRules = 0;
byte dstSetting = 0;
for (byte[] response: responses) {
if (response[0] == CasioConstants.characteristicToByte.get("CASIO_DST_WATCH_STATE") && response.length >= 9) {
if (response[1] == slot) {
dstSetting = response[3];
number = new byte[] {response[5], response[6]};
}
if (response[2] == slot) {
dstSetting = response[4];
number = new byte[] {response[7], response[8]};
}
} else if (response[0] == CasioConstants.characteristicToByte.get("CASIO_DST_SETTING") && response.length >= 7 && response[1] == slot) {
number = new byte[] {response[2], response[3]};
offset = response[4];
dstOffset = response[5];
dstRules = response[6];
} else if (response[0] == CasioConstants.characteristicToByte.get("CASIO_WORLD_CITY") && response.length >= 2 && response[1] == slot) {
int size;
for (size = 0; size < response.length-2; size++) {
if (response[2+size] == 0) {
break;
}
}
name = Arrays.copyOfRange(response, 2, 2+size);
}
}
return new CasioGWB5600TimeZone(name, number, offset, dstOffset, dstRules, dstSetting);
}
static CasioGWB5600TimeZone fromZoneId(ZoneId zone, Instant time, String zoneName) {
ZoneRules rules = zone.getRules();
byte[] name = zoneName.getBytes(StandardCharsets.US_ASCII);
byte[] number = {0,0};
byte offset = (byte) (rules.getStandardOffset(time).getTotalSeconds() / 60 / 15);
byte dstOffset = 0; // can be set only later once we now the next transition
byte dstRules = 0;
byte dstSetting = 0;
ZoneOffsetTransition next = rules.nextTransition(time);
int nextYear = next.getInstant().atZone(zone).getYear();
ZoneOffsetTransition next2 = (next == null ? null: rules.nextTransition(next.getInstant()));
int next2Year = (next2 == null ? 0 : next2.getInstant().atZone(zone).getYear());
if (next == null) {
// no DST is easy
dstSetting = DST_SETTING_AUTO;
} else {
// we need an Instant with DST on to get the dstOffset
if (rules.isDaylightSavings(time)) {
dstOffset = (byte) (rules.getDaylightSavings(time).getSeconds() / 60 / 15);
} else {
dstOffset = (byte) (rules.getDaylightSavings(next.getInstant().plusSeconds(1)).getSeconds() / 60 / 15);
}
// find Watch DST rules
dstRules = findWatchDstRules(offset, dstOffset, next, nextYear, next2, next2Year);
if (dstRules != 0) {
// DST AUTO if the watch knows at least the next transition
// otherwise will result in incorrect time between actual DST change and next sync
dstSetting |= DST_SETTING_AUTO;
}
// if DST bit is incorrect, the watch will substract or add time
if (rules.isDaylightSavings(time)) {
dstSetting |= DST_SETTING_ON;
}
}
return new CasioGWB5600TimeZone(name, number, offset, dstOffset, dstRules, dstSetting);
}
// We are searching for watch DST rules which match the next two transitions.
// In case only the next transition matches a rule, the rule is still used
static byte findWatchDstRules(byte offset, byte dstOffset, ZoneOffsetTransition next, int nextYear, ZoneOffsetTransition next2, int next2Year) {
WatchDstRules candidate = null;
for (WatchDstRules r: watchDstRules) {
int match = r.matches(offset, dstOffset, next, nextYear, next2, next2Year);
if (match == 2)
return r.dstRules;
if (match == 1 && candidate == null)
candidate = r;
}
if (candidate != null)
return candidate.dstRules;
return (byte) 0;
}
static class WatchDstRules {
final byte offset;
final byte dstOffset;
final byte dstRules;
final ZoneOffsetTransitionRule ruleA;
final ZoneOffsetTransitionRule ruleB;
WatchDstRules(int offset, int dstOffset, int dstRules, ZoneOffsetTransitionRule ruleA, ZoneOffsetTransitionRule ruleB) {
this.offset = (byte) offset;
this.dstOffset = (byte) dstOffset;
this.dstRules = (byte) dstRules;
this.ruleA = ruleA;
this.ruleB = ruleB;
}
// returns how many of the next transitions match the rules
int matches(byte offset, byte dstOffset, ZoneOffsetTransition next, int nextYear, ZoneOffsetTransition next2, int next2Year) {
if (offset != this.offset || dstOffset != this.dstOffset)
return -1;
if (this.ruleA.createTransition(nextYear).equals(next)) {
if (this.ruleB.createTransition(next2Year).equals(next2))
return 2;
return 1;
}
if (this.ruleB.createTransition(nextYear).equals(next)) {
if (this.ruleA.createTransition(next2Year).equals(next2))
return 2;
return 1;
}
return 0;
}
}
// All known Watch DST Rules
// Possibly incomplete and incorrect
//
// When adding new WatchDstRules, test them:
// 1. Apply the following changes to CasioGWB5600InitOperation
// + static int nextNext = 0;
// private void setClocks(TransactionBuilder builder) {
// ZoneId tz = ZoneId.systemDefault();
// Instant now = Instant.now().plusSeconds(2);
// + now = tz.getRules().nextTransition(now).getInstant();
// + if (nextNext != 0)
// + now = tz.getRules().nextTransition(now).getInstant();
// + nextNext ^= 1;
// + now = now.minusSeconds(10);
// 2. Sync the time on the watch and observe a DST change
// 3. Repeat the time sync to obseve a second DST change
static final WatchDstRules[] watchDstRules = {
// Europe/London
new WatchDstRules(0x00, 0x04, 0x02,
ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(0), ZoneOffset.ofTotalSeconds(0), ZoneOffset.ofTotalSeconds(3600)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(0), ZoneOffset.ofTotalSeconds(3600), ZoneOffset.ofTotalSeconds(0))),
// Europe/Paris
new WatchDstRules(0x04, 0x04, 0x02,
ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(3600), ZoneOffset.ofTotalSeconds(3600), ZoneOffset.ofTotalSeconds(7200)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(3600), ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(3600))),
// Europe/Athen
new WatchDstRules(0x08, 0x04, 0x02,
ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800), ZoneOffset.ofTotalSeconds(7200))),
// Asia/Beirut
new WatchDstRules(0x08, 0x04, 0x0C,
ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800), ZoneOffset.ofTotalSeconds(7200))),
// Asia/Jerusalem
new WatchDstRules(0x08, 0x04, 0x2A,
ZoneOffsetTransitionRule.of(Month.MARCH, 23, DayOfWeek.FRIDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(7200), ZoneOffset.ofTotalSeconds(10800), ZoneOffset.ofTotalSeconds(7200))),
// Australia/Adelaide
new WatchDstRules(0x26, 0x04, 0x04,
ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(34200), ZoneOffset.ofTotalSeconds(37800), ZoneOffset.ofTotalSeconds(34200)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(34200), ZoneOffset.ofTotalSeconds(34200), ZoneOffset.ofTotalSeconds(37800))),
// Australia/Sydney
new WatchDstRules(0x28, 0x04, 0x04,
ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(36000), ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(36000)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(36000), ZoneOffset.ofTotalSeconds(36000), ZoneOffset.ofTotalSeconds(39600))),
// Australia/Lord_Howe
new WatchDstRules(0x2A, 0x02, 0x12,
ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(37800), ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(37800)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(37800), ZoneOffset.ofTotalSeconds(37800), ZoneOffset.ofTotalSeconds(39600))),
// Pacific/Norfolk
new WatchDstRules(0x2C, 0x04, 0x04,
ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(43200), ZoneOffset.ofTotalSeconds(39600)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(39600), ZoneOffset.ofTotalSeconds(43200))),
// Pacific/Auckland
new WatchDstRules(0x30, 0x04, 0x05,
ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(43200), ZoneOffset.ofTotalSeconds(46800), ZoneOffset.ofTotalSeconds(43200)),
ZoneOffsetTransitionRule.of(Month.SEPTEMBER, 24, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(43200), ZoneOffset.ofTotalSeconds(43200), ZoneOffset.ofTotalSeconds(46800))),
// Pacific/Chatham
new WatchDstRules(0x33, 0x04, 0x17,
ZoneOffsetTransitionRule.of(Month.APRIL, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 45), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(45900), ZoneOffset.ofTotalSeconds(49500), ZoneOffset.ofTotalSeconds(45900)),
ZoneOffsetTransitionRule.of(Month.SEPTEMBER, 24, DayOfWeek.SUNDAY, LocalTime.of(2, 45), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(45900), ZoneOffset.ofTotalSeconds(45900), ZoneOffset.ofTotalSeconds(49500))),
// America/Anchorage
new WatchDstRules(0xDC, 0x04, 0x01,
ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-32400), ZoneOffset.ofTotalSeconds(-32400), ZoneOffset.ofTotalSeconds(-28800)),
ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-32400), ZoneOffset.ofTotalSeconds(-28800), ZoneOffset.ofTotalSeconds(-32400))),
// America/Los_Angeles
new WatchDstRules(0xE0, 0x04, 0x01,
ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-28800), ZoneOffset.ofTotalSeconds(-28800), ZoneOffset.ofTotalSeconds(-25200)),
ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-28800), ZoneOffset.ofTotalSeconds(-25200), ZoneOffset.ofTotalSeconds(-28800))),
// America/Denver
new WatchDstRules(0xE4, 0x04, 0x01,
ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-25200), ZoneOffset.ofTotalSeconds(-25200), ZoneOffset.ofTotalSeconds(-21600)),
ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-25200), ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-25200))),
// America/Chicago
new WatchDstRules(0xE8, 0x04, 0x01,
ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-18000)),
ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-21600))),
// Chile/EasterIsland
new WatchDstRules(0xE8, 0x04, 0x1C,
ZoneOffsetTransitionRule.of(Month.APRIL, 2, DayOfWeek.SUNDAY, LocalTime.of(3, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-21600)),
ZoneOffsetTransitionRule.of(Month.SEPTEMBER, 2, DayOfWeek.SUNDAY, LocalTime.of(4, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-21600), ZoneOffset.ofTotalSeconds(-18000))),
// America/New_York
new WatchDstRules(0xEC, 0x04, 0x01,
ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-14400)),
ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-18000))),
// America/Havana
new WatchDstRules(0xEC, 0x04, 0x15,
ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-14400)),
ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofTotalSeconds(-18000), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-18000))),
// America/Santiago
new WatchDstRules(0xF0, 0x04, 0x1B,
ZoneOffsetTransitionRule.of(Month.APRIL, 2, DayOfWeek.SUNDAY, LocalTime.of(3, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800), ZoneOffset.ofTotalSeconds(-14400)),
ZoneOffsetTransitionRule.of(Month.SEPTEMBER, 2, DayOfWeek.SUNDAY, LocalTime.of(4, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800))),
// America/Halifax
new WatchDstRules(0xF0, 0x04, 0x01,
ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800)),
ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800), ZoneOffset.ofTotalSeconds(-14400))),
// America/Asuncion
new WatchDstRules(0xF0, 0x04, 0x09,
ZoneOffsetTransitionRule.of(Month.MARCH, 22, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800), ZoneOffset.ofTotalSeconds(-14400)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 1, DayOfWeek.SUNDAY, LocalTime.of(0, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-14400), ZoneOffset.ofTotalSeconds(-10800))),
// America/St_Johns
new WatchDstRules(0xF2, 0x04, 0x01,
ZoneOffsetTransitionRule.of(Month.MARCH, 8, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-12600), ZoneOffset.ofTotalSeconds(-12600), ZoneOffset.ofTotalSeconds(-9000)),
ZoneOffsetTransitionRule.of(Month.NOVEMBER, 1, DayOfWeek.SUNDAY, LocalTime.of(2, 0), false, ZoneOffsetTransitionRule.TimeDefinition.WALL, ZoneOffset.ofTotalSeconds(-12600), ZoneOffset.ofTotalSeconds(-9000), ZoneOffset.ofTotalSeconds(-12600))),
// Atlantic/Azores
new WatchDstRules(0xFC, 0x04, 0x02,
ZoneOffsetTransitionRule.of(Month.MARCH, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-3600), ZoneOffset.ofTotalSeconds(-3600), ZoneOffset.ofTotalSeconds(0)),
ZoneOffsetTransitionRule.of(Month.OCTOBER, 25, DayOfWeek.SUNDAY, LocalTime.of(1, 0), false, ZoneOffsetTransitionRule.TimeDefinition.UTC, ZoneOffset.ofTotalSeconds(-3600), ZoneOffset.ofTotalSeconds(0), ZoneOffset.ofTotalSeconds(-3600))),
};
}

View File

@ -0,0 +1,129 @@
/* Copyright (C) 2023 Johannes Krude
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gwb5600;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.LinkedList;
import java.util.UUID;
import java.util.Locale;
import org.threeten.bp.Instant;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZonedDateTime;
import org.threeten.bp.format.TextStyle;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.CasioConstants;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gwb5600.CasioGWB5600DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.gwb5600.CasioGWB5600TimeZone;
public class InitOperation extends AbstractBTLEOperation<CasioGWB5600DeviceSupport> {
private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class);
private final TransactionBuilder builder;
private final CasioGWB5600DeviceSupport support;
private List<byte[]> responses = new LinkedList<byte[]>();
public InitOperation(CasioGWB5600DeviceSupport support, TransactionBuilder builder) {
super(support);
this.support = support;
this.builder = builder;
builder.setCallback(this);
}
@Override
public TransactionBuilder performInitialized(String taskName) throws IOException {
throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
}
@Override
protected void doPerform() {//throws IOException {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
builder.notify(getCharacteristic(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID), true);
for (int i = 1; i < 6; i++) {
if (i%2 == 1)
support.writeAllFeaturesRequest(builder, CasioGWB5600TimeZone.dstWatchStateRequest(i-1));
support.writeAllFeaturesRequest(builder, CasioGWB5600TimeZone.dstSettingRequest(i));
support.writeAllFeaturesRequest(builder, CasioGWB5600TimeZone.worldCityRequest(i));
}
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (characteristicUUID.equals(CasioConstants.CASIO_ALL_FEATURES_CHARACTERISTIC_UUID) && data.length > 0 &&
(data[0] == CasioConstants.characteristicToByte.get("CASIO_DST_WATCH_STATE") ||
data[0] == CasioConstants.characteristicToByte.get("CASIO_DST_SETTING") ||
data[0] == CasioConstants.characteristicToByte.get("CASIO_WORLD_CITY"))) {
responses.add(data);
if (responses.size() == 13) {
TransactionBuilder builder = createTransactionBuilder("setClocks");
setClocks(builder);
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
builder.setCallback(null);
builder.queue(support.getQueue());
operationFinished();
}
return true;
} else {
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
return super.onCharacteristicChanged(gatt, characteristic);
}
}
private void setClocks(TransactionBuilder builder) {
ZoneId tz = ZoneId.systemDefault();
Instant now = Instant.now().plusSeconds(2);
CasioGWB5600TimeZone[] timezones = {
CasioGWB5600TimeZone.fromZoneId(tz, now, tz.getDisplayName(TextStyle.SHORT, Locale.getDefault())),
CasioGWB5600TimeZone.fromWatchResponses(responses, 1),
CasioGWB5600TimeZone.fromWatchResponses(responses, 2),
CasioGWB5600TimeZone.fromWatchResponses(responses, 3),
CasioGWB5600TimeZone.fromWatchResponses(responses, 4),
CasioGWB5600TimeZone.fromWatchResponses(responses, 5),
};
for (int i = 5; i >= 0; i--) {
if (i%2 == 0)
support.writeAllFeatures(builder, CasioGWB5600TimeZone.dstWatchStateBytes(i, timezones[i], i+1, timezones[i+1]));
support.writeAllFeatures(builder, timezones[i].dstSettingBytes(i));
support.writeAllFeatures(builder, timezones[i].worldCityBytes(i));
}
support.writeCurrentTime(builder, ZonedDateTime.ofInstant(now, tz));
}
@Override
protected void operationFinished() {
operationStatus = OperationStatus.FINISHED;
}
}

View File

@ -59,6 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.gb6900.CasioGB6900DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.casio.gb6900.CasioGB6900DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100.CasioGBX100DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100.CasioGBX100DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.gwb5600.CasioGWB5600DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.domyos.DomyosT540Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.domyos.DomyosT540Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBuds2DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBuds2DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.flipper.zero.FlipperZeroCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.flipper.zero.FlipperZeroCoordinator;
@ -336,6 +337,7 @@ public class DeviceHelper {
result.add(new Y5Coordinator()); result.add(new Y5Coordinator());
result.add(new CasioGB6900DeviceCoordinator()); result.add(new CasioGB6900DeviceCoordinator());
result.add(new CasioGBX100DeviceCoordinator()); result.add(new CasioGBX100DeviceCoordinator());
result.add(new CasioGWB5600DeviceCoordinator());
result.add(new BFH16DeviceCoordinator()); result.add(new BFH16DeviceCoordinator());
result.add(new MijiaLywsd02Coordinator()); result.add(new MijiaLywsd02Coordinator());
result.add(new ITagCoordinator()); result.add(new ITagCoordinator());

View File

@ -1291,6 +1291,7 @@
<string name="devicetype_y5">Y5</string> <string name="devicetype_y5">Y5</string>
<string name="devicetype_casiogb6900">Casio GB-6900</string> <string name="devicetype_casiogb6900">Casio GB-6900</string>
<string name="devicetype_casiogbx100">Casio GBX-100</string> <string name="devicetype_casiogbx100">Casio GBX-100</string>
<string name="devicetype_casiogwb5600">Casio GW-B5600</string>
<string name="devicetype_miscale2">Mi Scale 2</string> <string name="devicetype_miscale2">Mi Scale 2</string>
<string name="devicetype_itag">iTag</string> <string name="devicetype_itag">iTag</string>
<string name="devicetype_bfh16">BFH-16</string> <string name="devicetype_bfh16">BFH-16</string>
@ -1523,7 +1524,7 @@
<string name="about_contributors">Contributors</string> <string name="about_contributors">Contributors</string>
<string name="about_core_team_members" translatable="false">Andreas Shimokawa\nCarsten Pfeiffer\nDaniele Gobbetti</string> <string name="about_core_team_members" translatable="false">Andreas Shimokawa\nCarsten Pfeiffer\nDaniele Gobbetti</string>
<string name="about_additional_device_support">Additional device support</string> <string name="about_additional_device_support">Additional device support</string>
<string name="about_additional_contributors" translatable="false">João Paulo Barraca (HPlus)\nVitaly Svyastyn (NO.1 F1)\nSami Alaoui (Teclast H30)\n“ladbsoft” (XWatch)\nSebastian Kranz (ZeTime)\nVadim Kaushan (ID115)\n“maxirnilian” (Lenovo Watch 9)\n“ksiwczynski”, “mkusnierz”, “mamutcho” (Lenovo Watch X Plus)\nAndreas Böhler (Casio GB-6900B, Casio GB-5600B, Casio GBX-100)\nJean-François Greffier (Mi Scale 2)\nJohannes Schmitt (BFH-16)\nLukas Schwichtenberg (Makibes HR3)\nDaniel Dakhno (Fossil Q Hybrid, Fossil Hybrid HR)\nGordon Williams (Bangle.js)\nPavel Elagin (JYou Y5)\nTaavi Eomäe (iTag)</string> <string name="about_additional_contributors" translatable="false">João Paulo Barraca (HPlus)\nVitaly Svyastyn (NO.1 F1)\nSami Alaoui (Teclast H30)\n“ladbsoft” (XWatch)\nSebastian Kranz (ZeTime)\nVadim Kaushan (ID115)\n“maxirnilian” (Lenovo Watch 9)\n“ksiwczynski”, “mkusnierz”, “mamutcho” (Lenovo Watch X Plus)\nAndreas Böhler (Casio GB-6900B, Casio GB-5600B, Casio GBX-100)\nJean-François Greffier (Mi Scale 2)\nJohannes Schmitt (BFH-16)\nLukas Schwichtenberg (Makibes HR3)\nDaniel Dakhno (Fossil Q Hybrid, Fossil Hybrid HR)\nGordon Williams (Bangle.js)\nPavel Elagin (JYou Y5)\nTaavi Eomäe (iTag)\nJohannes Krude(Casio GW-B5600)</string>
<string name="about_additional_contributions">Many thanks to all unlisted contributors for contributing code, translations, support, ideas, motivation, bug reports, money… ✊</string> <string name="about_additional_contributions">Many thanks to all unlisted contributors for contributing code, translations, support, ideas, motivation, bug reports, money… ✊</string>
<string name="about_links">Links</string> <string name="about_links">Links</string>
<string name="permission_granting_mandatory">All these permissions are required and instability might occur if not granted</string> <string name="permission_granting_mandatory">All these permissions are required and instability might occur if not granted</string>