[iCloud] Fix things reappearing in inbox (#14661)

* Use deviceDiscoveryId instead of id
* Use device display name if discovery id is empty

Signed-off-by: Simon Spielmann <simon.spielmann@gmx.de>
This commit is contained in:
Simon Spielmann 2023-05-03 12:27:48 +02:00 committed by GitHub
parent dc5d76d762
commit 67f1a80afd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 443 additions and 7 deletions

View File

@ -63,19 +63,23 @@ public class ICloudDeviceDiscovery extends AbstractDiscoveryService implements I
String deviceOwnerName = deviceInformationRecord.getName(); String deviceOwnerName = deviceInformationRecord.getName();
String thingLabel = deviceOwnerName + " (" + deviceTypeName + ")"; String thingLabel = deviceOwnerName + " (" + deviceTypeName + ")";
String deviceId = deviceInformationRecord.getId(); String deviceDiscoveryId = deviceInformationRecord.getDeviceDiscoveryId();
String deviceIdHash = Integer.toHexString(deviceId.hashCode()); if (deviceDiscoveryId == null || deviceDiscoveryId.isBlank()) {
logger.debug("deviceDiscoveryId is empty, using device name for identification.");
deviceDiscoveryId = deviceOwnerName;
}
String deviceIdHash = Integer.toHexString(deviceDiscoveryId.hashCode());
logger.debug("iCloud device discovery for [{}]", deviceInformationRecord.getDeviceDisplayName()); logger.debug("iCloud device discovery for [{}]", deviceInformationRecord.getDeviceDisplayName());
ThingUID uid = new ThingUID(THING_TYPE_ICLOUDDEVICE, bridgeUID, deviceIdHash); ThingUID uid = new ThingUID(THING_TYPE_ICLOUDDEVICE, bridgeUID, deviceIdHash);
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID) DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withProperty(DEVICE_PROPERTY_ID, deviceId) .withProperty(DEVICE_PROPERTY_ID, deviceDiscoveryId)
.withProperty(translatorService.getText(DEVICE_PROPERTY_ID_LABEL), deviceId) .withProperty(translatorService.getText(DEVICE_PROPERTY_ID_LABEL), deviceDiscoveryId)
.withProperty(translatorService.getText(DEVICE_PROPERTY_OWNER_LABEL), deviceOwnerName) .withProperty(translatorService.getText(DEVICE_PROPERTY_OWNER_LABEL), deviceOwnerName)
.withRepresentationProperty(DEVICE_PROPERTY_ID).withLabel(thingLabel).build(); .withRepresentationProperty(DEVICE_PROPERTY_ID).withLabel(thingLabel).build();
logger.debug("Device [{}, {}] found.", deviceIdHash, deviceId); logger.debug("Device [{}, {}] found.", deviceIdHash, deviceDiscoveryId);
thingDiscovered(result); thingDiscovered(result);

View File

