1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-29 03:55:49 +01:00

Add initial support for Shell Racing cars

- adds initial device support
- can control driving forward/back/left/right
- probably could be implemented further:
  - battery reading
  - device name?
  - lights on
  - fast/slow speed mode
This commit is contained in:
vanous 2022-09-25 21:31:58 +02:00
parent 63b1cffe2a
commit e6411b15ab
14 changed files with 934 additions and 0 deletions

View File

@ -689,6 +689,10 @@
android:name=".devices.um25.Activity.DataActivity"
android:exported="true" />
<activity
android:name=".devices.supercars.ControlActivity"
android:exported="true" />
<activity
android:name=".devices.binary_sensor.activity.DataActivity"
android:exported="true" />

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg2"
width="28.740126"
height="28.740126"
viewBox="0 0 28.740126 28.740126"
sodipodi:docname="ic_device_supercars.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 14.370063 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="28.740126 : 14.370063 : 1"
inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
id="perspective903" />
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1035"
id="namedview4"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="11.313709"
inkscape:cx="-18.826717"
inkscape:cy="11.357902"
inkscape:window-x="0"
inkscape:window-y="45"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
inkscape:pagecheckerboard="0"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid972" />
</sodipodi:namedview>
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Background"
style="display:inline">
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Bottom"
style="display:inline"
sodipodi:insensitive="true">
<rect
style="display:inline;fill:#1f7fdb;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect817-5"
width="22.819572"
height="21.904638"
x="2.9239759"
y="3.8768768"
ry="0.9472276" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Middle"
style="display:inline"
sodipodi:insensitive="true">
<rect
style="display:inline;fill:#4dabf5;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect817-7"
width="22.819572"
height="21.904638"
x="2.9317887"
y="3.0349438"
ry="0.9472276" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Top"
style="display:inline"
sodipodi:insensitive="true">
<rect
style="fill:#2196f3;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect817"
width="22.819572"
height="21.904638"
x="2.9238882"
y="3.4130721"
ry="0.9472276" />
</g>
<g
id="g8"
transform="matrix(0.75,0,0,0.75,5.3712747,5.3700628)"
style="fill:#ffffff;fill-opacity:1;stroke-width:1.33333">
<path
d="M 21.96,11.22 C 21.57,7.01 17.76,4 13.56,4 13.37,4 13.18,4.01 12.99,4.02 2,4.74 2,17.2 2,17.2 V 18 c 0,1.1 0.9,2 2,2 h 10 c 4.67,0 8.41,-4.01 7.96,-8.78 z M 5.26,11.56 C 5.83,10.27 6.54,9.21 7.4,8.37 l 3.62,1.53 c 0.6,0.25 0.98,0.83 0.98,1.48 0,0.89 -0.72,1.61 -1.61,1.61 H 4.72 c 0.15,-0.46 0.32,-0.94 0.54,-1.43 z m 13.18,4.48 C 17.3,17.29 15.68,18 14,18 H 4 V 17.2 C 4,17.18 4.01,16.28 4.24,15 h 6.15 C 12.38,15 14,13.38 14,11.39 14,9.94 13.13,8.63 11.8,8.07 L 9.3,7.01 C 10.4,6.44 11.67,6.11 13.12,6.02 13.27,6 13.42,6 13.56,6 c 3.31,0 6.13,2.37 6.41,5.41 0.16,1.72 -0.38,3.36 -1.53,4.63 z"
id="path6"
style="fill:#ffffff;fill-opacity:1;stroke-width:1.33333" />
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="Original"
style="display:inline" />
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg2"
width="28.740126"
height="28.740126"
viewBox="0 0 28.740126 28.740126"
sodipodi:docname="ic_device_supercars_disabled.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:android="http://schemas.android.com/apk/res/android">
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 14.370063 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="28.740126 : 14.370063 : 1"
inkscape:persp3d-origin="14.370063 : 9.580042 : 1"
id="perspective903" />
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1035"
id="namedview4"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="16"
inkscape:cx="0.125"
inkscape:cy="7.65625"
inkscape:window-x="0"
inkscape:window-y="45"
inkscape:window-maximized="1"
inkscape:current-layer="layer6"
inkscape:pagecheckerboard="0"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid972" />
</sodipodi:namedview>
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Background"
style="display:inline"
sodipodi:insensitive="true">
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Bottom"
style="display:inline"
sodipodi:insensitive="true">
<rect
style="display:inline;fill:#7a7a7a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect817-5"
width="22.819572"
height="21.904638"
x="2.9239759"
y="3.8768768"
ry="0.9472276" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Middle"
style="display:inline"
sodipodi:insensitive="true">
<rect
style="display:inline;fill:#9f9f9f;fill-opacity:0.941176;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect817-7"
width="22.819572"
height="21.904638"
x="2.9317887"
y="3.0349438"
ry="0.9472276" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Top"
style="display:inline"
sodipodi:insensitive="true">
<rect
style="fill:#8a8a8a;fill-opacity:1;stroke:none;stroke-width:3.57115;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect817"
width="22.819572"
height="21.904638"
x="2.9238882"
y="3.4130721"
ry="0.9472276" />
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer6"
inkscape:label="Foreground"
style="display:inline">
<g
inkscape:groupmode="layer"
id="layer7"
inkscape:label="Backplane"
style="display:inline" />
<g
inkscape:groupmode="layer"
id="layer8"
inkscape:label="Frontplane"
style="display:inline">
<g
inkscape:groupmode="layer"
id="layer9"
inkscape:label="Base"
style="display:inline" />
<g
inkscape:groupmode="layer"
id="layer10"
inkscape:label="Top"
style="display:inline" />
<g
inkscape:groupmode="layer"
id="layer11"
inkscape:label="Toptop"
style="display:inline" />
<g
inkscape:groupmode="layer"
id="layer12"
inkscape:label="Toptoptop"
style="display:inline" />
</g>
<path
inkscape:connector-curvature="0"
style="fill:#8a8a8a;fill-opacity:0.94117647"
d="m 12.48275,10.865376 h 3.907 v 6.978 h -3.907 z"
android:strokeWidth="1"
id="path2735" />
<g
id="g8"
transform="matrix(0.75,0,0,0.75,5.3712747,5.3700628)"
style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:1.33333">
<path
d="M 21.96,11.22 C 21.57,7.01 17.76,4 13.56,4 13.37,4 13.18,4.01 12.99,4.02 2,4.74 2,17.2 2,17.2 V 18 c 0,1.1 0.9,2 2,2 h 10 c 4.67,0 8.41,-4.01 7.96,-8.78 z M 5.26,11.56 C 5.83,10.27 6.54,9.21 7.4,8.37 l 3.62,1.53 c 0.6,0.25 0.98,0.83 0.98,1.48 0,0.89 -0.72,1.61 -1.61,1.61 H 4.72 c 0.15,-0.46 0.32,-0.94 0.54,-1.43 z m 13.18,4.48 C 17.3,17.29 15.68,18 14,18 H 4 V 17.2 C 4,17.18 4.01,16.28 4.24,15 h 6.15 C 12.38,15 14,13.38 14,11.39 14,9.94 13.13,8.63 11.8,8.07 L 9.3,7.01 C 10.4,6.44 11.67,6.11 13.12,6.02 13.27,6 13.42,6 13.56,6 c 3.31,0 6.13,2.37 6.41,5.41 0.16,1.72 -0.38,3.36 -1.53,4.63 z"
id="path6"
style="fill:#ffffff;fill-opacity:1;stroke-width:1.33333" />
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="Original"
style="display:inline" />
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,62 @@
package nodomain.freeyourgadget.gadgetbridge.devices.supercars;
import android.content.Intent;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.service.devices.supercars.SuperCarsSupport;
public class ControlActivity extends AbstractGBActivity {
LocalBroadcastManager localBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_supercars_control);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
View.OnTouchListener controlTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
String command = "idle";
if (v.getId() == R.id.supercars_control_button_left_up) {
command = "left_up";
} else if (v.getId() == R.id.supercars_control_button_center_up) {
command = "center_up";
} else if (v.getId() == R.id.supercars_control_button_right_up) {
command = "right_up";
}
if (v.getId() == R.id.supercars_control_button_left_down) {
command = "left_down";
} else if (v.getId() == R.id.supercars_control_button_center_down) {
command = "center_down";
} else if (v.getId() == R.id.supercars_control_button_right_down) {
command = "right_down";
}
Intent intent = new Intent(SuperCarsSupport.COMMAND_DRIVE_CONTROL);
intent.putExtra(SuperCarsSupport.EXTRA_DIRECTION, command);
sendLocalBroadcast(intent);
return true;
}
};
findViewById(R.id.supercars_control_button_left_up).setOnTouchListener(controlTouchListener);
findViewById(R.id.supercars_control_button_center_up).setOnTouchListener(controlTouchListener);
findViewById(R.id.supercars_control_button_right_up).setOnTouchListener(controlTouchListener);
findViewById(R.id.supercars_control_button_left_down).setOnTouchListener(controlTouchListener);
findViewById(R.id.supercars_control_button_center_down).setOnTouchListener(controlTouchListener);
findViewById(R.id.supercars_control_button_right_down).setOnTouchListener(controlTouchListener);
}
private void sendLocalBroadcast(Intent intent) {
localBroadcastManager.sendBroadcast(intent);
}
}

