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

[mqtt] Have a working Group Id #5156

Merged
merged 5 commits into from
Mar 23, 2019
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
import org.junit.Test;
import org.mockito.Mock;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.AbstractComponent;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.ChannelConfigurationTypeAdapterFactory;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.ComponentSwitch;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents.ComponentDiscovered;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.ChannelConfigurationTypeAdapterFactory;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;
import org.openhab.binding.mqtt.generic.internal.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.internal.generic.MqttChannelTypeProvider;
Expand Down Expand Up @@ -175,7 +175,7 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo
// and add the types to the channelTypeProvider, like in the real Thing handler.
final CountDownLatch latch = new CountDownLatch(1);
ComponentDiscovered cd = (haID, c) -> {
haComponents.put(haID.getChannelGroupID(), c);
haComponents.put(c.uid().getId(), c);
c.addChannelTypes(channelTypeProvider);
channelTypeProvider.setChannelGroupType(c.groupTypeUID(), c.type());
latch.countDown();
Expand All @@ -192,17 +192,19 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo
assertTrue(latch.await(300, TimeUnit.MILLISECONDS));
future.get(100, TimeUnit.MILLISECONDS);

// No failure expected and one discoverd result
// No failure expected and one discovered result
assertNull(failure);
assertThat(haComponents.size(), is(1));

// For the switch component we should have one channel group type and one channel type
verify(channelTypeProvider, times(1)).setChannelGroupType(any(), any());
verify(channelTypeProvider, times(1)).setChannelType(any(), any());

String channelGroupId = ThingChannelConstants.testHomeAssistantThing.getId() + "_switch";

// We expect a switch component with an OnOff channel with the initial value UNDEF:
State value = haComponents.get(haID.getChannelGroupID()).channelTypes().get(ComponentSwitch.switchChannelID)
.getState().getCache().getChannelState();
State value = haComponents.get(channelGroupId).channelTypes().get(ComponentSwitch.switchChannelID).getState()
.getCache().getChannelState();
assertThat(value, is(UnDefType.UNDEF));

haComponents.values().stream().map(e -> e.start(connection, scheduler, 100))
Expand All @@ -215,8 +217,8 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo
verify(channelStateUpdateListener, times(1)).updateChannelState(any(), any());

// Value should be ON now.
value = haComponents.get(haID.getChannelGroupID()).channelTypes().get(ComponentSwitch.switchChannelID)
.getState().getCache().getChannelState();
value = haComponents.get(channelGroupId).channelTypes().get(ComponentSwitch.switchChannelID).getState()
.getCache().getChannelState();
assertThat(value, is(OnOffType.ON));

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ public void discoveryTimeTest() throws InterruptedException, ExecutionException,
DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.testHomeAssistantThing,
scheduler, null, gson, transformationServiceProvider));

discover.startDiscovery(connection, 50, new HaID("homeassistant", "object", "node", "component"), discovered)
.get(100, TimeUnit.MILLISECONDS);
HandlerConfiguration config = new HandlerConfiguration("homeassistant", "object");

discover.startDiscovery(connection, 50, HaID.fromConfig(config), discovered).get(100, TimeUnit.MILLISECONDS);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,53 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.eclipse.smarthome.core.thing.type.ChannelTypeUID;
import org.eclipse.smarthome.config.core.Configuration;
import org.junit.Test;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;

