mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Zepp OS: Derive device sources from PNP ID
This commit is contained in:
+8
@@ -18,6 +18,8 @@ package nodomain.freeyourgadget.gadgetbridge.deviceevents;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
|
||||
@@ -25,6 +27,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
|
||||
public class GBDeviceEventUpdateDeviceInfo extends GBDeviceEvent {
|
||||
public ItemWithDetails item;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + (item != null ? ("item: " + item.getName() + "=" + item.getDetails()) : "<null>");
|
||||
}
|
||||
|
||||
public GBDeviceEventUpdateDeviceInfo(final ItemWithDetails item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
+21
-7
@@ -19,7 +19,11 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class DeviceInfo implements Parcelable{
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class DeviceInfo implements Parcelable {
|
||||
private String manufacturerName;
|
||||
private String modelNumber;
|
||||
private String serialNumber;
|
||||
@@ -28,7 +32,7 @@ public class DeviceInfo implements Parcelable{
|
||||
private String softwareRevision;
|
||||
private String systemId;
|
||||
private String regulatoryCertificationDataList;
|
||||
private String pnpId;
|
||||
private byte[] pnpId;
|
||||
|
||||
public DeviceInfo() {
|
||||
}
|
||||
@@ -42,7 +46,11 @@ public class DeviceInfo implements Parcelable{
|
||||
softwareRevision = in.readString();
|
||||
systemId = in.readString();
|
||||
regulatoryCertificationDataList = in.readString();
|
||||
pnpId = in.readString();
|
||||
final int pnpLength = in.readInt();
|
||||
if (pnpLength >= 0) {
|
||||
pnpId = new byte[pnpLength];
|
||||
in.readByteArray(pnpId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +63,12 @@ public class DeviceInfo implements Parcelable{
|
||||
dest.writeString(softwareRevision);
|
||||
dest.writeString(systemId);
|
||||
dest.writeString(regulatoryCertificationDataList);
|
||||
dest.writeString(pnpId);
|
||||
if (pnpId != null) {
|
||||
dest.writeInt(pnpId.length);
|
||||
dest.writeByteArray(pnpId);
|
||||
} else {
|
||||
dest.writeInt(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,14 +152,15 @@ public class DeviceInfo implements Parcelable{
|
||||
this.regulatoryCertificationDataList = regulatoryCertificationDataList;
|
||||
}
|
||||
|
||||
public String getPnpId() {
|
||||
public byte[] getPnpId() {
|
||||
return pnpId;
|
||||
}
|
||||
|
||||
public void setPnpId(String pnpId) {
|
||||
public void setPnpId(byte[] pnpId) {
|
||||
this.pnpId = pnpId;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DeviceInfo{" +
|
||||
@@ -158,7 +172,7 @@ public class DeviceInfo implements Parcelable{
|
||||
", softwareRevision='" + softwareRevision + '\'' +
|
||||
", systemId='" + systemId + '\'' +
|
||||
", regulatoryCertificationDataList='" + regulatoryCertificationDataList + '\'' +
|
||||
", pnpId='" + pnpId + '\'' +
|
||||
", pnpId='" + GB.hexdump(pnpId) + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
+2
-8
@@ -168,14 +168,8 @@ public class DeviceInfoProfile<T extends AbstractBTLESingleDeviceSupport> extend
|
||||
}
|
||||
|
||||
private void handlePnpId(final byte[] value) {
|
||||
if (value.length == 7) {
|
||||
// int vendorSource
|
||||
//
|
||||
// deviceInfo.setPnpId(pnpId);
|
||||
notify(createIntent(deviceInfo));
|
||||
} else {
|
||||
// TODO: LOG warning
|
||||
}
|
||||
deviceInfo.setPnpId(value);
|
||||
notify(createIntent(deviceInfo));
|
||||
}
|
||||
|
||||
private Intent createIntent(final DeviceInfo deviceInfo) {
|
||||
|
||||
+10
@@ -33,6 +33,7 @@ import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
|
||||
@@ -54,6 +55,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsDeviceInfoService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class ZeppOsBtleSupport extends AbstractBTLESingleDeviceSupport implements ZeppOsCommunicator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsBtleSupport.class);
|
||||
@@ -90,6 +93,13 @@ public class ZeppOsBtleSupport extends AbstractBTLESingleDeviceSupport implement
|
||||
versionCmd.fwVersion = versionCmd.fwVersion.substring(1);
|
||||
}
|
||||
handleGBDeviceEvent(versionCmd);
|
||||
|
||||
if (info.getPnpId() != null) {
|
||||
new GBDeviceEventUpdatePreferences(
|
||||
ZeppOsDeviceInfoService.PREF_KEY_DEVICE_PNP_ID,
|
||||
GB.hexdump(info.getPnpId())
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
addSupportedProfile(deviceInfoProfile);
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos
|
||||
|
||||
data class ZeppOsDeviceInfo(
|
||||
val productId: Int,
|
||||
val productVersion: Int,
|
||||
val deviceSource: Int,
|
||||
)
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos
|
||||
|
||||
object ZeppOsDeviceSources {
|
||||
// jq '.[] | [.value.productId, .value.productVersion, .deviceSource] | @csv' -r devices.json
|
||||
// TODO: A few are missing: mi band 7, amazfit band 7, amazfit gtr mini
|
||||
val DEVICE_SOURCES = listOf(
|
||||
ZeppOsDeviceInfo(94, 256, 224),
|
||||
ZeppOsDeviceInfo(94, 257, 225),
|
||||
ZeppOsDeviceInfo(95, 256, 226),
|
||||
ZeppOsDeviceInfo(95, 257, 227),
|
||||
ZeppOsDeviceInfo(93, 256, 229),
|
||||
ZeppOsDeviceInfo(93, 257, 230),
|
||||
ZeppOsDeviceInfo(105, 257, 242),
|
||||
ZeppOsDeviceInfo(115, 256, 246),
|
||||
ZeppOsDeviceInfo(115, 259, 247),
|
||||
ZeppOsDeviceInfo(116, 257, 251),
|
||||
ZeppOsDeviceInfo(117, 259, 254),
|
||||
ZeppOsDeviceInfo(63, 256, 414),
|
||||
ZeppOsDeviceInfo(63, 257, 415),
|
||||
ZeppOsDeviceInfo(113, 256, 418),
|
||||
ZeppOsDeviceInfo(113, 257, 419),
|
||||
ZeppOsDeviceInfo(93, 258, 6095106),
|
||||
ZeppOsDeviceInfo(100, 256, 6553856),
|
||||
ZeppOsDeviceInfo(100, 257, 6553857),
|
||||
ZeppOsDeviceInfo(120, 257, 7864577),
|
||||
ZeppOsDeviceInfo(121, 256, 7930112),
|
||||
ZeppOsDeviceInfo(121, 257, 7930113),
|
||||
ZeppOsDeviceInfo(122, 256, 7995648),
|
||||
ZeppOsDeviceInfo(122, 257, 7995649),
|
||||
ZeppOsDeviceInfo(124, 256, 8126720),
|
||||
ZeppOsDeviceInfo(124, 257, 8126721),
|
||||
ZeppOsDeviceInfo(124, 263, 8126727),
|
||||
ZeppOsDeviceInfo(125, 256, 8192256),
|
||||
ZeppOsDeviceInfo(125, 257, 8192257),
|
||||
ZeppOsDeviceInfo(126, 257, 8257793),
|
||||
ZeppOsDeviceInfo(127, 256, 8323328),
|
||||
ZeppOsDeviceInfo(127, 257, 8323329),
|
||||
ZeppOsDeviceInfo(128, 256, 8388864),
|
||||
ZeppOsDeviceInfo(128, 257, 8388865),
|
||||
ZeppOsDeviceInfo(129, 256, 8454400),
|
||||
ZeppOsDeviceInfo(129, 257, 8454401),
|
||||
ZeppOsDeviceInfo(130, 256, 8519936),
|
||||
ZeppOsDeviceInfo(130, 257, 8519937),
|
||||
ZeppOsDeviceInfo(130, 259, 8519939),
|
||||
ZeppOsDeviceInfo(133, 256, 8716544),
|
||||
ZeppOsDeviceInfo(133, 257, 8716545),
|
||||
ZeppOsDeviceInfo(133, 259, 8716547),
|
||||
ZeppOsDeviceInfo(134, 257, 8782081),
|
||||
ZeppOsDeviceInfo(134, 264, 8782088),
|
||||
ZeppOsDeviceInfo(134, 265, 8782089),
|
||||
ZeppOsDeviceInfo(136, 256, 8913152),
|
||||
ZeppOsDeviceInfo(136, 257, 8913153),
|
||||
ZeppOsDeviceInfo(136, 259, 8913155),
|
||||
ZeppOsDeviceInfo(136, 263, 8913159),
|
||||
ZeppOsDeviceInfo(144, 257, 9437441),
|
||||
ZeppOsDeviceInfo(145, 257, 9502977),
|
||||
ZeppOsDeviceInfo(146, 256, 9568512),
|
||||
ZeppOsDeviceInfo(146, 257, 9568513),
|
||||
ZeppOsDeviceInfo(146, 259, 9568515),
|
||||
ZeppOsDeviceInfo(149, 256, 9765120),
|
||||
ZeppOsDeviceInfo(149, 257, 9765121),
|
||||
ZeppOsDeviceInfo(150, 256, 9830656),
|
||||
ZeppOsDeviceInfo(150, 257, 9830657),
|
||||
ZeppOsDeviceInfo(150, 259, 9830659),
|
||||
ZeppOsDeviceInfo(154, 256, 10092800),
|
||||
ZeppOsDeviceInfo(154, 257, 10092801),
|
||||
ZeppOsDeviceInfo(154, 259, 10092803),
|
||||
ZeppOsDeviceInfo(154, 263, 10092807),
|
||||
ZeppOsDeviceInfo(155, 257, 10158337),
|
||||
ZeppOsDeviceInfo(156, 256, 10223872),
|
||||
ZeppOsDeviceInfo(156, 257, 10223873),
|
||||
ZeppOsDeviceInfo(156, 259, 10223875),
|
||||
ZeppOsDeviceInfo(160, 256, 10486016),
|
||||
ZeppOsDeviceInfo(160, 257, 10486017),
|
||||
ZeppOsDeviceInfo(160, 259, 10486019),
|
||||
ZeppOsDeviceInfo(161, 256, 10551552),
|
||||
ZeppOsDeviceInfo(161, 257, 10551553),
|
||||
ZeppOsDeviceInfo(161, 259, 10551555),
|
||||
ZeppOsDeviceInfo(162, 259, 10617091),
|
||||
ZeppOsDeviceInfo(163, 256, 10682624),
|
||||
ZeppOsDeviceInfo(163, 257, 10682625),
|
||||
ZeppOsDeviceInfo(163, 259, 10682627),
|
||||
ZeppOsDeviceInfo(165, 257, 10813697),
|
||||
ZeppOsDeviceInfo(165, 259, 10813699),
|
||||
)
|
||||
|
||||
fun resolve(productId: Int, productVersion: Int): ZeppOsDeviceInfo? {
|
||||
return DEVICE_SOURCES.firstOrNull { it.productId == productId && it.productVersion == productVersion }
|
||||
}
|
||||
}
|
||||
+75
-5
@@ -16,17 +16,31 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsDeviceInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsDeviceSources;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
|
||||
|
||||
public class ZeppOsDeviceInfoService extends AbstractZeppOsService {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsDeviceInfoService.class);
|
||||
@@ -36,6 +50,8 @@ public class ZeppOsDeviceInfoService extends AbstractZeppOsService {
|
||||
private static final byte CMD_REQUEST = 0x01;
|
||||
private static final byte CMD_REPLY = 0x02;
|
||||
|
||||
public static String PREF_KEY_DEVICE_PNP_ID = "zepp_os_pnp_id";
|
||||
|
||||
public ZeppOsDeviceInfoService(final ZeppOsSupport support) {
|
||||
super(support, false);
|
||||
}
|
||||
@@ -52,15 +68,20 @@ public class ZeppOsDeviceInfoService extends AbstractZeppOsService {
|
||||
return;
|
||||
}
|
||||
|
||||
final GBDeviceEventVersionInfo versionInfo = decodeDeviceInfo(payload);
|
||||
evaluateGBDeviceEvent(versionInfo);
|
||||
final List<GBDeviceEvent> events = decodeDeviceInfo(payload);
|
||||
for (GBDeviceEvent event : events) {
|
||||
evaluateGBDeviceEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public static GBDeviceEventVersionInfo decodeDeviceInfo(final byte[] payload) {
|
||||
public static List<GBDeviceEvent> decodeDeviceInfo(final byte[] payload) {
|
||||
final ByteBuffer buf = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
final GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo();
|
||||
|
||||
final List<GBDeviceEvent> events = new ArrayList<>(2);
|
||||
events.add(versionInfo);
|
||||
|
||||
if (buf.get() != CMD_REPLY) {
|
||||
throw new IllegalArgumentException("not a device info reply payload");
|
||||
}
|
||||
@@ -68,7 +89,7 @@ public class ZeppOsDeviceInfoService extends AbstractZeppOsService {
|
||||
final byte one = buf.get();
|
||||
if (one != 1) {
|
||||
LOG.warn("Unexpected device info payload 2nd byte {}", String.format("0x%02x", one));
|
||||
return versionInfo;
|
||||
return events;
|
||||
}
|
||||
|
||||
// Active 2: 0x00000000000000ff
|
||||
@@ -96,7 +117,56 @@ public class ZeppOsDeviceInfoService extends AbstractZeppOsService {
|
||||
versionInfo.fwVersion = StringUtils.untilNullTerminator(buf);
|
||||
}
|
||||
|
||||
return versionInfo;
|
||||
if ((flags & 16) != 0 && buf.remaining() >= 7) {
|
||||
final byte[] pnpId = new byte[7];
|
||||
buf.get(pnpId);
|
||||
|
||||
events.add(new GBDeviceEventUpdatePreferences(
|
||||
PREF_KEY_DEVICE_PNP_ID,
|
||||
GB.hexdump(pnpId)
|
||||
));
|
||||
|
||||
final ZeppOsDeviceInfo deviceInfo = getDeviceInfo(pnpId);
|
||||
|
||||
LOG.debug("Got PNP ID={} -> {}", GB.hexdump(pnpId), deviceInfo);
|
||||
|
||||
if (deviceInfo != null) {
|
||||
events.add(new GBDeviceEventUpdateDeviceInfo("PRODUCT_ID: ", String.valueOf(deviceInfo.getProductId())));
|
||||
events.add(new GBDeviceEventUpdateDeviceInfo("PRODUCT_VERSION: ", String.valueOf(deviceInfo.getProductVersion())));
|
||||
events.add(new GBDeviceEventUpdateDeviceInfo("DEVICE_SOURCE: ", String.valueOf(deviceInfo.getDeviceSource())));
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ZeppOsDeviceInfo getDeviceInfo(final GBDevice gbDevice) {
|
||||
final DevicePrefs devicePrefs = GBApplication.getDevicePrefs(gbDevice);
|
||||
final String pnpHex = devicePrefs.getString(PREF_KEY_DEVICE_PNP_ID, null);
|
||||
if (pnpHex == null || pnpHex.length() != 14) {
|
||||
LOG.error("Unknown or invalid PNP ID for {}: {}", gbDevice, pnpHex);
|
||||
return null;
|
||||
}
|
||||
|
||||
final byte[] pnpId = GB.hexStringToByteArray(pnpHex);
|
||||
|
||||
return getDeviceInfo(pnpId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ZeppOsDeviceInfo getDeviceInfo(final byte[] pnpId) {
|
||||
final int productId = BLETypeConversions.toUint16(pnpId, 3);
|
||||
final int productVersion = BLETypeConversions.toUint16(pnpId, 5);
|
||||
|
||||
LOG.debug(
|
||||
"Resolving source from pnpId={} for productId={}, productVersion={}",
|
||||
GB.hexdump(pnpId),
|
||||
productId,
|
||||
productVersion
|
||||
);
|
||||
|
||||
return ZeppOsDeviceSources.INSTANCE.resolve(productId, productVersion);
|
||||
}
|
||||
|
||||
public void requestDeviceInfo(final ZeppOsTransactionBuilder builder) {
|
||||
|
||||
+6
@@ -1,5 +1,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.kotlin
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions
|
||||
|
||||
fun ByteArray.startsWith(prefix: ByteArray): Boolean {
|
||||
if (prefix.size > this.size) {
|
||||
return false
|
||||
@@ -11,3 +13,7 @@ fun ByteArray.startsWith(prefix: ByteArray): Boolean {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun ByteArray.readUint16LE(offset: Int): Int {
|
||||
return BLETypeConversions.toUint16(this, offset)
|
||||
}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class ZeppOsDeviceSourcesTest {
|
||||
@Test
|
||||
fun testNoDuplicateProductIdAndVersion() {
|
||||
// We need all (productId, productVersion) pairs to be distinct
|
||||
val pairs = ZeppOsDeviceSources.DEVICE_SOURCES.map { Pair(it.productId, it.productVersion) }
|
||||
val uniquePairs = pairs.toSet()
|
||||
assertEquals(
|
||||
"Found duplicate (productId, productVersion) pairs in DEVICE_SOURCES",
|
||||
pairs.size,
|
||||
uniquePairs.size
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user