View File

@ -0,0 +1,28 @@
package nodomain.freeyourgadget.gadgetbridge.devices.supercars;
import java.util.UUID;
public class SuperCarsConstants {
public static final UUID SERVICE_UUID_FFF = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
public static final UUID CHARACTERISTIC_UUID_FFF1 = UUID.fromString("d44bc439-abfd-45a2-b575-925416129600");
public static final UUID CHARACTERISTIC_UUID_FFF2 = UUID.fromString("d44bc439-abfd-45a2-b575-92541612960a");
public static final UUID CHARACTERISTIC_UUID_FFF3 = UUID.fromString("d44bc439-abfd-45a2-b575-92541612960b");
public static final UUID CHARACTERISTIC_UUID_FFF4 = UUID.fromString("d44bc439-abfd-45a2-b575-925416129601");
public static final UUID SERVICE_UUID_FD = UUID.fromString("0000fd00-0000-1000-8000-00805f9b34fb");
public static final UUID CHARACTERISTIC_UUID_FD1 = UUID.fromString("0000fd01-0000-1000-8000-00805f9b34fb");
public static final UUID CHARACTERISTIC_UUID_FD2 = UUID.fromString("0000fd02-0000-1000-8000-00805f9b34fb");
public static final byte[] idle_data = new byte[]{0x02, 0x5e, 0x69, 0x5a, 0x48, (byte) 0xff, 0x2a, 0x43, (byte) 0x8c, (byte) 0xa6, (byte) 0x80, (byte) 0xf8, 0x3e, 0x04, (byte) 0xe4, 0x5d};
public static final byte[] up_data = new byte[]{0x29, 0x60, (byte) 0x9c, 0x66, 0x48, 0x52, (byte) 0xcf, (byte) 0xf1, (byte) 0xb0, (byte) 0xf0, (byte) 0xcb, (byte) 0xb9, (byte) 0x80, 0x14, (byte) 0xbd, 0x2c};
public static final byte[] down_data = new byte[]{0x03, 0x20, (byte) 0x99, 0x09, (byte) 0xba, (byte) 0x9d, (byte) 0xa1, (byte) 0xc8, (byte) 0xb9, (byte) 0x86, 0x16, 0x3c, 0x6d, 0x48, 0x46, 0x55};
public static final byte[] left_data = new byte[]{0x51, 0x38, 0x21, 0x12, 0x13, 0x5c, (byte) 0xcc, (byte) 0xdb, (byte) 0x46, (byte) 0xcf, (byte) 0x89, 0x21, (byte) 0xb7, 0x05, 0x49, (byte) 0x9a};
public static final byte[] right_data = new byte[]{0x1b, 0x57, 0x69, (byte) 0xcd, (byte) 0xf1, 0x3e, (byte) 0x8a, (byte) 0xb6, 0x27, 0x08, 0x0f, (byte) 0xf3, (byte) 0xce, (byte) 0xfc, 0x3b, (byte) 0xc0};
public static final byte[] up_left_data = new byte[]{(byte) 0x99, 0x28, (byte) 0xe5, (byte) 0x90, (byte) 0xdf, (byte) 0xe8, 0x21, 0x48, 0x5f, 0x41, 0x4f, (byte) 0xbb, 0x63, 0x3d, 0x5c, 0x4e};
public static final byte[] up_right_data = new byte[]{0x0f, 0x2c, (byte) 0xe5, 0x66, 0x62, (byte) 0xd4, (byte) 0xfd, (byte) 0x9d, 0x32, (byte) 0xa4, 0x4f, 0x10, 0x2b, (byte) 0xf2, 0x0a, (byte) 0xa7};
public static final byte[] down_left_data = new byte[]{(byte) 0x98, (byte) 0xce, (byte) 0x98, 0x1d, 0x58, (byte) 0xd1, 0x15, (byte) 0xaf, (byte) 0xe1, 0x19, 0x60, (byte) 0xbf, 0x46, 0x13, (byte) 0x92, 0x5c};
public static final byte[] down_right_data = new byte[]{(byte) 0xf2, 0x52, 0x0f, (byte) 0xba, 0x31, 0x44, (byte) 0xfb, 0x11, 0x46, (byte) 0x8f, (byte) 0xe0, (byte) 0x80, (byte) 0xc6, (byte) 0xc2, (byte) 0xc2, 0x3c};
}

