mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-11 01:21:56 +01:00
Device FlipperZero: added basic support for the Flipper Zero (#2840)
This PR adds support for the flipper zero device. It's main purpose currently is to provide an Intent-based API to Tasker and similar apps to play sub-GHz files. In the future, file management and other features might be useful. Co-authored-by: Daniel Dakhno <dakhnod@gmail.com> Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2840 Co-authored-by: dakhnod <dakhnod@noreply.codeberg.org> Co-committed-by: dakhnod <dakhnod@noreply.codeberg.org>
This commit is contained in:
parent
42853df591
commit
ee207c978f
@ -175,6 +175,15 @@ public class DeviceManager {
|
||||
return Collections.unmodifiableList(deviceList);
|
||||
}
|
||||
|
||||
public GBDevice getDeviceByAddress(String address){
|
||||
for(GBDevice device : deviceList){
|
||||
if(device.getAddress().compareToIgnoreCase(address) == 0){
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<GBDevice> getSelectedDevices() {
|
||||
return selectedDevices;
|
||||
}
|
||||
|
@ -0,0 +1,118 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.flipper.zero;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class FlipperZeroCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
if(candidate.supportsService(UUID.fromString("00003082-0000-1000-8000-00805f9b34fb"))){
|
||||
return DeviceType.FLIPPER_ZERO; // need to filter for flipper here
|
||||
}
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.FLIPPER_ZERO;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends Activity> getPairingActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityDataFetching() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(Uri uri, Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScreenshots() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSmartWakeup(GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Flipper devices";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppsManagement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRealtimeData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFindDevice() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_NONE;
|
||||
}
|
||||
}
|
@ -115,6 +115,7 @@ public enum DeviceType {
|
||||
VESC_NRF(500, R.drawable.ic_device_vesc, R.drawable.ic_device_vesc_disabled, R.string.devicetype_vesc),
|
||||
VESC_HM10(501, R.drawable.ic_device_vesc, R.drawable.ic_device_vesc_disabled, R.string.devicetype_vesc),
|
||||
BINARY_SENSOR(510, R.drawable.ic_device_unknown, R.drawable.ic_device_unknown_disabled, R.string.devicetype_binary_sensor),
|
||||
FLIPPER_ZERO(520, R.drawable.ic_device_unknown, R.drawable.ic_device_unknown_disabled, R.string.devicetype_flipper_zero),
|
||||
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
|
||||
|
||||
private final int key;
|
||||
|
@ -32,6 +32,7 @@ import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.widget.Toast;
|
||||
@ -53,6 +54,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
|
||||
@ -337,6 +339,68 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
"com.spotify.music.playbackstatechanged"
|
||||
};
|
||||
|
||||
private final String COMMAND_BLUETOOTH_CONNECT = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECT";
|
||||
private final String ACTION_DEVICE_CONNECTED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECTED";
|
||||
private boolean allowBluetoothIntentApi = false;
|
||||
|
||||
private void sendDeviceConnectedBroadcast(String address){
|
||||
if(!allowBluetoothIntentApi){
|
||||
GB.log("not sending API event due to settings", GB.INFO, null);
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(ACTION_DEVICE_CONNECTED);
|
||||
intent.putExtra("EXTRA_DEVICE_ADDRESS", address);
|
||||
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
BroadcastReceiver bluetoothCommandReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()){
|
||||
case COMMAND_BLUETOOTH_CONNECT:
|
||||
if(!allowBluetoothIntentApi){
|
||||
GB.log("Connection API not allowed in settings", GB.ERROR, null);
|
||||
return;
|
||||
}
|
||||
Bundle extras = intent.getExtras();
|
||||
if(extras == null){
|
||||
GB.log("no extras provided in Intent", GB.ERROR, null);
|
||||
return;
|
||||
}
|
||||
String address = extras.getString("EXTRA_DEVICE_ADDRESS", "");
|
||||
if(address.isEmpty()){
|
||||
GB.log("no bluetooth address provided in Intent", GB.ERROR, null);
|
||||
return;
|
||||
}
|
||||
if(isDeviceConnected(address)){
|
||||
GB.log(String.format("device %s already connected", address), GB.INFO, null);
|
||||
sendDeviceConnectedBroadcast(address);
|
||||
return;
|
||||
}
|
||||
|
||||
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
|
||||
GBDevice targetDevice = GBApplication
|
||||
.app()
|
||||
.getDeviceManager()
|
||||
.getDeviceByAddress(address);
|
||||
|
||||
if(targetDevice == null){
|
||||
GB.log(String.format("device %s not registered", address), GB.ERROR, null);
|
||||
return;
|
||||
}
|
||||
|
||||
GB.log(String.format("connecting to %s", address), GB.INFO, null);
|
||||
|
||||
GBApplication
|
||||
.deviceService(targetDevice)
|
||||
.connect();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* For testing!
|
||||
*
|
||||
@ -366,6 +430,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
cachedStruct.setCoordinator(newCoordinator);
|
||||
}
|
||||
updateReceiversState();
|
||||
|
||||
if(device.isInitialized()){
|
||||
sendDeviceConnectedBroadcast(device.getAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -412,7 +480,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
|
||||
if (hasPrefs()) {
|
||||
getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||
allowBluetoothIntentApi = getPrefs().getBoolean(GBPrefs.PREF_ALLOW_INTENT_API, false);
|
||||
}
|
||||
|
||||
IntentFilter bluetoothCommandFilter = new IntentFilter();
|
||||
bluetoothCommandFilter.addAction(COMMAND_BLUETOOTH_CONNECT);
|
||||
registerReceiver(bluetoothCommandReceiver, bluetoothCommandFilter);
|
||||
}
|
||||
|
||||
private DeviceSupportFactory getDeviceSupportFactory() {
|
||||
@ -957,8 +1030,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
|
||||
private boolean isDeviceConnected(GBDevice device) {
|
||||
return isDeviceConnected(device.getAddress());
|
||||
}
|
||||
|
||||
private boolean isDeviceConnected(String deviceAddress) {
|
||||
for(DeviceStruct struct : deviceStructs){
|
||||
if(struct.getDevice().equals(device) ){
|
||||
if(struct.getDevice().getAddress().compareToIgnoreCase(deviceAddress) == 0){
|
||||
return struct.getDevice().isConnected();
|
||||
}
|
||||
}
|
||||
@ -966,8 +1043,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
|
||||
private boolean isDeviceConnecting(GBDevice device) {
|
||||
return isDeviceConnecting(device.getAddress());
|
||||
}
|
||||
|
||||
private boolean isDeviceConnecting(String deviceAddress) {
|
||||
for(DeviceStruct struct : deviceStructs){
|
||||
if(struct.getDevice().equals(device) ){
|
||||
if(struct.getDevice().getAddress().compareToIgnoreCase(deviceAddress) == 0){
|
||||
return struct.getDevice().isConnecting();
|
||||
}
|
||||
}
|
||||
@ -975,8 +1056,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
|
||||
private boolean isDeviceInitialized(GBDevice device) {
|
||||
return isDeviceInitialized(device.getAddress());
|
||||
}
|
||||
|
||||
private boolean isDeviceInitialized(String deviceAddress) {
|
||||
for(DeviceStruct struct : deviceStructs){
|
||||
if(struct.getDevice().equals(device) ){
|
||||
if(struct.getDevice().getAddress().compareToIgnoreCase(deviceAddress) == 0){
|
||||
return struct.getDevice().isInitialized();
|
||||
}
|
||||
}
|
||||
@ -1188,6 +1273,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
}
|
||||
GB.removeNotification(GB.NOTIFICATION_ID, this); // need to do this because the updated notification won't be cancelled when service stops
|
||||
|
||||
unregisterReceiver(bluetoothCommandReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1206,6 +1293,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
if (GBPrefs.CHART_MAX_HEART_RATE.equals(key) || GBPrefs.CHART_MIN_HEART_RATE.equals(key)) {
|
||||
HeartRateUtils.getInstance().updateCachedHeartRatePreferences();
|
||||
}
|
||||
if (GBPrefs.PREF_ALLOW_INTENT_API.equals(key)){
|
||||
allowBluetoothIntentApi = sharedPreferences.getBoolean(GBPrefs.PREF_ALLOW_INTENT_API, false);
|
||||
GB.log("allowBluetoothIntentApi changed to " + allowBluetoothIntentApi, GB.INFO, null);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasPrefs() {
|
||||
|
@ -39,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDev
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGB6900DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGBX100DeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.domyos.DomyosT540Support;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support.FlipperZeroSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.galaxy_buds.GalaxyBudsDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
|
||||
@ -324,6 +325,8 @@ public class DeviceSupportFactory {
|
||||
return new ServiceDeviceSupport(new QC35BaseSupport());
|
||||
case BINARY_SENSOR:
|
||||
return new ServiceDeviceSupport(new BinarySensorSupport());
|
||||
case FLIPPER_ZERO:
|
||||
return new ServiceDeviceSupport(new FlipperZeroSupport());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -550,6 +550,8 @@ public final class BtLEQueue {
|
||||
if(getCallbackToUse() != null){
|
||||
getCallbackToUse().onMtuChanged(gatt, mtu, status);
|
||||
}
|
||||
|
||||
mWaitForActionResultLatch.countDown();
|
||||
}
|
||||
|
||||
|
||||
|
@ -36,7 +36,7 @@ public class RequestMtuAction extends BtLEAction {
|
||||
|
||||
@Override
|
||||
public boolean expectsResult() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
|
@ -0,0 +1,179 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
|
||||
public class FlipperZeroBaseSupport extends AbstractBTLEDeviceSupport {
|
||||
public FlipperZeroBaseSupport() {
|
||||
super(LoggerFactory.getLogger(FlipperZeroBaseSupport.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteNotification(int id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCallState(CallSpec callSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicState(MusicStateSpec stateSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeSteps(boolean enable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstallApp(Uri uri) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppInfoReq() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppStart(UUID uuid, boolean start) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppDelete(UUID uuid) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppConfiguration(UUID appUuid, String config, Integer id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppReorder(UUID[] uuids) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchRecordedData(int dataTypes) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(int flags) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetConstantVibration(int integer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScreenshotReq() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableHeartRateSleepSupport(boolean enable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetHeartRateMeasurementInterval(int seconds) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteCalendarEvent(byte type, long id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadConfiguration(String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class FlipperZeroSupport extends FlipperZeroBaseSupport{
|
||||
private BatteryInfoProfile batteryInfoProfile = new BatteryInfoProfile(this);
|
||||
|
||||
private final String UUID_SERIAL_SERVICE = "8fe5b3d5-2e7f-4a98-2a48-7acc60fe0000";
|
||||
private final String UUID_SERIAL_CHARACTERISTIC_WRITE = "19ed82ae-ed21-4c9d-4145-228e62fe0000";
|
||||
private final String UUID_SERIAL_CHARACTERISTIC_RESPONSE = "19ed82ae-ed21-4c9d-4145-228e61fe0000";
|
||||
|
||||
private final String COMMAND_PLAY_FILE = "nodomain.freeyourgadget.gadgetbridge.flipper.zero.PLAY_FILE";
|
||||
private final String ACTION_PLAY_DONE = "nodomain.freeyourgadget.gadgetbridge.flipper.zero.PLAY_DONE";
|
||||
|
||||
BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, final Intent intent) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(COMMAND_PLAY_FILE.equals(intent.getAction())){
|
||||
handlePlaySubGHZ(intent);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
};
|
||||
|
||||
private void handlePlaySubGHZ(Intent intent) {
|
||||
String appName = intent.getExtras().getString("EXTRA_APP_NAME", "Sub-GHz");
|
||||
|
||||
String filePath = intent.getStringExtra("EXTRA_FILE_PATH");
|
||||
if(filePath == null){
|
||||
GB.log("missing EXTRA_FILE_PATH in intent", GB.ERROR, null);
|
||||
return;
|
||||
}
|
||||
if(filePath.isEmpty()){
|
||||
GB.log("empty EXTRA_FILE_PATH in intent", GB.ERROR, null);
|
||||
return;
|
||||
}
|
||||
|
||||
GB.toast(String.format("playing %s file", appName), Toast.LENGTH_SHORT, GB.INFO);
|
||||
playFile(appName, filePath);
|
||||
|
||||
Intent response = new Intent(ACTION_PLAY_DONE);
|
||||
getContext().sendBroadcast(response);
|
||||
}
|
||||
|
||||
public FlipperZeroSupport() {
|
||||
super();
|
||||
|
||||
batteryInfoProfile.addListener(new IntentListener() {
|
||||
@Override
|
||||
public void notify(Intent intent) {
|
||||
BatteryInfo info = intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO);
|
||||
GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
|
||||
batteryEvent.state = BatteryState.BATTERY_NORMAL;
|
||||
batteryEvent.level = info.getPercentCharged();
|
||||
evaluateGBDeviceEvent(batteryEvent);
|
||||
}
|
||||
});
|
||||
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
|
||||
addSupportedProfile(batteryInfoProfile);
|
||||
|
||||
addSupportedService(UUID.fromString(UUID_SERIAL_SERVICE));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
getContext().registerReceiver(receiver, new IntentFilter(COMMAND_PLAY_FILE));
|
||||
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
|
||||
batteryInfoProfile.requestBatteryInfo(builder);
|
||||
batteryInfoProfile.enableNotify(builder, true);
|
||||
|
||||
return builder
|
||||
.notify(getCharacteristic(UUID.fromString(UUID_SERIAL_CHARACTERISTIC_RESPONSE)), true)
|
||||
.requestMtu(512)
|
||||
.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
|
||||
getContext().unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
private void sendSerialData(byte[] data){
|
||||
new TransactionBuilder("send serial data")
|
||||
.write(getCharacteristic(UUID.fromString(UUID_SERIAL_CHARACTERISTIC_WRITE)), data)
|
||||
.queue(getQueue());
|
||||
}
|
||||
|
||||
private void sendProtobufPacket(byte[] packet){
|
||||
byte[] fullPacket = new byte[packet.length + 1];
|
||||
fullPacket[0] = (byte) packet.length;
|
||||
System.arraycopy(packet, 0, fullPacket, 1, packet.length);
|
||||
sendSerialData(fullPacket);
|
||||
}
|
||||
|
||||
private void openApp(String appName){
|
||||
// sub ghz payload: 13-08-15-82-01-0E-0A-07- 53-75-62-2D-47-48-7A -12-03-52-50-43
|
||||
ByteBuffer buffer = ByteBuffer.allocate(12 + appName.length());
|
||||
buffer.put((byte)0x08);
|
||||
buffer.put((byte)0x15);
|
||||
// buffer.put((byte)(Math.random() * 256));
|
||||
buffer.put((byte)0x82);
|
||||
buffer.put((byte)0x01);
|
||||
buffer.put((byte) (appName.length() + 7));
|
||||
buffer.put((byte)0x0A);
|
||||
|
||||
buffer.put((byte) appName.length());
|
||||
buffer.put(appName.getBytes());
|
||||
|
||||
buffer.put((byte)0x12);
|
||||
buffer.put((byte)0x03);
|
||||
buffer.put((byte)0x52);
|
||||
buffer.put((byte)0x50);
|
||||
buffer.put((byte)0x43);
|
||||
|
||||
sendProtobufPacket(buffer.array());
|
||||
}
|
||||
|
||||
private void openSubGhzApp(){
|
||||
openApp("Sub-GHz");
|
||||
}
|
||||
|
||||
private void appLoadFile(String filePath){
|
||||
// example payload 1C-08-16-82-03-17-0A-15- 2F-61-6E-79-2F-73-75-62-67-68-7A-2F-74-65-73-6C-61-2E-73-75-62
|
||||
ByteBuffer buffer = ByteBuffer.allocate(7 + filePath.length());
|
||||
|
||||
buffer.put((byte) 0x08);
|
||||
buffer.put((byte) 0x16);
|
||||
buffer.put((byte) 0x82);
|
||||
buffer.put((byte) 0x03);
|
||||
buffer.put((byte) (filePath.length() + 2));
|
||||
buffer.put((byte) 0x0A);
|
||||
buffer.put((byte) filePath.length());
|
||||
buffer.put(filePath.getBytes());
|
||||
|
||||
sendProtobufPacket(buffer.array());
|
||||
}
|
||||
|
||||
private void appButtonPress(){
|
||||
sendProtobufPacket(new byte[]{
|
||||
(byte) 0x08, (byte) 0x17, (byte) 0x8A, (byte) 0x03, (byte) 0x00}
|
||||
);
|
||||
}
|
||||
|
||||
private void appButtonRelease(){
|
||||
sendProtobufPacket(new byte[]{
|
||||
(byte) 0x08, (byte) 0x18, (byte) 0x92, (byte) 0x03, (byte) 0x00}
|
||||
);
|
||||
}
|
||||
private void appExitRequest(){
|
||||
sendProtobufPacket(new byte[]{
|
||||
(byte) 0x08, (byte) 0x19, (byte) 0xFA, (byte) 0x02, (byte) 0x00
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
openApp("Infrared");
|
||||
}
|
||||
|
||||
private void playFile(String appName, String filePath){
|
||||
openApp(appName);
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
appLoadFile(filePath);
|
||||
Thread.sleep(500);
|
||||
appButtonPress();
|
||||
Thread.sleep(500);
|
||||
appButtonRelease();
|
||||
Thread.sleep(1000);
|
||||
appExitRequest();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchRecordedData(int dataTypes) {
|
||||
super.onFetchRecordedData(dataTypes);
|
||||
|
||||
onTestNewFunction();
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSCoordinator
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.casio.gb6900.CasioGB6900DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100.CasioGBX100DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.domyos.DomyosT540Cooridnator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.flipper.zero.FlipperZeroCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsLiveDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsProDeviceCoordinator;
|
||||
@ -335,6 +336,7 @@ public class DeviceHelper {
|
||||
result.add(new SonyWF1000XM3Coordinator());
|
||||
result.add(new QC35Coordinator());
|
||||
result.add(new BinarySensorCoordinator());
|
||||
result.add(new FlipperZeroCoordinator());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ public class GBPrefs {
|
||||
public static final String RTL_SUPPORT = "rtl";
|
||||
public static final String RTL_CONTEXTUAL_ARABIC = "contextualArabic";
|
||||
public static boolean AUTO_RECONNECT_DEFAULT = true;
|
||||
public static final String PREF_ALLOW_INTENT_API = "prefs_key_allow_bluetooth_intent_api";
|
||||
|
||||
public static final String USER_NAME = "mi_user_alias";
|
||||
public static final String USER_NAME_DEFAULT = "gadgetbridge-user";
|
||||
|
@ -1813,4 +1813,7 @@
|
||||
<string name="step_streaks_achievements_sharing_title">Steps Achievements</string>
|
||||
<string name="prefs_hourly_chime">Hourly chime</string>
|
||||
<string name="prefs_hourly_chime_summary">The watch will beep once an hour</string>
|
||||
<string name="devicetype_flipper_zero">Flipper zero</string>
|
||||
<string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent API</string>
|
||||
<string name="activity_prefs_summary_allow_bluetooth_intent_api">Allow controlling Bluetooth connection via Intent API</string>
|
||||
</resources>
|
||||
|
@ -386,5 +386,10 @@
|
||||
android:key="pref_discovery_pairing"
|
||||
android:title="@string/activity_prefs_discovery_pairing" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="prefs_key_allow_bluetooth_intent_api"
|
||||
android:title="@string/activity_prefs_allow_bluetooth_intent_api"
|
||||
android:summary="@string/activity_prefs_summary_allow_bluetooth_intent_api" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
Loading…
Reference in New Issue
Block a user