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][WIP] Make Homeassistant discovery work for Tasmota #4927

Closed
wants to merge 1 commit into from
Closed
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 @@ -19,6 +19,8 @@ Import-Package: com.jayway.jsonpath,
org.openhab.io.mqttembeddedbroker,
org.eclipse.smarthome.test.java,
org.hamcrest;core=split,
org.hamcrest.collection,
org.hamcrest.core,
org.junit,
org.mockito,
org.mockito.invocation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@
import org.junit.Before;
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.ComponentSwitch;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents.ComponentDiscovered;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HomeAssistentGroup;
import org.openhab.binding.mqtt.generic.internal.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.internal.generic.MqttChannelTypeProvider;
import org.openhab.binding.mqtt.generic.internal.generic.MqttTypeProvider;
import org.openhab.binding.mqtt.generic.internal.generic.TransformationServiceProvider;
import org.openhab.binding.mqtt.generic.internal.handler.ThingChannelConstants;
import org.osgi.service.cm.ConfigurationException;
import org.slf4j.Logger;
Expand All @@ -75,6 +76,9 @@ public class HomeAssistantMQTTImplementationTests extends JavaOSGiTest {
@Mock
ChannelStateUpdateListener channelStateUpdateListener;

@Mock
TransformationServiceProvider transformationServiceProvider;

/**
* Create an observer that fails the test as soon as the broker client connection changes its connection state
* to something else then CONNECTED.
Expand Down Expand Up @@ -122,6 +126,8 @@ public void setUp() throws InterruptedException, ExecutionException, TimeoutExce
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).get(200, TimeUnit.MILLISECONDS);

failure = null;

doReturn(null).when(transformationServiceProvider).getTransformationService(any());
}

@After
Expand Down Expand Up @@ -153,13 +159,13 @@ public void retrieveAllTopics() throws InterruptedException, ExecutionException,

@Test
public void parseHATree() throws InterruptedException, ExecutionException, TimeoutException {
MqttChannelTypeProvider channelTypeProvider = mock(MqttChannelTypeProvider.class);
MqttTypeProvider channelTypeProvider = mock(MqttTypeProvider.class);

final Map<String, AbstractComponent> haComponents = new HashMap<String, AbstractComponent>();
final Map<String, HomeAssistentGroup> haComponents = new HashMap<String, HomeAssistentGroup>();

ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(4);
DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.testHomeAssistantThing,
scheduler, channelStateUpdateListener, new Gson()));
scheduler, channelStateUpdateListener, new Gson(), transformationServiceProvider));

// The DiscoverComponents object calls ComponentDiscovered callbacks.
// In the following implementation we add the found component to the `haComponents` map
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2019 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.mqtt.generic.internal.convention.homeassistant;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.junit.Assert.assertThat;

import org.junit.Test;

import com.google.gson.Gson;

public class AbstractConfigurationTests {

@Test
public void testTasmotaSwitch() {
String json = "{\n"//
+ " \"name\":\"Licht Dachterasse\",\n" //
+ " \"cmd_t\":\"~cmnd/POWER\",\n" //
+ " \"stat_t\":\"~tele/STATE\",\n" //
+ " \"val_tpl\":\"{{value_json.POWER}}\",\n" //
+ " \"pl_off\":\"OFF\",\n" //
+ " \"pl_on\":\"ON\",\n" //
+ " \"avty_t\":\"~tele/LWT\",\n" //
+ " \"pl_avail\":\"Online\",\n" //
+ " \"pl_not_avail\":\"Offline\",\n" //
+ " \"uniq_id\":\"86C9AC_RL_1\",\n" //
+ " \"device\":{\n" //
+ " \"identifiers\":[\"86C9AC\"],\n" //
+ " \"name\":\"Licht Dachterasse\",\n" //
+ " \"model\":\"Sonoff TH\",\n" //
+ " \"sw_version\":\"6.4.1(release-sensors)\",\n" //
+ " \"manufacturer\":\"Tasmota\"\n" //
+ " },\n" //
+ " \"~\":\"sonoff-2476/\"\n" //
+ "}";

AbstractConfiguration config = new Gson().fromJson(json, AbstractConfiguration.class);

assertThat(config.name, is("Licht Dachterasse"));
assertThat(config.device, is(notNullValue()));
assertThat(config.device.identifiers, contains("86C9AC"));
assertThat(config.device.name, is("Licht Dachterasse"));
}

@Test
public void testSampleFanConfig() {
String json = "{\n" //
+ " \"name\": \"Bedroom Fan\",\n" //
+ " \"state_topic\": \"bedroom_fan/on/state\",\n" //
+ " \"command_topic\": \"bedroom_fan/on/set\",\n" //
+ " \"oscillation_state_topic\": \"bedroom_fan/oscillation/state\",\n" //
+ " \"oscillation_command_topic\": \"bedroom_fan/oscillation/set\",\n" //
+ " \"speed_state_topic\": \"bedroom_fan/speed/state\",\n" //
+ " \"speed_command_topic\": \"bedroom_fan/speed/set\",\n" //
+ " \"qos\": 0,\n" //
+ " \"payload_on\": \"true\",\n" //
+ " \"payload_off\": \"false\",\n" //
+ " \"payload_oscillation_on\": \"true\",\n" //
+ " \"payload_oscillation_off\": \"false\",\n"//
+ " \"payload_low_speed\": \"low\",\n" //
+ " \"payload_medium_speed\": \"medium\",\n" //
+ " \"payload_high_speed\": \"high\",\n" //
+ " \"speeds\": [\n" //
+ " \"low\", \"medium\", \"high\"\n" //
+ " ]\n" //
+ "}";

ComponentFan.Config config = new Gson().fromJson(json, ComponentFan.Config.class);
assertThat(config.name, is("Bedroom Fan"));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents.ComponentDiscovered;
import org.openhab.binding.mqtt.generic.internal.generic.TransformationServiceProvider;
import org.openhab.binding.mqtt.generic.internal.handler.ThingChannelConstants;

import com.google.gson.Gson;
Expand All @@ -47,6 +46,9 @@ public class DiscoverComponentsTests extends JavaOSGiTest {
@Mock
ComponentDiscovered discovered;

@Mock
TransformationServiceProvider transformationServiceProvider;

@Before
public void setUp() {
initMocks(this);
Expand All @@ -58,15 +60,16 @@ public void setUp() {
doReturn(CompletableFuture.completedFuture(true)).when(connection).publish(any(), any());
doReturn(CompletableFuture.completedFuture(true)).when(connection).publish(any(), any(), anyInt(),
anyBoolean());
doReturn(null).when(transformationServiceProvider).getTransformationService(any());
}

@Test
public void discoveryTimeTest() throws InterruptedException, ExecutionException, TimeoutException {
// Create a scheduler
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);

DiscoverComponents discover = spy(
new DiscoverComponents(ThingChannelConstants.testHomeAssistantThing, scheduler, null, new Gson()));
DiscoverComponents discover = spy(new DiscoverComponents(ThingChannelConstants.testHomeAssistantThing,
scheduler, null, new Gson(), transformationServiceProvider));

discover.startDiscovery(connection, 50, new HaID("homeassistant", "object", "node", "component"), discovered)
.get(100, TimeUnit.MILLISECONDS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,52 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.core.thing.ChannelGroupUID;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.core.thing.type.ChannelTypeUID;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Test;
import org.openhab.binding.mqtt.generic.internal.convention.homeassistant.HaID;
import org.openhab.binding.mqtt.generic.internal.MqttBindingConstants;

public class HaIDTests {

@Test(expected = IllegalArgumentException.class)
public void testTooShort() {
new HaID("homeassistant/switch/config");
}

@Test(expected = IllegalArgumentException.class)
public void testTooLong() {
new HaID("homeassistant/switch/a/b/c/config");
}

@Test(expected = IllegalArgumentException.class)
public void testNoConfig() {
new HaID("homeassistant/switch/a/b/c");
}

@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.getChannelGroupID(), is("switch"));

final String thingID = subject.getThingID();
final ThingUID bridge = new ThingUID("mqtt:broker:brokerId");
final ThingUID thingUID = new ThingUID(MqttBindingConstants.HOMEASSISTANT_MQTT_THING, bridge, thingID);
final ChannelGroupUID channelGroupUID = new ChannelGroupUID(thingUID, subject.getChannelGroupID());
final ChannelUID channelUID = new ChannelUID(channelGroupUID, "channel");

final HaID derived = new HaID(subject.baseTopic, channelUID);

assertThat(derived, isID(subject));
}

@Test
Expand All @@ -39,6 +71,46 @@ public void testWithNode() {
assertThat(subject.getChannelGroupTypeID(), is("name_switchnode"));
assertThat(subject.getChannelTypeID("channel"), is(new ChannelTypeUID("mqtt:name_switchnode_channel")));
assertThat(subject.getChannelGroupID(), is("switch_node"));

final String thingID = subject.getThingID();
final ThingUID bridge = new ThingUID("mqtt:broker:brokerId");
final ThingUID thingUID = new ThingUID(MqttBindingConstants.HOMEASSISTANT_MQTT_THING, bridge, thingID);
final ChannelGroupUID channelGroupUID = new ChannelGroupUID(thingUID, subject.getChannelGroupID());
final ChannelUID channelUID = new ChannelUID(channelGroupUID, "channel");

final HaID derived = new HaID(subject.baseTopic, channelUID);

assertThat(derived, isID(subject));
}

private static Matcher<HaID> isID(HaID other) {
return new HaIDMatcher(other);
}

private static final class HaIDMatcher extends TypeSafeMatcher<HaID> {
private final HaID other;

public HaIDMatcher(HaID other) {
super();
this.other = other;
}

@Override
protected boolean matchesSafely(HaID item) {
return StringUtils.equals(other.baseTopic, item.baseTopic)
&& StringUtils.equals(other.component, item.component)
&& StringUtils.equals(other.nodeID, item.nodeID)
&& StringUtils.equals(other.objectID, item.objectID);
}

@Override
public void describeTo(Description description) {
description.appendText(other.toString());
}

@Override
protected void describeMismatchSafely(HaID item, Description mismatchDescription) {
mismatchDescription.appendText("was").appendValue(item.toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,15 @@
import org.openhab.binding.mqtt.generic.internal.MqttBindingConstants;
import org.openhab.binding.mqtt.generic.internal.convention.homie300.Device;
import org.openhab.binding.mqtt.generic.internal.convention.homie300.DeviceAttributes;
import org.openhab.binding.mqtt.generic.internal.convention.homie300.DeviceAttributes.ReadyState;
import org.openhab.binding.mqtt.generic.internal.convention.homie300.Node;
import org.openhab.binding.mqtt.generic.internal.convention.homie300.NodeAttributes;
import org.openhab.binding.mqtt.generic.internal.convention.homie300.Property;
import org.openhab.binding.mqtt.generic.internal.convention.homie300.PropertyAttributes;
import org.openhab.binding.mqtt.generic.internal.convention.homie300.DeviceAttributes.ReadyState;
import org.openhab.binding.mqtt.generic.internal.convention.homie300.PropertyAttributes.DataTypeEnum;
import org.openhab.binding.mqtt.generic.internal.generic.ChannelState;
import org.openhab.binding.mqtt.generic.internal.generic.ChannelStateHelper;
import org.openhab.binding.mqtt.generic.internal.generic.MqttChannelTypeProvider;
import org.openhab.binding.mqtt.generic.internal.handler.HomieThingHandler;
import org.openhab.binding.mqtt.generic.internal.generic.MqttTypeProvider;
import org.openhab.binding.mqtt.generic.internal.mapping.AbstractMqttAttributeClass;
import org.openhab.binding.mqtt.generic.internal.mapping.SubscribeFieldToMQTTtopic;
import org.openhab.binding.mqtt.generic.internal.tools.ChildMap;
Expand Down Expand Up @@ -94,7 +93,7 @@ public class HomieThingHandlerTests {

private HomieThingHandler thingHandler;

private final MqttChannelTypeProvider channelTypeProvider = new MqttChannelTypeProvider();
private final MqttTypeProvider channelTypeProvider = new MqttTypeProvider();

private final String deviceID = ThingChannelConstants.testHomieThing.getId();
private final String deviceTopic = "homie/" + deviceID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<label>HomeAssistant MQTT Component</label>
<description>You need a configured Broker first. This Thing represents a device, that follows the "HomeAssistant MQTT Component" specification.</description>
<config-description>
<parameter name="objectid" type="text" required="true">
<parameter name="objectid" type="text" required="true" multiple="true">
<label>Object ID</label>
<description>HomeAssistant Object ID</description>
</parameter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ClassPath: .
Import-Package:
com.google.gson,
com.google.gson.annotations,
com.google.gson.stream,
org.apache.commons.lang,
org.apache.commons.net.util,
org.eclipse.jdt.annotation;resolution:=optional,
Expand Down
Loading