[remoteopenhab] Listen to the new ChannelDescriptionChangedEvent events to update dynamic state/command options (#10885)

Also handle dynamic command options

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo 2021-06-22 21:19:45 +02:00 committed by GitHub
parent 58c7928b23
commit 34e26f806d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 275 additions and 16 deletions

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2021 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.remoteopenhab.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of command options
*
* @author Laurent Garnier - Initial contribution
*/
@Component(service = { DynamicCommandDescriptionProvider.class, RemoteopenhabCommandDescriptionOptionProvider.class })
@NonNullByDefault
public class RemoteopenhabCommandDescriptionOptionProvider extends BaseDynamicCommandDescriptionProvider {
@Activate
public RemoteopenhabCommandDescriptionOptionProvider(final @Reference EventPublisher eventPublisher, //
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.eventPublisher = eventPublisher;
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@ -75,6 +75,7 @@ public class RemoteopenhabHandlerFactory extends BaseThingHandlerFactory {
private final SseEventSourceFactory eventSourceFactory;
private final RemoteopenhabChannelTypeProvider channelTypeProvider;
private final RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider;
private final RemoteopenhabCommandDescriptionOptionProvider commandDescriptionProvider;
private final Gson jsonParser;
private HttpClient httpClientTrustingCert;
@ -83,13 +84,15 @@ public class RemoteopenhabHandlerFactory extends BaseThingHandlerFactory {
public RemoteopenhabHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
final @Reference ClientBuilder clientBuilder, final @Reference SseEventSourceFactory eventSourceFactory,
final @Reference RemoteopenhabChannelTypeProvider channelTypeProvider,
final @Reference RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider) {
final @Reference RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider,
final @Reference RemoteopenhabCommandDescriptionOptionProvider commandDescriptionProvider) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.httpClientTrustingCert = httpClientFactory.createHttpClient(RemoteopenhabBindingConstants.BINDING_ID);
this.clientBuilder = clientBuilder;
this.eventSourceFactory = eventSourceFactory;
this.channelTypeProvider = channelTypeProvider;
this.stateDescriptionProvider = stateDescriptionProvider;
this.commandDescriptionProvider = commandDescriptionProvider;
this.jsonParser = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create();
try {
@ -196,7 +199,8 @@ public class RemoteopenhabHandlerFactory extends BaseThingHandlerFactory {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(RemoteopenhabBindingConstants.BRIDGE_TYPE_SERVER)) {
return new RemoteopenhabBridgeHandler((Bridge) thing, httpClient, httpClientTrustingCert, clientBuilder,
eventSourceFactory, channelTypeProvider, stateDescriptionProvider, jsonParser);
eventSourceFactory, channelTypeProvider, stateDescriptionProvider, commandDescriptionProvider,
jsonParser);
} else if (RemoteopenhabBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new RemoteopenhabThingHandler(thing);
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2021 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.remoteopenhab.internal.data;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Payload from ChannelDescriptionChangedEvent events received through the SSE connection.
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public class RemoteopenhabChannelDescriptionChangedEvent {
public String field = "";
public String channelUID = "";
public Set<String> linkedItemNames = Set.of();
public String value = "";
public @Nullable String oldValue = "";
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2021 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.remoteopenhab.internal.data;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Part of {@link RemoteopenhabItem} containing the command description
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public class RemoteopenhabCommandDescription {
public @Nullable List<RemoteopenhabCommandOption> commandOptions;
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2021 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.remoteopenhab.internal.data;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Part of {@link RemoteopenhabCommandDescription} containing one command option
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public class RemoteopenhabCommandOption {
public String command = "";
public String label = "";
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2021 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.remoteopenhab.internal.data;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Match content of field value from {@link RemoteopenhabChannelDescriptionChangedEvent) event payload when event is for
* COMMAND_OPTIONS
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public class RemoteopenhabCommandOptions {
public List<RemoteopenhabCommandOption> options = List.of();
}

View File

@ -29,4 +29,5 @@ public class RemoteopenhabItem {
public String state = "";
public String groupType = "";
public @Nullable RemoteopenhabStateDescription stateDescription;
public @Nullable RemoteopenhabCommandDescription commandDescription;
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2021 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.remoteopenhab.internal.data;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Match content of field value from {@link RemoteopenhabChannelDescriptionChangedEvent) event payload when event is for
* STATE_OPTIONS
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public class RemoteopenhabStateOptions {
public List<RemoteopenhabStateOption> options = List.of();
}

View File

@ -32,8 +32,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.remoteopenhab.internal.RemoteopenhabChannelTypeProvider;
import org.openhab.binding.remoteopenhab.internal.RemoteopenhabCommandDescriptionOptionProvider;
import org.openhab.binding.remoteopenhab.internal.RemoteopenhabStateDescriptionOptionProvider;
import org.openhab.binding.remoteopenhab.internal.config.RemoteopenhabServerConfiguration;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabCommandDescription;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabCommandOption;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabItem;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStateDescription;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStateOption;
@ -69,6 +72,7 @@ import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.CommandOption;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.StateDescriptionFragmentBuilder;
@ -101,6 +105,7 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler
private final HttpClient httpClientTrustingCert;
private final RemoteopenhabChannelTypeProvider channelTypeProvider;
private final RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider;
private final RemoteopenhabCommandDescriptionOptionProvider commandDescriptionProvider;
private final Object updateThingLock = new Object();
@ -114,11 +119,13 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler
public RemoteopenhabBridgeHandler(Bridge bridge, HttpClient httpClient, HttpClient httpClientTrustingCert,
ClientBuilder clientBuilder, SseEventSourceFactory eventSourceFactory,
RemoteopenhabChannelTypeProvider channelTypeProvider,
RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider, final Gson jsonParser) {
RemoteopenhabStateDescriptionOptionProvider stateDescriptionProvider,
RemoteopenhabCommandDescriptionOptionProvider commandDescriptionProvider, final Gson jsonParser) {
super(bridge);
this.httpClientTrustingCert = httpClientTrustingCert;
this.channelTypeProvider = channelTypeProvider;
this.stateDescriptionProvider = stateDescriptionProvider;
this.commandDescriptionProvider = commandDescriptionProvider;
this.restClient = new RemoteopenhabRestClient(httpClient, clientBuilder, eventSourceFactory, jsonParser);
}
@ -314,18 +321,31 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler
}
}
private void setStateOptions(List<RemoteopenhabItem> items) {
private void setDynamicOptions(List<RemoteopenhabItem> items) {
for (RemoteopenhabItem item : items) {
Channel channel = getThing().getChannel(item.name);
RemoteopenhabStateDescription descr = item.stateDescription;
List<RemoteopenhabStateOption> options = descr == null ? null : descr.options;
if (channel != null && options != null && !options.isEmpty()) {
List<StateOption> stateOptions = new ArrayList<>();
for (RemoteopenhabStateOption option : options) {
stateOptions.add(new StateOption(option.value, option.label));
if (channel == null) {
continue;
}
RemoteopenhabStateDescription stateDescr = item.stateDescription;
List<RemoteopenhabStateOption> stateOptions = stateDescr == null ? null : stateDescr.options;
if (stateOptions != null && !stateOptions.isEmpty()) {
List<StateOption> options = new ArrayList<>();
for (RemoteopenhabStateOption option : stateOptions) {
options.add(new StateOption(option.value, option.label));
}
stateDescriptionProvider.setStateOptions(channel.getUID(), stateOptions);
logger.trace("{} options set for the channel {}", options.size(), channel.getUID());
stateDescriptionProvider.setStateOptions(channel.getUID(), options);
logger.trace("{} state options set for the channel {}", options.size(), channel.getUID());
}
RemoteopenhabCommandDescription commandDescr = item.commandDescription;
List<RemoteopenhabCommandOption> commandOptions = commandDescr == null ? null : commandDescr.commandOptions;
if (commandOptions != null && !commandOptions.isEmpty()) {
List<CommandOption> options = new ArrayList<>();
for (RemoteopenhabCommandOption option : commandOptions) {
options.add(new CommandOption(option.command, option.label));
}
commandDescriptionProvider.setCommandOptions(channel.getUID(), options);
logger.trace("{} command options set for the channel {}", options.size(), channel.getUID());
}
}
}
@ -341,7 +361,7 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler
List<RemoteopenhabItem> items = restClient.getRemoteItems("name,type,groupType,state,stateDescription");
if (createChannels(items, true)) {
setStateOptions(items);
setDynamicOptions(items);
for (RemoteopenhabItem item : items) {
updateChannelState(item.name, null, item.state, false);
}
@ -467,6 +487,11 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler
}
}
@Override
public void onItemOptionsUpdatedd(RemoteopenhabItem item) {
setDynamicOptions(List.of(item));
}
private void updateChannelState(String itemName, @Nullable String stateType, String state,
boolean onlyIfStateChanged) {
Channel channel = getThing().getChannel(itemName);

View File

@ -43,4 +43,9 @@ public interface RemoteopenhabItemsDataListener {
* A new ItemUpdatedEvent was published.
*/
void onItemUpdated(RemoteopenhabItem newItem, RemoteopenhabItem oldItem);
/**
* A new ChannelDescriptionChangedEvent with updated state options or updated command options was published.
*/
void onItemOptionsUpdatedd(RemoteopenhabItem item);
}

View File

@ -46,11 +46,16 @@ import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabChannelDescriptionChangedEvent;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabChannelTriggerEvent;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabCommandDescription;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabCommandOptions;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabEvent;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabEventPayload;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabItem;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabRestApi;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStateDescription;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStateOptions;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabStatusInfo;
import org.openhab.binding.remoteopenhab.internal.data.RemoteopenhabThing;
import org.openhab.binding.remoteopenhab.internal.exceptions.RemoteopenhabException;
@ -321,8 +326,9 @@ public class RemoteopenhabRestClient {
String url;
try {
url = String.format("%s?topics=%s/items/*/*,%s/things/*/*,%s/channels/*/triggered", getRestApiUrl("events"),
getTopicNamespace(), getTopicNamespace(), getTopicNamespace());
url = String.format(
"%s?topics=%s/items/*/*,%s/things/*/*,%s/channels/*/triggered,openhab/channels/*/descriptionchanged",
getRestApiUrl("events"), getTopicNamespace(), getTopicNamespace(), getTopicNamespace());
} catch (RemoteopenhabException e) {
logger.debug("{}", e.getMessage());
return;
@ -383,7 +389,7 @@ public class RemoteopenhabRestClient {
private void onEvent(InboundSseEvent inboundEvent) {
String name = inboundEvent.getName();
String data = inboundEvent.readData();
logger.trace("Received event name {} date {}", name, data);
logger.trace("Received event name {} data {}", name, data);
lastEventTimestamp = System.currentTimeMillis();
if (!connected) {
@ -468,6 +474,35 @@ public class RemoteopenhabRestClient {
thingsListeners
.forEach(listener -> listener.onChannelTriggered(triggerEvent.channel, triggerEvent.event));
break;
case "ChannelDescriptionChangedEvent":
RemoteopenhabStateDescription stateDescription = new RemoteopenhabStateDescription();
RemoteopenhabCommandDescription commandDescription = new RemoteopenhabCommandDescription();
RemoteopenhabChannelDescriptionChangedEvent descriptionChanged = Objects.requireNonNull(
jsonParser.fromJson(event.payload, RemoteopenhabChannelDescriptionChangedEvent.class));
switch (descriptionChanged.field) {
case "STATE_OPTIONS":
RemoteopenhabStateOptions stateOptions = Objects.requireNonNull(
jsonParser.fromJson(descriptionChanged.value, RemoteopenhabStateOptions.class));
stateDescription.options = stateOptions.options;
break;
case "COMMAND_OPTIONS":
RemoteopenhabCommandOptions commandOptions = Objects.requireNonNull(
jsonParser.fromJson(descriptionChanged.value, RemoteopenhabCommandOptions.class));
commandDescription.commandOptions = commandOptions.options;
break;
default:
break;
}
if (stateDescription.options != null || commandDescription.commandOptions != null) {
descriptionChanged.linkedItemNames.forEach(linkedItemName -> {
RemoteopenhabItem item1 = new RemoteopenhabItem();
item1.name = linkedItemName;
item1.stateDescription = stateDescription;
item1.commandDescription = commandDescription;
itemsListeners.forEach(listener -> listener.onItemOptionsUpdatedd(item1));
});
}
break;
case "ItemStatePredictedEvent":
case "ItemCommandEvent":
case "ThingStatusInfoEvent":