mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-06-26 06:50:07 +02:00
266 lines
10 KiB
Java
266 lines
10 KiB
Java
package nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support;
|
|
|
|
import android.app.Notification;
|
|
import android.app.PendingIntent;
|
|
import android.bluetooth.BluetoothGatt;
|
|
import android.bluetooth.BluetoothGattCharacteristic;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.SharedPreferences;
|
|
|
|
import androidx.core.app.NotificationCompat;
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.nio.BufferOverflowException;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
|
import nodomain.freeyourgadget.gadgetbridge.devices.um25.Activity.DataActivity;
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Data.CaptureGroup;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Data.MeasurementData;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
|
|
|
public class UM25Support extends UM25BaseSupport {
|
|
public static final String UUID_SERVICE = "0000ffe0-0000-1000-8000-00805f9b34fb";
|
|
public static final String UUID_CHAR = "0000ffe1-0000-1000-8000-00805f9b34fb";
|
|
|
|
public static final String ACTION_MEASUREMENT_TAKEN = "com.nodomain.gadgetbridge.um25.MEASUREMENT_TAKEN";
|
|
public static final String ACTION_RESET_STATS = "com.nodomain.gadgetbridge.um25.RESET_STATS";
|
|
public static final String EXTRA_KEY_MEASUREMENT_DATA = "EXTRA_MEASUREMENT_DATA";
|
|
public static final int LOOP_DELAY = 500;
|
|
|
|
private final byte[] COMMAND_UPDATE = new byte[]{(byte) 0xF0};
|
|
private final byte[] COMMAND_RESET_STATS = new byte[]{(byte) 0xF4};
|
|
private final int PAYLOAD_LENGTH = 130;
|
|
|
|
private ByteBuffer buffer = ByteBuffer.allocate(PAYLOAD_LENGTH);
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(UM25Support.class);
|
|
|
|
SharedPreferences preferences;
|
|
|
|
private boolean notifyOnCurrentThreshold;
|
|
private int notificationCurrentThreshold;
|
|
private boolean wasOverNotificationCurrent = false;
|
|
private long lastOverThresholdTimestamp = 0;
|
|
|
|
public UM25Support() {
|
|
super(logger);
|
|
addSupportedService(UUID.fromString(UUID_SERVICE));
|
|
this.buffer.mark();
|
|
}
|
|
|
|
BroadcastReceiver resetReceiver = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
if(!ACTION_RESET_STATS.equals(intent.getAction())){
|
|
return;
|
|
}
|
|
new TransactionBuilder("reset stats")
|
|
.write(getCharacteristic(UUID.fromString(UUID_CHAR)), COMMAND_RESET_STATS)
|
|
.queue(getQueue());
|
|
}
|
|
};
|
|
|
|
void readPreferences(){
|
|
notifyOnCurrentThreshold = preferences.getBoolean(DeviceSettingsPreferenceConst.PREF_UM25_SHOW_THRESHOLD_NOTIFICATION, false);
|
|
notificationCurrentThreshold = Integer.parseInt(preferences.getString(DeviceSettingsPreferenceConst.PREF_UM25_SHOW_THRESHOLD, "100"));
|
|
}
|
|
|
|
@Override
|
|
public void onSendConfiguration(String config) {
|
|
readPreferences();
|
|
}
|
|
|
|
@Override
|
|
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
|
preferences = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
|
|
|
|
readPreferences();
|
|
|
|
getDevice().setFirmwareVersion("1.0");
|
|
|
|
return builder
|
|
.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()))
|
|
.notify(getCharacteristic(UUID.fromString(UUID_CHAR)), true)
|
|
.add(new BtLEAction(null) {
|
|
@Override
|
|
public boolean expectsResult() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean run(BluetoothGatt gatt) {
|
|
logger.debug("initialized, starting timers");
|
|
LocalBroadcastManager.getInstance(getContext())
|
|
.registerReceiver(
|
|
resetReceiver,
|
|
new IntentFilter(ACTION_RESET_STATS)
|
|
);
|
|
startLoop();
|
|
return true;
|
|
}
|
|
})
|
|
.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
|
}
|
|
|
|
@Override
|
|
public void dispose() {
|
|
super.dispose();
|
|
LocalBroadcastManager.getInstance(getContext())
|
|
.unregisterReceiver(resetReceiver);
|
|
}
|
|
|
|
private void startLoop(){
|
|
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
|
|
executor.scheduleWithFixedDelay(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
sendReadCommand();
|
|
}
|
|
}, 0, LOOP_DELAY, TimeUnit.MILLISECONDS);
|
|
}
|
|
|
|
private void sendReadCommand(){
|
|
logger.debug("sending read command");
|
|
buffer.reset();
|
|
new TransactionBuilder("send read command")
|
|
.write(getCharacteristic(UUID.fromString(UUID_CHAR)), COMMAND_UPDATE)
|
|
.queue(getQueue());
|
|
logger.debug("sent command");
|
|
}
|
|
|
|
@Override
|
|
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
|
if(!characteristic.getUuid().toString().equals(UUID_CHAR)) return false;
|
|
|
|
try{
|
|
buffer.put(characteristic.getValue());
|
|
|
|
if(buffer.position() == PAYLOAD_LENGTH){
|
|
handlePayload(buffer);
|
|
}
|
|
}catch (BufferOverflowException e){
|
|
logger.error("buffer overflow");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void handleCurrentNotification(int currentMa){
|
|
logger.debug("current: " + currentMa);
|
|
|
|
if(!notifyOnCurrentThreshold){
|
|
return;
|
|
}
|
|
|
|
boolean isOverNotificationCurrent = currentMa > notificationCurrentThreshold;
|
|
|
|
long now = System.currentTimeMillis();
|
|
|
|
if(isOverNotificationCurrent){
|
|
lastOverThresholdTimestamp = now;
|
|
wasOverNotificationCurrent = true;
|
|
return;
|
|
}
|
|
long deltaSinceOverThreshold = now - lastOverThresholdTimestamp;
|
|
|
|
if(deltaSinceOverThreshold < 5000){
|
|
// must be below threshold for over certain time before triggering notification
|
|
return;
|
|
}
|
|
|
|
if(wasOverNotificationCurrent){
|
|
// handle change from over threshold to below threshold
|
|
wasOverNotificationCurrent = false;
|
|
Intent activityIntent = new Intent(getContext(), DataActivity.class);
|
|
Notification notification = new NotificationCompat.Builder(getContext(), GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
|
|
.setSmallIcon(R.drawable.ic_notification_low_battery)
|
|
.setContentTitle("USB current")
|
|
.setContentText("USB current below threshold")
|
|
.setContentIntent(PendingIntentUtils.getActivity(getContext(), 0, activityIntent, PendingIntent.FLAG_CANCEL_CURRENT, false))
|
|
.build();
|
|
|
|
GB.notify(
|
|
GB.NOTIFICATION_ID_LOW_BATTERY,
|
|
notification,
|
|
getContext()
|
|
);
|
|
}
|
|
}
|
|
|
|
private void handlePayload(ByteBuffer payload){
|
|
String payloadString = StringUtils.bytesToHex(payload.array());
|
|
payloadString = payloadString.replaceAll("(..)", "$1 ");
|
|
logger.debug("payload: " + payloadString);
|
|
payload.order(ByteOrder.BIG_ENDIAN);
|
|
int voltage = payload.getShort(2);
|
|
int current = payload.getShort(4);
|
|
int wattage = payload.getShort(8);
|
|
int temperatureCelsius = payload.getShort(10);
|
|
int temperatureFahrenheit = payload.getShort(12);
|
|
|
|
final int STORAGE_START = 16;
|
|
|
|
CaptureGroup[] groups = new CaptureGroup[10];
|
|
|
|
for(int i = 0; i < 10; i++){
|
|
groups[i] = new CaptureGroup(
|
|
i,
|
|
payload.getInt(STORAGE_START + i * 4 + 0),
|
|
payload.getInt(STORAGE_START + i * 4 + 4)
|
|
);
|
|
}
|
|
|
|
int voltagePositive = payload.getShort(96);
|
|
int voltageNegative = payload.getShort(98);
|
|
int chargedCurrent = payload.getInt(102);
|
|
int chargedWattage = payload.getInt(106);
|
|
int thresholdCurrent = payload.get(111);
|
|
int chargingSeconds = payload.getInt(112);
|
|
int cableResistance = payload.getInt(122);
|
|
|
|
MeasurementData data = new MeasurementData(
|
|
voltage,
|
|
current,
|
|
wattage,
|
|
temperatureCelsius,
|
|
temperatureFahrenheit,
|
|
groups,
|
|
voltagePositive,
|
|
voltageNegative,
|
|
chargedCurrent,
|
|
chargedWattage,
|
|
thresholdCurrent,
|
|
chargingSeconds,
|
|
cableResistance
|
|
);
|
|
|
|
Intent measurementIntent = new Intent(ACTION_MEASUREMENT_TAKEN);
|
|
|
|
measurementIntent.putExtra(EXTRA_KEY_MEASUREMENT_DATA, data);
|
|
|
|
handleCurrentNotification(current / 10);
|
|
|
|
LocalBroadcastManager.getInstance(getContext())
|
|
.sendBroadcast(measurementIntent);
|
|
}
|
|
}
|