[netatmo] Console command to show all devices/modules ids (#13555)

* [netatmo] Console command to show all devices/modules ids

Fix #13091

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo 2022-10-19 08:47:04 +02:00 committed by GitHub
parent dda2e9a288
commit 77013bca39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 187 additions and 77 deletions

View File

@ -123,6 +123,12 @@ NB: Allowed ports for webhooks are 80, 88, 443 and 9443.
### Configure Things ### Configure Things
The easiest way to retrieve the IDs for all the devices and modules is to use the console command `openhab:netatmo showIds`.
It shows the hierarchy of all the devices and modules including their IDs.
This can help to define all your things in a configuration file.
**Another way to get the IDs is to use the developer documentation on the netatmo site:**
The IDs for the modules can be extracted from the developer documentation on the netatmo site. The IDs for the modules can be extracted from the developer documentation on the netatmo site.
First login with your user. First login with your user.
Then some examples of the documentation contain the **real results** of your weather station. Then some examples of the documentation contain the **real results** of your weather station.

View File

@ -207,6 +207,11 @@ public enum ModuleType {
: ModuleType.UNKNOWN.equals(getBridge()) ? "configurable" : "device"))); : ModuleType.UNKNOWN.equals(getBridge()) ? "configurable" : "device")));
} }
public int getDepth() {
ModuleType parent = bridgeType;
return parent == null ? 1 : 1 + parent.getDepth();
}
public static ModuleType from(ThingTypeUID thingTypeUID) { public static ModuleType from(ThingTypeUID thingTypeUID) {
return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst() return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
.orElseThrow(() -> new IllegalArgumentException()); .orElseThrow(() -> new IllegalArgumentException());

View File

@ -0,0 +1,97 @@
/**
* Copyright (c) 2010-2022 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.netatmo.internal.console;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.NetatmoBindingConstants;
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
import org.openhab.binding.netatmo.internal.api.dto.NAModule;
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
import org.openhab.core.io.console.Console;
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingRegistry;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link NetatmoCommandExtension} is responsible for handling console commands
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
@Component(service = ConsoleCommandExtension.class)
public class NetatmoCommandExtension extends AbstractConsoleCommandExtension {
private static final String SHOW_IDS = "showIds";
private final ThingRegistry thingRegistry;
private @Nullable Console console;
@Activate
public NetatmoCommandExtension(final @Reference ThingRegistry thingRegistry) {
super(NetatmoBindingConstants.BINDING_ID, "Interact with the Netatmo binding.");
this.thingRegistry = thingRegistry;
}
@Override
public void execute(String[] args, Console console) {
if (args.length == 1 && SHOW_IDS.equals(args[0])) {
this.console = console;
for (Thing thing : thingRegistry.getAll()) {
ThingHandler thingHandler = thing.getHandler();
if (thingHandler instanceof ApiBridgeHandler) {
console.println("Account bridge: " + thing.getLabel());
((ApiBridgeHandler) thingHandler).identifyAllModulesAndApplyAction(this::printThing);
}
}
} else {
printUsage(console);
}
}
private Optional<ThingUID> printThing(NAModule module, ThingUID bridgeUID) {
Console localConsole = this.console;
Optional<ThingUID> moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID);
if (localConsole != null && moduleUID.isPresent()) {
String indent = "";
for (int i = 2; i <= module.getType().getDepth(); i++) {
indent += " ";
}
localConsole.println(String.format("%s- ID \"%s\" for \"%s\" (thing type %s)", indent, module.getId(),
module.getName() != null ? module.getName() : "...", module.getType().thingTypeUID));
}
return moduleUID;
}
private Optional<ThingUID> findThingUID(ModuleType moduleType, String thingId, ThingUID bridgeUID) {
return moduleType.apiName.isBlank() ? Optional.empty()
: Optional.ofNullable(
new ThingUID(moduleType.thingTypeUID, bridgeUID, thingId.replaceAll("[^a-zA-Z0-9_]", "")));
}
@Override
public List<String> getUsages() {
return Arrays.asList(buildCommandUsage(SHOW_IDS, "list all devices and modules ids"));
}
}

View File

@ -12,24 +12,12 @@
*/ */
package org.openhab.binding.netatmo.internal.discovery; package org.openhab.binding.netatmo.internal.discovery;
import static java.util.Comparator.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.AircareApi;
import org.openhab.binding.netatmo.internal.api.HomeApi;
import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
import org.openhab.binding.netatmo.internal.api.NetatmoException;
import org.openhab.binding.netatmo.internal.api.WeatherApi;
import org.openhab.binding.netatmo.internal.api.data.ModuleType; import org.openhab.binding.netatmo.internal.api.data.ModuleType;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
import org.openhab.binding.netatmo.internal.api.dto.NAMain;
import org.openhab.binding.netatmo.internal.api.dto.NAModule; import org.openhab.binding.netatmo.internal.api.dto.NAModule;
import org.openhab.binding.netatmo.internal.config.NAThingConfiguration; import org.openhab.binding.netatmo.internal.config.NAThingConfiguration;
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler; import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
@ -65,77 +53,20 @@ public class NetatmoDiscoveryService extends AbstractDiscoveryService implements
public void startScan() { public void startScan() {
ApiBridgeHandler localHandler = handler; ApiBridgeHandler localHandler = handler;
if (localHandler != null) { if (localHandler != null) {
ThingUID accountUID = localHandler.getThing().getUID(); localHandler.identifyAllModulesAndApplyAction(this::createThing);
try {
AircareApi airCareApi = localHandler.getRestManager(AircareApi.class);
if (airCareApi != null) { // Search Healthy Home Coaches
ListBodyResponse<NAMain> body = airCareApi.getHomeCoachData(null).getBody();
if (body != null) {
body.getElements().stream().forEach(homeCoach -> createThing(homeCoach, accountUID));
}
}
WeatherApi weatherApi = localHandler.getRestManager(WeatherApi.class);
if (weatherApi != null) { // Search owned or favorite stations
weatherApi.getFavoriteAndGuestStationsData().stream().forEach(station -> {
if (!station.isReadOnly() || localHandler.getReadFriends()) {
createThing(station, accountUID).ifPresent(stationUID -> station.getModules().values()
.stream().forEach(module -> createThing(module, stationUID)));
}
});
}
HomeApi homeApi = localHandler.getRestManager(HomeApi.class);
if (homeApi != null) { // Search those depending from a home that has modules + not only weather modules
homeApi.getHomesData(null, null).stream()
.filter(h -> !(h.getFeatures().isEmpty()
|| h.getFeatures().contains(FeatureArea.WEATHER) && h.getFeatures().size() == 1))
.forEach(home -> {
createThing(home, accountUID).ifPresent(homeUID -> {
home.getKnownPersons().forEach(person -> createThing(person, homeUID));
Map<String, ThingUID> bridgesUids = new HashMap<>();
home.getRooms().values().stream().forEach(room -> {
room.getModuleIds().stream().map(id -> home.getModules().get(id))
.map(m -> m != null ? m.getType().feature : FeatureArea.NONE)
.filter(f -> FeatureArea.ENERGY.equals(f)).findAny().ifPresent(f -> {
createThing(room, homeUID).ifPresent(
roomUID -> bridgesUids.put(room.getId(), roomUID));
});
});
// Creating modules that have no bridge first, avoiding weather station itself
home.getModules().values().stream()
.filter(module -> module.getType().feature != FeatureArea.WEATHER)
.sorted(comparing(HomeDataModule::getBridge, nullsFirst(naturalOrder())))
.forEach(module -> {
String bridgeId = module.getBridge();
if (bridgeId == null) {
createThing(module, homeUID).ifPresent(
moduleUID -> bridgesUids.put(module.getId(), moduleUID));
} else {
createThing(module, bridgesUids.getOrDefault(bridgeId, homeUID));
}
});
});
});
}
} catch (NetatmoException e) {
logger.warn("Error during discovery process : {}", e.getMessage());
}
} }
} }
private @Nullable ThingUID findThingUID(ModuleType thingType, String thingId, ThingUID bridgeUID) { private Optional<ThingUID> findThingUID(ModuleType moduleType, String thingId, ThingUID bridgeUID) {
ThingTypeUID thingTypeUID = thingType.thingTypeUID; ThingTypeUID thingTypeUID = moduleType.thingTypeUID;
return getSupportedThingTypes().stream().filter(supported -> supported.equals(thingTypeUID)).findFirst() return getSupportedThingTypes().stream().filter(supported -> supported.equals(thingTypeUID)).findFirst()
.map(supported -> new ThingUID(supported, bridgeUID, thingId.replaceAll("[^a-zA-Z0-9_]", ""))) .map(supported -> new ThingUID(supported, bridgeUID, thingId.replaceAll("[^a-zA-Z0-9_]", "")));
.orElse(null);
} }
private Optional<ThingUID> createThing(NAModule module, ThingUID bridgeUID) { private Optional<ThingUID> createThing(NAModule module, ThingUID bridgeUID) {
ThingUID moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID); Optional<ThingUID> moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID);
if (moduleUID != null) { if (moduleUID.isPresent()) {
DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID) DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID.get())
.withProperty(NAThingConfiguration.ID, module.getId()) .withProperty(NAThingConfiguration.ID, module.getId())
.withRepresentationProperty(NAThingConfiguration.ID) .withRepresentationProperty(NAThingConfiguration.ID)
.withLabel(module.getName() != null ? module.getName() : module.getId()).withBridge(bridgeUID); .withLabel(module.getName() != null ? module.getName() : module.getId()).withBridge(bridgeUID);
@ -143,7 +74,7 @@ public class NetatmoDiscoveryService extends AbstractDiscoveryService implements
} else { } else {
logger.info("Module '{}' is not handled by this version of the binding - it is ignored.", module.getName()); logger.info("Module '{}' is not handled by this version of the binding - it is ignored.", module.getName());
} }
return Optional.ofNullable(moduleUID); return moduleUID;
} }
@Override @Override

