Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[netatmo] Add Siren capability to Presence Outdoor Camera #14485

Merged
merged 10 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -562,6 +562,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 @@ -613,7 +614,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 @@ -26,6 +26,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 @@ -127,6 +128,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 @@ -284,6 +284,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 @@ -370,10 +371,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 @@ -182,7 +182,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