diff --git a/.gitignore b/.gitignore
index 3d7774697..0cc505dbe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,6 +27,9 @@ proguard/
.idea/*
!.idea/icon.svg
+!.idea/dictionaries
+.idea/dictionaries/*
+!.idea/dictionaries/t.xml
*.iml
MPChartLib
diff --git a/.idea/dictionaries/t.xml b/.idea/dictionaries/t.xml
index 05caad5f7..e2a60bb75 100644
--- a/.idea/dictionaries/t.xml
+++ b/.idea/dictionaries/t.xml
@@ -17,10 +17,12 @@
autocrlf
avamander
baerts
+ baos
barraca
bedscastle
bloß
boun
+ breathwork
böhler
carsten
chamorro
@@ -31,6 +33,7 @@
dantas
dikay
diorite
+ dodgeball
dougal
dreamwalker
drobnič
@@ -43,8 +46,10 @@
françois
freeyourgadget
gadgetbridge
+ gateball
gbapplication
getpebble
+ gfdi
gideão
girolamo
gobbetti
@@ -52,11 +57,14 @@
greenrobot
greffier
guedes
+ handcycling
hasants
hauck
+ hiit
hplus
huami
ieee
+ infini
inkscape
irul
itag
@@ -68,15 +76,19 @@
josé
joão
julien
+ jumpmaster
junginger
jyou
+ kabaddi
kasha
kaushan
keeshii
+ kitesurfing
kompact
kranz
kromke
kronoz
+ lacross
ladbsoft
ladera
lenovo
@@ -94,14 +106,18 @@
mijia
morpheuz
mosenkovs
+ multisport
nephiel
nodomain
nordhøy
normano
novotny
oraclejdk
+ paddleboarding
+ padel
pebblekit
pfeiffer
+ pickleball
pinetime
pivotto
postsorino
@@ -114,6 +130,7 @@
rssi
sami
schrecker
+ sepak
sergey
sevostyanova
shahrabani
@@ -125,9 +142,12 @@
spotify
stacktraces
stefanek
+ stringset
+ subsport
szymon
taavi
tablename
+ takraw
teclast
tiparega
toleda
@@ -146,6 +166,8 @@
vebryn
veneziano
vibratissimo
+ wakeboarding
+ wakesurfing
walkjivefly
watchapp
watchapps
@@ -158,6 +180,7 @@
xwatch
yaron
zalewszczak
+ zepp
zetime
zhong
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsRemindersService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsRemindersService.java
index b973a4afb..20d7636da 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsRemindersService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/services/ZeppOsRemindersService.java
@@ -24,7 +24,13 @@ import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
@@ -60,6 +66,8 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
private static final String PREF_CAPABILITY = "huami_2021_capability_reminders";
+ private final Map deviceReminders = new HashMap<>();
+
public ZeppOsRemindersService(final ZeppOsSupport support) {
super(support, false);
}
@@ -99,6 +107,9 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
case CMD_RESPONSE:
LOG.info("Got reminders from band");
decodeAndUpdateReminders(payload);
+ final TransactionBuilder builder = getSupport().createTransactionBuilder("send reminders");
+ sendReminders(builder);
+ builder.queue(getSupport().getQueue());
return;
default:
LOG.warn("Unexpected reminders payload byte {}", String.format("0x%02x", payload[0]));
@@ -108,8 +119,7 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
@Override
public void initialize(final TransactionBuilder builder) {
requestCapabilities(builder);
- //requestReminders(builder);
- sendReminders(builder);
+ requestReminders(builder);
}
private void requestCapabilities(final TransactionBuilder builder) {
@@ -134,22 +144,72 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
return;
}
- // Send the reminders
- for (int i = 0; i < reminders.size(); i++) {
- LOG.debug("Sending reminder at position {}", i);
+ final Date currentDate = new Date();
+ final Set allReminders = new HashSet<>();
+ final LinkedList toDelete = new LinkedList<>();
+ final LinkedList toSend = new LinkedList<>();
- sendReminderToDevice(builder, i, reminders.get(i));
+ for (final Reminder reminder : reminders) {
+ if (currentDate.after(reminder.getDate())) {
+ // Disregard reminders from the past, device ignores them anyway (does not even send
+ // them back when requesting).
+ continue;
+ }
+
+ final ZeppOsReminder newReminder = new ZeppOsReminder(reminder);
+ allReminders.add(newReminder);
+
+ if (deviceReminders.containsKey(newReminder)) {
+ // Reminder exists and is up-to-date
+ continue;
+ }
+ toSend.push(newReminder);
}
- // Delete the remaining slots, skipping the sent reminders
- for (int i = reminders.size(); i < reminderSlotCount; i++) {
- LOG.debug("Deleting reminder at position {}", i);
+ for (final ZeppOsReminder reminder : deviceReminders.keySet()) {
+ if (!allReminders.contains(reminder)) {
+ toDelete.add(reminder);
+ }
+ }
- sendReminderToDevice(builder, i, null);
+ for (final ZeppOsReminder reminder : toSend) {
+ if (!toDelete.isEmpty()) {
+ // If we have reminders to delete, replace them with the ones we want to send
+ final ZeppOsReminder reminderToReplace = toDelete.pop();
+ final Integer position = deviceReminders.get(reminderToReplace);
+ if (position == null) {
+ LOG.error("Failed to find position for {} - this should never happen", reminderToReplace);
+ // We somehow got out of sync - request all reminders again
+ requestReminders(builder);
+ return;
+ }
+ LOG.debug("Updating reminder at position {}", position);
+ sendReminderToDevice(builder, position, true, reminder);
+ deviceReminders.remove(reminderToReplace);
+ deviceReminders.put(reminder, position);
+ } else {
+ // Find the next available position
+ for (int position = 0; position < reminderSlotCount; position++) {
+ if (!deviceReminders.containsValue(position)) {
+ LOG.debug("Creating reminder at position {}", position);
+ sendReminderToDevice(builder, position, false, reminder);
+ deviceReminders.put(reminder, position);
+ break;
+ }
+ }
+ }
+ }
+
+ for (final ZeppOsReminder reminder : toDelete) {
+ final Integer position = deviceReminders.remove(reminder);
+ if (position != null) {
+ LOG.debug("Deleting reminder at position {}", position);
+ sendReminderToDevice(builder, position, true, null);
+ }
}
}
- protected void sendReminderToDevice(final TransactionBuilder builder, int position, final Reminder reminder) {
+ private void sendReminderToDevice(final TransactionBuilder builder, int position, final boolean update, final ZeppOsReminder reminder) {
final DeviceCoordinator coordinator = getCoordinator();
final int reminderSlotCount = coordinator.getReminderSlotCount(getSupport().getDevice());
if (position + 1 > reminderSlotCount) {
@@ -164,56 +224,18 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
return;
}
- final String message;
- if (reminder.getMessage().length() > coordinator.getMaximumReminderMessageLength()) {
- LOG.warn("The reminder message length {} is longer than {}, will be truncated",
- reminder.getMessage().length(),
- coordinator.getMaximumReminderMessageLength()
- );
- message = StringUtils.truncate(reminder.getMessage(), coordinator.getMaximumReminderMessageLength());
- } else {
- message = reminder.getMessage();
- }
-
- final ByteBuffer buf = ByteBuffer.allocate(1 + 10 + message.getBytes(StandardCharsets.UTF_8).length + 1);
+ final ByteBuffer buf = ByteBuffer.allocate(1 + 10 + reminder.getText().getBytes(StandardCharsets.UTF_8).length + 1);
buf.order(ByteOrder.LITTLE_ENDIAN);
// Update does an upsert, so let's use it. If we call create twice on the same ID, it becomes weird
- buf.put(CMD_UPDATE);
+ buf.put(update ? CMD_UPDATE : CMD_CREATE);
buf.put((byte) (position & 0xFF));
- final Calendar cal = BLETypeConversions.createCalendar();
- cal.setTime(reminder.getDate());
-
- int reminderFlags = FLAG_ENABLED | FLAG_TEXT;
-
- switch (reminder.getRepetition()) {
- case Reminder.ONCE:
- // Default is once, nothing to do
- break;
- case Reminder.EVERY_DAY:
- reminderFlags |= 0x0fe0; // all week day bits set
- break;
- case Reminder.EVERY_WEEK:
- int dayOfWeek = BLETypeConversions.dayOfWeekToRawBytes(cal) - 1; // Monday = 0
- reminderFlags |= 0x20 << dayOfWeek;
- break;
- case Reminder.EVERY_MONTH:
- reminderFlags |= FLAG_REPEAT_MONTH;
- break;
- case Reminder.EVERY_YEAR:
- reminderFlags |= FLAG_REPEAT_YEAR;
- break;
- default:
- LOG.warn("Unknown repetition for reminder in position {}, defaulting to once", position);
- }
-
- buf.putInt(reminderFlags);
-
- buf.putInt((int) (cal.getTimeInMillis() / 1000L));
+ buf.putInt(reminder.getFlags());
+ buf.putInt(reminder.getTimestamp());
buf.put((byte) 0x00);
- buf.put(message.getBytes(StandardCharsets.UTF_8));
+ buf.put(reminder.getText().getBytes(StandardCharsets.UTF_8));
buf.put((byte) 0x00);
write(builder, buf.array());
@@ -229,6 +251,8 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
LOG.debug("Got {} reminders from band", numReminders);
+ deviceReminders.clear();
+
int i = 3;
while (i < payload.length) {
if (payload.length - i < 11) {
@@ -249,6 +273,14 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
return;
}
+ final ZeppOsReminder zeppOsReminder = new ZeppOsReminder(
+ reminderFlags,
+ reminderTimestamp,
+ reminderText
+ );
+
+ deviceReminders.put(zeppOsReminder, reminderPosition);
+
i += reminderText.length() + 1;
LOG.info("Reminder[{}]: {}, {}, {}", reminderPosition, String.format("0x%04x", reminderFlags), reminderDate, reminderText);
@@ -263,4 +295,85 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
public static int getSlotCount(final Prefs devicePrefs) {
return devicePrefs.getInt(PREF_CAPABILITY, 0);
}
+
+ private class ZeppOsReminder {
+ final int flags;
+ final int timestamp;
+ final String text;
+
+ private ZeppOsReminder(final int flags, final int timestamp, final String text) {
+ this.flags = flags;
+ this.timestamp = timestamp;
+ this.text = text;
+ }
+
+ private ZeppOsReminder(final Reminder reminder) {
+ this.flags = getReminderFlags(reminder);
+ this.timestamp = (int) (reminder.getDate().getTime() / 1000L);
+ if (reminder.getMessage().length() > getCoordinator().getMaximumReminderMessageLength()) {
+ LOG.warn("The reminder message length {} is longer than {}, will be truncated",
+ reminder.getMessage().length(),
+ getCoordinator().getMaximumReminderMessageLength()
+ );
+ text = StringUtils.truncate(reminder.getMessage(), getCoordinator().getMaximumReminderMessageLength());
+ } else {
+ text = reminder.getMessage();
+ }
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ private int getReminderFlags(final Reminder reminder) {
+ final Calendar cal = BLETypeConversions.createCalendar();
+ cal.setTime(reminder.getDate());
+
+ int reminderFlags = FLAG_ENABLED | FLAG_TEXT;
+
+ switch (reminder.getRepetition()) {
+ case Reminder.ONCE:
+ // Default is once, nothing to do
+ break;
+ case Reminder.EVERY_DAY:
+ reminderFlags |= 0x0fe0; // all week day bits set
+ break;
+ case Reminder.EVERY_WEEK:
+ int dayOfWeek = BLETypeConversions.dayOfWeekToRawBytes(cal) - 1; // Monday = 0
+ reminderFlags |= 0x20 << dayOfWeek;
+ break;
+ case Reminder.EVERY_MONTH:
+ reminderFlags |= FLAG_REPEAT_MONTH;
+ break;
+ case Reminder.EVERY_YEAR:
+ reminderFlags |= FLAG_REPEAT_YEAR;
+ break;
+ default:
+ LOG.warn("Unknown repetition for reminder {}, defaulting to once", reminder.getReminderId());
+ }
+
+ return reminderFlags;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ZeppOsReminder)) return false;
+ final ZeppOsReminder that = (ZeppOsReminder) o;
+ return flags == that.flags && timestamp == that.timestamp && Objects.equals(text, that.text);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(flags, timestamp, text);
+ }
+ }
}