From 198800e0879a5950c668aa324a4ab98bfdc2e3a0 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Tue, 19 Dec 2023 10:58:49 +0100 Subject: [PATCH] Pixoo: Decode alarms from device, support sending alarms This is probably not quite right yet. Also we need to properly chunk incoming protocol messages before decoding them --- .../devices/divoom/PixooCoordinator.java | 5 + .../service/devices/divoom/PixooIOThread.java | 5 + .../service/devices/divoom/PixooProtocol.java | 112 +++++++++++++++++- .../service/devices/divoom/PixooSupport.java | 1 + .../serial/AbstractSerialDeviceSupport.java | 7 ++ .../service/serial/GBDeviceProtocol.java | 5 + 6 files changed, 134 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooCoordinator.java index 0617bc37b..ac7893521 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/divoom/PixooCoordinator.java @@ -61,6 +61,11 @@ public class PixooCoordinator extends AbstractBLEDeviceCoordinator { return true; } + @Override + public int getAlarmSlotCount(GBDevice device) { + return 10; + } + @Override public int getDefaultIconResource() { return R.drawable.ic_device_lovetoy; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooIOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooIOThread.java index e4936ceae..d9fd8e1b8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooIOThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooIOThread.java @@ -31,19 +31,24 @@ import java.util.Arrays; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread; +import nodomain.freeyourgadget.gadgetbridge.service.devices.nothing.NothingProtocol; public class PixooIOThread extends BtClassicIoThread { private static final Logger LOG = LoggerFactory.getLogger(PixooIOThread.class); + private final PixooProtocol mPixooProtocol; @Override protected void initialize() { + write(mPixooProtocol.encodeReqestAlarms()); + setUpdateState(GBDevice.State.INITIALIZED); } public PixooIOThread(GBDevice device, Context context, PixooProtocol deviceProtocol, PixooSupport PixooSupport, BluetoothAdapter bluetoothAdapter) { super(device, context, deviceProtocol, PixooSupport, bluetoothAdapter); + mPixooProtocol = deviceProtocol; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooProtocol.java index 8cd6fa408..b3a2af975 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooProtocol.java @@ -17,8 +17,12 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.divoom; +import android.content.Intent; import android.content.SharedPreferences; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.apache.commons.lang3.ArrayUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,17 +30,22 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; import java.util.List; +import java.util.Map; import lineageos.weather.util.WeatherUtils; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; +import nodomain.freeyourgadget.gadgetbridge.entities.Alarm; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; @@ -64,10 +73,86 @@ public class PixooProtocol extends GBDeviceProtocol { ByteBuffer incoming = ByteBuffer.wrap(responseData); incoming.order(ByteOrder.LITTLE_ENDIAN); - + if (incoming.get() != 0x01) { + LOG.warn("first byte not 0x01"); + return devEvts.toArray(new GBDeviceEvent[0]); + } + int length = incoming.getShort() & 0xffff; + byte status = incoming.get(); // unsure + if (status != 0x04) { + LOG.warn("status byte not 0x04"); + return devEvts.toArray(new GBDeviceEvent[0]); + } + byte endpoint = incoming.get(); // unsure + LOG.info("endpoint " + endpoint); + if (endpoint == 0x42) { + decodeAlarms(incoming); + } return devEvts.toArray(new GBDeviceEvent[0]); } + private void decodeAlarms(ByteBuffer incoming) { + byte unknown = incoming.get(); + if (unknown != 0x55) { // expected + LOG.warn("unexpected byte when decoding Alarms " + unknown); + return; + } + // Map of alarm position to Alarm, as returned by the band + final Map payloadAlarms = new HashMap<>(); + + while (incoming.remaining() > 10) { + int position = incoming.get(); + boolean enabled = incoming.get() == 1; + int hour = incoming.get(); + int minute = incoming.get(); + int repeatMask = incoming.get(); + int unknown2 = incoming.getInt(); + byte unknown3 = incoming.get(); // normally 0x01, on fresh alarms 0x32 + final Alarm alarm = new nodomain.freeyourgadget.gadgetbridge.entities.Alarm(); + alarm.setEnabled(enabled); + alarm.setPosition(position); + alarm.setHour(hour); + alarm.setMinute(minute); + alarm.setRepetition(repeatMask); + alarm.setUnused(unknown3 == 0x32 && !enabled); + payloadAlarms.put(position, alarm); + } + final List dbAlarms = DBHelper.getAlarms(getDevice()); + int numUpdatedAlarms = 0; + + for (nodomain.freeyourgadget.gadgetbridge.entities.Alarm alarm : dbAlarms) { + final int pos = alarm.getPosition(); + final nodomain.freeyourgadget.gadgetbridge.model.Alarm updatedAlarm = payloadAlarms.get(pos); + final boolean alarmNeedsUpdate = updatedAlarm == null || + alarm.getUnused() != updatedAlarm.getUnused() || + alarm.getEnabled() != updatedAlarm.getEnabled() || + alarm.getSmartWakeup() != updatedAlarm.getSmartWakeup() || + alarm.getHour() != updatedAlarm.getHour() || + alarm.getMinute() != updatedAlarm.getMinute() || + alarm.getRepetition() != updatedAlarm.getRepetition(); + + if (alarmNeedsUpdate) { + numUpdatedAlarms++; + LOG.info("Updating alarm index={}, unused={}", pos, updatedAlarm == null); + alarm.setUnused(updatedAlarm == null); + if (updatedAlarm != null) { + alarm.setEnabled(updatedAlarm.getEnabled()); + alarm.setUnused(updatedAlarm.getUnused()); + alarm.setSmartWakeup(updatedAlarm.getSmartWakeup()); + alarm.setHour(updatedAlarm.getHour()); + alarm.setMinute(updatedAlarm.getMinute()); + alarm.setRepetition(updatedAlarm.getRepetition()); + } + DBHelper.store(alarm); + } + } + + if (numUpdatedAlarms > 0) { + final Intent intent = new Intent(DeviceService.ACTION_SAVE_ALARMS); + LocalBroadcastManager.getInstance(GBApplication.getContext()).sendBroadcast(intent); + } + } + @Override public byte[] encodeSendConfiguration(String config) { SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); @@ -171,6 +256,26 @@ public class PixooProtocol extends GBDeviceProtocol { } + @Override + public byte[] encodeSetAlarms(ArrayList alarms) { + byte[] complete_command = new byte[]{}; + for (nodomain.freeyourgadget.gadgetbridge.model.Alarm alarm : alarms) { + byte[] cmd = new byte[]{ + 0x43, + (byte) alarm.getPosition(), + (byte) (alarm.getEnabled() && !alarm.getUnused() ? 1 : 0), + (byte) alarm.getHour(), + (byte) alarm.getMinute(), + (byte) alarm.getRepetition(), + 0, 0, 0, 0, + (byte) (alarm.getUnused() ? 0x32 : 0x00)}; + + complete_command = ArrayUtils.addAll(complete_command, encodeProtocol(cmd)); + } + return complete_command; + } + + @Override public byte[] encodeSendWeather(WeatherSpec weatherSpec) { byte pixooWeatherCode = 0; @@ -238,6 +343,10 @@ public class PixooProtocol extends GBDeviceProtocol { }); } + public byte[] encodeReqestAlarms() { + return encodeProtocol(new byte[]{0x42}); + } + @Override public byte[] encodeTestNewFunction() { //return encodeAudioModeCommand(1); // works @@ -262,3 +371,4 @@ public class PixooProtocol extends GBDeviceProtocol { } } + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooSupport.java index 3c5b95f9d..6fdfcb26a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/divoom/PixooSupport.java @@ -20,6 +20,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.divoom; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java index 8992463f0..12dfa96df 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java @@ -25,6 +25,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler; +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; @@ -286,6 +287,12 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport sendToDevice(bytes); } + @Override + public void onSetAlarms(ArrayList alarms) { + byte[] bytes = gbDeviceProtocol.encodeSetAlarms(alarms); + sendToDevice(bytes); + } + @Override public void onSetReminders(ArrayList reminders) { byte[] bytes = gbDeviceProtocol.encodeReminders(reminders); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java index 5875623ec..8551787cd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java @@ -24,6 +24,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; @@ -158,6 +159,10 @@ public abstract class GBDeviceProtocol { return null; } + public byte[] encodeSetAlarms(ArrayList alarms) { + return null; + } + public byte[] encodeReminders(ArrayList reminders) { return null; }