1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-26 06:50:07 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/um25/Support/UM25Support.java
2023-06-13 00:14:04 +01:00

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);
}
}