public class HaIDTests {

@Test
public void testWithoutNode() {
HaID subject = new HaID("homeassistant/switch/name/config");

assertThat(subject.getThingID(), is("name"));
assertThat(subject.getChannelGroupTypeID(), is("name_switch"));
assertThat(subject.getChannelTypeID("channel"), is(new ChannelTypeUID("mqtt:name_switch_channel")));
assertThat(subject.getChannelGroupID(), is("switch_"));
assertThat(subject.objectID, is("name"));

assertThat(subject.component, is("switch"));
assertThat(subject.getTopic("suffix"), is("homeassistant/switch/name/suffix"));

Configuration config = new Configuration();
subject.toConfig(config);

HaID restore = HaID.fromConfig("homeassistant", config);

assertThat(restore, is(subject));

HandlerConfiguration haConfig = subject.toHandlerConfiguration();

restore = HaID.fromConfig(haConfig);
assertThat(restore, is(new HaID("homeassistant/+/name/config")));
}

@Test
public void testWithNode() {
HaID subject = new HaID("homeassistant/switch/node/name/config");

assertThat(subject.getThingID(), is("name"));
assertThat(subject.getChannelGroupTypeID(), is("name_switchnode"));
assertThat(subject.getChannelTypeID("channel"), is(new ChannelTypeUID("mqtt:name_switchnode_channel")));
assertThat(subject.getChannelGroupID(), is("switch_node"));
assertThat(subject.objectID, is("name"));

assertThat(subject.component, is("switch"));
assertThat(subject.getTopic("suffix"), is("homeassistant/switch/node/name/suffix"));

Configuration config = new Configuration();
subject.toConfig(config);

HaID restore = HaID.fromConfig("homeassistant", config);

assertThat(restore, is(subject));

HandlerConfiguration haConfig = subject.toHandlerConfiguration();

restore = HaID.fromConfig(haConfig);
assertThat(restore, is(new HaID("homeassistant/+/node/name/config")));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,22 @@
xsi:schemaLocation="http://eclipse.org/smarthome/schemas/config-description/v1.0.0 http://eclipse.org/smarthome/schemas/config-description-1.0.0.xsd">

<config-description uri="mqtt:ha_channel">
<parameter name="config" type="text">
<parameter name="component" type="text" readOnly="true" required="true">
<label>Component</label>
<description>Type of the channel group.</description>
<default></default>
</parameter>
<parameter name="nodeid" type="text" readOnly="true">
<label>Node ID</label>
<description>Optional node name of the component</description>
<default></default>
</parameter>
<parameter name="objectid" type="text" readOnly="true" required="true">
<label>Object ID</label>
<description>Object id of the component</description>
<default></default>
</parameter>
<parameter name="config" type="text" readOnly="true" required="true">
<label>Json Configuration</label>
<description>The json configuration string received by the component via MQTT.</description>
<default></default>
Expand Down
8 changes: 5 additions & 3 deletions addons/binding/org.openhab.binding.mqtt.generic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Find the next table to understand the topology mapping from Homie to the Framewo
| Property | Channel | homie/super-car/engine/temperature |

System trigger channels are supported using non-retained properties, with *enum* data type and with the following formats:

* Format: "PRESSED,RELEASED" -> system.rawbutton
* Format: "SHORT\_PRESSED,DOUBLE\_PRESSED,LONG\_PRESSED" -> system.button
* Format: "DIR1\_PRESSED,DIR1\_RELEASED,DIR2\_PRESSED,DIR2\_RELEASED" -> system.rawrocker
Expand Down Expand Up @@ -124,6 +125,7 @@ You can connect this channel to a Contact or Switch item.
* __on__: An optional string (like "BRIGHT") that is recognized as on state. (ON will always be recognized.)
* __off__: An optional string (like "DARK") that is recognized as off state. (OFF will always be recognized.)
* __onBrightness__: If you connect this channel to a Switch item and turn it on,

color and saturation are preserved from the last state, but
the brightness will be set to this configured initial brightness (default: 10%).

Expand Down Expand Up @@ -234,9 +236,9 @@ Here are a few examples:
## Troubleshooting

* If you get the error "No MQTT client": Please update your installation.
* If you use the Mosquitto broker: Please be aware that there is a relatively low setting
for retained messages. At some point messages will just not being delivered
anymore: Change the setting
* If you use the Mosquitto broker: Please be aware that there is a relatively low setting
for retained messages. At some point messages will just not being delivered anymore:
Change the setting

## Examples

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;

import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.thing.ChannelGroupUID;
Expand Down Expand Up @@ -65,14 +66,20 @@ public abstract class AbstractComponent<C extends BaseChannelConfiguration> {
*/
public AbstractComponent(CFactory.ComponentConfiguration componentConfiguration, Class<C> clazz) {
this.componentConfiguration = componentConfiguration;
this.haID = componentConfiguration.getHaID();
this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID,
haID.getChannelGroupTypeID());
this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), haID.getChannelGroupID());

this.channelConfigurationJson = componentConfiguration.getConfigJSON();
this.channelConfiguration = componentConfiguration.getConfig(clazz);
this.configHash = channelConfigurationJson.hashCode();

this.haID = componentConfiguration.getHaID();

String groupId = channelConfiguration.unique_id;
if (groupId == null || StringUtils.isBlank(groupId)) {
groupId = this.haID.getFallbackGroupId();
}

this.channelGroupTypeUID = new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, groupId);
this.channelGroupUID = new ChannelGroupUID(componentConfiguration.getThingUID(), groupId);
}