View File

@ -0,0 +1,136 @@
package nodomain.freeyourgadget.gadgetbridge.devices.supercars;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
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 SuperCarsCoordinator extends AbstractDeviceCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(SuperCarsCoordinator.class);
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
try {
BluetoothDevice device = candidate.getDevice();
String name = device.getName();
if (name != null && name.startsWith("QCAR-")) {
return DeviceType.SUPER_CARS;
}
} catch (Exception ex) {
LOG.error("unable to check device support", ex);
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.SUPER_CARS;
}
@Override
public int getBondingStyle() {
return BONDING_STYLE_NONE;
}
@Override
public int getBatteryCount() {
return 0;
}
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return ControlActivity.class;
}
@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 "Brand Base";
}
@Override
public boolean supportsAppsManagement() {
return true;
}
@Override
public boolean supportsCalendarEvents() {
return false;
}
@Override
public boolean supportsRealtimeData() {
return false;
}
@Override
public boolean supportsFindDevice() {
return false;
}
}

View File

@ -118,6 +118,7 @@ public enum DeviceType {
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_flipper, R.drawable.ic_device_flipper_disabled, R.string.devicetype_flipper_zero),
SUPER_CARS(530, R.drawable.ic_device_supercars, R.drawable.ic_device_supercars_disabled, R.string.devicetype_super_cars),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
private final int key;