@ -185,8 +185,11 @@ public class ICloudDeviceHandler extends BaseThingHandler implements ICloudDevic
this.logger.debug("Device: [{}]", this.deviceId); this.logger.debug("Device: [{}]", this.deviceId);
for (ICloudDeviceInformation deviceInformationRecord : deviceInformationList) { for (ICloudDeviceInformation deviceInformationRecord : deviceInformationList) {
String currentId = deviceInformationRecord.getId(); String currentId = deviceInformationRecord.getDeviceDiscoveryId();
if (currentId == null || currentId.isBlank()) {
logger.debug("deviceDiscoveryId is empty, using device name for identification.");
currentId = deviceInformationRecord.getName();
}
logger.debug("Current data element: [id = {}]", currentId); logger.debug("Current data element: [id = {}]", currentId);
if (currentId != null && currentId.equals(deviceId)) { if (currentId != null && currentId.equals(deviceId)) {

View File

@ -50,6 +50,8 @@ public class ICloudDeviceInformation {
private String id; private String id;
private String deviceDiscoveryId;
private boolean isLocating; private boolean isLocating;
private boolean isMac; private boolean isMac;
@ -160,6 +162,10 @@ public class ICloudDeviceInformation {
return this.id; return this.id;
} }
public String getDeviceDiscoveryId() {
return this.deviceDiscoveryId;
}
public boolean getIsLocating() { public boolean getIsLocating() {
return this.isLocating; return this.isLocating;
} }

View File

@ -13,22 +13,46 @@
package org.openhab.binding.icloud; package org.openhab.binding.icloud;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.openhab.binding.icloud.internal.ICloudBindingConstants.THING_TYPE_ICLOUDDEVICE;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.openhab.binding.icloud.internal.ICloudApiResponseException; import org.openhab.binding.icloud.internal.ICloudApiResponseException;
import org.openhab.binding.icloud.internal.ICloudBindingConstants;
import org.openhab.binding.icloud.internal.ICloudService; import org.openhab.binding.icloud.internal.ICloudService;
import org.openhab.binding.icloud.internal.handler.ICloudDeviceHandler;
import org.openhab.binding.icloud.internal.handler.dto.json.response.ICloudAccountDataResponse; import org.openhab.binding.icloud.internal.handler.dto.json.response.ICloudAccountDataResponse;
import org.openhab.binding.icloud.internal.utilities.JsonUtils; import org.openhab.binding.icloud.internal.utilities.JsonUtils;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.storage.json.internal.JsonStorage; import org.openhab.core.storage.json.internal.JsonStorage;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelGroupUID;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.internal.ThingImpl;
import org.openhab.core.thing.type.ChannelGroupTypeUID;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -94,4 +118,403 @@ public class TestICloud {
assertNotNull(deviceInfo); assertNotNull(deviceInfo);
stateStorage.flush(); stateStorage.flush();
} }
@Test
@EnabledIfSystemProperty(named = "icloud.test.email", matches = ".*", disabledReason = "Only for manual execution.")
public void testDiscovery() {
String icloudDeviceRespond = """
{
"userInfo": {
"accountFormatter": 0,
"firstName": "Firstname",
"lastName": "Lastname",
"membersInfo": {
"XXX~": {
"accountFormatter": 0,
"firstName": "Firstname",
"lastName": "Lastname",
"deviceFetchStatus": "LOADING",
"useAuthWidget": true,
"isHSA": true,
"appleId": "dummy@dummy.local"
}
},
"hasMembers": true
},
"alert": null,
"serverContext": {
"minCallbackIntervalInMS": 5000,
"preferredLanguage": "de-de",
"enable2FAFamilyActions": false,
"lastSessionExtensionTime": null,
"validRegion": true,
"callbackIntervalInMS": 2000,
"enableMapStats": true,
"timezone": {
"currentOffset": -25200000,
"previousTransition": 1678615199999,
"previousOffset": -28800000,
"tzCurrentName": "Pacific Daylight Time",
"tzName": "America/Los_Angeles"
},
"authToken": null,
"maxCallbackIntervalInMS": 60000,
"classicUser": false,
"isHSA": true,
"trackInfoCacheDurationInSecs": 86400,
"imageBaseUrl": "https://statici.icloud.com",
"minTrackLocThresholdInMts": 100,
"itemLearnMoreURL": "https://support.apple.com/kb/HT211331?viewlocale=de_DE",
"maxLocatingTime": 90000,
"itemsTabEnabled": true,
"sessionLifespan": 900000,
"info": "",
"prefsUpdateTime": 1679378160178,
"useAuthWidget": true,
"inaccuracyRadiusThreshold": 200,
"clientId": "",
"serverTimestamp": 1679640300550,
"enable2FAFamilyRemove": false,
"deviceImageVersion": "22",
"macCount": 0,
"deviceLoadStatus": "200",
"maxDeviceLoadTime": 60000,
"prsId": 146786264,
"showSllNow": false,
"cloudUser": true,
"enable2FAErase": false
},
"userPreferences": {
"webPrefs": {
"id": "web_prefs",
"selectedDeviceId": "XXX"
}
},
"content": [
{
"msg": null,
"activationLocked": true,
"passcodeLength": 6,
"features": {
"BTR": false,
"LLC": false,
"CLK": false,
"TEU": true,
"SND": true,
"ALS": false,
"CLT": false,
"SVP": false,
"SPN": false,
"XRM": false,
"NWF": true,
"CWP": false,
"MSG": true,
"LOC": true,
"LME": false,
"LMG": false,
"LYU": false,
"LKL": false,
"LST": true,
"LKM": false,
"WMG": true,
"SCA": false,
"PSS": false,
"EAL": false,
"LAE": false,
"PIN": false,
"LCK": true,
"REM": true,
"MCS": false,
"KEY": false,
"KPD": false,
"WIP": true
},
"scd": false,
"id": "device.id",
"remoteLock": null,
"rm2State": 0,
"modelDisplayName": "iPad",
"fmlyShare": false,
"lostModeCapable": true,
"wipedTimestamp": null,
"encodedDeviceId": null,
"scdPh": "",
"locationCapable": true,
"trackingInfo": null,
"name": "My iPad",
"isMac": false,
"thisDevice": false,
"deviceClass": "iPad",
"nwd": false,
"remoteWipe": null,
"canWipeAfterLock": true,
"baUUID": "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF",
"lostModeEnabled": false,
"wipeInProgress": false,
"deviceStatus": "200",
"deviceColor": "1-1-0",
"isConsideredAccessory": false,
"deviceWithYou": false,
"lowPowerMode": false,
"rawDeviceModel": "iPad13,1",
"deviceDiscoveryId": "AAAAAAAA-FFFF-FFFF-FFFF-FFFFFFFFFFFF",
"isLocating": true,
"lostTimestamp": "",
"mesg": null,
"batteryLevel": 0.1599999964237213,
"locationEnabled": true,
"lockedTimestamp": null,
"locFoundEnabled": false,
"snd": null,
"lostDevice": null,
"deviceDisplayName": "iPad Air (4th Generation)",
"prsId": null,
"audioChannels": [
],
"batteryStatus": "NotCharging",
"location": {
"isOld": false,
"isInaccurate": false,
"altitude": 0.0,
"positionType": "Unknown",
"secureLocation": null,
"secureLocationTs": 0,
"latitude": 20.00000000000000,
"floorLevel": 0,
"horizontalAccuracy": 1.0,
"locationType": "",
"timeStamp": 1679640074735,
"locationFinished": true,
"verticalAccuracy": 0.0,
"locationMode": null,
"longitude": 10.00000000000000
},
"deviceModel": "FourthGen-1-1-0",
"maxMsgChar": 160,
"darkWake": false
},
{
"msg": null,
"activationLocked": true,
"passcodeLength": 6,
"features": {
"BTR": false,
"LLC": false,
"CLK": false,
"TEU": true,
"SND": true,
"ALS": false,
"CLT": false,
"SVP": false,
"SPN": false,
"XRM": false,
"NWF": true,
"CWP": false,
"MSG": true,
"LOC": true,
"LME": false,
"LMG": false,
"LYU": false,
"LKL": false,
"LST": true,
"LKM": false,
"WMG": true,
"SCA": false,
"PSS": false,
"EAL": false,
"LAE": false,
"PIN": false,
"LCK": true,
"REM": true,
"MCS": false,
"KEY": false,
"KPD": false,
"WIP": true
},
"scd": false,
"id": "device.id",
"remoteLock": null,
"rm2State": 0,
"modelDisplayName": "iPad",
"fmlyShare": false,
"lostModeCapable": true,
"wipedTimestamp": null,
"encodedDeviceId": null,
"scdPh": "",
"locationCapable": true,
"trackingInfo": null,
"name": "iPad Air without ID",
"isMac": false,
"thisDevice": false,
"deviceClass": "iPad",
"nwd": false,
"remoteWipe": null,
"canWipeAfterLock": true,
"baUUID": "",
"lostModeEnabled": false,
"wipeInProgress": false,
"deviceStatus": "200",
"deviceColor": "1-1-0",
"isConsideredAccessory": false,
"deviceWithYou": false,
"lowPowerMode": false,
"rawDeviceModel": "iPad13,1",
"deviceDiscoveryId": "",
"isLocating": true,
"lostTimestamp": "",
"mesg": null,
"batteryLevel": 0.1599999964237213,
"locationEnabled": true,
"lockedTimestamp": null,
"locFoundEnabled": false,
"snd": null,
"lostDevice": null,
"deviceDisplayName": "iPad",
"prsId": null,
"audioChannels": [
],
"batteryStatus": "NotCharging",
"location": {
"isOld": false,
"isInaccurate": false,
"altitude": 0.0,
"positionType": "Unknown",
"secureLocation": null,
"secureLocationTs": 0,
"latitude": 20.00000000000000,
"floorLevel": 0,
"horizontalAccuracy": 1.0,
"locationType": "",
"timeStamp": 1679640074735,
"locationFinished": true,
"verticalAccuracy": 0.0,
"locationMode": null,
"longitude": 10.00000000000000
},
"deviceModel": "FourthGen-1-1-0",
"maxMsgChar": 160,
"darkWake": false
}
],
"statusCode": "200"
}
""";
ICloudAccountDataResponse deviceInfo = JsonUtils.fromJson(icloudDeviceRespond, ICloudAccountDataResponse.class);
// Check device with discoveryId
ThingImpl thing = createThing("AAAAAAAA-FFFF-FFFF-FFFF-FFFFFFFFFFFF");
ICloudDeviceHandler handler = createDeviceHandler(thing);
handler.deviceInformationUpdate(deviceInfo.getICloudDeviceInformationList());
assertEquals(ThingStatus.ONLINE, handler.getThing().getStatus());
// Check device without discoveryId
thing = createThing("iPad Air without ID");
handler = createDeviceHandler(thing);
handler.deviceInformationUpdate(deviceInfo.getICloudDeviceInformationList());
assertEquals(ThingStatus.ONLINE, handler.getThing().getStatus());
}
/**
* @return
*/
private ThingImpl createThing(String deviceId) {
String deviceIdHash = Integer.toHexString(deviceId.hashCode());
ThingUID uid = new ThingUID(THING_TYPE_ICLOUDDEVICE, "dummyBridgeId", deviceIdHash);
ThingImpl thing = new ThingImpl(THING_TYPE_ICLOUDDEVICE, uid);
thing.getConfiguration().put(ICloudBindingConstants.DEVICE_PROPERTY_ID, deviceId);
return thing;
}
/**
* @param thing
* @return
*/
private ICloudDeviceHandler createDeviceHandler(ThingImpl thing) {
ICloudDeviceHandler handler = new ICloudDeviceHandler(thing);
handler.setCallback(new ThingHandlerCallback() {
@Override
public void validateConfigurationParameters(Channel channel, Map<String, Object> configurationParameters) {
}
@Override
public void validateConfigurationParameters(Thing thing, Map<String, Object> configurationParameters) {
}
@Override
public void thingUpdated(Thing thing) {
}
@Override
public void statusUpdated(Thing thing, ThingStatusInfo thingStatus) {
thing.setStatusInfo(thingStatus);
}
@Override
public void stateUpdated(ChannelUID channelUID, State state) {
}
@Override
public void postCommand(ChannelUID channelUID, Command command) {
}
@Override
public void migrateThingType(Thing thing, ThingTypeUID thingTypeUID, Configuration configuration) {
}
@Override
public boolean isChannelLinked(ChannelUID channelUID) {
return false;
}
@Override
public @Nullable ConfigDescription getConfigDescription(ThingTypeUID thingTypeUID) {
return null;
}
@Override
public @Nullable ConfigDescription getConfigDescription(ChannelTypeUID channelTypeUID) {
return null;
}
@Override
public @Nullable Bridge getBridge(ThingUID bridgeUID) {
return null;
}
@Override
public ChannelBuilder editChannel(Thing thing, ChannelUID channelUID) {
return ChannelBuilder.create(channelUID); // dummy implementation, probably won't work.
}
@Override
public List<ChannelBuilder> createChannelBuilders(ChannelGroupUID channelGroupUID,
ChannelGroupTypeUID channelGroupTypeUID) {
return new ArrayList<ChannelBuilder>(); // dummy implementation, probably won't work.
}
@Override
public ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelTypeUID channelTypeUID) {
return ChannelBuilder.create(channelUID); // dummy implementation, probably won't work.
}
@Override
public void configurationUpdated(Thing thing) {
}
@Override
public void channelTriggered(Thing thing, ChannelUID channelUID, String event) {
}
});
handler.initialize();
return handler;
}
} }