Skip to content

Commit

Permalink
[netatmo] Add Siren capability to Presence Outdoor Camera (openhab#14485
Browse files Browse the repository at this point in the history
)

* Adding Siren capability to Presence Outdoor Camera
Resolves openhab#14466

---------

Signed-off-by: clinique <gael@lhopital.org>
Signed-off-by: Jørgen Austvik <jaustvik@acm.org>
  • Loading branch information
clinique authored and austvik committed Mar 27, 2024
1 parent 31e8fb1 commit f3d495d
Show file tree
Hide file tree
Showing 19 changed files with 160 additions and 70 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 @@ -563,6 +563,7 @@ Warnings:
| signal | strength | Number | Read-only | Signal strength (0 for no signal, 1 for weak...) |
| signal | value | Number:Power | Read-only | Signal strength in dBm |
| presence | floodlight | String | Read-write | Sets the floodlight to ON/OFF/AUTO |
| presence | siren | Switch | Read-write | Status of the siren, if silent or emitting an alarm |
| last-event | type | String | Read-only | Type of event |
| last-event | subtype | String | Read-only | Sub-type of event |
| last-event | time | DateTime | Read-only | Time of occurrence of event |
Expand Down Expand Up @@ -614,7 +615,7 @@ Note: live feeds either locally or via VPN are not available in Netatmo API.

