Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions bundles/org.openhab.binding.netatmo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ NB: Allowed ports for webhooks are 80, 88, 443 and 9443.

### 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.
First login with your user.
Then some examples of the documentation contain the **real results** of your weather station.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ public URI getConfigDescription() {
: 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) {
return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
.orElseThrow(() -> new IllegalArgumentException());
Expand Down
Original file line number Diff line number Diff line change
@@ -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.");
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
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"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,12 @@
*/
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.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
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.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.config.NAThingConfiguration;
import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
Expand Down Expand Up @@ -65,85 +53,28 @@ public NetatmoDiscoveryService() {
public void startScan() {
ApiBridgeHandler localHandler = handler;
if (localHandler != null) {
ThingUID accountUID = localHandler.getThing().getUID();
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());
}
localHandler.identifyAllModulesAndApplyAction(this::createThing);
}
}

private @Nullable ThingUID findThingUID(ModuleType thingType, String thingId, ThingUID bridgeUID) {
ThingTypeUID thingTypeUID = thingType.thingTypeUID;
private Optional<ThingUID> findThingUID(ModuleType moduleType, String thingId, ThingUID bridgeUID) {
ThingTypeUID thingTypeUID = moduleType.thingTypeUID;
return getSupportedThingTypes().stream().filter(supported -> supported.equals(thingTypeUID)).findFirst()
.map(supported -> new ThingUID(supported, bridgeUID, thingId.replaceAll("[^a-zA-Z0-9_]", "")))
.orElse(null);
.map(supported -> new ThingUID(supported, bridgeUID, thingId.replaceAll("[^a-zA-Z0-9_]", "")));
}

private Optional<ThingUID> createThing(NAModule module, ThingUID bridgeUID) {
ThingUID moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID);
if (moduleUID != null) {
DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID)
Optional<ThingUID> moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID);
if (moduleUID.isPresent()) {
DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID.get())
.withProperty(NAThingConfiguration.ID, module.getId())
.withRepresentationProperty(NAThingConfiguration.ID)
.withLabel(module.getName() != null ? module.getName() : module.getId()).withBridge(bridgeUID);
thingDiscovered(resultBuilder.build());
} else {
logger.info("Module '{}' is not handled by this version of the binding - it is ignored.", module.getName());
}
return Optional.ofNullable(moduleUID);
return moduleUID;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.binding.netatmo.internal.handler;

import static java.util.Comparator.*;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;

import java.io.ByteArrayInputStream;
Expand All @@ -32,6 +33,7 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;

import javax.ws.rs.core.UriBuilder;

Expand All @@ -45,13 +47,21 @@
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
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.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.RestManager;
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.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.BindingConfiguration;
import org.openhab.binding.netatmo.internal.config.ConfigurationLevel;
Expand All @@ -66,6 +76,7 @@
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
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.ThingHandlerService;
import org.openhab.core.types.Command;
Expand Down Expand Up @@ -286,6 +297,66 @@ public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz,
}
}

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() {
return bindingConf.readFriends;
}
Expand Down