|-----------------|---------|----------|---------------------------------------------------------------------|
| host | text | true | Device IP address |
| token | text | true | Token for communication (in Hex) |
-| deviceId | text | true | Device ID number for communication (in Hex) |
+| deviceId | text | true | Device Id (typically a number for normal devices) for communication |
| model | text | false | Device model string, used to determine the subtype |
| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) |
| timeout | integer | false | Timeout time in milliseconds |
### Example Thing file
-`Thing miio:basic:light "My Light" [ host="192.168.x.x", token="put here your token", deviceId="0326xxxx", model="philips.light.bulb", communication="direct" ]`
+`Thing miio:basic:light "My Light" [ host="192.168.x.x", token="put here your token", deviceId="326xxxx", model="philips.light.bulb", communication="direct" ]`
or in case of unknown models include the model information of a similar device that is supported:
-`Thing miio:vacuum:s50 "vacuum" @ "livingroom" [ host="192.168.15.20", token="xxxxxxx", deviceId=“0470DDAA”, model="roborock.vacuum.s4", communication="cloud"]`
+`Thing miio:vacuum:s50 "vacuum" @ "livingroom" [ host="192.168.15.20", token="xxxxxxx", deviceId="326xxxx", model="roborock.vacuum.s4", communication="direct" ]`
# Advanced: Unsupported devices
|-----------------|---------|----------|---------------------------------------------------------------------|
| host | text | true | Device IP address |
| token | text | true | Token for communication (in Hex) |
-| deviceId | text | true | Device ID number for communication (in Hex) |
+| deviceId | text | true | Device Id (typically a number for normal devices) for communication |
| model | text | false | Device model string, used to determine the subtype |
| refreshInterval | integer | false | Refresh interval for refreshing the data in seconds. (0=disabled) |
| timeout | integer | false | Timeout time in milliseconds |
### Example Thing file
-`Thing miio:basic:light "My Light" [ host="192.168.x.x", token="put here your token", deviceId="0326xxxx", model="philips.light.bulb", communication="direct" ]`
+`Thing miio:basic:light "My Light" [ host="192.168.x.x", token="put here your token", deviceId="326xxxx", model="philips.light.bulb", communication="direct" ]`
or in case of unknown models include the model information of a similar device that is supported:
-`Thing miio:vacuum:s50 "vacuum" @ "livingroom" [ host="192.168.15.20", token="xxxxxxx", deviceId=“0470DDAA”, model="roborock.vacuum.s4", communication="cloud"]`
+`Thing miio:vacuum:s50 "vacuum" @ "livingroom" [ host="192.168.15.20", token="xxxxxxx", deviceId="326xxxx", model="roborock.vacuum.s4", communication="direct" ]`
# Advanced: Unsupported devices
}
return value;
}
+
+ /**
+ * Formats the deviceId to a hex string if possible. Otherwise returns the id unmodified.
+ *
+ * @param did
+ * @return did
+ */
+ public static String getHexId(String did) {
+ if (!did.isBlank() && !did.contains(".")) {
+ return toHEX(did);
+ }
+ return did;
+ }
}
if (deviceListState != DeviceListState.AVAILABLE) {
return null;
}
- String did = Long.toString(Long.parseUnsignedLong(id, 16));
List<CloudDeviceDTO> devicedata = new ArrayList<>();
for (CloudDeviceDTO deviceDetails : deviceList) {
- if (deviceDetails.getDid().contentEquals(did)) {
+ if (deviceDetails.getDid().contentEquals(id)) {
devicedata.add(deviceDetails);
}
}
if (cloudConnector.isConnected()) {
List<CloudDeviceDTO> dv = cloudConnector.getDevicesList();
for (CloudDeviceDTO device : dv) {
- String id = Utils.toHEX(device.getDid());
+ String id = device.getDid();
if (cloudDiscoveryMode.contentEquals(SUPPORTED)) {
if (MiIoDevices.getType(device.getModel()).getThingType().equals(THING_TYPE_UNSUPPORTED)) {
logger.warn("Discovered from cloud, but ignored because not supported: {} {}", id, device);
logger.debug("Discovered from cloud: {} {}", id, device);
cloudDevices.put(id, device.getLocalip());
String token = device.getToken();
- String label = device.getName() + " " + id + " (" + device.getDid() + ")";
+ String label = device.getName() + " " + id + " (" + Utils.getHexId(id) + ")";
String country = device.getServer();
boolean isOnline = device.getIsOnline();
String ip = device.getLocalip();
logger.trace("Discovery responses from : {}:{}", ip, Utils.getSpacedHex(response));
Message msg = new Message(response);
String token = Utils.getHex(msg.getChecksum());
- String id = Utils.getHex(msg.getDeviceId());
- String label = "Xiaomi Mi Device " + id + " (" + Utils.fromHEX(id) + ")";
+ String hexId = Utils.getHex(msg.getDeviceId());
+ String id = Utils.fromHEX(hexId);
+ String label = "Xiaomi Mi Device " + id + " (" + Utils.getHexId(id) + ")";
String country = "";
boolean isOnline = false;
if (ip.equals(cloudDevices.get(id))) {
if (cloudInfo != null) {
logger.debug("Cloud Info: {}", cloudInfo);
token = cloudInfo.getToken();
- label = cloudInfo.getName() + " " + id + " (" + Utils.fromHEX(id) + ")";
+ label = cloudInfo.getName() + " " + id + " (" + Utils.getHexId(id) + ")";
country = cloudInfo.getServer();
isOnline = cloudInfo.getIsOnline();
}
}
private void submitDiscovery(String ip, String token, String id, String label, String country, boolean isOnline) {
- ThingUID uid = new ThingUID(THING_TYPE_MIIO, id.replace(".", "_"));
+ ThingUID uid = new ThingUID(THING_TYPE_MIIO, Utils.getHexId(id).replace(".", "_"));
DiscoveryResultBuilder dr = DiscoveryResultBuilder.create(uid).withProperty(PROPERTY_HOST_IP, ip)
.withProperty(PROPERTY_DID, id);
if (IGNORED_TOKENS.contains(token)) {
- logger.debug("Discovered Mi Device {} ({}) at {} as {}", id, Utils.fromHEX(id), ip, uid);
+ logger.debug("Discovered Mi Device {} ({}) at {} as {}", id, Utils.getHexId(id), ip, uid);
logger.debug(
"No token discovered for device {}. For options how to get the token, check the binding readme.",
id);
dr = dr.withRepresentationProperty(PROPERTY_DID).withLabel(label);
} else {
- logger.debug("Discovered Mi Device {} ({}) at {} as {} with token {}", id, Utils.fromHEX(id), ip, uid,
+ logger.debug("Discovered Mi Device {} ({}) at {} as {} with token {}", id, Utils.getHexId(id), ip, uid,
token);
dr = dr.withProperty(PROPERTY_TOKEN, token).withRepresentationProperty(PROPERTY_DID)
.withLabel(label + " with token");
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.miio.internal.discovery;
-
-import static org.openhab.binding.miio.internal.MiIoBindingConstants.*;
-
-import java.net.InetAddress;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jmdns.ServiceInfo;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.miio.internal.MiIoDevices;
-import org.openhab.binding.miio.internal.cloud.CloudConnector;
-import org.openhab.binding.miio.internal.cloud.CloudDeviceDTO;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Discovers Mi IO devices announced by mDNS
- *
- * @author Marcel Verpaalen - Initial contribution
- *
- */
-@NonNullByDefault
-@Component(service = MDNSDiscoveryParticipant.class)
-public class MiIoDiscoveryParticipant implements MDNSDiscoveryParticipant {
-
- private final CloudConnector cloudConnector;
- private Logger logger = LoggerFactory.getLogger(MiIoDiscoveryParticipant.class);
-
- @Activate
- public MiIoDiscoveryParticipant(@Reference CloudConnector cloudConnector) {
- this.cloudConnector = cloudConnector;
- logger.debug("Start Xiaomi Mi IO mDNS discovery");
- }
-
- @Override
- public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
- return (NONGENERIC_THING_TYPES_UIDS);
- }
-
- @Override
- public String getServiceType() {
- return "_miio._udp.local.";
- }
-
- @Override
- public @Nullable ThingUID getThingUID(@Nullable ServiceInfo service) {
- if (service == null) {
- return null;
- }
- logger.trace("ServiceInfo: {}", service);
- String id[] = service.getName().split("_miio");
- if (id.length != 2) {
- logger.trace("mDNS Could not identify Type / Device Id from '{}'", service.getName());
- return null;
- }
- long did;
- try {
- did = Long.parseUnsignedLong(id[1]);
- } catch (Exception e) {
- logger.trace("mDNS Could not identify Device ID from '{}'", id[1]);
- return null;
- }
- ThingTypeUID thingType = MiIoDevices.getType(id[0].replaceAll("-", ".")).getThingType();
- String uidName = String.format("%08X", did);
- logger.debug("mDNS {} identified as thingtype {} with did {} ({})", id[0], thingType, uidName, did);
- return new ThingUID(thingType, uidName);
- }
-
- private @Nullable InetAddress getIpAddress(ServiceInfo service) {
- InetAddress address = null;
- for (InetAddress addr : service.getInet4Addresses()) {
- return addr;
- }
- // Fallback for Inet6addresses
- for (InetAddress addr : service.getInet6Addresses()) {
- return addr;
- }
- return address;
- }
-
- @Override
- public @Nullable DiscoveryResult createResult(ServiceInfo service) {
- DiscoveryResult result = null;
- ThingUID uid = getThingUID(service);
- if (uid != null) {
- Map<String, Object> properties = new HashMap<>(2);
- // remove the domain from the name
- InetAddress ip = getIpAddress(service);
- if (ip == null) {
- logger.debug("Mi IO mDNS Discovery could not determine ip address from service info: {}", service);
- return null;
- }
- String inetAddress = ip.toString().substring(1); // trim leading slash
- String id = uid.getId();
- String label = "Xiaomi Mi Device " + id + " (" + Long.parseUnsignedLong(id, 16) + ") " + service.getName();
- if (cloudConnector.isConnected()) {
- cloudConnector.getDevicesList();
- CloudDeviceDTO cloudInfo = cloudConnector.getDeviceInfo(id);
- if (cloudInfo != null) {
- logger.debug("Cloud Info: {}", cloudInfo);
- properties.put(PROPERTY_TOKEN, cloudInfo.getToken());
- label = label + " with token";
- String country = cloudInfo.getServer();
- if (!country.isEmpty() && cloudInfo.getIsOnline()) {
- properties.put(PROPERTY_CLOUDSERVER, country);
- }
- }
- }
- properties.put(PROPERTY_HOST_IP, inetAddress);
- properties.put(PROPERTY_DID, id);
- result = DiscoveryResultBuilder.create(uid).withProperties(properties)
- .withRepresentationProperty(PROPERTY_DID).withLabel(label).build();
- logger.debug("Mi IO mDNS Discovery found {} with address '{}:{}' name '{}'", uid, inetAddress,
- service.getPort(), label);
- }
- return result;
- }
-}
}
@Nullable
String deviceId = configuration.deviceId;
+ if (deviceId.length() == 8 && deviceId.matches("^.*[a-zA-Z]+.*$")) {
+ logger.warn(
+ "As per openHAB version 3.2 the deviceId is no longer a string with hexadecimals, instead it is a string with the numeric respresentation of the deviceId. If you continue seeing this message, update deviceId in your thing configuration");
+ deviceId = "";
+ }
try {
- if (deviceId.length() == 8 && tokenCheckPass(configuration.token)) {
- final MiIoAsyncCommunication miioCom = new MiIoAsyncCommunication(configuration.host, token,
- Utils.hexStringToByteArray(deviceId), lastId, configuration.timeout, cloudConnector);
+ if (!deviceId.isBlank() && tokenCheckPass(configuration.token)) {
+ final MiIoAsyncCommunication miioCom = new MiIoAsyncCommunication(configuration.host, token, deviceId,
+ lastId, configuration.timeout, cloudConnector);
if (getCloudServer().isBlank()) {
- logger.debug("Ping Mi device {} at {}", deviceId, configuration.host);
+ logger.debug("Ping Mi deviceId '{}' at {}", deviceId, configuration.host);
Message miIoResponse = miioCom.sendPing(configuration.host);
if (miIoResponse != null) {
- logger.debug("Ping response from device {} at {}. Time stamp: {}, OH time {}, delta {}",
- Utils.getHex(miIoResponse.getDeviceId()), configuration.host,
+ logger.debug("Ping response from deviceId '{}' at {}. Time stamp: {}, OH time {}, delta {}",
+ Utils.fromHEX(Utils.getHex(miIoResponse.getDeviceId())), configuration.host,
miIoResponse.getTimestamp(), LocalDateTime.now(), miioCom.getTimeDelta());
miioCom.registerListener(this);
this.miioCom = miioCom;
return miioCom;
}
} else {
- logger.debug("No device ID defined. Retrieving Mi device ID");
- final MiIoAsyncCommunication miioCom = new MiIoAsyncCommunication(configuration.host, token,
- new byte[0], lastId, configuration.timeout, cloudConnector);
+ logger.debug("No deviceId defined. Retrieving Mi deviceId");
+ final MiIoAsyncCommunication miioCom = new MiIoAsyncCommunication(configuration.host, token, "", lastId,
+ configuration.timeout, cloudConnector);
Message miIoResponse = miioCom.sendPing(configuration.host);
if (miIoResponse != null) {
- logger.debug("Ping response from device {} at {}. Time stamp: {}, OH time {}, delta {}",
- Utils.getHex(miIoResponse.getDeviceId()), configuration.host, miIoResponse.getTimestamp(),
- LocalDateTime.now(), miioCom.getTimeDelta());
- deviceId = Utils.getHex(miIoResponse.getDeviceId());
- logger.debug("Ping response from device {} at {}. Time stamp: {}, OH time {}, delta {}", deviceId,
- configuration.host, miIoResponse.getTimestamp(), LocalDateTime.now(),
+ deviceId = Utils.fromHEX(Utils.getHex(miIoResponse.getDeviceId()));
+ logger.debug("Ping response from deviceId '{}' at {}. Time stamp: {}, OH time {}, delta {}",
+ deviceId, configuration.host, miIoResponse.getTimestamp(), LocalDateTime.now(),
miioCom.getTimeDelta());
- miioCom.setDeviceId(miIoResponse.getDeviceId());
- logger.debug("Using retrieved Mi device ID: {}", deviceId);
+ miioCom.setDeviceId(deviceId);
+ logger.debug("Using retrieved Mi deviceId: {}", deviceId);
updateDeviceIdConfig(deviceId);
miioCom.registerListener(this);
this.miioCom = miioCom;
miioCom.close();
}
}
- logger.debug("Ping response from device {} at {} FAILED", configuration.deviceId, configuration.host);
+ logger.debug("Ping response from deviceId '{}' at {} FAILED", configuration.deviceId, configuration.host);
disconnectedNoResponse();
return null;
} catch (IOException e) {
config.put(PROPERTY_DID, deviceId);
updateConfiguration(config);
} else {
- logger.debug("Could not update config with device ID: {}", deviceId);
+ logger.debug("Could not update config with deviceId: {}", deviceId);
}
}
@Override
public void onMessageReceived(MiIoSendCommand response) {
- logger.debug("Received response for {} type: {}, result: {}, fullresponse: {}", getThing().getUID().getId(),
- response.getCommand(), response.getResult(), response.getResponse());
+ logger.debug("Received response for device {} type: {}, result: {}, fullresponse: {}",
+ getThing().getUID().getId(), response.getCommand(), response.getResult(), response.getResponse());
if (response.isError()) {
- logger.debug("Error received: {}", response.getResponse().get("error"));
+ logger.debug("Error received for command '{}': {}.", response.getCommandString(),
+ response.getResponse().get("error"));
if (MiIoCommand.MIIO_INFO.equals(response.getCommand())) {
network.invalidateValue();
}
private final String ip;
private final byte[] token;
- private byte[] deviceId;
+ private String deviceId;
private @Nullable DatagramSocket socket;
private List<MiIoMessageListener> listeners = new CopyOnWriteArrayList<>();
private ConcurrentLinkedQueue<MiIoSendCommand> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();
- public MiIoAsyncCommunication(String ip, byte[] token, byte[] did, int id, int timeout,
+ public MiIoAsyncCommunication(String ip, byte[] token, String did, int id, int timeout,
CloudConnector cloudConnector) {
this.ip = ip;
this.token = token;
// Obfuscate part of the token to allow sharing of the logfiles
String tokenText = Utils.obfuscateToken(Utils.getHex(token));
logger.debug("Command added to Queue {} -> {} (Device: {} token: {} Queue: {}).{}{}",
- fullCommand.toString(), ip, Utils.getHex(deviceId), tokenText, concurrentLinkedQueue.size(),
+ fullCommand.toString(), ip, deviceId, tokenText, concurrentLinkedQueue.size(),
cloudServer.isBlank() ? "" : " Send via cloudserver: ", cloudServer);
}
if (needPing && cloudServer.isBlank()) {
return cmdId;
} catch (JsonSyntaxException e) {
logger.warn("Send command '{}' with parameters {} -> {} (Device: {}) gave error {}", command, params, ip,
- Utils.getHex(deviceId), e.getMessage());
+ deviceId, e.getMessage());
throw e;
}
}
if (miIoSendCommand.getCloudServer().isBlank()) {
decryptedResponse = sendCommand(miIoSendCommand.getCommandString(), token, ip, deviceId);
} else {
- decryptedResponse = cloudConnector.sendRPCCommand(Utils.getHex(deviceId),
+ decryptedResponse = cloudConnector.sendRPCCommand(Utils.getHexId(deviceId),
miIoSendCommand.getCloudServer(), miIoSendCommand);
logger.debug("Command {} send via cloudserver {}", miIoSendCommand.getCommandString(),
miIoSendCommand.getCloudServer());
logger.debug("{}: {}", errorMsg, decryptedResponse);
} catch (MiIoCryptoException | IOException e) {
logger.debug("Send command '{}' -> {} (Device: {}) gave error {}", miIoSendCommand.getCommandString(), ip,
- Utils.getHex(deviceId), e.getMessage());
+ deviceId, e.getMessage());
errorMsg = e.getMessage();
} catch (JsonSyntaxException e) {
logger.warn("Could not parse '{}' <- {} (Device: {}) gave error {}", decryptedResponse,
- miIoSendCommand.getCommandString(), Utils.getHex(deviceId), e.getMessage());
+ miIoSendCommand.getCommandString(), deviceId, e.getMessage());
errorMsg = "Received message is invalid JSON";
} catch (MiCloudException e) {
logger.debug("Send command '{}' -> cloudserver '{}' (Device: {}) gave error {}",
- miIoSendCommand.getCommandString(), miIoSendCommand.getCloudServer(), Utils.getHex(deviceId),
- e.getMessage());
+ miIoSendCommand.getCommandString(), miIoSendCommand.getCloudServer(), deviceId, e.getMessage());
errorMsg = e.getMessage();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
}
- private String sendCommand(String command, byte[] token, String ip, byte[] deviceId)
+ private String sendCommand(String command, byte[] token, String ip, String deviceId)
throws MiIoCryptoException, IOException {
byte[] sendMsg = new byte[0];
if (!command.isBlank()) {
byte[] encr;
encr = MiIoCrypto.encrypt(command.getBytes(StandardCharsets.UTF_8), token);
timeStamp = (int) Instant.now().getEpochSecond();
- sendMsg = Message.createMsgData(encr, token, deviceId, timeStamp + timeDelta);
+ sendMsg = Message.createMsgData(encr, token, Utils.hexStringToByteArray(Utils.getHexId(deviceId)),
+ timeStamp + timeDelta);
}
Message miIoResponseMsg = sendData(sendMsg, ip);
if (miIoResponseMsg == null) {
if (logger.isTraceEnabled()) {
- logger.trace("No response from device {} at {} for command {}.\r\n{}", Utils.getHex(deviceId), ip,
- command, (new Message(sendMsg)).toSting());
+ logger.trace("No response from device {} at {} for command {}.\r\n{}", deviceId, ip, command,
+ (new Message(sendMsg)).toSting());
} else {
- logger.debug("No response from device {} at {} for command {}.", Utils.getHex(deviceId), ip, command);
+ logger.debug("No response from device {} at {} for command {}.", deviceId, ip, command);
}
errorCounter++;
if (errorCounter > MAX_ERRORS) {
public @Nullable Message sendPing(String ip) throws IOException {
for (int i = 0; i < 3; i++) {
- logger.debug("Sending Ping {} ({})", Utils.getHex(deviceId), ip);
+ logger.debug("Sending Ping to device '{}' ({})", deviceId, ip);
Message resp = sendData(MiIoBindingConstants.DISCOVER_STRING, ip);
if (resp != null) {
pingSuccess();
}
private void pingFail() {
- logger.debug("Ping {} ({}) failed", Utils.getHex(deviceId), ip);
+ logger.debug("Ping to device '{}' ({}) failed", deviceId, ip);
connected = false;
status = ThingStatusDetail.COMMUNICATION_ERROR;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
private void pingSuccess() {
- logger.debug("Ping {} ({}) success", Utils.getHex(deviceId), ip);
+ logger.debug("Ping to device '{}' ({}) success", deviceId, ip);
if (!connected) {
connected = true;
status = ThingStatusDetail.NONE;
return timeDelta;
}
- public byte[] getDeviceId() {
+ public String getDeviceId() {
return deviceId;
}
- public void setDeviceId(byte[] deviceId) {
+ public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}