| 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 | status | Switch | 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 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class NetatmoBindingConstants {
public static final String PROPERTY_COUNTRY = "country";
public static final String PROPERTY_TIMEZONE = "timezone";
public static final String PROPERTY_FEATURE = "feature";
public static final String PROPERTY_THING_TYPE_VERSION = "thingTypeVersion";

// Channel group ids
public static final String GROUP_LAST_EVENT = "last-event";
Expand Down Expand Up @@ -113,6 +114,7 @@ public class NetatmoBindingConstants {
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_SIREN = GROUP_SIREN;
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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.ServiceError;

/**
* The {@link ApiError} models an errored response from API
* The {@link ApiError} models an error response from API
*
* @author Gaël L'hopital - Initial contribution
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ public class NetatmoException extends IOException {
private ServiceError statusCode = ServiceError.UNKNOWN;

public NetatmoException(String format, Object... args) {
super(String.format(format, args));
super(format.formatted(args));
}

public NetatmoException(Exception e, String format, Object... args) {
super(String.format(format, args), e);
super(format.formatted(args), e);
}

public NetatmoException(String message) {
Expand All @@ -54,6 +54,6 @@ public ServiceError getStatusCode() {
String message = super.getMessage();
return message == null ? null
: ServiceError.UNKNOWN.equals(statusCode) ? message
: String.format("Rest call failed: statusCode=%s, message=%s", statusCode, message);
: "Rest call failed: statusCode=%s, message=%s".formatted(statusCode, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.eclipse.jdt.annotation.Nullable;
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.data.NetatmoConstants.SirenStatus;
import org.openhab.binding.netatmo.internal.api.dto.Home;
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent;
import org.openhab.binding.netatmo.internal.api.dto.HomeEvent.NAEventsDataResponse;
Expand Down Expand Up @@ -126,6 +127,12 @@ public void changeFloodLightMode(String homeId, String cameraId, FloodLightMode
post(uriBuilder, ApiResponse.Ok.class, payload);
}

public void changeSirenStatus(String homeId, String moduleId, SirenStatus status) throws NetatmoException {
UriBuilder uriBuilder = getApiUriBuilder(PATH_STATE);
String payload = PAYLOAD_SIREN_PRESENCE.formatted(homeId, moduleId, status.name().toLowerCase());
post(uriBuilder, ApiResponse.Ok.class, payload);
}

public void setPersonAwayStatus(String homeId, String personId, boolean away) throws NetatmoException {
UriBuilder uriBuilder = getApiUriBuilder(away ? SUB_PATH_PERSON_AWAY : SUB_PATH_PERSON_HOME);
String payload = String.format(away ? PAYLOAD_PERSON_AWAY : PAYLOAD_PERSON_HOME, homeId, personId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,89 +64,91 @@
*/
@NonNullByDefault
public enum ModuleType {
UNKNOWN(FeatureArea.NONE, "", null, Set.of()),
UNKNOWN(FeatureArea.NONE, "", 1, null, Set.of()),

ACCOUNT(FeatureArea.NONE, "", null, Set.of(), new ChannelGroup(ApiBridgeChannelHelper.class, GROUP_MONITORING)),
ACCOUNT(FeatureArea.NONE, "", 1, null, Set.of(), new ChannelGroup(ApiBridgeChannelHelper.class, GROUP_MONITORING)),

HOME(FeatureArea.NONE, "NAHome", ACCOUNT,
HOME(FeatureArea.NONE, "NAHome", 1, ACCOUNT,
Set.of(DeviceCapability.class, HomeCapability.class, ChannelHelperCapability.class),
new ChannelGroup(SecurityChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_SECURITY),
new ChannelGroup(EnergyChannelHelper.class, GROUP_ENERGY)),

PERSON(FeatureArea.SECURITY, "NAPerson", HOME, Set.of(PersonCapability.class, ChannelHelperCapability.class),
PERSON(FeatureArea.SECURITY, "NAPerson", 1, HOME, Set.of(PersonCapability.class, ChannelHelperCapability.class),
new ChannelGroup(PersonChannelHelper.class, GROUP_PERSON),
new ChannelGroup(EventPersonChannelHelper.class, GROUP_PERSON_LAST_EVENT)),

WELCOME(FeatureArea.SECURITY, "NACamera", HOME, Set.of(CameraCapability.class, ChannelHelperCapability.class),
WELCOME(FeatureArea.SECURITY, "NACamera", 1, HOME, Set.of(CameraCapability.class, ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.EVENT,
new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE)),

TAG(FeatureArea.SECURITY, "NACamDoorTag", WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
TAG(FeatureArea.SECURITY, "NACamDoorTag", 1, WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(DoorTagChannelHelper.class, GROUP_TAG)),

SIREN(FeatureArea.SECURITY, "NIS", WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
SIREN(FeatureArea.SECURITY, "NIS", 1, WELCOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.BATTERY, ChannelGroup.TIMESTAMP, new ChannelGroup(SirenChannelHelper.class, GROUP_SIREN)),

PRESENCE(FeatureArea.SECURITY, "NOC", HOME, Set.of(PresenceCapability.class, ChannelHelperCapability.class),
PRESENCE(FeatureArea.SECURITY, "NOC", 1, HOME, Set.of(PresenceCapability.class, ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.EVENT,
new ChannelGroup(PresenceChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_CAM_STATUS, GROUP_CAM_LIVE,
GROUP_PRESENCE),
new ChannelGroup(EventCameraChannelHelper.class, GROUP_SUB_EVENT)),

DOORBELL(FeatureArea.SECURITY, "NDB", HOME, Set.of(DoorbellCapability.class, ChannelHelperCapability.class),
DOORBELL(FeatureArea.SECURITY, "NDB", 1, HOME, Set.of(DoorbellCapability.class, ChannelHelperCapability.class),
ChannelGroup.SIGNAL,
new ChannelGroup(CameraChannelHelper.class, GROUP_SECURITY_EVENT, GROUP_DOORBELL_STATUS,
GROUP_DOORBELL_LIVE),
new ChannelGroup(EventCameraChannelHelper.class, GROUP_DOORBELL_LAST_EVENT, GROUP_DOORBELL_SUB_EVENT)),

WEATHER_STATION(FeatureArea.WEATHER, "NAMain", ACCOUNT,
WEATHER_STATION(FeatureArea.WEATHER, "NAMain", 1, ACCOUNT,
Set.of(DeviceCapability.class, WeatherCapability.class, MeasureCapability.class,
ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.HUMIDITY, ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE,
ChannelGroup.AIR_QUALITY, ChannelGroup.LOCATION, ChannelGroup.NOISE, ChannelGroup.TEMP_INSIDE_EXT,
new ChannelGroup(PressureChannelHelper.class, MeasureClass.PRESSURE, GROUP_TYPE_PRESSURE_EXTENDED)),

OUTDOOR(FeatureArea.WEATHER, "NAModule1", WEATHER_STATION,
OUTDOOR(FeatureArea.WEATHER, "NAModule1", 1, WEATHER_STATION,
Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL, ChannelGroup.HUMIDITY,
ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.TEMP_OUTSIDE_EXT),

WIND(FeatureArea.WEATHER, "NAModule2", WEATHER_STATION, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.TSTAMP_EXT, ChannelGroup.BATTERY, new ChannelGroup(WindChannelHelper.class, GROUP_WIND)),
WIND(FeatureArea.WEATHER, "NAModule2", 1, WEATHER_STATION, Set.of(ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.TSTAMP_EXT, ChannelGroup.BATTERY,
new ChannelGroup(WindChannelHelper.class, GROUP_WIND)),

RAIN(FeatureArea.WEATHER, "NAModule3", WEATHER_STATION,
RAIN(FeatureArea.WEATHER, "NAModule3", 1, WEATHER_STATION,
Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY,
new ChannelGroup(RainChannelHelper.class, MeasureClass.RAIN_QUANTITY, GROUP_RAIN)),

INDOOR(FeatureArea.WEATHER, "NAModule4", WEATHER_STATION,
INDOOR(FeatureArea.WEATHER, "NAModule4", 1, WEATHER_STATION,
Set.of(MeasureCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.TSTAMP_EXT, ChannelGroup.MEASURE, ChannelGroup.BATTERY, ChannelGroup.HUMIDITY,
ChannelGroup.TEMP_INSIDE_EXT, ChannelGroup.AIR_QUALITY),

HOME_COACH(FeatureArea.AIR_CARE, "NHC", ACCOUNT,
HOME_COACH(FeatureArea.AIR_CARE, "NHC", 1, ACCOUNT,
Set.of(DeviceCapability.class, AirCareCapability.class, MeasureCapability.class,
ChannelHelperCapability.class),
ChannelGroup.LOCATION, ChannelGroup.SIGNAL, ChannelGroup.NOISE, ChannelGroup.HUMIDITY,
ChannelGroup.TEMP_INSIDE, ChannelGroup.MEASURE, ChannelGroup.TSTAMP_EXT,
new ChannelGroup(AirQualityChannelHelper.class, GROUP_TYPE_AIR_QUALITY_EXTENDED),
new ChannelGroup(PressureChannelHelper.class, MeasureClass.PRESSURE, GROUP_PRESSURE)),

PLUG(FeatureArea.ENERGY, "NAPlug", HOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL),
PLUG(FeatureArea.ENERGY, "NAPlug", 1, HOME, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL),

VALVE(FeatureArea.ENERGY, "NRV", PLUG, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
VALVE(FeatureArea.ENERGY, "NRV", 1, PLUG, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.BATTERY_EXT),

THERMOSTAT(FeatureArea.ENERGY, "NATherm1", PLUG, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
THERMOSTAT(FeatureArea.ENERGY, "NATherm1", 1, PLUG, Set.of(ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.BATTERY_EXT, new ChannelGroup(Therm1ChannelHelper.class, GROUP_TYPE_TH_PROPERTIES)),

ROOM(FeatureArea.ENERGY, "NARoom", HOME, Set.of(RoomCapability.class, ChannelHelperCapability.class),
ROOM(FeatureArea.ENERGY, "NARoom", 1, HOME, Set.of(RoomCapability.class, ChannelHelperCapability.class),
new ChannelGroup(RoomChannelHelper.class, GROUP_TYPE_ROOM_PROPERTIES, GROUP_TYPE_ROOM_TEMPERATURE),
new ChannelGroup(SetpointChannelHelper.class, GROUP_SETPOINT)),

SMOKE_DETECTOR(FeatureArea.SECURITY, "NSD", HOME, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT),
SMOKE_DETECTOR(FeatureArea.SECURITY, "NSD", 1, HOME,
Set.of(AlarmEventCapability.class, ChannelHelperCapability.class), ChannelGroup.SIGNAL,
ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT),

CO_DETECTOR(FeatureArea.SECURITY, "NCO", HOME, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class),
CO_DETECTOR(FeatureArea.SECURITY, "NCO", 1, HOME, Set.of(AlarmEventCapability.class, ChannelHelperCapability.class),
ChannelGroup.SIGNAL, ChannelGroup.TIMESTAMP, ChannelGroup.ALARM_LAST_EVENT);

public static final EnumSet<ModuleType> AS_SET = EnumSet.allOf(ModuleType.class);
Expand All @@ -157,15 +159,17 @@ ChannelGroup.BATTERY_EXT, new ChannelGroup(Therm1ChannelHelper.class, GROUP_TYPE
public final ThingTypeUID thingTypeUID;
public final FeatureArea feature;
public final String apiName;
public final int thingTypeVersion;

ModuleType(FeatureArea feature, String apiName, @Nullable ModuleType bridge,
ModuleType(FeatureArea feature, String apiName, int thingTypeVersion, @Nullable ModuleType bridge,
Set<Class<? extends Capability>> capabilities, ChannelGroup... channelGroups) {
this.bridgeType = Optional.ofNullable(bridge);
this.feature = feature;
this.capabilities = capabilities;
this.apiName = apiName;
this.channelGroups = Set.of(channelGroups);
this.thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace("_", "-"));
this.thingTypeVersion = thingTypeVersion;
}

public boolean isLogical() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ public enum MeasureClass {

// Payloads
public static final String PAYLOAD_FLOODLIGHT = "{\"home\": {\"id\":\"%s\",\"modules\": [ {\"id\":\"%s\",\"floodlight\":\"%s\"} ]}}";
public static final String PAYLOAD_SIREN_PRESENCE = "{\"home\": {\"id\":\"%s\",\"modules\": [ {\"id\":\"%s\",\"siren_status\":\"%s\"} ]}}";
public static final String PAYLOAD_PERSON_AWAY = "{\"home_id\":\"%s\",\"person_id\":\"%s\"}";
public static final String PAYLOAD_PERSON_HOME = "{\"home_id\":\"%s\",\"person_ids\":[\"%s\"]}";

Expand Down Expand Up @@ -348,6 +349,20 @@ public enum AlimentationStatus {
UNKNOWN;
}

public enum SirenStatus {
SOUND,
NO_SOUND,
UNKNOWN;

public static SirenStatus get(String value) {
try {
return valueOf(value.toUpperCase());
} catch (IllegalArgumentException e) {
return UNKNOWN;
}
}
}

public enum BatteryState {
@SerializedName("full")
FULL(100),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.BatteryState;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FloodLightMode;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SdCardStatus;
import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.SirenStatus;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.types.State;
Expand All @@ -42,7 +43,7 @@ public class HomeStatusModule extends NAThing {
private FloodLightMode floodlight = FloodLightMode.UNKNOWN;
private SdCardStatus sdStatus = SdCardStatus.UNKNOWN;
private AlimentationStatus alimStatus = AlimentationStatus.UNKNOWN;
private @Nullable String sirenStatus;
private SirenStatus sirenStatus = SirenStatus.UNKNOWN;
private @Nullable String vpnUrl;
private boolean isLocal;
private BatteryState batteryState = BatteryState.UNKNOWN;
Expand Down Expand Up @@ -90,8 +91,8 @@ public AlimentationStatus getAlimStatus() {
return alimStatus;
}

public Optional<String> getSirenStatus() {
return Optional.ofNullable(sirenStatus);
public SirenStatus getSirenStatus() {
return sirenStatus;
}

public @Nullable String getVpnUrl() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz,
InputStream stream = new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8));
try (InputStreamContentProvider inputStreamContentProvider = new InputStreamContentProvider(stream)) {
request.content(inputStreamContentProvider, contentType);
request.header(HttpHeader.ACCEPT, "application/json");
}
logger.trace(" -with payload : {} ", payload);
}
Expand Down Expand Up @@ -404,10 +405,9 @@ public void identifyAllModulesAndApplyAction(BiFunction<NAModule, ThingUID, Opti
home.getRooms().values().stream().forEach(room -> {
room.getModuleIds().stream().map(id -> home.getModules().get(id))
.map(m -> m != null ? m.getType().feature : FeatureArea.NONE)
.filter(f -> FeatureArea.ENERGY.equals(f)).findAny().ifPresent(f -> {
action.apply(room, homeUID)
.ifPresent(roomUID -> bridgesUids.put(room.getId(), roomUID));
});
.filter(f -> FeatureArea.ENERGY.equals(f)).findAny()
.ifPresent(f -> action.apply(room, homeUID)
.ifPresent(roomUID -> bridgesUids.put(room.getId(), roomUID)));
});

// Creating modules that have no bridge first, avoiding weather station itself
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ default void commonHandleCommand(ChannelUID channelUID, Command command) {
String channelName = channelUID.getIdWithoutGroup();
getCapabilities().values().forEach(cap -> cap.handleCommand(channelName, command));
} else {
getLogger().debug("Command {}, on channel {} dropped - thing is not ONLINE", command, channelUID);
getLogger().debug("Command {} on channel {} dropped - thing is not ONLINE", command, channelUID);
}
}

Expand Down
Loading

0 comments on commit f3d495d

Please sign in to comment.