View File

@ -12,6 +12,7 @@
*/ */
package org.openhab.binding.netatmo.internal.handler; package org.openhab.binding.netatmo.internal.handler;
import static java.util.Comparator.*;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -32,6 +33,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
@ -45,13 +47,21 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpStatus.Code; import org.eclipse.jetty.http.HttpStatus.Code;
import org.openhab.binding.netatmo.internal.api.AircareApi;
import org.openhab.binding.netatmo.internal.api.ApiError; import org.openhab.binding.netatmo.internal.api.ApiError;
import org.openhab.binding.netatmo.internal.api.AuthenticationApi; import org.openhab.binding.netatmo.internal.api.AuthenticationApi;
import org.openhab.binding.netatmo.internal.api.HomeApi;
import org.openhab.binding.netatmo.internal.api.ListBodyResponse;
import org.openhab.binding.netatmo.internal.api.NetatmoException; import org.openhab.binding.netatmo.internal.api.NetatmoException;
import org.openhab.binding.netatmo.internal.api.RestManager; import org.openhab.binding.netatmo.internal.api.RestManager;
import org.openhab.binding.netatmo.internal.api.SecurityApi; import org.openhab.binding.netatmo.internal.api.SecurityApi;
import org.openhab.binding.netatmo.internal.api.WeatherApi;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope; import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError; import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
import org.openhab.binding.netatmo.internal.api.dto.NAMain;
import org.openhab.binding.netatmo.internal.api.dto.NAModule;
import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration; import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration;
import org.openhab.binding.netatmo.internal.config.BindingConfiguration; import org.openhab.binding.netatmo.internal.config.BindingConfiguration;
import org.openhab.binding.netatmo.internal.config.ConfigurationLevel; import org.openhab.binding.netatmo.internal.config.ConfigurationLevel;
@ -66,6 +76,7 @@ import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
@ -286,6 +297,66 @@ public class ApiBridgeHandler extends BaseBridgeHandler {
} }
} }
public void identifyAllModulesAndApplyAction(BiFunction<NAModule, ThingUID, Optional<ThingUID>> action) {
ThingUID accountUID = getThing().getUID();
try {
AircareApi airCareApi = getRestManager(AircareApi.class);
if (airCareApi != null) { // Search Healthy Home Coaches
ListBodyResponse<NAMain> body = airCareApi.getHomeCoachData(null).getBody();
if (body != null) {
body.getElements().stream().forEach(homeCoach -> action.apply(homeCoach, accountUID));
}
}
WeatherApi weatherApi = getRestManager(WeatherApi.class);
if (weatherApi != null) { // Search owned or favorite stations
weatherApi.getFavoriteAndGuestStationsData().stream().forEach(station -> {
if (!station.isReadOnly() || getReadFriends()) {
action.apply(station, accountUID).ifPresent(stationUID -> station.getModules().values().stream()
.forEach(module -> action.apply(module, stationUID)));
}
});
}
HomeApi homeApi = getRestManager(HomeApi.class);
if (homeApi != null) { // Search those depending from a home that has modules + not only weather modules
homeApi.getHomesData(null, null).stream()
.filter(h -> !(h.getFeatures().isEmpty()
|| h.getFeatures().contains(FeatureArea.WEATHER) && h.getFeatures().size() == 1))
.forEach(home -> {
action.apply(home, accountUID).ifPresent(homeUID -> {
home.getKnownPersons().forEach(person -> action.apply(person, homeUID));
Map<String, ThingUID> bridgesUids = new HashMap<>();
home.getRooms().values().stream().forEach(room -> {
room.getModuleIds().stream().map(id -> home.getModules().get(id))
.map(m -> m != null ? m.getType().feature : FeatureArea.NONE)
.filter(f -> FeatureArea.ENERGY.equals(f)).findAny().ifPresent(f -> {
action.apply(room, homeUID)
.ifPresent(roomUID -> bridgesUids.put(room.getId(), roomUID));
});
});
// Creating modules that have no bridge first, avoiding weather station itself
home.getModules().values().stream()
.filter(module -> module.getType().feature != FeatureArea.WEATHER)
.sorted(comparing(HomeDataModule::getBridge, nullsFirst(naturalOrder())))
.forEach(module -> {
String bridgeId = module.getBridge();
if (bridgeId == null) {
action.apply(module, homeUID).ifPresent(
moduleUID -> bridgesUids.put(module.getId(), moduleUID));
} else {
action.apply(module, bridgesUids.getOrDefault(bridgeId, homeUID));
}
});
});
});
}
} catch (NetatmoException e) {
logger.warn("Error while identifying all modules : {}", e.getMessage());
}
}
public boolean getReadFriends() { public boolean getReadFriends() {
return bindingConf.readFriends; return bindingConf.readFriends;
} }