diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pavlok/PavlokCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pavlok/PavlokCoordinator.java
new file mode 100644
index 000000000..248008739
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pavlok/PavlokCoordinator.java
@@ -0,0 +1,42 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.pavlok;
+
+import androidx.annotation.NonNull;
+
+import java.util.regex.Pattern;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.pavlok.PavlokSupport;
+
+public class PavlokCoordinator extends AbstractBLEDeviceCoordinator {
+ @Override
+ protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
+
+ }
+
+ @Override
+ protected Pattern getSupportedDeviceName() {
+ return Pattern.compile("^Pavlok-.*");
+ }
+
+ @Override
+ public String getManufacturer() {
+ return "Pavlok";
+ }
+
+ @NonNull
+ @Override
+ public Class extends DeviceSupport> getDeviceSupportClass() {
+ return PavlokSupport.class;
+ }
+
+ @Override
+ public int getDeviceNameResource() {
+ return R.string.devicetype_pavlok;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
index c1b0014cc..20cb3fe1b 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -178,6 +178,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.nothing.Ear1Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.nothing.Ear2Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.nothing.EarStickCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.nut.NutCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.pavlok.PavlokCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.qc35.QC35Coordinator;
@@ -444,6 +445,7 @@ public enum DeviceType {
HAMA_FIT6900(HamaFit6900DeviceCoordinator.class),
SCANNABLE(ScannableDeviceCoordinator.class),
CYCLING_SENSOR(CyclingSensorCoordinator.class),
+ PAVLOK(PavlokCoordinator.class),
TEST(TestDeviceCoordinator.class);
private DeviceCoordinator coordinator;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pavlok/PavlokSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pavlok/PavlokSupport.java
new file mode 100644
index 000000000..0c39b5d68
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pavlok/PavlokSupport.java
@@ -0,0 +1,95 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.pavlok;
+
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ReadAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.nut.NutSupport;
+
+public class PavlokSupport extends AbstractBTLEDeviceSupport {
+ private static final Logger LOG = LoggerFactory.getLogger(PavlokSupport.class);
+
+ private static final String ACTION_ZAP = "nodomain.freeyourgadget.gadgetbridge.devices.pavlok.ACTION_ZAP";
+
+ private static final UUID UUID_PAVLOK_SERVICE = UUID.fromString("156e1000-a300-4fea-897b-86f698d74461");
+ private static final UUID UUID_PAVLOK_CHARACTERISTIC_ZAP = UUID.fromString("00001003-0000-1000-8000-00805f9b34fb");
+
+ BroadcastReceiver zapReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int intensity = intent.getIntExtra("EXTRA_INTENSITY", 0);
+ if((intensity <= 0) || (intensity > 100)){
+ LOG.error("EXTRA_INTENSITY missing, <= 0 or > 100");
+ return;
+ }
+
+ sendZap(intensity);
+ }
+ };
+
+ private void sendZap(int intensity){
+ byte[] packet = new byte[]{(byte) 0x89, (byte) intensity};
+
+ new TransactionBuilder("send pavlok zap")
+ .write(getCharacteristic(UUID_PAVLOK_CHARACTERISTIC_ZAP), packet)
+ .queue(getQueue());
+ }
+
+ public PavlokSupport() {
+ super(LOG);
+ addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
+ addSupportedService(UUID_PAVLOK_SERVICE);
+ }
+
+ @Override
+ public void dispose() {
+ getContext().unregisterReceiver(zapReceiver);
+ super.dispose();
+ }
+
+ @Override
+ public boolean useAutoConnect() {
+ return false;
+ }
+
+ @Override
+ protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+ getContext().registerReceiver(
+ zapReceiver,
+ new IntentFilter(ACTION_ZAP), Context.RECEIVER_EXPORTED
+ );
+
+ return builder
+ .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()))
+ .add(new ReadAction(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_BATTERY_LEVEL)))
+ .add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+ }
+
+ @Override
+ public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if(characteristic.getUuid().equals(GattCharacteristic.UUID_CHARACTERISTIC_BATTERY_LEVEL)){
+ int level = (int) characteristic.getValue()[0];
+ getDevice().setBatteryLevel(level);
+ getDevice().sendDeviceUpdateIntent(getContext());
+ }
+
+ return super.onCharacteristicRead(gatt, characteristic, status);
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d97643e56..fd9d7d620 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2925,4 +2925,5 @@
%1$s%%
Fetch unknown files
Fetch unknown activity files from the watch. They will not be processed, but will be saved in the phone.
+ Pavlok shocker