View File

@ -99,6 +99,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport
import nodomain.freeyourgadget.gadgetbridge.service.devices.smaq2oss.SMAQ2OSSSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.SonyHeadphonesSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.supercars.SuperCarsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.vesc.VescDeviceSupport;
@ -333,6 +334,8 @@ public class DeviceSupportFactory {
return new ServiceDeviceSupport(new BinarySensorSupport());
case FLIPPER_ZERO:
return new ServiceDeviceSupport(new FlipperZeroSupport());
case SUPER_CARS:
return new ServiceDeviceSupport(new SuperCarsSupport());
}
return null;
}

View File

@ -0,0 +1,261 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.supercars;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.devices.supercars.SuperCarsConstants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
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;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.fitpro.FitProDeviceSupport;
public class SuperCarsSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(SuperCarsSupport.class);
public static final String COMMAND_DRIVE_CONTROL = "nodomain.freeyourgadget.gadgetbridge.supercars.command.DRIVE_CONTROL";
public static final String EXTRA_DIRECTION = "EXTRA_DIRECTION";
public SuperCarsSupport() {
super(LOG);
addSupportedService(SuperCarsConstants.SERVICE_UUID_FFF);
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
IntentFilter filter = new IntentFilter();
filter.addAction(COMMAND_DRIVE_CONTROL);
broadcastManager.registerReceiver(commandReceiver, filter);
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
LOG.debug("name " + gbDevice.getName());
return builder;
}
BroadcastReceiver commandReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(COMMAND_DRIVE_CONTROL)) {
SuperCarsSupport.this.setDirection(
intent.getStringExtra(EXTRA_DIRECTION)
);
}
}
};
@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;
}
private void setDirection(String direction) {
byte[] command = SuperCarsConstants.idle_data;
switch (direction) {
case "left_up":
command = SuperCarsConstants.up_left_data;
break;
case "center_up":
command = SuperCarsConstants.up_data;
break;
case "right_up":
command = SuperCarsConstants.up_right_data;
break;
case "left_down":
command = SuperCarsConstants.down_left_data;
break;
case "center_down":
command = SuperCarsConstants.down_data;
break;
case "right_down":
command = SuperCarsConstants.down_right_data;
break;
default:
command = SuperCarsConstants.idle_data;
}
TransactionBuilder builder = new TransactionBuilder("test");
BluetoothGattCharacteristic writeCharacteristic = getCharacteristic(SuperCarsConstants.CHARACTERISTIC_UUID_FFF1);
builder.write(writeCharacteristic, command);
builder.queue(getQueue());
}
@Override
public void dispose() {
super.dispose();
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(commandReceiver);
}
}

