Skip to content

Commit

Permalink
[netatmo] Add support for the Presence camera (#3059) (#7660)
Browse files Browse the repository at this point in the history
* Support for the Presence camera added
* Files and thing types renamed because it handles now various camera types (not only the Welcome camera)
* README updated to clarify the Welcome and Presence product names
* Permission configurations renamed because it handles now various camera types (not only the Welcome camera) ; Reverted. Now it is separated, so the user has explicitly to decide if he wants to grant the access to outdoor cameras.
* Camera channels separated to remove "welcome" from the (Presence) channel names. The channels of the Welcome camera were not renamed to be downward compatible.
* welcomeHomeEvent channel removed for the Presence camera, event handling for the Presence will get realized later

Signed-off-by: Sven Strohschein <[email protected]>
  • Loading branch information
Novanic authored May 22, 2020
1 parent 309f6f9 commit efc84f1
Show file tree
Hide file tree
Showing 14 changed files with 267 additions and 97 deletions.
27 changes: 20 additions & 7 deletions bundles/org.openhab.binding.netatmo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ 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.
- *Welcome Camera*. Reports last event and persons at home, consult picture and video from event/camera.
- *Indoor Camera / Welcome*. Reports last event and persons at home, consult picture and video from event/camera.
- *Outdoor Camera / Presence*. Consult picture and video from camera. The last event is also available, but without content yet, this will get enhanced later.

See http://www.netatmo.com/ for details on their product.

Expand Down Expand Up @@ -33,7 +34,7 @@ Once you will get needed informations from the Netatmo API, you will be able to
E.g.

```
Bridge netatmo:netatmoapi:home [ clientId="<CLIENT_ID>", clientSecret="<CLIENT_SECRET>", username = "<USERNAME>", password = "<PASSWORD>", readStation=true|false, readHealthyHomeCoach=true|false, readThermostat=true|false, readWelcome=true|false] {
Bridge netatmo:netatmoapi:home [ clientId="<CLIENT_ID>", clientSecret="<CLIENT_SECRET>", username = "<USERNAME>", password = "<PASSWORD>", readStation=true|false, readHealthyHomeCoach=true|false, readThermostat=true|false, readWelcome=true|false, readPresence=true|false] {
Thing NAMain inside [ id="aa:aa:aa:aa:aa:aa" ]
Thing NAModule1 outside [ id="yy:yy:yy:yy:yy:yy", parentId="aa:aa:aa:aa:aa:aa" ]
Thing NHC homecoach [ id="cc:cc:cc:cc:cc:cc", [refreshInterval=60000] ]
Expand Down Expand Up @@ -469,9 +470,14 @@ All these channels except Sp_Temperature, SetpointMode and Planning are read onl
All these channels are read only.


### Welcome Camera
### Welcome and Presence Camera

**Supported channels for the Camera thing:**
All these channels are read only.

Warning : 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.

**Supported channels for the Welcome Camera thing:**

| Channel ID | Item Type | Description |
|-----------------------------|-----------|----------------------------------------------------------|
Expand All @@ -483,10 +489,17 @@ All these channels are read only.
| welcomeCameraLivePictureUrl | String | Url of the live snapshot for this camera |
| welcomeCameraLiveStreamUrl | String | Url of the live stream for this camera |

All these channels are read only.
**Supported channels for the Presence Camera thing:**

Warning : the URL of the live snapshot is a fixed URL so the value of the channel 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.
| Channel ID | Item Type | Description |
|-----------------------------|-----------|----------------------------------------------------------|
| cameraStatus | Switch | State of the camera |
| cameraSdStatus | Switch | State of the SD card |
| cameraAlimStatus | Switch | State of the power connector |
| cameraIsLocal | Switch | indicates whether the camera is on the same network than the openHAB Netatmo Binding |
| cameraLivePicture | Image | Camera Live Snapshot |
| cameraLivePictureUrl | String | Url of the live snapshot for this camera |
| cameraLiveStreamUrl | String | Url of the live stream for this camera |


### Welcome Person
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ public static State toOnOffType(@Nullable String yesno) {
}

public static State toOnOffType(@Nullable Integer value) {
return value != null ? (value == 1 ? OnOffType.ON : OnOffType.OFF) : UnDefType.NULL;
return value != null ? (value == 1 ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF;
}

public static State toOnOffType(@Nullable Boolean value) {
return value != null ? (value ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF;
}

public static State toQuantityType(@Nullable Float value, Unit<?> unit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ public class NetatmoBindingConstants {
public static final ThingTypeUID WELCOME_HOME_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomeHome");
public static final ThingTypeUID WELCOME_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NACamera");
public static final ThingTypeUID WELCOME_PERSON_THING_TYPE = new ThingTypeUID(BINDING_ID, "NAWelcomePerson");
// Presence camera
public static final ThingTypeUID PRESENCE_CAMERA_THING_TYPE = new ThingTypeUID(BINDING_ID, "NOC");

// Weather Station Channel ids
public static final String CHANNEL_TEMPERATURE = "Temperature";
Expand Down Expand Up @@ -254,6 +256,15 @@ public class NetatmoBindingConstants {
public static final String CHANNEL_WELCOME_EVENT_MESSAGE = "welcomeEventMessage";
public static final String CHANNEL_WELCOME_EVENT_SUBTYPE = "welcomeEventSubType";

// Camera specific channels
public static final String CHANNEL_CAMERA_STATUS = "cameraStatus";
public static final String CHANNEL_CAMERA_SDSTATUS = "cameraSdStatus";
public static final String CHANNEL_CAMERA_ALIMSTATUS = "cameraAlimStatus";
public static final String CHANNEL_CAMERA_ISLOCAL = "cameraIsLocal";
public static final String CHANNEL_CAMERA_LIVEPICTURE = "cameraLivePicture";
public static final String CHANNEL_CAMERA_LIVEPICTURE_URL = "cameraLivePictureUrl";
public static final String CHANNEL_CAMERA_LIVESTREAM_URL = "cameraLiveStreamUrl";

public static final String WELCOME_PICTURE_URL = "https://api.netatmo.com/api/getcamerapicture";
public static final String WELCOME_PICTURE_IMAGEID = "image_id";
public static final String WELCOME_PICTURE_KEY = "key";
Expand All @@ -262,7 +273,8 @@ public class NetatmoBindingConstants {
public static final Set<ThingTypeUID> SUPPORTED_DEVICE_THING_TYPES_UIDS = Stream
.of(MAIN_THING_TYPE, MODULE1_THING_TYPE, MODULE2_THING_TYPE, MODULE3_THING_TYPE, MODULE4_THING_TYPE,
HOMECOACH_THING_TYPE, PLUG_THING_TYPE, THERM1_THING_TYPE, WELCOME_HOME_THING_TYPE,
WELCOME_CAMERA_THING_TYPE, WELCOME_PERSON_THING_TYPE)
WELCOME_CAMERA_THING_TYPE, WELCOME_PERSON_THING_TYPE,
PRESENCE_CAMERA_THING_TYPE)
.collect(Collectors.toSet());

// List of all adressable things in OH = SUPPORTED_DEVICE_THING_TYPES_UIDS + the virtual bridge
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return new NATherm1Handler(thing, stateDescriptionProvider);
} else if (thingTypeUID.equals(WELCOME_HOME_THING_TYPE)) {
return new NAWelcomeHomeHandler(thing);
} else if (thingTypeUID.equals(WELCOME_CAMERA_THING_TYPE)) {
} else if (thingTypeUID.equals(WELCOME_CAMERA_THING_TYPE)
|| thingTypeUID.equals(PRESENCE_CAMERA_THING_TYPE)) {
return new NAWelcomeCameraHandler(thing);
} else if (thingTypeUID.equals(WELCOME_PERSON_THING_TYPE)) {
return new NAWelcomePersonHandler(thing);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* Copyright (c) 2010-2020 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.camera;

import io.swagger.client.model.NAWelcomeCamera;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.smarthome.core.library.types.StringType;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.core.types.UnDefType;
import org.eclipse.smarthome.io.net.http.HttpUtil;
import org.openhab.binding.netatmo.internal.handler.NetatmoModuleHandler;

import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.toOnOffType;
import static org.openhab.binding.netatmo.internal.ChannelTypeUtils.toStringType;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;

/**
* {@link CameraHandler} is the class used to handle Camera Data
*
* @author Sven Strohschein (partly moved code from NAWelcomeCameraHandler to introduce inheritance, see NAWelcomeCameraHandler)
*
*/
public class CameraHandler extends NetatmoModuleHandler<NAWelcomeCamera> {

private static final String LIVE_PICTURE = "/live/snapshot_720.jpg";

public CameraHandler(@NonNull Thing thing) {
super(thing);
}

@Override
protected void updateProperties(NAWelcomeCamera moduleData) {
updateProperties(null, moduleData.getType());
}

@SuppressWarnings("null")
@Override
protected State getNAThingProperty(String channelId) {
switch (channelId) {
case CHANNEL_CAMERA_STATUS:
return getStatusState();
case CHANNEL_CAMERA_SDSTATUS:
return getSdStatusState();
case CHANNEL_CAMERA_ALIMSTATUS:
return getAlimStatusState();
case CHANNEL_CAMERA_ISLOCAL:
return getIsLocalState();
case CHANNEL_CAMERA_LIVEPICTURE_URL:
return getLivePictureURLState();
case CHANNEL_CAMERA_LIVEPICTURE:
return getLivePictureState();
case CHANNEL_CAMERA_LIVESTREAM_URL:
return getLiveStreamState();
}
return super.getNAThingProperty(channelId);
}

protected State getStatusState() {
return module != null ? toOnOffType(module.getStatus()) : UnDefType.UNDEF;
}

protected State getSdStatusState() {
return module != null ? toOnOffType(module.getSdStatus()) : UnDefType.UNDEF;
}

protected State getAlimStatusState() {
return module != null ? toOnOffType(module.getAlimStatus()) : UnDefType.UNDEF;
}

protected State getIsLocalState() {
return module != null ? toOnOffType(module.getIsLocal()) : UnDefType.UNDEF;
}

protected State getLivePictureURLState() {
String livePictureURL = getLivePictureURL();
return livePictureURL == null ? UnDefType.UNDEF : toStringType(livePictureURL);
}

protected State getLivePictureState() {
String livePictureURL = getLivePictureURL();
return livePictureURL == null ? UnDefType.UNDEF : HttpUtil.downloadImage(livePictureURL);
}

protected State getLiveStreamState() {
String liveStreamURL = getLiveStreamURL();
return liveStreamURL == null ? UnDefType.UNDEF : new StringType(liveStreamURL);
}

/**
* Get the url for the live snapshot
*
* @return Url of the live snapshot
*/
private String getLivePictureURL() {
String result = getVpnUrl();
if (result != null) {
result += LIVE_PICTURE;
}
return result;
}

/**
* Get the url for the live stream depending wether local or not
*
* @return Url of the live stream
*/
private String getLiveStreamURL() {
String result = getVpnUrl();
if (result == null) {
return null;
}

StringBuilder resultStringBuilder = new StringBuilder(result);
resultStringBuilder.append("/live/index");
if(isLocal()) {
resultStringBuilder.append("_local");
}
resultStringBuilder.append(".m3u8");
return resultStringBuilder.toString();
}

@SuppressWarnings("null")
private String getVpnUrl() {
return (module == null) ? null : module.getVpnUrl();
}

public String getStreamURL(String videoId) {
String result = getVpnUrl();
if(result == null) {
return null;
}

StringBuilder resultStringBuilder = new StringBuilder(result);
resultStringBuilder.append("/vod/");
resultStringBuilder.append(videoId);
resultStringBuilder.append("/index");
if(isLocal()) {
resultStringBuilder.append("_local");
}
resultStringBuilder.append(".m3u8");
return resultStringBuilder.toString();
}

@SuppressWarnings("null")
private boolean isLocal() {
return (module == null || module.getIsLocal() == null) ? false : module.getIsLocal();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class NetatmoBridgeConfiguration {
public Boolean readThermostat;
public Boolean readHealthyHomeCoach;
public Boolean readWelcome;
public Boolean readPresence;
public String webHookUrl;
public Integer reconnectInterval;
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ public void startScan() {
});
}
}
if (netatmoBridgeHandler.configuration.readWelcome) {
if (netatmoBridgeHandler.configuration.readWelcome
|| netatmoBridgeHandler.configuration.readPresence) {
NAWelcomeHomeData welcomeHomeData = netatmoBridgeHandler.getWelcomeDataBody(null);
if (welcomeHomeData != null) {
welcomeHomeData.getHomes().forEach(home -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ private String getApiScope() {
scopes.add("write_camera");
}

if (configuration.readPresence) {
scopes.add("read_presence");
scopes.add("access_presence");
}

return String.join(" ", scopes);
}

Expand Down Expand Up @@ -348,7 +353,9 @@ public void webHookEvent(NAWebhookCameraEvent event) {

private String getWebHookURI() {
String webHookURI = null;
if (configuration.webHookUrl != null && configuration.readWelcome && webHookServlet != null) {
if (configuration.webHookUrl != null
&& (configuration.readWelcome || configuration.readPresence)
&& webHookServlet != null) {
webHookURI = configuration.webHookUrl + webHookServlet.getPath();
}
return webHookURI;
Expand Down
Loading

0 comments on commit efc84f1

Please sign in to comment.