[freeboxos] Complete Alarm system handling (#17233)

* Initiating the addition of the PIR sensor
* Finalized integration of the alarm system
* Corrected bug in initialization of basic-shutter

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Gaël L'hopital 2024-08-20 18:24:37 +02:00 committed by Ciprian Pascu
parent ca8f34cebd
commit d26ca6debe
14 changed files with 299 additions and 60 deletions

View File

@ -71,9 +71,9 @@ public class FreeboxOsBindingConstants {
public static final Set<ThingTypeUID> THINGS_TYPES_UIDS = Set.of(THING_TYPE_FXS, THING_TYPE_DECT, THING_TYPE_CALL, public static final Set<ThingTypeUID> THINGS_TYPES_UIDS = Set.of(THING_TYPE_FXS, THING_TYPE_DECT, THING_TYPE_CALL,
THING_TYPE_HOST, THING_TYPE_VM, THING_TYPE_PLAYER, THING_TYPE_ACTIVE_PLAYER, THING_TYPE_DELTA, THING_TYPE_HOST, THING_TYPE_VM, THING_TYPE_PLAYER, THING_TYPE_ACTIVE_PLAYER, THING_TYPE_DELTA,
THING_TYPE_REVOLUTION, THING_TYPE_REPEATER, THING_TYPE_WIFI_HOST, THING_TYPE_FREEPLUG); THING_TYPE_REVOLUTION, THING_TYPE_REPEATER, THING_TYPE_WIFI_HOST, THING_TYPE_FREEPLUG);
public static final Set<ThingTypeUID> HOME_TYPES_UIDS = Set.of(Category.BASIC_SHUTTER.getThingTypeUID(), public static final Set<ThingTypeUID> HOME_TYPES_UIDS = Set.of(Category.BASIC_SHUTTER.thingTypeUID,
Category.SHUTTER.getThingTypeUID(), Category.KFB.getThingTypeUID(), Category.CAMERA.getThingTypeUID(), Category.SHUTTER.thingTypeUID, Category.KFB.thingTypeUID, Category.CAMERA.thingTypeUID,
Category.ALARM.getThingTypeUID()); Category.ALARM.thingTypeUID, Category.PIR.thingTypeUID);
protected static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream protected static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.of(BRIDGE_TYPE_UIDS, THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet()); .of(BRIDGE_TYPE_UIDS, THINGS_TYPES_UIDS, HOME_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet());
@ -175,20 +175,30 @@ public class FreeboxOsBindingConstants {
public static final String XDSL_UPTIME = "uptime"; public static final String XDSL_UPTIME = "uptime";
// Home channels // Home channels
public static final String TIMESTAMP_POSTFIX = "-timestamp";
public static final String KEYFOB_ENABLE = "enable"; public static final String KEYFOB_ENABLE = "enable";
public static final String KEYFOB_PUSHED = "pushed";
public static final String KEYFOB_PUSHED_UPDATE = KEYFOB_PUSHED + TIMESTAMP_POSTFIX;
public static final String NODE_BATTERY = "battery"; public static final String NODE_BATTERY = "battery";
public static final String SHUTTER_POSITION = "position-set"; public static final String SHUTTER_POSITION = "position-set";
public static final String SHUTTER_STOP = "stop"; public static final String SHUTTER_STOP = "stop";
public static final String BASIC_SHUTTER_STATE = "state"; public static final String BASIC_SHUTTER_STATE = "state";
public static final String BASIC_SHUTTER_UP = "up"; public static final String BASIC_SHUTTER_UP = "up";
public static final String BASIC_SHUTTER_DOWN = "down"; public static final String BASIC_SHUTTER_DOWN = "down";
// public static final String BASIC_SHUTTER_CMD = "basic-shutter";
public static final String ALARM_PIN = "pin"; public static final String ALARM_PIN = "pin";
public static final String ALARM_SOUND = "sound"; public static final String ALARM_SOUND = "sound";
public static final String ALARM_VOLUME = "volume"; public static final String ALARM_VOLUME = "volume";
public static final String ALARM_TIMEOUT1 = "timeout1"; public static final String ALARM_TIMEOUT1 = "timeout1";
public static final String ALARM_TIMEOUT2 = "timeout2"; public static final String ALARM_TIMEOUT2 = "timeout2";
public static final String ALARM_TIMEOUT3 = "timeout3"; public static final String ALARM_TIMEOUT3 = "timeout3";
public static final String ALARM_STATE = "state";
public static final String PIR_TAMPER = "tamper";
public static final String PIR_TRIGGER = "trigger";
public static final String PIR_TAMPER_UPDATE = PIR_TAMPER + TIMESTAMP_POSTFIX;
public static final String PIR_TRIGGER_UPDATE = PIR_TRIGGER + TIMESTAMP_POSTFIX;
public static final Set<Command> TRUE_COMMANDS = Set.of(OnOffType.ON, UpDownType.UP, OpenClosedType.OPEN); public static final Set<Command> TRUE_COMMANDS = Set.of(OnOffType.ON, UpDownType.UP, OpenClosedType.OPEN);
public static final Set<Class<?>> ON_OFF_CLASSES = Set.of(OnOffType.class, UpDownType.class, OpenClosedType.class); public static final Set<Class<?>> ON_OFF_CLASSES = Set.of(OnOffType.class, UpDownType.class, OpenClosedType.class);

View File

@ -20,7 +20,6 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.freeboxos.internal.api.ApiHandler; import org.openhab.binding.freeboxos.internal.api.ApiHandler;
import org.openhab.binding.freeboxos.internal.api.rest.FreeboxOsSession; import org.openhab.binding.freeboxos.internal.api.rest.FreeboxOsSession;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Category; import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Category;
@ -35,6 +34,7 @@ import org.openhab.binding.freeboxos.internal.handler.FreeplugHandler;
import org.openhab.binding.freeboxos.internal.handler.FxsHandler; import org.openhab.binding.freeboxos.internal.handler.FxsHandler;
import org.openhab.binding.freeboxos.internal.handler.HostHandler; import org.openhab.binding.freeboxos.internal.handler.HostHandler;
import org.openhab.binding.freeboxos.internal.handler.KeyfobHandler; import org.openhab.binding.freeboxos.internal.handler.KeyfobHandler;
import org.openhab.binding.freeboxos.internal.handler.PirHandler;
import org.openhab.binding.freeboxos.internal.handler.PlayerHandler; import org.openhab.binding.freeboxos.internal.handler.PlayerHandler;
import org.openhab.binding.freeboxos.internal.handler.RepeaterHandler; import org.openhab.binding.freeboxos.internal.handler.RepeaterHandler;
import org.openhab.binding.freeboxos.internal.handler.RevolutionHandler; import org.openhab.binding.freeboxos.internal.handler.RevolutionHandler;
@ -56,6 +56,7 @@ import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.ComponentContext; import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -76,7 +77,6 @@ public class FreeboxOsHandlerFactory extends BaseThingHandlerFactory {
private final NetworkAddressService networkAddressService; private final NetworkAddressService networkAddressService;
private final AudioHTTPServer audioHTTPServer; private final AudioHTTPServer audioHTTPServer;
private final HttpClient httpClient;
private final ApiHandler apiHandler; private final ApiHandler apiHandler;
private String callbackURL = ""; private String callbackURL = "";
@ -88,13 +88,19 @@ public class FreeboxOsHandlerFactory extends BaseThingHandlerFactory {
super.activate(componentContext); super.activate(componentContext);
this.audioHTTPServer = audioHTTPServer; this.audioHTTPServer = audioHTTPServer;
this.httpClient = httpClientFactory.getCommonHttpClient();
this.networkAddressService = networkAddressService; this.networkAddressService = networkAddressService;
this.apiHandler = new ApiHandler(httpClient, timeZoneProvider); this.apiHandler = new ApiHandler(httpClientFactory, timeZoneProvider);
configChanged(config); configChanged(config);
} }
@Override
@Deactivate
public void deactivate(ComponentContext componentContext) {
super.deactivate(componentContext);
apiHandler.dispose();
}
@Modified @Modified
public void configChanged(Map<String, Object> config) { public void configChanged(Map<String, Object> config) {
String timeout = (String) config.getOrDefault(TIMEOUT, "8"); String timeout = (String) config.getOrDefault(TIMEOUT, "8");
@ -149,16 +155,18 @@ public class FreeboxOsHandlerFactory extends BaseThingHandlerFactory {
return new ActivePlayerHandler(thing); return new ActivePlayerHandler(thing);
} else if (THING_TYPE_PLAYER.equals(thingTypeUID)) { } else if (THING_TYPE_PLAYER.equals(thingTypeUID)) {
return new PlayerHandler(thing); return new PlayerHandler(thing);
} else if (Category.BASIC_SHUTTER.getThingTypeUID().equals(thingTypeUID)) { } else if (Category.BASIC_SHUTTER.thingTypeUID.equals(thingTypeUID)) {
return new BasicShutterHandler(thing); return new BasicShutterHandler(thing);
} else if (Category.SHUTTER.getThingTypeUID().equals(thingTypeUID)) { } else if (Category.SHUTTER.thingTypeUID.equals(thingTypeUID)) {
return new ShutterHandler(thing); return new ShutterHandler(thing);
} else if (Category.ALARM.getThingTypeUID().equals(thingTypeUID)) { } else if (Category.ALARM.thingTypeUID.equals(thingTypeUID)) {
return new AlarmHandler(thing); return new AlarmHandler(thing);
} else if (Category.KFB.getThingTypeUID().equals(thingTypeUID)) { } else if (Category.KFB.thingTypeUID.equals(thingTypeUID)) {
return new KeyfobHandler(thing); return new KeyfobHandler(thing);
} else if (Category.CAMERA.getThingTypeUID().equals(thingTypeUID)) { } else if (Category.CAMERA.thingTypeUID.equals(thingTypeUID)) {
return new CameraHandler(thing); return new CameraHandler(thing);
} else if (Category.PIR.thingTypeUID.equals(thingTypeUID)) {
return new PirHandler(thing);
} }
return null; return null;

View File

@ -32,11 +32,13 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpStatus.Code; import org.eclipse.jetty.http.HttpStatus.Code;
import org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants;
import org.openhab.binding.freeboxos.internal.api.deserialization.ForegroundAppDeserializer; import org.openhab.binding.freeboxos.internal.api.deserialization.ForegroundAppDeserializer;
import org.openhab.binding.freeboxos.internal.api.deserialization.ListDeserializer; import org.openhab.binding.freeboxos.internal.api.deserialization.ListDeserializer;
import org.openhab.binding.freeboxos.internal.api.deserialization.StrictEnumTypeAdapterFactory; import org.openhab.binding.freeboxos.internal.api.deserialization.StrictEnumTypeAdapterFactory;
import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.ForegroundApp; import org.openhab.binding.freeboxos.internal.api.rest.PlayerManager.ForegroundApp;
import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -58,9 +60,10 @@ import inet.ipaddr.mac.MACAddress;
*/ */
@NonNullByDefault @NonNullByDefault
public class ApiHandler { public class ApiHandler {
public static final String AUTH_HEADER = "X-Fbx-App-Auth";
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private static final String CONTENT_TYPE = "application/json; charset=" + DEFAULT_CHARSET.name(); private static final String CONTENT_TYPE = "application/json; charset=" + DEFAULT_CHARSET.name();
private static final int RESPONSE_BUFFER_SIZE = 65536;
public static final String AUTH_HEADER = "X-Fbx-App-Auth";
private final Logger logger = LoggerFactory.getLogger(ApiHandler.class); private final Logger logger = LoggerFactory.getLogger(ApiHandler.class);
private final HttpClient httpClient; private final HttpClient httpClient;
@ -68,8 +71,7 @@ public class ApiHandler {
private long timeoutInMs = TimeUnit.SECONDS.toMillis(8); private long timeoutInMs = TimeUnit.SECONDS.toMillis(8);
public ApiHandler(HttpClient httpClient, TimeZoneProvider timeZoneProvider) { public ApiHandler(HttpClientFactory httpClientFactory, TimeZoneProvider timeZoneProvider) {
this.httpClient = httpClient;
this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) this.gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(ZonedDateTime.class, .registerTypeAdapter(ZonedDateTime.class,
(JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> { (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
@ -86,6 +88,21 @@ public class ApiHandler {
.registerTypeAdapter(ForegroundApp.class, new ForegroundAppDeserializer()) .registerTypeAdapter(ForegroundApp.class, new ForegroundAppDeserializer())
.registerTypeAdapter(List.class, new ListDeserializer()).serializeNulls() .registerTypeAdapter(List.class, new ListDeserializer()).serializeNulls()
.registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()).create(); .registerTypeAdapterFactory(new StrictEnumTypeAdapterFactory()).create();
httpClient = httpClientFactory.createHttpClient(FreeboxOsBindingConstants.BINDING_ID);
httpClient.setResponseBufferSize(RESPONSE_BUFFER_SIZE);
try {
httpClient.start();
} catch (Exception e) {
logger.warn("Unable to start httpClient: {}", e.getMessage());
}
}
public void dispose() {
try {
httpClient.stop();
} catch (Exception e) {
logger.warn("Unable to stop httpClient: {}", e.getMessage());
}
} }
public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String sessionToken, public synchronized <T> T executeUri(URI uri, HttpMethod method, Class<T> clazz, @Nullable String sessionToken,

View File

@ -14,8 +14,10 @@ package org.openhab.binding.freeboxos.internal.api.rest;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.BINDING_ID; import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.BINDING_ID;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -102,17 +104,25 @@ public class HomeManager extends RestManager {
} }
} }
private static record LogEntry(long timestamp, int value) { public static record LogEntry(Long timestamp, int value) {
} }
public static record Endpoint(int id, String name, String label, EpType epType, Visibility visibility, int refresh, public static record Endpoint(int id, String name, String label, EpType epType, Visibility visibility, int refresh,
ValueType valueType, EndpointUi ui, @Nullable String category, Object value, List<LogEntry> history) { ValueType valueType, EndpointUi ui, @Nullable String category, Object value,
@Nullable List<LogEntry> history) {
private static final Comparator<LogEntry> HISTORY_COMPARATOR = Comparator.comparing(LogEntry::timestamp);
private enum Visibility { private enum Visibility {
INTERNAL, INTERNAL,
NORMAL, NORMAL,
DASHBOARD, DASHBOARD,
UNKNOWN UNKNOWN
} }
public Optional<LogEntry> getLastChange() {
return history != null ? history.stream().max(HISTORY_COMPARATOR) : Optional.empty();
}
} }
private enum Status { private enum Status {
@ -129,16 +139,13 @@ public class HomeManager extends RestManager {
ALARM, ALARM,
KFB, KFB,
CAMERA, CAMERA,
PIR,
UNKNOWN; UNKNOWN;
private final ThingTypeUID thingTypeUID; public final ThingTypeUID thingTypeUID;
Category() { Category() {
thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase()); thingTypeUID = new ThingTypeUID(BINDING_ID, name().toLowerCase().replace('_', '-'));
}
public ThingTypeUID getThingTypeUID() {
return thingTypeUID;
} }
} }
@ -148,6 +155,17 @@ public class HomeManager extends RestManager {
public static record HomeNode(int id, @Nullable String name, @Nullable String label, Category category, public static record HomeNode(int id, @Nullable String name, @Nullable String label, Category category,
Status status, List<Endpoint> showEndpoints, Map<String, String> props, NodeType type) { Status status, List<Endpoint> showEndpoints, Map<String, String> props, NodeType type) {
private static final Comparator<Endpoint> ENPOINT_COMPARATOR = Comparator.comparing(Endpoint::refresh);
public Optional<Endpoint> getMinRefresh() {
return showEndpoints.stream().filter(ep -> EpType.SIGNAL.equals(ep.epType) && ep.refresh() != 0)
.min(ENPOINT_COMPARATOR);
}
public Optional<Endpoint> getEndpoint(int slotId) {
return showEndpoints.stream().filter(ep -> ep.id == slotId).findAny();
}
} }
public HomeManager(FreeboxOsSession session) throws FreeboxException { public HomeManager(FreeboxOsSession session) throws FreeboxException {

View File

@ -41,7 +41,7 @@ public class NodeConfigurationBuilder {
if (node.category() == Category.UNKNOWN) { if (node.category() == Category.UNKNOWN) {
return Optional.empty(); return Optional.empty();
} }
ThingUID thingUID = new ThingUID(node.category().getThingTypeUID(), bridgeUID, Integer.toString(node.id())); ThingUID thingUID = new ThingUID(node.category().thingTypeUID, bridgeUID, Integer.toString(node.id()));
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID); DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID);
discoveryResultBuilder.withProperty(ClientConfiguration.ID, node.id()).withLabel(node.label()) discoveryResultBuilder.withProperty(ClientConfiguration.ID, node.id()).withLabel(node.label())
.withRepresentationProperty(ClientConfiguration.ID).withBridge(bridgeUID); .withRepresentationProperty(ClientConfiguration.ID).withBridge(bridgeUID);

View File

@ -14,8 +14,10 @@ package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*; import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager; import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState; import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
@ -38,7 +40,7 @@ public class AlarmHandler extends HomeNodeHandler {
} }
@Override @Override
protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) { protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
String value = state.value(); String value = state.value();
if (value == null) { if (value == null) {
@ -47,7 +49,7 @@ public class AlarmHandler extends HomeNodeHandler {
return switch (channelId) { return switch (channelId) {
case NODE_BATTERY -> DecimalType.valueOf(value); case NODE_BATTERY -> DecimalType.valueOf(value);
case ALARM_PIN -> StringType.valueOf(value); case ALARM_STATE, ALARM_PIN -> StringType.valueOf(value);
case ALARM_SOUND, ALARM_VOLUME -> QuantityType.valueOf(value + " %"); case ALARM_SOUND, ALARM_VOLUME -> QuantityType.valueOf(value + " %");
case ALARM_TIMEOUT1, ALARM_TIMEOUT2, ALARM_TIMEOUT3 -> QuantityType.valueOf(value + " s"); case ALARM_TIMEOUT1, ALARM_TIMEOUT2, ALARM_TIMEOUT3 -> QuantityType.valueOf(value + " s");
default -> UnDefType.NULL; default -> UnDefType.NULL;

View File

@ -15,6 +15,7 @@ package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*; import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -50,7 +51,7 @@ public class BasicShutterHandler extends HomeNodeHandler {
} }
@Override @Override
protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) { protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
String value = state.value(); String value = state.value();
return value != null && channelId.equals(BASIC_SHUTTER_STATE) return value != null && channelId.equals(BASIC_SHUTTER_STATE)
? state.asBoolean() ? OpenClosedType.CLOSED : OpenClosedType.OPEN ? state.asBoolean() ? OpenClosedType.CLOSED : OpenClosedType.OPEN

View File

@ -12,10 +12,12 @@
*/ */
package org.openhab.binding.freeboxos.internal.handler; package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.TIMESTAMP_POSTFIX;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -53,48 +55,52 @@ public abstract class HomeNodeHandler extends ApiConsumerHandler {
HomeNode node = getManager(HomeManager.class).getHomeNode(getClientId()); HomeNode node = getManager(HomeManager.class).getHomeNode(getClientId());
// Gets the lowest refresh time or else, we'll keep configuration default // Gets the lowest refresh time or else, we'll keep configuration default
node.showEndpoints().stream().filter(ep -> ep.epType() == EpType.SIGNAL).filter(ep -> ep.refresh() != 0) node.getMinRefresh().map(Endpoint::refresh).ifPresent(rate -> {
.min(Comparator.comparing(Endpoint::refresh)).map(Endpoint::refresh).ifPresent(rate -> { Configuration thingConfig = editConfiguration();
Configuration thingConfig = editConfiguration(); thingConfig.put(ApiConsumerConfiguration.REFRESH_INTERVAL, Integer.toString(rate / 1000));
thingConfig.put(ApiConsumerConfiguration.REFRESH_INTERVAL, Integer.toString(rate / 1000)); updateConfiguration(thingConfig);
updateConfiguration(thingConfig); });
});
properties.putAll(node.props()); properties.putAll(node.props());
getThing().getChannels().forEach(channel -> { getThing().getChannels().forEach(channel -> {
Configuration conf = channel.getConfiguration(); Configuration conf = channel.getConfiguration();
node.type().endpoints().stream().filter(ep -> ep.name().equals(channel.getUID().getIdWithoutGroup())) String channelId = channel.getUID().getIdWithoutGroup();
node.type().endpoints().stream().filter(ep -> ep.name().equals(channelId))
.forEach(endPoint -> conf.put(endPoint.epType().asConfId(), endPoint.id())); .forEach(endPoint -> conf.put(endPoint.epType().asConfId(), endPoint.id()));
internalConfigureChannel(channel.getUID().getIdWithoutGroup(), conf, node.type().endpoints()); internalConfigureChannel(channelId, conf, node.type().endpoints());
}); });
} }
protected void internalConfigureChannel(String channelId, Configuration conf, List<Endpoint> endpoints) { protected void internalConfigureChannel(String channelId, Configuration conf, List<Endpoint> endpoints) {
if (channelId.endsWith(TIMESTAMP_POSTFIX)) {
String baseEndpoint = channelId.replace(TIMESTAMP_POSTFIX, "");
endpoints.stream().filter(ep -> ep.name().equals(baseEndpoint)).forEach(ep -> {
conf.put(ep.name(), ep.id());
conf.put("signal", ep.id());
});
}
} }
@Override @Override
protected void internalPoll() throws FreeboxException { protected void internalPoll() throws FreeboxException {
HomeManager homeManager = getManager(HomeManager.class); HomeManager homeManager = getManager(HomeManager.class);
getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID())).forEach(channel -> { HomeNode node = homeManager.getHomeNode(getClientId());
State result = UnDefType.UNDEF; List<Channel> linkedChannels = getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID()))
.toList();
for (Channel channel : linkedChannels) {
State result = null;
Integer slotId = getSlotId(channel.getConfiguration(), EpType.SIGNAL.asConfId()); Integer slotId = getSlotId(channel.getConfiguration(), EpType.SIGNAL.asConfId());
if (slotId instanceof Integer) { if (slotId instanceof Integer) {
try { EndpointState state = homeManager.getEndpointsState(getClientId(), slotId);
EndpointState state = homeManager.getEndpointsState(getClientId(), slotId); Optional<Endpoint> endPoint = node.getEndpoint(slotId);
if (state != null) { if (state != null) {
result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup(), state); result = getChannelState(channel.getUID().getIdWithoutGroup(), state, endPoint);
} else {
result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup());
}
} catch (FreeboxException e) {
logger.warn("Error updating channel: {}", e.getMessage());
} }
} else {
result = getChannelState(homeManager, channel.getUID().getIdWithoutGroup());
} }
updateState(channel.getUID(), result); updateState(channel.getUID(), result != null ? result : UnDefType.UNDEF);
}); }
} }
@Override @Override
@ -126,9 +132,5 @@ public abstract class HomeNodeHandler extends ApiConsumerHandler {
return false; return false;
} }
protected State getChannelState(HomeManager homeManager, String channelWG) { protected abstract State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint);
return UnDefType.UNDEF;
}
protected abstract State getChannelState(HomeManager homeManager, String channelId, EndpointState state);
} }

View File

@ -14,11 +14,19 @@ package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*; import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException; import org.openhab.binding.freeboxos.internal.api.FreeboxException;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager; import org.openhab.binding.freeboxos.internal.api.rest.HomeManager;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState; import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -40,7 +48,15 @@ public class KeyfobHandler extends HomeNodeHandler {
} }
@Override @Override
protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) { protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
if (channelId.startsWith(KEYFOB_PUSHED)) {
return Objects.requireNonNull(endPoint.map(ep -> ep
.getLastChange().map(
change -> (State) (KEYFOB_PUSHED.equals(channelId) ? new DecimalType(change.value())
: new DateTimeType(ZonedDateTime
.ofInstant(Instant.ofEpochSecond(change.timestamp()), ZoneOffset.UTC))))
.orElse(UnDefType.UNDEF)).orElse(UnDefType.UNDEF));
}
String value = state.value(); String value = state.value();
if (value != null) { if (value != null) {
switch (channelId) { switch (channelId) {

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2024 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.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.Endpoint;
import org.openhab.binding.freeboxos.internal.api.rest.HomeManager.EndpointState;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link PirHandler} is responsible for handling everything associated to
* any Freebox Home PIR motion detection thing type.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class PirHandler extends HomeNodeHandler {
public PirHandler(Thing thing) {
super(thing);
}
@Override
protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
if (PIR_TAMPER_UPDATE.equals(channelId) || PIR_TRIGGER_UPDATE.equals(channelId)) {
return Objects.requireNonNull(endPoint.map(ep -> ep.getLastChange()
.map(change -> (State) new DateTimeType(
ZonedDateTime.ofInstant(Instant.ofEpochSecond(change.timestamp()), ZoneOffset.UTC)))
.orElse(UnDefType.UNDEF)).orElse(UnDefType.UNDEF));
}
String value = state.value();
if (value == null) {
return UnDefType.NULL;
}
return switch (channelId) {
case NODE_BATTERY -> DecimalType.valueOf(value);
case PIR_TAMPER -> state.asBoolean() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
case PIR_TRIGGER -> OnOffType.from(value);
default -> UnDefType.NULL;
};
}
}

View File

@ -15,6 +15,7 @@ package org.openhab.binding.freeboxos.internal.handler;
import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*; import static org.openhab.binding.freeboxos.internal.FreeboxOsBindingConstants.*;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.freeboxos.internal.api.FreeboxException; import org.openhab.binding.freeboxos.internal.api.FreeboxException;
@ -49,7 +50,7 @@ public class ShutterHandler extends HomeNodeHandler {
} }
@Override @Override
protected State getChannelState(HomeManager homeManager, String channelId, EndpointState state) { protected State getChannelState(String channelId, EndpointState state, Optional<Endpoint> endPoint) {
String value = state.value(); String value = state.value();
return value != null && channelId.equals(SHUTTER_POSITION) ? QuantityType.valueOf(value + " %") return value != null && channelId.equals(SHUTTER_POSITION) ? QuantityType.valueOf(value + " %")
: UnDefType.NULL; : UnDefType.NULL;

View File

@ -46,6 +46,14 @@ thing-type.freeboxos.host.label = Network Device
thing-type.freeboxos.host.description = Provides network device reachability thing-type.freeboxos.host.description = Provides network device reachability
thing-type.freeboxos.kfb.label = Freebox Keyfob thing-type.freeboxos.kfb.label = Freebox Keyfob
thing-type.freeboxos.kfb.description = A keyfob configured in your Freebox Server thing-type.freeboxos.kfb.description = A keyfob configured in your Freebox Server
thing-type.freeboxos.kfb.channel.pushed-timestamp.label = Timestamp
thing-type.freeboxos.kfb.channel.pushed-timestamp.description = Timestamp of the last action on the keyfob
thing-type.freeboxos.pir.label = Freebox Home PIR
thing-type.freeboxos.pir.description = A motion Sensor
thing-type.freeboxos.pir.channel.tamper-timestamp.label = Tamper Timestamp
thing-type.freeboxos.pir.channel.tamper-timestamp.description = Timestamp of the last cover tampered state change
thing-type.freeboxos.pir.channel.trigger-timestamp.label = Trigger Timestamp
thing-type.freeboxos.pir.channel.trigger-timestamp.description = Timestamp of the last state change
thing-type.freeboxos.player.label = Freebox Player thing-type.freeboxos.player.label = Freebox Player
thing-type.freeboxos.player.description = The player is the device connected to your TV thing-type.freeboxos.player.description = The player is the device connected to your TV
thing-type.freeboxos.repeater.label = Wifi Repeater thing-type.freeboxos.repeater.label = Wifi Repeater
@ -209,6 +217,13 @@ channel-type.freeboxos.afp-file-status.description = Status of Mac OS File Shari
channel-type.freeboxos.airmedia-status.label = Air Media Enabled channel-type.freeboxos.airmedia-status.label = Air Media Enabled
channel-type.freeboxos.airmedia-status.description = Indicates whether Air Media is enabled channel-type.freeboxos.airmedia-status.description = Indicates whether Air Media is enabled
channel-type.freeboxos.alarm-pin.label = PIN Code channel-type.freeboxos.alarm-pin.label = PIN Code
channel-type.freeboxos.alarm-state.label = Alarm State
channel-type.freeboxos.alarm-state.description = Current state of the alarm system
channel-type.freeboxos.alarm-state.state.option.idle = Idle
channel-type.freeboxos.alarm-state.state.option.alarm1_arming = Arming (Absent Mode)
channel-type.freeboxos.alarm-state.state.option.alarm1_armed = Armed (Absent Mode)
channel-type.freeboxos.alarm-state.state.option.alarm2_arming = Arming (Night Mode)
channel-type.freeboxos.alarm-state.state.option.alarm2_armed = Armed (Night Mode)
channel-type.freeboxos.alarm-timeout.label = Alarm Duration channel-type.freeboxos.alarm-timeout.label = Alarm Duration
channel-type.freeboxos.alarm-volume.label = Alarm Volume channel-type.freeboxos.alarm-volume.label = Alarm Volume
channel-type.freeboxos.alternate-ring.label = Alternating Ring channel-type.freeboxos.alternate-ring.label = Alternating Ring
@ -282,6 +297,11 @@ channel-type.freeboxos.key-code.state.option.ok = OK
channel-type.freeboxos.key-code.state.option.home = Home channel-type.freeboxos.key-code.state.option.home = Home
channel-type.freeboxos.keyfob-enable.label = Keyfob Enabled channel-type.freeboxos.keyfob-enable.label = Keyfob Enabled
channel-type.freeboxos.keyfob-enable.description = Activates / deactivates the keyfob channel-type.freeboxos.keyfob-enable.description = Activates / deactivates the keyfob
channel-type.freeboxos.kfb-pushed.label = Key Code Pushed
channel-type.freeboxos.kfb-pushed.description = Last key pushed on the remote
channel-type.freeboxos.kfb-pushed.state.option.1 = Arm Absent Mode
channel-type.freeboxos.kfb-pushed.state.option.2 = Disarm
channel-type.freeboxos.kfb-pushed.state.option.3 = Arm Night Mode
channel-type.freeboxos.lcd-brightness.label = Screen Brightness channel-type.freeboxos.lcd-brightness.label = Screen Brightness
channel-type.freeboxos.lcd-brightness.description = Brightness level of the screen in percent channel-type.freeboxos.lcd-brightness.description = Brightness level of the screen in percent
channel-type.freeboxos.lcd-forced.label = Forced Orientation channel-type.freeboxos.lcd-forced.label = Forced Orientation
@ -323,6 +343,8 @@ channel-type.freeboxos.package.description = Name of the package currently activ
channel-type.freeboxos.phone-event.label = Phone Event channel-type.freeboxos.phone-event.label = Phone Event
channel-type.freeboxos.phone-event.description = Triggers when an event related to the phone has been detected channel-type.freeboxos.phone-event.description = Triggers when an event related to the phone has been detected
channel-type.freeboxos.phone-number.label = Phone Number channel-type.freeboxos.phone-number.label = Phone Number
channel-type.freeboxos.pir-tamper.label = Cover Tamper
channel-type.freeboxos.pir-trigger.label = Détection
channel-type.freeboxos.player-status.label = Player Status channel-type.freeboxos.player-status.label = Player Status
channel-type.freeboxos.player-status.description = Status of the Freebox TV player channel-type.freeboxos.player-status.description = Status of the Freebox TV player
channel-type.freeboxos.reachable.label = Reachable channel-type.freeboxos.reachable.label = Reachable

View File

@ -496,6 +496,46 @@
<state readOnly="true" pattern="%.2f dBm"/> <state readOnly="true" pattern="%.2f dBm"/>
</channel-type> </channel-type>
<channel-type id="pir-trigger">
<item-type>Switch</item-type>
<label>Détection</label>
<category>oh:freeboxos:mouvement</category>
</channel-type>
<channel-type id="pir-tamper">
<item-type>Contact</item-type>
<label>Cover Tamper</label>
<category>oh:freeboxos:warning</category>
</channel-type>
<channel-type id="kfb-pushed">
<item-type>Number</item-type>
<label>Key Code Pushed</label>
<description>Last key pushed on the remote</description>
<state readOnly="true" pattern="%d">
<options>
<option value="1">Arm Absent Mode</option>
<option value="2">Disarm</option>
<option value="3">Arm Night Mode</option>
</options>
</state>
</channel-type>
<channel-type id="alarm-state">
<item-type>String</item-type>
<label>Alarm State</label>
<description>Current state of the alarm system</description>
<state readOnly="true" pattern="%s">
<options>
<option value="idle">Idle</option>
<option value="alarm1_arming">Arming (Absent Mode)</option>
<option value="alarm1_armed">Armed (Absent Mode)</option>
<option value="alarm2_arming">Arming (Night Mode)</option>
<option value="alarm2_armed">Armed (Night Mode)</option>
</options>
</state>
</channel-type>
<channel-type id="xdsl-ready"> <channel-type id="xdsl-ready">
<item-type>Switch</item-type> <item-type>Switch</item-type>
<label>Ready</label> <label>Ready</label>

View File

@ -21,6 +21,33 @@
<config-description-ref uri="thing-type:freeboxos:home-node"/> <config-description-ref uri="thing-type:freeboxos:home-node"/>
</thing-type> </thing-type>
<thing-type id="pir">
<supported-bridge-type-refs>
<bridge-type-ref id="api"/>
</supported-bridge-type-refs>
<label>Freebox Home PIR</label>
<description>A motion Sensor</description>
<channels>
<channel id="trigger" typeId="pir-trigger"/>
<channel id="trigger-timestamp" typeId="timestamp">
<label>Trigger Timestamp</label>
<description>Timestamp of the last state change</description>
</channel>
<channel id="tamper" typeId="pir-tamper"/>
<channel id="tamper-timestamp" typeId="timestamp">
<label>Tamper Timestamp</label>
<description>Timestamp of the last cover tampered state change</description>
</channel>
<channel id="battery" typeId="system.battery-level"/>
</channels>
<representation-property>id</representation-property>
<config-description-ref uri="thing-type:freeboxos:home-node"/>
</thing-type>
<thing-type id="alarm"> <thing-type id="alarm">
<supported-bridge-type-refs> <supported-bridge-type-refs>
<bridge-type-ref id="api"/> <bridge-type-ref id="api"/>
@ -30,6 +57,7 @@
<description>The Alarm system configured in your Freebox Server</description> <description>The Alarm system configured in your Freebox Server</description>
<channels> <channels>
<channel id="state" typeId="alarm-state"/>
<channel id="pin" typeId="alarm-pin"/> <channel id="pin" typeId="alarm-pin"/>
<channel id="sound" typeId="alarm-volume"> <channel id="sound" typeId="alarm-volume">
<label>Bips Volume</label> <label>Bips Volume</label>
@ -64,6 +92,11 @@
<description>A keyfob configured in your Freebox Server</description> <description>A keyfob configured in your Freebox Server</description>
<channels> <channels>
<channel id="pushed" typeId="kfb-pushed"/>
<channel id="pushed-timestamp" typeId="timestamp">
<label>Timestamp</label>
<description>Timestamp of the last action on the keyfob</description>
</channel>
<channel id="enable" typeId="keyfob-enable"/> <channel id="enable" typeId="keyfob-enable"/>
<channel id="battery" typeId="system.battery-level"/> <channel id="battery" typeId="system.battery-level"/>
</channels> </channels>