[netatmo] Add siren device (#12805)

* Starting Siren addition

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2022-05-28 11:26:30 +02:00 committed by GitHub
parent 5291d7ddc9
commit 1aec6c9b30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 289 additions and 120 deletions

View File

@ -5,7 +5,9 @@ The Netatmo binding integrates the following Netatmo products:
- *Personal Weather Station*. Reports temperature, humidity, air pressure, carbon dioxide concentration in the air, as well as the ambient noise level.
- *Thermostat*. Reports ambient temperature, allow to check target temperature, consult and change furnace heating status.
- *Indoor Camera / Welcome*. Reports last event and persons at home, consult picture and video from event/camera.
- *Siren*
- *Outdoor Camera / Presence*. Reports last event, consult picture and video from event/camera.
- *Doorbell*
See https://www.netatmo.com/ for details on their product.
@ -505,7 +507,7 @@ Warnings:
(*) This channel is configurable : low, poor, high.
**Supported channels for the Welcome Doorbell thing:**
**Supported channels for the Doorbell thing:**
| Channel Group | Channel ID | Item Type | Read/Write | Description |
|---------------|-------------------|--------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------|
@ -532,6 +534,18 @@ Warnings:
Note: live feeds either locally or via VPN are not available in Netatmo API.
**Supported channels for the Siren thing:**
| Channel Group | Channel ID | Item Type | Read/Write | Description |
|---------------|-------------------|--------------|------------|------------------------------------------------------|
| siren | status | String | Read-only | Status of the siren, if silent or emitting an alarm |
| siren | monitoring | Switch | Read-only | State of the siren device |
| signal | strength | Number | Read-only | Signal strength (0 for no signal, 1 for weak...) |
| signal | value | Number:Power | Read-only | Signal strength in dBm |
| timestamp | last-seen | DateTime | Read-only | Last time the module reported its presence |
| battery | value | Number | Read-only | Battery level |
| battery | low-battery | Switch | Read-only | Low battery |
### Welcome Person

View File

@ -53,6 +53,7 @@ public class NetatmoBindingConstants {
public static final String GROUP_CAM_STATUS = "status";
public static final String GROUP_CAM_LIVE = "live";
public static final String GROUP_PRESENCE = "presence";
public static final String GROUP_SIREN = "siren";
public static final String GROUP_PERSON = "person";
public static final String GROUP_PROPERTIES = "properties";
public static final String GROUP_SETPOINT = "setpoint";
@ -105,6 +106,7 @@ public class NetatmoBindingConstants {
public static final String CHANNEL_SUM_RAIN1 = "sum-1";
public static final String CHANNEL_SUM_RAIN24 = "sum-24";
public static final String CHANNEL_WIND_ANGLE = "angle";
public static final String CHANNEL_STATUS = GROUP_CAM_STATUS;
public static final String CHANNEL_WIND_STRENGTH = "strength";
public static final String CHANNEL_MAX_WIND_STRENGTH = "max-strength";
public static final String CHANNEL_DATE_MAX_WIND_STRENGTH = "max-strength-date";

View File

@ -43,7 +43,6 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
public class AuthenticationApi extends RestManager {
private static final String ALL_SCOPES = FeatureArea.toScopeString(FeatureArea.AS_SET);
private static final UriBuilder OAUTH_BUILDER = getApiBaseBuilder().path(PATH_OAUTH);
private static final UriBuilder AUTH_BUILDER = OAUTH_BUILDER.clone().path(SUB_PATH_AUTHORIZE);
private static final URI TOKEN_URI = OAUTH_BUILDER.clone().path(SUB_PATH_TOKEN).build();
@ -62,7 +61,7 @@ public class AuthenticationApi extends RestManager {
public String authorize(ApiHandlerConfiguration credentials, @Nullable String code, @Nullable String redirectUri)
throws NetatmoException {
if (!(credentials.clientId.isBlank() || credentials.clientSecret.isBlank())) {
Map<String, String> params = new HashMap<>(Map.of(SCOPE, ALL_SCOPES));
Map<String, String> params = new HashMap<>(Map.of(SCOPE, FeatureArea.ALL_SCOPES));
String refreshToken = credentials.refreshToken;
if (!refreshToken.isBlank()) {
params.put(REFRESH_TOKEN, refreshToken);
@ -118,7 +117,7 @@ public class AuthenticationApi extends RestManager {
}
public static UriBuilder getAuthorizationBuilder(String clientId) {
return AUTH_BUILDER.clone().queryParam(CLIENT_ID, clientId).queryParam(SCOPE, ALL_SCOPES).queryParam(STATE,
clientId);
return AUTH_BUILDER.clone().queryParam(CLIENT_ID, clientId).queryParam(SCOPE, FeatureArea.ALL_SCOPES)
.queryParam(STATE, clientId);
}
}

View File

@ -59,6 +59,7 @@ import org.openhab.binding.netatmo.internal.handler.channelhelper.RainChannelHel
import org.openhab.binding.netatmo.internal.handler.channelhelper.RoomChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.SetpointChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.SignalChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.SirenChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureExtChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.TemperatureOutChannelHelper;
@ -68,8 +69,6 @@ import org.openhab.binding.netatmo.internal.handler.channelhelper.TimestampExtCh
import org.openhab.binding.netatmo.internal.handler.channelhelper.WindChannelHelper;
import org.openhab.core.thing.ThingTypeUID;
import com.google.gson.annotations.SerializedName;
/**
* This enum all handled Netatmo modules and devices along with their capabilities
*
@ -79,75 +78,76 @@ import com.google.gson.annotations.SerializedName;
public enum ModuleType {
UNKNOWN(FeatureArea.NONE, "", null, List.of(), List.of()),
ACCOUNT(FeatureArea.NONE, "", null, List.of(), List.of()),
@SerializedName("NAHome")
HOME(FeatureArea.NONE, "NAHome", ACCOUNT,
List.of(DeviceCapability.class, EventCapability.class, HomeCapability.class, ChannelHelperCapability.class),
List.of(HomeSecurityChannelHelper.class, HomeEnergyChannelHelper.class)),
@SerializedName("NAPerson")
PERSON(FeatureArea.SECURITY, "NAPerson", HOME,
List.of(EventCapability.class, PersonCapability.class, ChannelHelperCapability.class),
List.of(PersonChannelHelper.class, EventPersonChannelHelper.class)),
@SerializedName("NACamera")
WELCOME(FeatureArea.SECURITY, "NACamera", HOME,
List.of(EventCapability.class, CameraCapability.class, ChannelHelperCapability.class),
List.of(CameraChannelHelper.class, SignalChannelHelper.class, EventChannelHelper.class)),
@SerializedName("NOC")
SIREN(FeatureArea.SECURITY, "NIS", WELCOME, List.of(ChannelHelperCapability.class),
List.of(SirenChannelHelper.class, BatteryChannelHelper.class, TimestampChannelHelper.class,
SignalChannelHelper.class)),
PRESENCE(FeatureArea.SECURITY, "NOC", HOME,
List.of(EventCapability.class, PresenceCapability.class, ChannelHelperCapability.class),
List.of(PresenceChannelHelper.class, SignalChannelHelper.class, EventChannelHelper.class)),
@SerializedName("NIS")
SIREN(FeatureArea.SECURITY, "NIS", HOME, List.of(ChannelHelperCapability.class),
List.of(BatteryChannelHelper.class, TimestampChannelHelper.class, SignalChannelHelper.class)),
@SerializedName("NDB")
DOORBELL(FeatureArea.SECURITY, "NDB", HOME,
List.of(EventCapability.class, CameraCapability.class, ChannelHelperCapability.class),
List.of(DoorbellChannelHelper.class, SignalChannelHelper.class, EventDoorbellChannelHelper.class)),
@SerializedName("NAMain")
WEATHER_STATION(FeatureArea.WEATHER, "NAMain", ACCOUNT,
List.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class,
ChannelHelperCapability.class),
List.of(PressureExtChannelHelper.class, NoiseChannelHelper.class, HumidityChannelHelper.class,
TemperatureExtChannelHelper.class, AirQualityChannelHelper.class, LocationChannelHelper.class,
TimestampExtChannelHelper.class, MeasuresChannelHelper.class, SignalChannelHelper.class)),
@SerializedName("NAModule1")
OUTDOOR(FeatureArea.WEATHER, "NAModule1", WEATHER_STATION,
List.of(MeasureCapability.class, ChannelHelperCapability.class),
List.of(HumidityChannelHelper.class, TemperatureOutChannelHelper.class, BatteryChannelHelper.class,
MeasuresChannelHelper.class, TimestampExtChannelHelper.class, SignalChannelHelper.class)),
@SerializedName("NAModule2")
WIND(FeatureArea.WEATHER, "NAModule2", WEATHER_STATION, List.of(ChannelHelperCapability.class),
List.of(WindChannelHelper.class, BatteryChannelHelper.class, TimestampExtChannelHelper.class,
SignalChannelHelper.class)),
@SerializedName("NAModule3")
RAIN(FeatureArea.WEATHER, "NAModule3", WEATHER_STATION,
List.of(MeasureCapability.class, ChannelHelperCapability.class),
List.of(RainChannelHelper.class, BatteryChannelHelper.class, MeasuresChannelHelper.class,
TimestampExtChannelHelper.class, SignalChannelHelper.class)),
@SerializedName("NAModule4")
INDOOR(FeatureArea.WEATHER, "NAModule4", WEATHER_STATION,
List.of(MeasureCapability.class, ChannelHelperCapability.class),
List.of(HumidityChannelHelper.class, TemperatureExtChannelHelper.class, AirQualityChannelHelper.class,
BatteryChannelHelper.class, MeasuresChannelHelper.class, TimestampExtChannelHelper.class,
SignalChannelHelper.class)),
@SerializedName("NHC")
HOME_COACH(FeatureArea.AIR_CARE, "NHC", ACCOUNT,
List.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class,
ChannelHelperCapability.class),
List.of(NoiseChannelHelper.class, HumidityChannelHelper.class, AirQualityExtChannelHelper.class,
TemperatureChannelHelper.class, PressureChannelHelper.class, TimestampExtChannelHelper.class,
SignalChannelHelper.class, MeasuresChannelHelper.class, LocationChannelHelper.class)),
@SerializedName("NAPlug")
PLUG(FeatureArea.ENERGY, "NAPlug", HOME, List.of(ChannelHelperCapability.class),
List.of(SignalChannelHelper.class)),
@SerializedName("NATherm1")
THERMOSTAT(FeatureArea.ENERGY, "NATherm1", HOME, List.of(ChannelHelperCapability.class),
VALVE(FeatureArea.ENERGY, "NRV", PLUG, List.of(ChannelHelperCapability.class),
List.of(BatteryExtChannelHelper.class, SignalChannelHelper.class)),
THERMOSTAT(FeatureArea.ENERGY, "NATherm1", PLUG, List.of(ChannelHelperCapability.class),
List.of(Therm1ChannelHelper.class, BatteryExtChannelHelper.class, SignalChannelHelper.class)),
@SerializedName("NARoom")
ROOM(FeatureArea.ENERGY, "NARoom", HOME, List.of(RoomCapability.class, ChannelHelperCapability.class),
List.of(RoomChannelHelper.class, SetpointChannelHelper.class)),
@SerializedName("NRV")
VALVE(FeatureArea.ENERGY, "NRV", HOME, List.of(ChannelHelperCapability.class),
List.of(BatteryExtChannelHelper.class, SignalChannelHelper.class));
List.of(RoomChannelHelper.class, SetpointChannelHelper.class));
public static final EnumSet<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class);

View File

@ -17,6 +17,7 @@ import static org.openhab.core.library.CoreItemFactory.*;
import static org.openhab.core.library.unit.MetricPrefix.*;
import java.net.URI;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
@ -190,36 +191,35 @@ public class NetatmoConstants {
UNKNOWN;
}
private static final Set<Scope> SMOKE = Set.of(Scope.READ_SMOKEDETECTOR);
private static final Set<Scope> WELCOME = Set.of(Scope.READ_CAMERA, Scope.WRITE_CAMERA, Scope.ACCESS_CAMERA);
private static final Set<Scope> DOORBELL = Set.of(Scope.READ_DOORBELL, Scope.WRITE_DOORBELL, Scope.ACCESS_DOORBELL);
private static final Set<Scope> PRESENCE = Set.of(Scope.READ_PRESENCE, Scope.WRITE_PRESENCE, Scope.ACCESS_PRESENCE);
private static final Scope[] SMOKE_SCOPES = { Scope.READ_SMOKEDETECTOR };
private static final Scope[] AIR_CARE_SCOPES = { Scope.READ_HOMECOACH };
private static final Scope[] WEATHER_SCOPES = { Scope.READ_STATION };
private static final Scope[] THERMOSTAT_SCOPES = { Scope.READ_THERMOSTAT, Scope.WRITE_THERMOSTAT };
private static final Scope[] WELCOME_SCOPES = { Scope.READ_CAMERA, Scope.WRITE_CAMERA, Scope.ACCESS_CAMERA };
private static final Scope[] DOORBELL_SCOPES = { Scope.READ_DOORBELL, Scope.WRITE_DOORBELL, Scope.ACCESS_DOORBELL };
private static final Scope[] PRESENCE_SCOPES = { Scope.READ_PRESENCE, Scope.WRITE_PRESENCE, Scope.ACCESS_PRESENCE };
public static enum FeatureArea {
AIR_CARE(AIR_CARE_SCOPES),
WEATHER(WEATHER_SCOPES),
ENERGY(THERMOSTAT_SCOPES),
SECURITY(WELCOME_SCOPES, PRESENCE_SCOPES, SMOKE_SCOPES, DOORBELL_SCOPES),
NONE();
public static String ALL_SCOPES = EnumSet.allOf(FeatureArea.class).stream().map(fa -> fa.scopes)
.flatMap(Set::stream).map(s -> s.name().toLowerCase()).collect(Collectors.joining(" "));
public final Set<Scope> scopes;
FeatureArea(Scope[]... scopeArrays) {
this.scopes = Stream.of(scopeArrays).flatMap(Arrays::stream).collect(Collectors.toSet());
}
}
// Radio signal quality thresholds
static final int[] WIFI_SIGNAL_LEVELS = new int[] { 99, 84, 69, 54 }; // Resp : bad, average, good, full
static final int[] RADIO_SIGNAL_LEVELS = new int[] { 90, 80, 70, 60 }; // Resp : low, medium, high, full
public static enum FeatureArea {
AIR_CARE(Scope.READ_HOMECOACH),
WEATHER(Scope.READ_STATION),
ENERGY(Scope.READ_THERMOSTAT, Scope.WRITE_THERMOSTAT),
SECURITY(Stream.of(WELCOME, PRESENCE, SMOKE, DOORBELL).flatMap(Set::stream).toArray(Scope[]::new)),
NONE();
public static final Set<FeatureArea> AS_SET = EnumSet.allOf(FeatureArea.class);
public static String toScopeString(Set<FeatureArea> featureSet) {
return featureSet.stream().map(fa -> fa.scopes).flatMap(Set::stream).map(s -> s.name().toLowerCase())
.collect(Collectors.joining(" "));
}
public final Set<Scope> scopes;
FeatureArea(Scope... scopes) {
this.scopes = Set.of(scopes);
}
}
// Thermostat definitions
public static enum SetpointMode {
@SerializedName("program")

View File

@ -43,4 +43,9 @@ public class HomeDataModule extends NAThing implements NAModule {
public List<String> getModuleBridged() {
return moduleBridged;
}
@Override
public boolean isIgnoredForThingUpdate() {
return true;
}
}

View File

@ -0,0 +1,39 @@
/**
* 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.deserialization;
import java.lang.reflect.Type;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
/**
* Specialized deserializer for ModuleType class
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class ModuleTypeDeserializer implements JsonDeserializer<ModuleType> {
@Override
public @Nullable ModuleType deserialize(JsonElement json, Type clazz, JsonDeserializationContext context) {
String string = json.getAsString();
return ModuleType.AS_SET.stream().filter(mt -> mt.apiName.equalsIgnoreCase(string)).findFirst()
.orElse(ModuleType.UNKNOWN);
}
}

View File

@ -18,6 +18,7 @@ import java.time.ZonedDateTime;
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.data.ModuleType;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
@ -47,6 +48,7 @@ public class NADeserializer {
.registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory())
.registerTypeAdapter(NAObjectMap.class, new NAObjectMapDeserializer())
.registerTypeAdapter(NAPushType.class, new NAPushTypeDeserializer())
.registerTypeAdapter(ModuleType.class, new ModuleTypeDeserializer())
.registerTypeAdapter(ZonedDateTime.class,
(JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
long netatmoTS = json.getAsJsonPrimitive().getAsLong();

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.netatmo.internal.discovery;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -47,7 +49,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public class NetatmoDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService, DiscoveryService {
private static final Set<ModuleType> SKIPPED_TYPES = Set.of(ModuleType.UNKNOWN, ModuleType.ACCOUNT);
private static final int DISCOVER_TIMEOUT_SECONDS = 5;
private static final int DISCOVER_TIMEOUT_SECONDS = 3;
private final Logger logger = LoggerFactory.getLogger(NetatmoDiscoveryService.class);
private @Nullable ApiBridgeHandler handler;
@ -60,13 +62,13 @@ public class NetatmoDiscoveryService extends AbstractDiscoveryService implements
public void startScan() {
ApiBridgeHandler localHandler = handler;
if (localHandler != null) {
ThingUID apiBridgeUID = localHandler.getThing().getUID();
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, apiBridgeUID));
body.getElements().stream().forEach(homeCoach -> createThing(homeCoach, accountUID));
}
}
if (localHandler.getReadFriends()) {
@ -74,31 +76,34 @@ public class NetatmoDiscoveryService extends AbstractDiscoveryService implements
if (weatherApi != null) { // Search favorite stations
weatherApi.getFavoriteAndGuestStationsData().stream().filter(NAMain::isReadOnly)
.forEach(station -> {
ThingUID bridgeUID = createThing(station, apiBridgeUID);
ThingUID bridgeUID = createThing(station, accountUID);
station.getModules().values().stream()
.forEach(module -> createThing(module, bridgeUID));
});
}
}
HomeApi homeApi = localHandler.getRestManager(HomeApi.class);
if (homeApi != null) { // Search all the rest
if (homeApi != null) { // Search those who depend from a home
homeApi.getHomesData(null, null).stream().filter(h -> !h.getFeatures().isEmpty()).forEach(home -> {
ThingUID homeUID = createThing(home, apiBridgeUID);
ThingUID homeUID = createThing(home, accountUID);
home.getKnownPersons().forEach(person -> createThing(person, homeUID));
home.getModules().values().stream().forEach(device -> {
ModuleType deviceType = device.getType();
String deviceBridge = device.getBridge();
ThingUID bridgeUID = deviceBridge != null && deviceType.getBridge() != ModuleType.HOME
? findThingUID(deviceType.getBridge(), deviceBridge, apiBridgeUID)
: deviceType.getBridge() == ModuleType.HOME ? homeUID : apiBridgeUID;
createThing(device, bridgeUID);
});
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(f -> bridgesUids.put(room.getId(), createThing(room, homeUID)));
});
// Creating modules that have no bridge first
home.getModules().values().stream().filter(module -> module.getBridge() == null)
.forEach(device -> bridgesUids.put(device.getId(), createThing(device, homeUID)));
// Then the others
home.getModules().values().stream().filter(module -> module.getBridge() != null).forEach(
device -> createThing(device, bridgesUids.getOrDefault(device.getBridge(), homeUID)));
});
}
} catch (NetatmoException e) {
@ -107,26 +112,19 @@ public class NetatmoDiscoveryService extends AbstractDiscoveryService implements
}
}
private ThingUID findThingUID(ModuleType thingType, String thingId, @Nullable ThingUID brigdeUID) {
for (ThingTypeUID supported : getSupportedThingTypes()) {
ThingTypeUID thingTypeUID = thingType.thingTypeUID;
if (supported.equals(thingTypeUID)) {
String id = thingId.replaceAll("[^a-zA-Z0-9_]", "");
return brigdeUID == null ? new ThingUID(supported, id) : new ThingUID(supported, brigdeUID, id);
}
}
throw new IllegalArgumentException("Unsupported device type discovered : " + thingType);
private ThingUID findThingUID(ModuleType thingType, String thingId, ThingUID bridgeUID) {
ThingTypeUID thingTypeUID = thingType.thingTypeUID;
return getSupportedThingTypes().stream().filter(supported -> supported.equals(thingTypeUID)).findFirst()
.map(supported -> new ThingUID(supported, bridgeUID, thingId.replaceAll("[^a-zA-Z0-9_]", "")))
.orElseThrow(() -> new IllegalArgumentException("Unsupported device type discovered : " + thingType));
}
private ThingUID createThing(NAModule module, @Nullable ThingUID bridgeUID) {
private ThingUID createThing(NAModule module, ThingUID bridgeUID) {
ThingUID moduleUID = findThingUID(module.getType(), module.getId(), bridgeUID);
DiscoveryResultBuilder resultBuilder = DiscoveryResultBuilder.create(moduleUID)
.withProperty(NAThingConfiguration.ID, module.getId())
.withRepresentationProperty(NAThingConfiguration.ID)
.withLabel(module.getName() != null ? module.getName() : module.getId());
if (bridgeUID != null) {
resultBuilder.withBridge(bridgeUID);
}
.withLabel(module.getName() != null ? module.getName() : module.getId()).withBridge(bridgeUID);
thingDiscovered(resultBuilder.build());
return moduleUID;
}

View File

@ -25,6 +25,7 @@ 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.dto.NAObject;
import org.openhab.binding.netatmo.internal.api.dto.NAThing;
import org.openhab.binding.netatmo.internal.config.NAThingConfiguration;
import org.openhab.binding.netatmo.internal.handler.capability.Capability;
import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
@ -139,6 +140,14 @@ public interface CommonInterface {
}
default void setNewData(NAObject newData) {
if (newData instanceof NAThing) {
NAThing thingData = (NAThing) newData;
if (getId().equals(thingData.getBridge())) {
getActiveChildren().stream().filter(child -> child.getId().equals(thingData.getId())).findFirst()
.ifPresent(child -> child.setNewData(thingData));
return;
}
}
String finalReason = null;
for (Capability cap : getCapabilities().values()) {
String thingStatusReason = cap.setNewData(newData);

View File

@ -94,8 +94,9 @@ public class Capability {
properties = new HashMap<>(thing.getProperties());
firstLaunch = properties.isEmpty();
if (firstLaunch && !moduleType.isLogical()) {
String name = moduleType.apiName.isBlank() ? moduleType.name() : moduleType.apiName;
properties.put(PROPERTY_MODEL_ID, name);
properties.put(PROPERTY_VENDOR, VENDOR);
properties.put(PROPERTY_MODEL_ID, moduleType.name());
}
statusReason = null;
}

View File

@ -58,17 +58,21 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
protected void updateHomeData(HomeData homeData) {
NAObjectMap<HomeDataRoom> rooms = homeData.getRooms();
NAObjectMap<HomeDataModule> modules = homeData.getModules();
handler.getActiveChildren().forEach(handler -> {
HomeDataRoom roomData = rooms.get(handler.getId());
if (roomData != null) {
handler.getActiveChildren().forEach(childHandler -> {
String childId = childHandler.getId();
rooms.getOpt(childId).ifPresentOrElse(roomData -> {
roomData.setIgnoredForThingUpdate(true);
handler.setNewData(roomData);
}
HomeDataModule moduleData = modules.get(handler.getId());
if (moduleData != null) {
moduleData.setIgnoredForThingUpdate(true);
handler.setNewData(moduleData);
}
childHandler.setNewData(roomData);
}, () -> {
modules.getOpt(childId).ifPresent(childData -> {
childData.setIgnoredForThingUpdate(true);
childHandler.setNewData(childData);
});
modules.values().stream().filter(module -> childId.equals(module.getBridge()))
.forEach(bridgedModule -> {
childHandler.setNewData(bridgedModule);
});
});
});
descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), GROUP_ENERGY, CHANNEL_PLANNING),
homeData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName()))
@ -80,15 +84,23 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
protected void updateHomeStatus(HomeStatus homeStatus) {
NAObjectMap<Room> rooms = homeStatus.getRooms();
NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
handler.getActiveChildren().forEach(handler -> {
Room roomData = rooms.get(handler.getId());
if (roomData != null) {
handler.setNewData(roomData);
}
HomeStatusModule data = modules.get(handler.getId());
if (data != null) {
handler.setNewData(data);
}
handler.getActiveChildren().forEach(childHandler -> {
String childId = childHandler.getId();
rooms.getOpt(childId).ifPresentOrElse(roomData -> childHandler.setNewData(roomData), () -> {
modules.getOpt(childId).ifPresentOrElse(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);
});
});
});
}

View File

@ -52,14 +52,18 @@ class SecurityCapability extends RestCapability<SecurityApi> {
NAObjectMap<HomeDataModule> modules = homeData.getModules();
handler.getActiveChildren().forEach(childHandler -> {
String childId = childHandler.getId();
persons.getOpt(childId).ifPresentOrElse(person -> {
person.setIgnoredForThingUpdate(true);
childHandler.setNewData(person);
persons.getOpt(childId).ifPresentOrElse(personData -> {
personData.setIgnoredForThingUpdate(true);
childHandler.setNewData(personData);
}, () -> {
modules.getOpt(childId).ifPresent(module -> {
module.setIgnoredForThingUpdate(true);
childHandler.setNewData(module);
modules.getOpt(childId).ifPresent(childData -> {
childData.setIgnoredForThingUpdate(true);
childHandler.setNewData(childData);
});
modules.values().stream().filter(module -> childId.equals(module.getBridge()))
.forEach(bridgedModule -> {
childHandler.setNewData(bridgedModule);
});
});
});
}
@ -70,8 +74,15 @@ class SecurityCapability extends RestCapability<SecurityApi> {
NAObjectMap<HomeStatusModule> modules = homeStatus.getModules();
handler.getActiveChildren().forEach(childHandler -> {
String childId = childHandler.getId();
persons.getOpt(childId).ifPresentOrElse(person -> childHandler.setNewData(person), () -> {
modules.getOpt(childId).ifPresentOrElse(module -> childHandler.setNewData(module), () -> {
persons.getOpt(childId).ifPresentOrElse(personData -> childHandler.setNewData(personData), () -> {
modules.getOpt(childId).ifPresentOrElse(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);

View File

@ -0,0 +1,52 @@
/**
* 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.handler.channelhelper;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import static org.openhab.binding.netatmo.internal.utils.ChannelTypeUtils.toStringType;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.dto.HomeStatusModule;
import org.openhab.binding.netatmo.internal.api.dto.NAThing;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link SirenChannelHelper} handles specific behavior of the siren module
*
* @author Gaël L'hopital - Initial contribution
*
*/
@NonNullByDefault
public class SirenChannelHelper extends ChannelHelper {
public SirenChannelHelper() {
super(GROUP_SIREN);
}
@Override
protected @Nullable State internalGetProperty(String channelId, NAThing naThing, Configuration config) {
if (naThing instanceof HomeStatusModule) {
HomeStatusModule homeStatus = (HomeStatusModule) naThing;
switch (channelId) {
case CHANNEL_MONITORING:
return homeStatus.getMonitoring();
case CHANNEL_STATUS:
return homeStatus.getStatus().map(status -> toStringType(status)).orElse(UnDefType.UNDEF);
}
}
return null;
}
}

View File

@ -55,7 +55,6 @@ channel-group-type.netatmo.noise.label = Noise
channel-group-type.netatmo.person.label = Person
channel-group-type.netatmo.person.channel.last-seen.label = Last Seen
channel-group-type.netatmo.person.channel.last-seen.description = Moment when this person was last seen.
channel-group-type.netatmo.plug.label = Thermostat Plug
channel-group-type.netatmo.presence.label = Presence Camera
channel-group-type.netatmo.pressure-extended.label = Pressure
channel-group-type.netatmo.pressure-extended.channel.trend.label = Pressure Trend
@ -74,6 +73,7 @@ channel-group-type.netatmo.setpoint.channel.end.description = End time of the cu
channel-group-type.netatmo.setpoint.channel.start.label = Setpoint Start
channel-group-type.netatmo.setpoint.channel.start.description = Start time of the currently applied setpoint.
channel-group-type.netatmo.signal.label = Signal
channel-group-type.netatmo.siren.label = Siren Status
channel-group-type.netatmo.status-doorbell.label = Camera Status
channel-group-type.netatmo.status.label = Camera Status
channel-group-type.netatmo.sub-event-doorbell.label = Sub Event
@ -268,6 +268,12 @@ channel-type.netatmo.setpoint-duration.label = Setpoint Duration
channel-type.netatmo.setpoint-duration.description = Default duration of manual setpoint changes.
channel-type.netatmo.setpoint.label = Setpoint
channel-type.netatmo.setpoint.description = Thermostat temperature setpoint.
channel-type.netatmo.siren-monitoring.label = Monitoring
channel-type.netatmo.siren-monitoring.description = Monitoring state of the equipment
channel-type.netatmo.siren-status.label = Status
channel-type.netatmo.siren-status.description = Status of the siren
channel-type.netatmo.siren-status.state.option.no_sound = Silent
channel-type.netatmo.siren-status.state.option.sound = Alarm
channel-type.netatmo.th-mode.label = Thermostat Mode
channel-type.netatmo.th-mode.description = Chosen thermostat mode (home, frost guard, manual, max).
channel-type.netatmo.th-mode.state.option.HOME = Home

View File

@ -55,7 +55,6 @@ channel-group-type.netatmo.noise.label = Rumore
channel-group-type.netatmo.person.label = Persona
channel-group-type.netatmo.person.channel.last-seen.label = Visto l'ultima volta
channel-group-type.netatmo.person.channel.last-seen.description = Momento quando questa persona è stata vista per l'ultima volta.
channel-group-type.netatmo.plug.label = Spina Termostato
channel-group-type.netatmo.presence.label = Fotocamera Presenza
channel-group-type.netatmo.pressure-extended.label = Pressione
channel-group-type.netatmo.pressure-extended.channel.trend.label = Grafico Pressione

View File

@ -10,6 +10,25 @@
<description>Monitoring state of the camera</description>
</channel-type>
<channel-type id="siren-monitoring">
<item-type>Switch</item-type>
<label>Monitoring</label>
<description>Monitoring state of the equipment</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="siren-status">
<item-type>String</item-type>
<label>Status</label>
<description>Status of the siren</description>
<state pattern="%s" readOnly="true">
<options>
<option value="no_sound">Silent</option>
<option value="sound">Alarm</option>
</options>
</state>
</channel-type>
<channel-type id="window-open">
<item-type>Contact</item-type>
<label>Window Status</label>

View File

@ -4,13 +4,6 @@
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-group-type id="plug">
<label>Thermostat Plug</label>
<channels>
<channel id="boiler-status" typeId="boiler-status"/>
</channels>
</channel-group-type>
<channel-group-type id="temperature-room">
<label>Room Temperature</label>
<channels>

View File

@ -23,6 +23,14 @@
</channels>
</channel-group-type>
<channel-group-type id="siren">
<label>Siren Status</label>
<channels>
<channel id="status" typeId="siren-status"/>
<channel id="monitoring" typeId="siren-monitoring"/>
</channels>
</channel-group-type>
<channel-group-type id="status-doorbell">
<label>Camera Status</label>
<channels>