]> git.basschouten.com Git - openhab-addons.git/blob
5753c93727a8a8bc0dce4a06bc52444a7c50f2b2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mqtt.generic;
14
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.assertTrue;
18 import static org.mockito.ArgumentMatchers.*;
19 import static org.mockito.ArgumentMatchers.any;
20 import static org.mockito.Mockito.*;
21
22 import java.math.BigDecimal;
23 import java.time.ZonedDateTime;
24 import java.time.format.DateTimeFormatter;
25 import java.util.Arrays;
26 import java.util.concurrent.CompletableFuture;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.ScheduledExecutorService;
29 import java.util.concurrent.ScheduledThreadPoolExecutor;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.TimeoutException;
32
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.junit.jupiter.api.AfterEach;
35 import org.junit.jupiter.api.BeforeEach;
36 import org.junit.jupiter.api.Test;
37 import org.junit.jupiter.api.extension.ExtendWith;
38 import org.mockito.Mock;
39 import org.mockito.Spy;
40 import org.mockito.junit.jupiter.MockitoExtension;
41 import org.mockito.junit.jupiter.MockitoSettings;
42 import org.mockito.quality.Strictness;
43 import org.openhab.binding.mqtt.generic.mapping.ColorMode;
44 import org.openhab.binding.mqtt.generic.values.ColorValue;
45 import org.openhab.binding.mqtt.generic.values.DateTimeValue;
46 import org.openhab.binding.mqtt.generic.values.ImageValue;
47 import org.openhab.binding.mqtt.generic.values.LocationValue;
48 import org.openhab.binding.mqtt.generic.values.NumberValue;
49 import org.openhab.binding.mqtt.generic.values.PercentageValue;
50 import org.openhab.binding.mqtt.generic.values.TextValue;
51 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
52 import org.openhab.core.library.types.HSBType;
53 import org.openhab.core.library.types.RawType;
54 import org.openhab.core.library.types.StringType;
55 import org.openhab.core.thing.ChannelUID;
56
57 /**
58  * Tests the {@link ChannelState} class.
59  *
60  * @author David Graeff - Initial contribution
61  */
62 @ExtendWith(MockitoExtension.class)
63 @MockitoSettings(strictness = Strictness.WARN)
64 public class ChannelStateTests {
65
66     private @Mock MqttBrokerConnection connection;
67     private @Mock ChannelStateUpdateListener channelStateUpdateListener;
68     private @Mock ChannelUID channelUID;
69     private @Spy TextValue textValue;
70
71     private ScheduledExecutorService scheduler;
72
73     private ChannelConfig config = ChannelConfigBuilder.create("state", "command").build();
74
75     @BeforeEach
76     public void setUp() {
77         CompletableFuture<Void> voidFutureComplete = new CompletableFuture<>();
78         voidFutureComplete.complete(null);
79         doReturn(voidFutureComplete).when(connection).unsubscribeAll();
80         doReturn(CompletableFuture.completedFuture(true)).when(connection).subscribe(any(), any());
81         doReturn(CompletableFuture.completedFuture(true)).when(connection).unsubscribe(any(), any());
82         doReturn(CompletableFuture.completedFuture(true)).when(connection).publish(any(), any(), anyInt(),
83                 anyBoolean());
84
85         scheduler = new ScheduledThreadPoolExecutor(1);
86     }
87
88     @AfterEach
89     public void tearDown() {
90         scheduler.shutdownNow();
91     }
92
93     @Test
94     public void noInteractionTimeoutTest() throws InterruptedException, ExecutionException, TimeoutException {
95         ChannelState c = spy(new ChannelState(config, channelUID, textValue, channelStateUpdateListener));
96         c.start(connection, scheduler, 50).get(100, TimeUnit.MILLISECONDS);
97         verify(connection).subscribe(eq("state"), eq(c));
98         c.stop().get();
99         verify(connection).unsubscribe(eq("state"), eq(c));
100     }
101
102     @Test
103     public void publishFormatTest() throws InterruptedException, ExecutionException, TimeoutException {
104         ChannelState c = spy(new ChannelState(config, channelUID, textValue, channelStateUpdateListener));
105
106         c.start(connection, scheduler, 0).get(50, TimeUnit.MILLISECONDS);
107         verify(connection).subscribe(eq("state"), eq(c));
108
109         c.publishValue(new StringType("UPDATE")).get();
110         verify(connection).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE".getBytes())), anyInt(),
111                 eq(false));
112
113         c.config.formatBeforePublish = "prefix%s";
114         c.publishValue(new StringType("UPDATE")).get();
115         verify(connection).publish(eq("command"), argThat(p -> Arrays.equals(p, "prefixUPDATE".getBytes())), anyInt(),
116                 eq(false));
117
118         c.config.formatBeforePublish = "%1$s-%1$s";
119         c.publishValue(new StringType("UPDATE")).get();
120         verify(connection).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE-UPDATE".getBytes())), anyInt(),
121                 eq(false));
122
123         c.config.formatBeforePublish = "%s";
124         c.config.retained = true;
125         c.publishValue(new StringType("UPDATE")).get();
126         verify(connection).publish(eq("command"), any(), anyInt(), eq(true));
127
128         c.stop().get();
129         verify(connection).unsubscribe(eq("state"), eq(c));
130     }
131
132     @Test
133     public void receiveWildcardTest() throws InterruptedException, ExecutionException, TimeoutException {
134         ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state/+/topic", "command").build(),
135                 channelUID, textValue, channelStateUpdateListener));
136
137         CompletableFuture<@Nullable Void> future = c.start(connection, scheduler, 100);
138         c.processMessage("state/bla/topic", "A TEST".getBytes());
139         future.get(300, TimeUnit.MILLISECONDS);
140
141         assertThat(textValue.getChannelState().toString(), is("A TEST"));
142         verify(channelStateUpdateListener).updateChannelState(eq(channelUID), any());
143     }
144
145     @Test
146     public void receiveStringTest() throws InterruptedException, ExecutionException, TimeoutException {
147         ChannelState c = spy(new ChannelState(config, channelUID, textValue, channelStateUpdateListener));
148
149         CompletableFuture<@Nullable Void> future = c.start(connection, scheduler, 100);
150         c.processMessage("state", "A TEST".getBytes());
151         future.get(300, TimeUnit.MILLISECONDS);
152
153         assertThat(textValue.getChannelState().toString(), is("A TEST"));
154         verify(channelStateUpdateListener).updateChannelState(eq(channelUID), any());
155     }
156
157     @Test
158     public void receiveDecimalTest() {
159         NumberValue value = new NumberValue(null, null, new BigDecimal(10), null);
160         ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
161         c.start(connection, mock(ScheduledExecutorService.class), 100);
162
163         c.processMessage("state", "15".getBytes());
164         assertThat(value.getChannelState().toString(), is("15"));
165
166         c.processMessage("state", "INCREASE".getBytes());
167         assertThat(value.getChannelState().toString(), is("25"));
168
169         c.processMessage("state", "DECREASE".getBytes());
170         assertThat(value.getChannelState().toString(), is("15"));
171
172         verify(channelStateUpdateListener, times(3)).updateChannelState(eq(channelUID), any());
173     }
174
175     @Test
176     public void receiveDecimalFractionalTest() {
177         NumberValue value = new NumberValue(null, null, new BigDecimal(10.5), null);
178         ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
179         c.start(connection, mock(ScheduledExecutorService.class), 100);
180
181         c.processMessage("state", "5.5".getBytes());
182         assertThat(value.getChannelState().toString(), is("5.5"));
183
184         c.processMessage("state", "INCREASE".getBytes());
185         assertThat(value.getChannelState().toString(), is("16.0"));
186     }
187
188     @Test
189     public void receivePercentageTest() {
190         PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10), null,
191                 null);
192         ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
193         c.start(connection, mock(ScheduledExecutorService.class), 100);
194
195         c.processMessage("state", "-100".getBytes()); // 0%
196         assertThat(value.getChannelState().toString(), is("0"));
197
198         c.processMessage("state", "100".getBytes()); // 100%
199         assertThat(value.getChannelState().toString(), is("100"));
200
201         c.processMessage("state", "0".getBytes()); // 50%
202         assertThat(value.getChannelState().toString(), is("50"));
203
204         c.processMessage("state", "INCREASE".getBytes());
205         assertThat(value.getChannelState().toString(), is("55"));
206         assertThat(value.getMQTTpublishValue(null), is("10"));
207         assertThat(value.getMQTTpublishValue("%03.0f"), is("010"));
208     }
209
210     @Test
211     public void receiveRGBColorTest() {
212         ColorValue value = new ColorValue(ColorMode.RGB, "FON", "FOFF", 10);
213         ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
214         c.start(connection, mock(ScheduledExecutorService.class), 100);
215
216         c.processMessage("state", "ON".getBytes()); // Normal on state
217         assertThat(value.getChannelState().toString(), is("0,0,10"));
218         assertThat(value.getMQTTpublishValue(null), is("25,25,25"));
219
220         c.processMessage("state", "FOFF".getBytes()); // Custom off state
221         assertThat(value.getChannelState().toString(), is("0,0,0"));
222         assertThat(value.getMQTTpublishValue(null), is("0,0,0"));
223
224         c.processMessage("state", "10".getBytes()); // Brightness only
225         assertThat(value.getChannelState().toString(), is("0,0,10"));
226         assertThat(value.getMQTTpublishValue(null), is("25,25,25"));
227
228         HSBType t = HSBType.fromRGB(12, 18, 231);
229
230         c.processMessage("state", "12,18,231".getBytes());
231         assertThat(value.getChannelState(), is(t)); // HSB
232         // rgb -> hsv -> rgb is quite lossy
233         assertThat(value.getMQTTpublishValue(null), is("13,20,225"));
234         assertThat(value.getMQTTpublishValue("%3$d,%2$d,%1$d"), is("225,20,13"));
235     }
236
237     @Test
238     public void receiveHSBColorTest() {
239         ColorValue value = new ColorValue(ColorMode.HSB, "FON", "FOFF", 10);
240         ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
241         c.start(connection, mock(ScheduledExecutorService.class), 100);
242
243         c.processMessage("state", "ON".getBytes()); // Normal on state
244         assertThat(value.getChannelState().toString(), is("0,0,10"));
245         assertThat(value.getMQTTpublishValue(null), is("0,0,10"));
246
247         c.processMessage("state", "FOFF".getBytes()); // Custom off state
248         assertThat(value.getChannelState().toString(), is("0,0,0"));
249         assertThat(value.getMQTTpublishValue(null), is("0,0,0"));
250
251         c.processMessage("state", "10".getBytes()); // Brightness only
252         assertThat(value.getChannelState().toString(), is("0,0,10"));
253         assertThat(value.getMQTTpublishValue(null), is("0,0,10"));
254
255         c.processMessage("state", "12,18,100".getBytes());
256         assertThat(value.getChannelState().toString(), is("12,18,100"));
257         assertThat(value.getMQTTpublishValue(null), is("12,18,100"));
258     }
259
260     @Test
261     public void receiveXYYColorTest() {
262         ColorValue value = new ColorValue(ColorMode.XYY, "FON", "FOFF", 10);
263         ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
264         c.start(connection, mock(ScheduledExecutorService.class), 100);
265
266         c.processMessage("state", "ON".getBytes()); // Normal on state
267         assertThat(value.getChannelState().toString(), is("0,0,10"));
268         assertThat(value.getMQTTpublishValue(null), is("0.312716,0.329002,10.00"));
269
270         c.processMessage("state", "FOFF".getBytes()); // Custom off state
271         assertThat(value.getChannelState().toString(), is("0,0,0"));
272         assertThat(value.getMQTTpublishValue(null), is("0.312716,0.329002,0.00"));
273
274         c.processMessage("state", "10".getBytes()); // Brightness only
275         assertThat(value.getChannelState().toString(), is("0,0,10"));
276         assertThat(value.getMQTTpublishValue(null), is("0.312716,0.329002,10.00"));
277
278         HSBType t = HSBType.fromXY(0.3f, 0.6f);
279
280         c.processMessage("state", "0.3,0.6,100".getBytes());
281         assertThat(value.getChannelState(), is(t)); // HSB
282         assertThat(value.getMQTTpublishValue(null), is("0.300000,0.600000,100.00"));
283         assertThat(value.getMQTTpublishValue("%3$.1f,%2$.4f,%1$.4f"), is("100.0,0.6000,0.3000"));
284     }
285
286     @Test
287     public void receiveLocationTest() {
288         LocationValue value = new LocationValue();
289         ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
290         c.start(connection, mock(ScheduledExecutorService.class), 100);
291
292         c.processMessage("state", "46.833974, 7.108433".getBytes());
293         assertThat(value.getChannelState().toString(), is("46.833974,7.108433"));
294         assertThat(value.getMQTTpublishValue(null), is("46.833974,7.108433"));
295     }
296
297     @Test
298     public void receiveDateTimeTest() {
299         DateTimeValue value = new DateTimeValue();
300         ChannelState subject = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
301         subject.start(connection, mock(ScheduledExecutorService.class), 100);
302
303         ZonedDateTime zd = ZonedDateTime.now();
304         String datetime = zd.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
305
306         subject.processMessage("state", datetime.getBytes());
307
308         String channelState = value.getChannelState().toString();
309         assertTrue(channelState.startsWith(datetime),
310                 "Expected '" + channelState + "' to start with '" + datetime + "'");
311         assertThat(value.getMQTTpublishValue(null), is(datetime));
312     }
313
314     @Test
315     public void receiveImageTest() {
316         ImageValue value = new ImageValue();
317         ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
318         c.start(connection, mock(ScheduledExecutorService.class), 100);
319
320         byte[] payload = new byte[] { (byte) 0xFF, (byte) 0xD8, 0x01, 0x02, (byte) 0xFF, (byte) 0xD9 };
321         c.processMessage("state", payload);
322         assertThat(value.getChannelState(), is(instanceOf(RawType.class)));
323         assertThat(((RawType) value.getChannelState()).getMimeType(), is("image/jpeg"));
324     }
325 }