View File

@ -43,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.supercars.SuperCarsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSCoordinator;
@ -341,6 +342,7 @@ public class DeviceHelper {
result.add(new QC35Coordinator());
result.add(new BinarySensorCoordinator());
result.add(new FlipperZeroCoordinator());
result.add(new SuperCarsCoordinator());
return result;
}

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="45sp"
android:height="45sp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:fillColor="#1f7fdb"
android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
android:strokeWidth="3.57115" />
<path
android:fillColor="#4dabf5"
android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
android:strokeWidth="3.57115" />
<path
android:fillColor="#2196f3"
android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
android:strokeWidth="3.57115" />
<path
android:fillColor="#ffffff"
android:pathData="M21.841 13.785c-0.292-3.157-3.15-5.415-6.3-5.415-0.142 0-0.285 0.008-0.427 0.015C6.87 8.925 6.87 18.27 6.87 18.27v0.6c0 0.825 0.675 1.5 1.5 1.5h7.5c3.503 0 6.308-3.007 5.97-6.585zM9.316 14.04c0.428-0.967 0.96-1.762 1.605-2.392l2.715 1.147c0.45 0.188 0.735 0.623 0.735 1.11 0 0.668-0.54 1.208-1.207 1.208H8.91c0.113-0.345 0.24-0.705 0.405-1.073zm9.885 3.36c-0.855 0.938-2.07 1.47-3.33 1.47h-7.5v-0.6c0-0.015 0.008-0.69 0.18-1.65h4.613c1.492 0 2.707-1.215 2.707-2.707 0-1.088-0.652-2.07-1.65-2.49l-1.875-0.795c0.825-0.428 1.778-0.675 2.865-0.743 0.113-0.015 0.225-0.015 0.33-0.015 2.483 0 4.598 1.778 4.808 4.058 0.12 1.29-0.285 2.52-1.148 3.472z"
android:strokeWidth="0.9999975" />
</vector>

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="45sp"
android:height="45sp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:fillColor="#7a7a7a"
android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
android:strokeWidth="3.57115" />
<path
android:fillAlpha="0.941176"
android:fillColor="#9f9f9f"
android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
android:strokeWidth="3.57115" />
<path
android:fillColor="#8a8a8a"
android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
android:strokeWidth="3.57115" />
<path
android:fillAlpha="0.9411765"
android:fillColor="#8a8a8a"
android:pathData="M12.483 10.865h3.907v6.978h-3.907z" />
<path
android:fillColor="#ffffff"
android:pathData="M21.841 13.785c-0.292-3.157-3.15-5.415-6.3-5.415-0.142 0-0.285 0.008-0.427 0.015C6.87 8.925 6.87 18.27 6.87 18.27v0.6c0 0.825 0.675 1.5 1.5 1.5h7.5c3.503 0 6.308-3.007 5.97-6.585zM9.316 14.04c0.428-0.967 0.96-1.762 1.605-2.392l2.715 1.147c0.45 0.188 0.735 0.623 0.735 1.11 0 0.668-0.54 1.208-1.207 1.208H8.91c0.113-0.345 0.24-0.705 0.405-1.073zm9.885 3.36c-0.855 0.938-2.07 1.47-3.33 1.47h-7.5v-0.6c0-0.015 0.008-0.69 0.18-1.65h4.613c1.492 0 2.707-1.215 2.707-2.707 0-1.088-0.652-2.07-1.65-2.49l-1.875-0.795c0.825-0.428 1.778-0.675 2.865-0.743 0.113-0.015 0.225-0.015 0.33-0.015 2.483 0 4.598 1.778 4.808 4.058 0.12 1.29-0.285 2.52-1.148 3.472z"
android:strokeWidth="0.9999975" />
</vector>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/supercars_control_button_left_up"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_weight="0.5"
android:text="left" />
<Button
android:id="@+id/supercars_control_button_center_up"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_weight="0.5"
android:text="forward" />
<Button
android:id="@+id/supercars_control_button_right_up"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_weight="0.5"
android:text="right" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/supercars_control_button_left_down"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_weight="0.5"
android:text="left" />
<Button
android:id="@+id/supercars_control_button_center_down"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_weight="0.5"
android:text="back" />
<Button
android:id="@+id/supercars_control_button_right_down"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_weight="0.5"
android:text="right" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1843,6 +1843,7 @@
<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="devicetype_super_cars">Shell Racing</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>