[Netatmo] Add the capability to have a single home (#14595)

* Adding the capability to have a single home, common to energy and security

---------

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2023-03-25 16:59:51 +01:00 committed by GitHub
parent f2e7597db6
commit f37c4e555d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 225 additions and 161 deletions

View File

@ -444,11 +444,31 @@ All these channels except setpoint and setpoint-mode are read only.
| battery | low-battery | Switch | Low battery |
| battery | status | String | Description of the battery status (*) |
### Welcome Home
### Home
All these channels are read only.
A Home is the Thing holding various modules and devices. They can hold two areas of equipments : Security and Energy.
Depending on the way it is configured the behaviour will be adapted and available channels can vary.
**Supported channels for the Home thing:**
**Home Configuration**
The Home thing has the following configuration elements:
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | ----------------------------------------------------------------------------------- |
| id (1) | String | No | If you have a single type of equipment, this id is to be used for the home |
| energyId | String | No | Id of a home holding energy control devices |
| securityId | String | No | Id of a home holding security monitoring devices |
At least one of these parameter must be filled - at most two :
* id or securityId
* id or energyId
* securityId and energyId
(1) this parameter is only kept for backward compatibility.
All channels are read only.
**Supported channels for the Security Home thing:**
| Channel Group | Channel Id | Item Type | Description |
| ------------- | ---------------------- | --------- | ------------------------------------------------ |
@ -456,9 +476,7 @@ All these channels are read only.
| security | unknown-person-count | Number | Total number of unknown persons that are at home |
| security | unknown-person-picture | Image | Snapshot of unknown person that is at home |
All these channels are read only.
**Supported trigger channels for the Home thing:**
**Supported trigger channels for the Security Home thing:**
| Channel Type ID | Options | Description |
| ---------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@ -493,7 +511,7 @@ All these channels are read only.
Warnings:
- The URL of the live snapshot is a fixed URL so the value of the channel cameraLivePictureUrl / welcomeCameraLivePictureUrl will never be updated once first set by the binding. So to get a refreshed picture, you need to use the refresh parameter in your sitemap image element.
- Some features like the video surveillance are accessed via the local network, so it may be helpful to set a static IP address for the camera within your local network.
- Some features like the video monitoring are accessed via the local network, so it may be helpful to set a static IP address for the camera within your local network.
**Supported channels for the Welcome Camera thing:**

View File

@ -84,7 +84,7 @@ public class RoomActions implements ThingActions {
return;
}
getEnergyCapability()
.ifPresent(cap -> cap.setRoomThermTemp(roomHandler.getId(), temp, endTime, SetpointMode.MANUAL));
.ifPresent(cap -> cap.setRoomThermTemp(roomHandler.getId(), SetpointMode.MANUAL, endTime, temp));
}
@RuleAction(label = "@text/actionSetThermRoomModeSetpointLabel", description = "@text/actionSetThermRoomModeSetpointDesc")
@ -123,7 +123,7 @@ public class RoomActions implements ThingActions {
long setpointEnd = targetEndTime;
SetpointMode setpointMode = targetMode;
getEnergyCapability().ifPresent(cap -> cap.setRoomThermTemp(roomHandler.getId(), 0, setpointEnd, setpointMode));
getEnergyCapability().ifPresent(cap -> cap.setRoomThermTemp(roomHandler.getId(), setpointMode, setpointEnd, 0));
}
public static void setThermRoomTempSetpoint(ThingActions actions, @Nullable Double temp, @Nullable Long endTime) {

View File

@ -44,7 +44,8 @@ public class NAObject {
return ignoredForThingUpdate;
}
public void setIgnoredForThingUpdate(boolean ignoredForThingUpdate) {
this.ignoredForThingUpdate = ignoredForThingUpdate;
public NAObject ignoringForThingUpdate() {
this.ignoredForThingUpdate = true;
return this;
}
}

View File

@ -55,10 +55,6 @@ public class NAThing extends NAObject implements NAModule {
return localReachable != null ? localReachable : true;
}
public void setReachable(boolean reachable) {
this.reachable = reachable;
}
public @Nullable Dashboard getDashboardData() {
return dashboardData;
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2023 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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
/**
* The {@link HomeConfiguration} is responsible for holding configuration information for any
* Netatmo Home - security or energy, or both
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class HomeConfiguration extends NAThingConfiguration {
public String securityId = "";
public String energyId = "";
@Override
public String getId() {
return getIdForArea(energyId.isBlank() ? FeatureArea.SECURITY : FeatureArea.ENERGY);
}
public String getIdForArea(FeatureArea feature) {
return FeatureArea.ENERGY.equals(feature) ? energyId.isBlank() ? id : energyId
: FeatureArea.SECURITY.equals(feature) ? securityId.isBlank() ? id : securityId : id;
}
}

View File

@ -24,6 +24,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
public class NAThingConfiguration {
public static final String ID = "id";
public String id = "";
protected String id = "";
public int refreshInterval = -1;
public String getId() {
return id;
}
}

View File

@ -24,6 +24,7 @@ import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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.NAObject;
import org.openhab.binding.netatmo.internal.api.dto.NAThing;
import org.openhab.binding.netatmo.internal.config.NAThingConfiguration;
@ -32,6 +33,7 @@ import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
import org.openhab.binding.netatmo.internal.handler.capability.RefreshCapability;
import org.openhab.binding.netatmo.internal.handler.capability.RestCapability;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
@ -107,7 +109,11 @@ public interface CommonInterface {
}
default String getId() {
return (String) getThing().getConfiguration().get(NAThingConfiguration.ID);
return getConfiguration().as(NAThingConfiguration.class).getId();
}
default Configuration getConfiguration() {
return getThing().getConfiguration();
}
default Stream<Channel> getActiveChannels() {
@ -135,6 +141,10 @@ public interface CommonInterface {
return List.of();
}
default Stream<CommonInterface> getActiveChildren(FeatureArea area) {
return getActiveChildren().stream().filter(child -> child.getModuleType().feature == area);
}
default <T extends RestCapability<?>> Optional<T> getHomeCapability(Class<T> clazz) {
return getHomeHandler().map(handler -> handler.getCapabilities().get(clazz)).orElse(Optional.empty());
}
@ -197,7 +207,6 @@ public interface CommonInterface {
} else {
setThingStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null);
setRefreshCapability();
getCapabilities().values().forEach(cap -> cap.initialize());
getScheduler().schedule(() -> {
CommonInterface bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
@ -207,9 +216,12 @@ public interface CommonInterface {
}
}
default ModuleType getModuleType() {
return ModuleType.from(getThing().getThingTypeUID());
}
default void setRefreshCapability() {
ModuleType moduleType = ModuleType.from(getThing().getThingTypeUID());
if (ModuleType.ACCOUNT.equals(moduleType.getBridge())) {
if (ModuleType.ACCOUNT.equals(getModuleType().getBridge())) {
NAThingConfiguration config = getThing().getConfiguration().as(NAThingConfiguration.class);
getCapabilities().put(new RefreshCapability(this, getScheduler(), config.refreshInterval));
}

View File

@ -89,7 +89,7 @@ public class CameraCapability extends HomeSecurityThingCapability {
@Override
protected void beforeNewData() {
super.beforeNewData();
homeCapability.ifPresent(cap -> {
securityCapability.ifPresent(cap -> {
NAObjectMap<HomeDataPerson> persons = cap.getPersons();
descriptionProvider.setStateOptions(personChannelUID, persons.values().stream()
.map(p -> new StateOption(p.getId(), p.getName())).collect(Collectors.toList()));

View File

@ -28,9 +28,10 @@ public class CapabilityMap extends ConcurrentHashMap<Class<?>, Capability> {
private static final long serialVersionUID = -3043492242108419801L;
public void put(Capability capability) {
Class<? extends Capability> clazz = capability.getClass();
Class<?> clazz = capability.getClass();
if (super.get(clazz) == null) {
super.put(clazz, capability);
capability.initialize();
}
}

View File

@ -20,6 +20,7 @@ import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.netatmo.internal.api.EnergyApi;
import org.openhab.binding.netatmo.internal.api.NetatmoException;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
@ -27,6 +28,7 @@ import org.openhab.binding.netatmo.internal.api.dto.HomeDataRoom;
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
import org.openhab.binding.netatmo.internal.api.dto.Room;
import org.openhab.binding.netatmo.internal.config.HomeConfiguration;
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
@ -48,30 +50,31 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
private int setPointDefaultDuration = -1;
private final NetatmoDescriptionProvider descriptionProvider;
private String energyId = "";
EnergyCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
super(handler, EnergyApi.class);
this.descriptionProvider = descriptionProvider;
}
@Override
public void initialize() {
super.initialize();
energyId = handler.getConfiguration().as(HomeConfiguration.class).getIdForArea(FeatureArea.ENERGY);
}
@Override
protected void updateHomeData(HomeData homeData) {
NAObjectMap<HomeDataRoom> rooms = homeData.getRooms();
NAObjectMap<HomeDataModule> modules = homeData.getModules();
handler.getActiveChildren().forEach(childHandler -> {
handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> {
String childId = childHandler.getId();
rooms.getOpt(childId).ifPresentOrElse(roomData -> {
roomData.setIgnoredForThingUpdate(true);
childHandler.setNewData(roomData);
}, () -> {
modules.getOpt(childId).ifPresent(childData -> {
childData.setIgnoredForThingUpdate(true);
childHandler.setNewData(childData);
});
rooms.getOpt(childId)
.ifPresentOrElse(roomData -> childHandler.setNewData(roomData.ignoringForThingUpdate()), () -> {
modules.getOpt(childId)
.ifPresent(childData -> childHandler.setNewData(childData.ignoringForThingUpdate()));
modules.values().stream().filter(module -> childId.equals(module.getBridge()))
.forEach(bridgedModule -> {
childHandler.setNewData(bridgedModule);
});
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
});
});
descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING),
@ -84,46 +87,22 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
protected void updateHomeStatus(HomeStatus homeStatus) {
NAObjectMap<Room> rooms = homeStatus.getRooms();
NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
handler.getActiveChildren().forEach(childHandler -> {
handler.getActiveChildren(FeatureArea.ENERGY).forEach(childHandler -> {
String childId = childHandler.getId();
rooms.getOpt(childId).ifPresentOrElse(roomData -> childHandler.setNewData(roomData), () -> {
modules.getOpt(childId).ifPresentOrElse(childData -> {
childHandler.setNewData(childData);
modules.getOpt(childId).ifPresent(moduleData -> {
childHandler.setNewData(moduleData);
modules.values().stream().filter(module -> childId.equals(module.getBridge()))
.forEach(bridgedModule -> {
childHandler.setNewData(bridgedModule);
});
}, () -> {
// This module is not present in the homestatus data, so it is considered as unreachable
HomeStatusModule module = new HomeStatusModule();
module.setReachable(false);
childHandler.setNewData(module);
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
});
});
});
}
public int getSetpointDefaultDuration() {
return setPointDefaultDuration;
}
public void setRoomThermMode(String roomId, SetpointMode targetMode) {
public void setThermPoint(String roomId, SetpointMode mode, long endtime, double temp) {
getApi().ifPresent(api -> {
try {
api.setThermpoint(handler.getId(), roomId, targetMode,
targetMode == SetpointMode.MAX ? setpointEndTimeFromNow(setPointDefaultDuration) : 0, 0);
handler.expireData();
} catch (NetatmoException e) {
logger.warn("Error setting room thermostat mode '{}' : {}", targetMode, e.getMessage());
}
});
}
public void setRoomThermTemp(String roomId, double temperature, long endtime, SetpointMode mode) {
getApi().ifPresent(api -> {
try {
api.setThermpoint(handler.getId(), roomId, mode, endtime, temperature);
api.setThermpoint(energyId, roomId, mode, endtime, temp);
handler.expireData();
} catch (NetatmoException e) {
logger.warn("Error setting room thermostat mode '{}' : {}", mode, e.getMessage());
@ -131,8 +110,16 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
});
}
public void setRoomThermTemp(String roomId, double temperature) {
setRoomThermTemp(roomId, temperature, setpointEndTimeFromNow(setPointDefaultDuration), SetpointMode.MANUAL);
public void setRoomThermTemp(String roomId, SetpointMode mode, long endtime, double temp) {
setThermPoint(roomId, mode, endtime, temp);
}
public void setRoomThermMode(String roomId, SetpointMode targetMode) {
setThermPoint(roomId, targetMode, targetMode == SetpointMode.MAX ? setpointEndTimeFromNow() : 0, 0);
}
public void setRoomThermTemp(String roomId, double temp) {
setThermPoint(roomId, SetpointMode.MANUAL, setpointEndTimeFromNow(), temp);
}
@Override
@ -141,7 +128,7 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
try {
switch (channelName) {
case CHANNEL_PLANNING:
api.switchSchedule(handler.getId(), command.toString());
api.switchSchedule(energyId, command.toString());
break;
case CHANNEL_SETPOINT_MODE:
SetpointMode targetMode = SetpointMode.valueOf(command.toString());
@ -149,7 +136,7 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
logger.info("Switch to 'Manual' is done by setting a setpoint temp, command ignored");
return;
}
api.setThermMode(handler.getId(), targetMode.apiDescriptor);
api.setThermMode(energyId, targetMode.apiDescriptor);
break;
}
handler.expireData();
@ -161,7 +148,7 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
});
}
private static long setpointEndTimeFromNow(int duration_min) {
return ZonedDateTime.now().plusMinutes(duration_min).toEpochSecond();
private long setpointEndTimeFromNow() {
return ZonedDateTime.now().plusMinutes(setPointDefaultDuration).toEpochSecond();
}
}

View File

@ -15,6 +15,7 @@ package org.openhab.binding.netatmo.internal.handler.capability;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -25,12 +26,10 @@ import org.openhab.binding.netatmo.internal.api.HomeApi;
import org.openhab.binding.netatmo.internal.api.NetatmoException;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataPerson;
import org.openhab.binding.netatmo.internal.api.dto.Location;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
import org.openhab.binding.netatmo.internal.config.HomeConfiguration;
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
import org.slf4j.Logger;
@ -44,77 +43,74 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
public class HomeCapability extends RestCapability<HomeApi> {
private final Logger logger = LoggerFactory.getLogger(HomeCapability.class);
private final Set<FeatureArea> featureAreas = new HashSet<>();
private final NetatmoDescriptionProvider descriptionProvider;
private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>();
private Set<FeatureArea> featuresArea = Set.of();
private Set<String> homeIds = Set.of();
public HomeCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
super(handler, HomeApi.class);
this.descriptionProvider = descriptionProvider;
}
@Override
public void initialize() {
super.initialize();
HomeConfiguration config = handler.getConfiguration().as(HomeConfiguration.class);
homeIds = Set.of(config.getId(), config.energyId, config.securityId);
}
@Override
protected void updateHomeData(HomeData home) {
featuresArea = home.getFeatures();
if (hasFeature(FeatureArea.SECURITY) && !handler.getCapabilities().containsKey(SecurityCapability.class)) {
if (hasArea(FeatureArea.SECURITY) && !handler.getCapabilities().containsKey(SecurityCapability.class)) {
handler.getCapabilities().put(new SecurityCapability(handler));
}
if (hasFeature(FeatureArea.ENERGY) && !handler.getCapabilities().containsKey(EnergyCapability.class)) {
if (hasArea(FeatureArea.ENERGY) && !handler.getCapabilities().containsKey(EnergyCapability.class)) {
handler.getCapabilities().put(new EnergyCapability(handler, descriptionProvider));
}
if (firstLaunch) {
home.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country));
home.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz));
properties.put(GROUP_LOCATION, ((Location) home).getLocation().toString());
properties.put(PROPERTY_FEATURE, featuresArea.stream().map(f -> f.name()).collect(Collectors.joining(",")));
properties.put(PROPERTY_FEATURE,
featureAreas.stream().map(FeatureArea::name).collect(Collectors.joining(",")));
}
}
@Override
protected void afterNewData(@Nullable NAObject newData) {
super.afterNewData(newData);
if (firstLaunch && !hasFeature(FeatureArea.SECURITY)) {
if (firstLaunch && !hasArea(FeatureArea.SECURITY)) {
handler.removeChannels(thing.getChannelsOfGroup(GROUP_SECURITY));
}
if (firstLaunch && !hasFeature(FeatureArea.ENERGY)) {
if (firstLaunch && !hasArea(FeatureArea.ENERGY)) {
handler.removeChannels(thing.getChannelsOfGroup(GROUP_ENERGY));
}
}
private boolean hasFeature(FeatureArea seeked) {
return featuresArea.contains(seeked);
}
public NAObjectMap<HomeDataPerson> getPersons() {
return persons;
}
public NAObjectMap<HomeDataModule> getModules() {
return modules;
private boolean hasArea(FeatureArea searched) {
return featureAreas.contains(searched);
}
@Override
protected List<NAObject> updateReadings(HomeApi api) {
List<NAObject> result = new ArrayList<>();
homeIds.stream().filter(id -> !id.isEmpty()).forEach(id -> {
try {
HomeData homeData = api.getHomeData(handler.getId());
HomeData homeData = api.getHomeData(id);
if (homeData != null) {
result.add(homeData);
persons = homeData.getPersons();
modules = homeData.getModules();
featureAreas.addAll(homeData.getFeatures());
}
HomeStatus homeStatus = api.getHomeStatus(handler.getId());
HomeStatus homeStatus = api.getHomeStatus(id);
if (homeStatus != null) {
result.add(homeStatus);
}
} catch (NetatmoException e) {
logger.warn("Error getting Home informations : {}", e.getMessage());
}
});
return result;
}
}

View File

@ -56,7 +56,7 @@ public class PersonCapability extends HomeSecurityThingCapability {
@Override
protected void beforeNewData() {
super.beforeNewData();
homeCapability.ifPresent(cap -> {
securityCapability.ifPresent(cap -> {
Stream<HomeDataModule> cameras = cap.getModules().values().stream()
.filter(module -> module.getType() == ModuleType.WELCOME);
descriptionProvider.setStateOptions(cameraChannelUID,

View File

@ -23,6 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.NetatmoException;
import org.openhab.binding.netatmo.internal.api.SecurityApi;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
import org.openhab.binding.netatmo.internal.api.dto.HomeData;
import org.openhab.binding.netatmo.internal.api.dto.HomeDataModule;
@ -32,6 +33,7 @@ import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusPerson;
import org.openhab.binding.netatmo.internal.api.dto.NAHomeStatus.HomeStatus;
import org.openhab.binding.netatmo.internal.api.dto.NAObject;
import org.openhab.binding.netatmo.internal.config.HomeConfiguration;
import org.openhab.binding.netatmo.internal.deserialization.NAObjectMap;
import org.openhab.binding.netatmo.internal.handler.CommonInterface;
import org.slf4j.Logger;
@ -50,6 +52,10 @@ class SecurityCapability extends RestCapability<SecurityApi> {
private final Map<String, HomeEvent> eventBuffer = new HashMap<>();
private @Nullable ZonedDateTime freshestEventTime;
private NAObjectMap<HomeDataPerson> persons = new NAObjectMap<>();
private NAObjectMap<HomeDataModule> modules = new NAObjectMap<>();
private String securityId = "";
SecurityCapability(CommonInterface handler) {
super(handler, SecurityApi.class);
}
@ -58,26 +64,21 @@ class SecurityCapability extends RestCapability<SecurityApi> {
public void initialize() {
super.initialize();
freshestEventTime = null;
securityId = handler.getConfiguration().as(HomeConfiguration.class).getIdForArea(FeatureArea.SECURITY);
}
@Override
protected void updateHomeData(HomeData homeData) {
NAObjectMap<HomeDataPerson> persons = homeData.getPersons();
NAObjectMap<HomeDataModule> modules = homeData.getModules();
handler.getActiveChildren().forEach(childHandler -> {
persons = homeData.getPersons();
modules = homeData.getModules();
handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> {
String childId = childHandler.getId();
persons.getOpt(childId).ifPresentOrElse(personData -> {
personData.setIgnoredForThingUpdate(true);
childHandler.setNewData(personData);
}, () -> {
modules.getOpt(childId).ifPresent(childData -> {
childData.setIgnoredForThingUpdate(true);
childHandler.setNewData(childData);
});
persons.getOpt(childId)
.ifPresentOrElse(personData -> childHandler.setNewData(personData.ignoringForThingUpdate()), () -> {
modules.getOpt(childId)
.ifPresent(childData -> childHandler.setNewData(childData.ignoringForThingUpdate()));
modules.values().stream().filter(module -> childId.equals(module.getBridge()))
.forEach(bridgedModule -> {
childHandler.setNewData(bridgedModule);
});
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
});
});
}
@ -86,21 +87,13 @@ class SecurityCapability extends RestCapability<SecurityApi> {
protected void updateHomeStatus(HomeStatus homeStatus) {
NAObjectMap<HomeStatusPerson> persons = homeStatus.getPersons();
NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
handler.getActiveChildren().forEach(childHandler -> {
handler.getActiveChildren(FeatureArea.SECURITY).forEach(childHandler -> {
String childId = childHandler.getId();
persons.getOpt(childId).ifPresentOrElse(personData -> childHandler.setNewData(personData), () -> {
modules.getOpt(childId).ifPresentOrElse(childData -> {
modules.getOpt(childId).ifPresent(childData -> {
childHandler.setNewData(childData);
modules.values().stream().filter(module -> childId.equals(module.getBridge()))
.forEach(bridgedModule -> {
childHandler.setNewData(bridgedModule);
});
}, () -> {
// This module is not present in the homestatus data, so it is considered as unreachable
HomeStatusModule module = new HomeStatusModule();
module.setReachable(false);
childHandler.setNewData(module);
.forEach(bridgedModule -> childHandler.setNewData(bridgedModule));
});
});
});
@ -108,27 +101,23 @@ class SecurityCapability extends RestCapability<SecurityApi> {
@Override
protected void updateHomeEvent(HomeEvent homeEvent) {
String personId = homeEvent.getPersonId();
if (personId != null) {
handler.getActiveChildren().stream().filter(handler -> personId.equals(handler.getId())).findFirst()
.ifPresent(handler -> {
homeEvent.setIgnoredForThingUpdate(true);
handler.setNewData(homeEvent);
});
addEventIfKnownObject(homeEvent, homeEvent.getPersonId());
addEventIfKnownObject(homeEvent, homeEvent.getCameraId());
}
String cameraId = homeEvent.getCameraId();
handler.getActiveChildren().stream().filter(handler -> cameraId.equals(handler.getId())).findFirst()
.ifPresent(handler -> {
homeEvent.setIgnoredForThingUpdate(true);
handler.setNewData(homeEvent);
});
private void addEventIfKnownObject(HomeEvent homeEvent, @Nullable String objectId) {
if (objectId == null) {
return;
}
handler.getActiveChildren(FeatureArea.SECURITY).filter(child -> child.getId().equals(objectId))
.forEach(child -> child.setNewData(homeEvent.ignoringForThingUpdate()));
}
@Override
protected List<NAObject> updateReadings(SecurityApi api) {
List<NAObject> result = new ArrayList<>();
try {
for (HomeEvent event : api.getHomeEvents(handler.getId(), freshestEventTime)) {
for (HomeEvent event : api.getHomeEvents(securityId, freshestEventTime)) {
HomeEvent previousEvent = eventBuffer.get(event.getCameraId());
if (previousEvent == null || previousEvent.getTime().isBefore(event.getTime())) {
eventBuffer.put(event.getCameraId(), event);
@ -145,11 +134,19 @@ class SecurityCapability extends RestCapability<SecurityApi> {
}
}
} catch (NetatmoException e) {
logger.warn("Error retrieving last events for home '{}' : {}", handler.getId(), e.getMessage());
logger.warn("Error retrieving last events for home '{}' : {}", securityId, e.getMessage());
}
return result;
}
public NAObjectMap<HomeDataPerson> getPersons() {
return persons;
}
public NAObjectMap<HomeDataModule> getModules() {
return modules;
}
public @Nullable HomeEvent getLastPersonEvent(String personId) {
HomeEvent event = eventBuffer.get(personId);
if (event == null) {
@ -177,7 +174,7 @@ class SecurityCapability extends RestCapability<SecurityApi> {
private Collection<HomeEvent> requestDeviceEvents(String moduleId, String deviceType) {
return getApi().map(api -> {
try {
return api.getDeviceEvents(handler.getId(), moduleId, deviceType);
return api.getDeviceEvents(securityId, moduleId, deviceType);
} catch (NetatmoException e) {
logger.warn("Error retrieving last events of camera '{}' : {}", moduleId, e.getMessage());
return null;
@ -188,7 +185,7 @@ class SecurityCapability extends RestCapability<SecurityApi> {
private Collection<HomeEvent> requestPersonEvents(String personId) {
return getApi().map(api -> {
try {
return api.getPersonEvents(handler.getId(), personId);
return api.getPersonEvents(securityId, personId);
} catch (NetatmoException e) {
logger.warn("Error retrieving last events of person '{}' : {}", personId, e.getMessage());
return null;
@ -199,7 +196,7 @@ class SecurityCapability extends RestCapability<SecurityApi> {
public void setPersonAway(String personId, boolean away) {
getApi().ifPresent(api -> {
try {
api.setPersonAwayStatus(handler.getId(), personId, away);
api.setPersonAwayStatus(securityId, personId, away);
handler.expireData();
} catch (NetatmoException e) {
logger.warn("Error setting person away/at home '{}' : {}", personId, e.getMessage());
@ -208,9 +205,7 @@ class SecurityCapability extends RestCapability<SecurityApi> {
}
public @Nullable String ping(String vpnUrl) {
return getApi().map(api -> {
return api.ping(vpnUrl);
}).orElse(null);
return getApi().map(api -> api.ping(vpnUrl)).orElse(null);
}
public void changeStatus(@Nullable String localURL, boolean status) {
@ -231,7 +226,7 @@ class SecurityCapability extends RestCapability<SecurityApi> {
public void changeFloodlightMode(String cameraId, FloodLightMode mode) {
getApi().ifPresent(api -> {
try {
api.changeFloodLightMode(handler.getId(), cameraId, mode);
api.changeFloodLightMode(securityId, cameraId, mode);
handler.expireData();
} catch (NetatmoException e) {
logger.warn("Error changing Presence floodlight mode '{}' : {}", mode, e.getMessage());

View File

@ -111,11 +111,23 @@
</config-description>
<config-description uri="netatmo:home">
<parameter name="id" type="text" required="true">
<parameter name="id" type="text" required="false">
<label>@text/config.thingId.label</label>
<description>@text/config.thingId.description</description>
</parameter>
<parameter name="securityId" type="text" required="false">
<label>@text/config.securityId.label</label>
<description>@text/config.securityId.description</description>
<advanced>true</advanced>
</parameter>
<parameter name="energyId" type="text" required="false">
<label>@text/config.energyId.label</label>
<description>@text/config.energyId.description</description>
<advanced>true</advanced>
</parameter>
<parameter name="refreshInterval" type="integer" min="20" unit="s">
<label>@text/config.refreshInterval.label</label>
<description>@text/config.refreshInterval.description</description>

View File

@ -437,6 +437,10 @@ config.equipmentId.label = Equipment ID
config.equipmentId.description = ID of the device (MAC address).
config.thingId.label = Thing ID
config.thingId.description = Unique identifier of the thing defined by Netatmo.
config.securityId.label = Security ID
config.securityId.description = Unique identifier of the home handling security devices.
config.energyId.label = Energy ID
config.energyId.description = Unique identifier of the home handling energy devices.
config.refreshInterval.label = Refresh Interval
config.refreshInterval.description = The refresh interval to poll Netatmo API (in seconds).