protected CChannel.Builder buildChannel(String channelID, Value valueState, String label) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ public CChannel build(boolean addToComponent) {
ChannelTypeUID channelTypeUID;

channelUID = new ChannelUID(component.channelGroupUID, channelID);
channelTypeUID = component.haID.getChannelTypeID(channelID);
channelTypeUID = new ChannelTypeUID(MqttBindingConstants.BINDING_ID,
channelUID.getGroupId() + "_" + channelID);
channelState = new ChannelState(
ChannelConfigBuilder.create().withRetain(retain).withStateTopic(state_topic)
.withCommandTopic(command_topic).build(),
Expand All @@ -197,6 +198,8 @@ public CChannel build(boolean addToComponent) {

Configuration configuration = new Configuration();
configuration.put("config", component.channelConfigurationJson);
component.haID.toConfig(configuration);

channel = ChannelBuilder.create(channelUID, channelState.getItemType()).withType(channelTypeUID)
.withKind(type.getKind()).withLabel(label).withConfiguration(configuration).build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.thing.Channel;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.openhab.binding.mqtt.generic.internal.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.internal.generic.TransformationServiceProvider;
Expand Down Expand Up @@ -44,11 +43,12 @@ public class CFactory {
* @param updateListener A channel state update listener
* @return A HA MQTT Component
*/
public static @Nullable AbstractComponent<?> createComponent(ThingUID thingUID, HaID haID, String configJSON,
@Nullable ChannelStateUpdateListener updateListener, Gson gson,
public static @Nullable AbstractComponent<?> createComponent(ThingUID thingUID, HaID haID,
String channelConfigurationJSON, @Nullable ChannelStateUpdateListener updateListener, Gson gson,
TransformationServiceProvider transformationServiceProvider) {
ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID, configJSON, gson)
.listener(updateListener).transformationProvider(transformationServiceProvider);
ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID,
channelConfigurationJSON, gson).listener(updateListener)
.transformationProvider(transformationServiceProvider);
try {
switch (haID.component) {
case "alarm_control_panel":
Expand Down Expand Up @@ -78,27 +78,6 @@ public class CFactory {
return null;
}

/**
* Create a HA MQTT component by a given channel configuration.
*
* @param basetopic The MQTT base topic, usually "homeassistant"
* @param channel A channel with the JSON configuration embedded as configuration (key: 'config')
* @param updateListener A channel state update listener
* @return A HA MQTT Component
*/
public static @Nullable AbstractComponent<?> createComponent(String basetopic, Channel channel,
@Nullable ChannelStateUpdateListener updateListener, Gson gson,
TransformationServiceProvider transformationServiceProvider) {
HaID haID = new HaID(basetopic, channel.getUID());
ThingUID thingUID = channel.getUID().getThingUID();
String configJSON = (String) channel.getConfiguration().get("config");
if (configJSON == null) {
logger.warn("Provided channel does not have a 'config' configuration key!");
return null;
}
return createComponent(thingUID, haID, configJSON, updateListener, gson, transformationServiceProvider);
}

protected static class ComponentConfiguration {
private ThingUID thingUID;
private HaID haID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public class DiscoverComponents implements MqttMessageSubscriber {
private WeakReference<@Nullable MqttBrokerConnection> connectionRef = new WeakReference<>(null);
protected @NonNullByDefault({}) ComponentDiscovered discoveredListener;
private int discoverTime;
private String topicWithNode = "";
private String topic = "";

/**
Expand Down Expand Up @@ -106,8 +105,10 @@ public void processMessage(String topic, byte[] payload) {
* </p>
*
* @param connection A MQTT broker connection
* @param discoverTime The time in milliseconds for the discovery to run. Can be 0 to disable the timeout.
* You need to call {@link #stopDiscovery(MqttBrokerConnection)} at some point in that case.
* @param discoverTime The time in milliseconds for the discovery to run. Can be 0 to disable the
* timeout.
* You need to call {@link #stopDiscovery(MqttBrokerConnection)} at some
* point in that case.
* @param topicDescription Contains the object-id (=device id) and potentially a node-id as well.
* @param componentsDiscoveredListener Listener for results
* @return A future that completes normally after the given time in milliseconds or exceptionally on any error.
Expand All @@ -116,15 +117,13 @@ public void processMessage(String topic, byte[] payload) {
public CompletableFuture<@Nullable Void> startDiscovery(MqttBrokerConnection connection, int discoverTime,
HaID topicDescription, ComponentDiscovered componentsDiscoveredListener) {

this.topicWithNode = topicDescription.baseTopic + "/+/+/" + topicDescription.objectID + "/config";
this.topic = topicDescription.baseTopic + "/+/" + topicDescription.objectID + "/config";
this.topic = topicDescription.getTopic("config");
this.discoverTime = discoverTime;
this.discoveredListener = componentsDiscoveredListener;
this.connectionRef = new WeakReference<>(connection);

// Subscribe to the wildcard topics and start receive MQTT retained topics
CompletableFuture.allOf(connection.subscribe(topic, this), connection.subscribe(topicWithNode, this))
.thenRun(this::subscribeSuccess).exceptionally(this::subscribeFail);
// Subscribe to the wildcard topic and start receive MQTT retained topics
connection.subscribe(topic, this).thenRun(this::subscribeSuccess).exceptionally(this::subscribeFail);

return discoverFinishedFuture;
}
Expand All @@ -135,7 +134,6 @@ private void subscribeSuccess() {
if (connection != null && discoverTime > 0) {
this.stopDiscoveryFuture = scheduler.schedule(() -> {
this.stopDiscoveryFuture = null;
connection.unsubscribe(topicWithNode, this);
connection.unsubscribe(topic, this);
this.discoveredListener = null;
discoverFinishedFuture.complete(null);
Expand All @@ -155,7 +153,6 @@ private void subscribeSuccess() {
this.discoveredListener = null;
final MqttBrokerConnection connection = connectionRef.get();
if (connection != null) {
connection.unsubscribe(topicWithNode, this);
connection.unsubscribe(topic, this);
connectionRef.clear();
}
Expand Down
Loading