Skip to content

Commit

Permalink
Simplified the handler architecture.
Browse files Browse the repository at this point in the history
Introduced variable capabilities.
Not all things are bridge anymore.
I think I've completely cleaned up the base of the binding. Some testings needed now.

Signed-off-by: clinique <gael@lhopital.org>
  • Loading branch information
clinique committed Mar 3, 2022
1 parent 96563c0 commit 82220e1
Show file tree
Hide file tree
Showing 77 changed files with 1,832 additions and 1,779 deletions.
3 changes: 2 additions & 1 deletion bundles/org.openhab.binding.netatmo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ The following icons are used by original Netatmo web app:

## Rule Action

This binding includes rule action reconnecting the API - this action is valid for each type of thing (NAMain, NHC, NAHome...).
This binding includes rule action reconnecting the API - this action is valid for each type of bridge (NAMain, NHC, NAHome...).

Example:

Expand All @@ -654,3 +654,4 @@ if(actions === null) {
}
```

Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public class NetatmoBindingConstants {
public static final String GROUP_TIMESTAMP = "timestamp";
public static final String GROUP_RAIN = "rain";
public static final String GROUP_WIND = "wind";
public static final String GROUP_HOME_ENERGY = "energy";
public static final String GROUP_ENERGY = "energy";
public static final String GROUP_SIGNAL = "signal";
public static final String GROUP_BATTERY = "battery";
public static final String GROUP_HOME_SECURITY = "security";
public static final String GROUP_SECURITY = "security";
public static final String GROUP_CAM_STATUS = "status";
public static final String GROUP_CAM_LIVE = "live";
public static final String GROUP_PRESENCE = "presence";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,30 @@
*/
package org.openhab.binding.netatmo.internal;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.ApiBridge;
import org.openhab.binding.netatmo.internal.api.data.ModuleType;
import org.openhab.binding.netatmo.internal.handler.channelhelper.AbstractChannelHelper;
import org.openhab.binding.netatmo.internal.handler.NABridgeHandler;
import org.openhab.binding.netatmo.internal.handler.NACommonInterface;
import org.openhab.binding.netatmo.internal.handler.NAThingHandler;
import org.openhab.binding.netatmo.internal.handler.capability.AirCareCapability;
import org.openhab.binding.netatmo.internal.handler.capability.CameraCapability;
import org.openhab.binding.netatmo.internal.handler.capability.Capability;
import org.openhab.binding.netatmo.internal.handler.capability.ChannelHelperCapability;
import org.openhab.binding.netatmo.internal.handler.capability.EventCapability;
import org.openhab.binding.netatmo.internal.handler.capability.HomeCapability;
import org.openhab.binding.netatmo.internal.handler.capability.MeasureCapability;
import org.openhab.binding.netatmo.internal.handler.capability.ModuleCapability;
import org.openhab.binding.netatmo.internal.handler.capability.PersonCapability;
import org.openhab.binding.netatmo.internal.handler.capability.PresenceCapability;
import org.openhab.binding.netatmo.internal.handler.capability.RoomCapability;
import org.openhab.binding.netatmo.internal.handler.capability.WeatherCapability;
import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
import org.openhab.binding.netatmo.internal.handler.channelhelper.MeasuresChannelHelper;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
import org.openhab.binding.netatmo.internal.webhook.NetatmoServlet;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
Expand Down Expand Up @@ -68,28 +83,59 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
Bridge bridge = (Bridge) thing;
BaseThingHandler handler = ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID))
.findFirst().map(mt -> buildThing(bridge, mt)).orElse(null);
return handler;
return ModuleType.AS_SET.stream().filter(mt -> mt.thingTypeUID.equals(thingTypeUID)).findFirst()
.map(mt -> buildNAHandler(thing, mt)).orElse(null);
}

private @Nullable BaseThingHandler buildThing(Bridge bridge, ModuleType moduleType) {
List<AbstractChannelHelper> helpers = new ArrayList<>();
try {
Constructor<?> handlerConstructor = moduleType.getHandlerConstructor();
for (Class<?> helperClass : moduleType.channelHelpers) {
Constructor<?> helperConstructor = helperClass.getConstructor();
Object helper = helperConstructor.newInstance();
if (helper instanceof AbstractChannelHelper) {
helpers.add((AbstractChannelHelper) helper);
private BaseThingHandler buildNAHandler(Thing thing, ModuleType moduleType) {
NACommonInterface handler = moduleType.isABridge() ? new NABridgeHandler((Bridge) thing, apiBridge)
: new NAThingHandler(thing, apiBridge);

List<ChannelHelper> helpers = new ArrayList<>();
moduleType.channelHelpers.forEach(helperClass -> {
try {
ChannelHelper helper = helperClass.getConstructor().newInstance();
helpers.add(helper);
if (helper instanceof MeasuresChannelHelper) {
handler.getCapabilities()
.put(new MeasureCapability(handler, (MeasuresChannelHelper) helper, apiBridge));
}
} catch (ReflectiveOperationException e) {
logger.warn("Error creating or initializing helper class : {}", e.getMessage());
}
return (BaseThingHandler) handlerConstructor.newInstance(bridge, helpers, apiBridge,
stateDescriptionProvider, webhookServlet);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
logger.warn("Error creating or initializing constructor : {}", e.getMessage());
});
if (!helpers.isEmpty()) {
handler.getCapabilities().put(new ChannelHelperCapability(handler, apiBridge, helpers));
}
return null;

moduleType.capabilities.forEach(capability -> {
Capability newCap = null;
if (capability == ModuleCapability.class) {
newCap = new ModuleCapability(handler);
} else if (capability == AirCareCapability.class) {
newCap = new AirCareCapability(handler, apiBridge);
} else if (capability == EventCapability.class) {
newCap = new EventCapability(handler, apiBridge, webhookServlet);
} else if (capability == HomeCapability.class) {
newCap = new HomeCapability(handler, apiBridge, stateDescriptionProvider);
} else if (capability == WeatherCapability.class) {
newCap = new WeatherCapability(handler, apiBridge);
} else if (capability == RoomCapability.class) {
newCap = new RoomCapability(handler);
} else if (capability == PersonCapability.class) {
newCap = new PersonCapability(handler, stateDescriptionProvider);
} else if (capability == CameraCapability.class) {
newCap = new CameraCapability(handler, stateDescriptionProvider, helpers);
} else if (capability == PresenceCapability.class) {
newCap = new PresenceCapability(handler, stateDescriptionProvider, helpers);
}
if (newCap != null) {
handler.getCapabilities().put(newCap);
} else {
logger.warn("No factory entry defined to create Capability : {}", capability);
}
});

return (BaseThingHandler) handler;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.handler.NetatmoHandler;
import org.openhab.binding.netatmo.internal.handler.NABridgeHandler;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
Expand All @@ -23,25 +23,25 @@
import org.slf4j.LoggerFactory;

/**
* The {@link DeviceActions} defines thing actions for each Netatmo thing.
* The {@link NABridgeActions} defines thing actions for each Netatmo bridge.
*
* @author Gaël L'hopital - Initial contribution
*/
@ThingActionsScope(name = "netatmo")
@NonNullByDefault
public class DeviceActions implements ThingActions {
private final Logger logger = LoggerFactory.getLogger(DeviceActions.class);
public class NABridgeActions implements ThingActions {
private final Logger logger = LoggerFactory.getLogger(NABridgeActions.class);

private @Nullable NetatmoHandler handler;
private @Nullable NABridgeHandler handler;

public DeviceActions() {
logger.trace("Netatmo Device actions service created");
public NABridgeActions() {
logger.trace("NABridgeActions service created");
}

@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof NetatmoHandler) {
this.handler = (NetatmoHandler) handler;
if (handler instanceof NABridgeHandler) {
this.handler = (NABridgeHandler) handler;
}
}

Expand All @@ -50,9 +50,9 @@ public void setThingHandler(@Nullable ThingHandler handler) {
return handler;
}

@RuleAction(label = "@text/reconnectApiLabel", description = "@text/reconnectApiDesc")
@RuleAction(label = "Reconnect API", description = "Reopens the Netatmo API session.")
public void reconnectApi() {
NetatmoHandler localHandler = handler;
NABridgeHandler localHandler = handler;
if (localHandler == null) {
logger.debug("Handler not set for device thing actions.");
return;
Expand All @@ -65,6 +65,6 @@ public void reconnectApi() {
* Static setLevel method for Rules DSL backward compatibility
*/
public static void reconnectApi(ThingActions actions) {
((DeviceActions) actions).reconnectApi();
((NABridgeActions) actions).reconnectApi();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@
*/
package org.openhab.binding.netatmo.internal.action;

import java.util.Optional;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SetpointMode;
import org.openhab.binding.netatmo.internal.handler.RoomHandler;
import org.openhab.binding.netatmo.internal.handler.NACommonInterface;
import org.openhab.binding.netatmo.internal.handler.capability.EnergyCapability;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
Expand All @@ -33,23 +37,28 @@
@NonNullByDefault
public class RoomActions implements ThingActions {
private final Logger logger = LoggerFactory.getLogger(RoomActions.class);
private static final Set<SetpointMode> ALLOWED_MODES = Set.of(SetpointMode.MAX, SetpointMode.MANUAL,
SetpointMode.HOME);

private @Nullable RoomHandler handler;
private @Nullable NACommonInterface handler;
private Optional<EnergyCapability> energy = Optional.empty();

public RoomActions() {
logger.trace("Netatmo room actions service created");
logger.debug("Netatmo RoomActions service created");
}

@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof RoomHandler) {
this.handler = (RoomHandler) handler;
if (handler instanceof NACommonInterface) {
NACommonInterface commonHandler = (NACommonInterface) handler;
this.handler = commonHandler;
energy = commonHandler.getHomeCapability(EnergyCapability.class);
}
}

@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
return (ThingHandler) handler;
}

/**
Expand All @@ -59,7 +68,7 @@ public void setThingHandler(@Nullable ThingHandler handler) {
public void setRoomThermpoint(
@ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp,
@ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime) {
setRoomThermpoint(temp, endTime, null);
setRoomThermpoint(temp, endTime, "MANUAL");
}

@RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
Expand All @@ -74,44 +83,52 @@ public void setRoomThermpoint(
@ActionInput(name = "setpoint", label = "@text/actionInputSetpointLabel", description = "@text/actionInputSetpointDesc") @Nullable Double temp,
@ActionInput(name = "endtime", label = "@text/actionInputEndtimeLabel", description = "@text/actionInputEndtimeDesc") @Nullable Long endTime,
@ActionInput(name = "mode", label = "@text/actionInputModeLabel", description = "@text/actionInputModeDesc") @Nullable String mode) {
RoomHandler roomHandler = handler;
if (roomHandler == null) {
logger.debug("Handler not set for room thing actions.");
return;
}

String targetMode = mode;
Long targetEndTime = endTime;
Double targetTemp = temp;
if (targetMode != null) {
if (!(targetMode.equals(SetpointMode.MAX.toString()) || targetMode.equals(SetpointMode.HOME.toString())
|| targetMode.equals(SetpointMode.MANUAL.toString()))) {
logger.debug("Mode can only be MAX, HOME or MANUAL for a room");
return;
NACommonInterface roomHandler = handler;
if (roomHandler != null) {
String roomId = roomHandler.getId();
SetpointMode targetMode = SetpointMode.UNKNOWN;
Long targetEndTime = endTime;
Double targetTemp = temp;
if (mode != null) {
try {
targetMode = SetpointMode.valueOf(mode);
if (!ALLOWED_MODES.contains(targetMode)) {
logger.info("Mode can only be MAX, HOME or MANUAL for a room");
return;
}
} catch (IllegalArgumentException e) {
logger.info("Invalid mode passed : {} - {}", mode, e.getMessage());
return;
}
}
}
if (temp != null) {
logger.debug("Temperature provided, mode forced to MANUAL.");
targetMode = SetpointMode.MANUAL.toString();
if (targetEndTime == null) {
logger.debug("Temperature provided but no endtime given, action ignored");
return;
if (temp != null) {
logger.debug("Temperature provided, mode forced to MANUAL.");
targetMode = SetpointMode.MANUAL;
if (targetEndTime == null) {
logger.info("Temperature provided but no endtime given, action ignored");
return;
}
} else {
if (SetpointMode.HOME.equals(targetMode)) {
targetEndTime = 0L;
targetTemp = 0.0;
} else {
logger.info("mode is required if no temperature setpoint provided");
return;
}
}
} else {
if (targetMode == null) {
logger.debug("mode is required if no temperature setpoint provided");
return;
} else if (targetMode.equalsIgnoreCase(SetpointMode.HOME.toString())) {
targetEndTime = 0L;
targetTemp = 0.0;
}
}

try {
roomHandler.setRoomThermTemp(targetTemp != null ? targetTemp : 0,
targetEndTime != null ? targetEndTime : 0L, SetpointMode.valueOf(targetMode));
} catch (IllegalArgumentException e) {
logger.debug("Ignoring setRoomThermpoint command due to illegal argument exception: {}", e.getMessage());
try {
double setpointTemp = targetTemp != null ? targetTemp : 0;
long setpointEnd = targetEndTime;
SetpointMode setpointMode = targetMode;
energy.ifPresent(cap -> cap.setRoomThermTemp(roomId, setpointTemp, setpointEnd, setpointMode));
} catch (IllegalArgumentException e) {
logger.debug("Ignoring setRoomThermpoint command due to illegal argument exception: {}",
e.getMessage());
}
} else {
logger.info("Handler not set for room thing actions.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ private void freeConnectJob() {
}

private void prepareReconnection(NetatmoException e) {
connectApi.disconnect();
notifyListeners();
freeConnectJob();
connectJob = Optional
Expand Down Expand Up @@ -175,6 +176,10 @@ synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nulla
if (statusCode == Code.BAD_REQUEST) {
ApiError error = deserializer.deserialize(ApiError.class, responseBody);
exception = new NetatmoException(error.getCode(), error.getMessage());
} else if (statusCode == Code.FORBIDDEN) {
ApiError error = deserializer.deserialize(ApiError.class, responseBody);
exception = new NetatmoException(error.getCode(), error.getMessage());
prepareReconnection(exception);
} else {
exception = new NetatmoException(statusCode.getCode(), responseBody);
prepareReconnection(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ private void requestToken(String clientId, String clientSecret, Map<String, Stri
answer = Optional.of(response);
}

public void disconnect() {
answer = Optional.empty();
}

public void dispose() {
freeTokenJob();
}
Expand Down
Loading

0 comments on commit 82220e1

Please sign in to comment.