]> git.basschouten.com Git - openhab-addons.git/blob
4bef106431a01ea265fb3589a6cc56a0151685b0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.ScheduledExecutorService;
28 import java.util.concurrent.ScheduledThreadPoolExecutor;
29 import java.util.concurrent.TimeUnit;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.junit.jupiter.api.AfterEach;
34 import org.junit.jupiter.api.BeforeEach;
35 import org.junit.jupiter.api.Test;
36 import org.junit.jupiter.api.extension.ExtendWith;
37 import org.mockito.Mock;
38 import org.mockito.Spy;
39 import org.mockito.junit.jupiter.MockitoExtension;
40 import org.mockito.junit.jupiter.MockitoSettings;
41 import org.mockito.quality.Strictness;
42 import org.openhab.binding.mqtt.generic.mapping.ColorMode;
43 import org.openhab.binding.mqtt.generic.values.ColorValue;
44 import org.openhab.binding.mqtt.generic.values.DateTimeValue;
45 import org.openhab.binding.mqtt.generic.values.ImageValue;
46 import org.openhab.binding.mqtt.generic.values.LocationValue;
47 import org.openhab.binding.mqtt.generic.values.NumberValue;
48 import org.openhab.binding.mqtt.generic.values.PercentageValue;
49 import org.openhab.binding.mqtt.generic.values.TextValue;
50 import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
51 import org.openhab.core.library.types.HSBType;
52 import org.openhab.core.library.types.RawType;
53 import org.openhab.core.library.types.StringType;
54 import org.openhab.core.library.unit.Units;
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.LENIENT)
64 @NonNullByDefault
65 public class ChannelStateTests {
66
67     private @Mock @NonNullByDefault({}) MqttBrokerConnection connectionMock;
68     private @Mock @NonNullByDefault({}) ChannelStateUpdateListener channelStateUpdateListenerMock;
69     private @Mock @NonNullByDefault({}) ChannelUID channelUIDMock;
70     private @Spy @NonNullByDefault({}) TextValue textValue;
71
72     private @NonNullByDefault({}) ScheduledExecutorService scheduler;
73
74     private ChannelConfig config = ChannelConfigBuilder.create("state", "command").build();
75
76     @BeforeEach
77     public void setUp() {
78         CompletableFuture<@Nullable Void> voidFutureComplete = new CompletableFuture<>();
79         voidFutureComplete.complete(null);
80         doReturn(voidFutureComplete).when(connectionMock).unsubscribeAll();
81         doReturn(CompletableFuture.completedFuture(true)).when(connectionMock).subscribe(any(), any());
82         doReturn(CompletableFuture.completedFuture(true)).when(connectionMock).unsubscribe(any(), any());
83         doReturn(CompletableFuture.completedFuture(true)).when(connectionMock).publish(any(), any(), anyInt(),
84                 anyBoolean());
85
86         scheduler = new ScheduledThreadPoolExecutor(1);
87     }
88
89     @AfterEach
90     public void tearDown() {
91         scheduler.shutdownNow();
92     }
93
94     @Test
95     public void noInteractionTimeoutTest() throws Exception {
96         ChannelState c = spy(new ChannelState(config, channelUIDMock, textValue, channelStateUpdateListenerMock));
97         c.start(connectionMock, scheduler, 50).get(100, TimeUnit.MILLISECONDS);
98         verify(connectionMock).subscribe(eq("state"), eq(c));
99         c.stop().get();
100         verify(connectionMock).unsubscribe(eq("state"), eq(c));
101     }
102
103     @Test
104     public void publishFormatTest() throws Exception {
105         ChannelState c = spy(new ChannelState(config, channelUIDMock, textValue, channelStateUpdateListenerMock));
106
107         c.start(connectionMock, scheduler, 0).get(50, TimeUnit.MILLISECONDS);
108         verify(connectionMock).subscribe(eq("state"), eq(c));
109
110         c.publishValue(new StringType("UPDATE")).get();
111         verify(connectionMock).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE".getBytes())), anyInt(),
112                 eq(false));
113
114         c.config.formatBeforePublish = "prefix%s";
115         c.publishValue(new StringType("UPDATE")).get();
116         verify(connectionMock).publish(eq("command"), argThat(p -> Arrays.equals(p, "prefixUPDATE".getBytes())),
117                 anyInt(), eq(false));
118
119         c.config.formatBeforePublish = "%1$s-%1$s";
120         c.publishValue(new StringType("UPDATE")).get();
121         verify(connectionMock).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE-UPDATE".getBytes())),
122                 anyInt(), eq(false));
123
124         c.config.formatBeforePublish = "%s";
125         c.config.retained = true;
126         c.publishValue(new StringType("UPDATE")).get();
127         verify(connectionMock).publish(eq("command"), any(), anyInt(), eq(true));
128
129         c.stop().get();
130         verify(connectionMock).unsubscribe(eq("state"), eq(c));
131     }
132
133     @Test
134     public void receiveWildcardTest() throws Exception {
135         ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state/+/topic", "command").build(),
136                 channelUIDMock, textValue, channelStateUpdateListenerMock));
137
138         CompletableFuture<@Nullable Void> future = c.start(connectionMock, scheduler, 100);
139         c.processMessage("state/bla/topic", "A TEST".getBytes());
140         future.get(300, TimeUnit.MILLISECONDS);
141
142         assertThat(textValue.getChannelState().toString(), is("A TEST"));
143         verify(channelStateUpdateListenerMock).updateChannelState(eq(channelUIDMock), any());
144     }
145
146     @Test
147     public void receiveStringTest() throws Exception {
148         ChannelState c = spy(new ChannelState(config, channelUIDMock, textValue, channelStateUpdateListenerMock));
149
150         CompletableFuture<@Nullable Void> future = c.start(connectionMock, scheduler, 100);
151         c.processMessage("state", "A TEST".getBytes());
152         future.get(300, TimeUnit.MILLISECONDS);
153
154         assertThat(textValue.getChannelState().toString(), is("A TEST"));
155         verify(channelStateUpdateListenerMock).updateChannelState(eq(channelUIDMock), any());
156     }
157
158     @Test
159     public void receiveDecimalTest() {
160         NumberValue value = new NumberValue(null, null, new BigDecimal(10), null);
161         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
162         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
163
164         c.processMessage("state", "15".getBytes());
165         assertThat(value.getChannelState().toString(), is("15"));
166
167         c.processMessage("state", "INCREASE".getBytes());
168         assertThat(value.getChannelState().toString(), is("25"));
169
170         c.processMessage("state", "DECREASE".getBytes());
171         assertThat(value.getChannelState().toString(), is("15"));
172
173         verify(channelStateUpdateListenerMock, times(3)).updateChannelState(eq(channelUIDMock), any());
174     }
175
176     @Test
177     public void receiveDecimalFractionalTest() {
178         NumberValue value = new NumberValue(null, null, new BigDecimal(10.5), null);
179         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
180         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
181
182         c.processMessage("state", "5.5".getBytes());
183         assertThat(value.getChannelState().toString(), is("5.5"));
184
185         c.processMessage("state", "INCREASE".getBytes());
186         assertThat(value.getChannelState().toString(), is("16.0"));
187     }
188
189     @Test
190     public void receiveDecimalUnitTest() {
191         NumberValue value = new NumberValue(null, null, new BigDecimal(10), Units.WATT);
192         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
193         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
194
195         c.processMessage("state", "15".getBytes());
196         assertThat(value.getChannelState().toString(), is("15 W"));
197
198         c.processMessage("state", "INCREASE".getBytes());
199         assertThat(value.getChannelState().toString(), is("25 W"));
200
201         c.processMessage("state", "DECREASE".getBytes());
202         assertThat(value.getChannelState().toString(), is("15 W"));
203
204         verify(channelStateUpdateListenerMock, times(3)).updateChannelState(eq(channelUIDMock), any());
205     }
206
207     @Test
208     public void receiveDecimalAsPercentageUnitTest() {
209         NumberValue value = new NumberValue(null, null, new BigDecimal(10), Units.PERCENT);
210         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
211         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
212
213         c.processMessage("state", "63.7".getBytes());
214         assertThat(value.getChannelState().toString(), is("63.7 %"));
215
216         verify(channelStateUpdateListenerMock, times(1)).updateChannelState(eq(channelUIDMock), any());
217     }
218
219     @Test
220     public void receivePercentageTest() {
221         PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10), null,
222                 null);
223         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
224         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
225
226         c.processMessage("state", "-100".getBytes()); // 0%
227         assertThat(value.getChannelState().toString(), is("0"));
228
229         c.processMessage("state", "100".getBytes()); // 100%
230         assertThat(value.getChannelState().toString(), is("100"));
231
232         c.processMessage("state", "0".getBytes()); // 50%
233         assertThat(value.getChannelState().toString(), is("50"));
234
235         c.processMessage("state", "INCREASE".getBytes());
236         assertThat(value.getChannelState().toString(), is("55"));
237         assertThat(value.getMQTTpublishValue(null), is("10"));
238         assertThat(value.getMQTTpublishValue("%03.0f"), is("010"));
239     }
240
241     @Test
242     public void receiveRGBColorTest() {
243         ColorValue value = new ColorValue(ColorMode.RGB, "FON", "FOFF", 10);
244         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
245         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
246
247         c.processMessage("state", "ON".getBytes()); // Normal on state
248         assertThat(value.getChannelState().toString(), is("0,0,10"));
249         assertThat(value.getMQTTpublishValue(null), is("25,25,25"));
250
251         c.processMessage("state", "FOFF".getBytes()); // Custom off state
252         assertThat(value.getChannelState().toString(), is("0,0,0"));
253         assertThat(value.getMQTTpublishValue(null), is("0,0,0"));
254
255         c.processMessage("state", "10".getBytes()); // Brightness only
256         assertThat(value.getChannelState().toString(), is("0,0,10"));
257         assertThat(value.getMQTTpublishValue(null), is("25,25,25"));
258
259         HSBType t = HSBType.fromRGB(12, 18, 231);
260
261         c.processMessage("state", "12,18,231".getBytes());
262         assertThat(value.getChannelState(), is(t)); // HSB
263         // rgb -> hsv -> rgb is quite lossy
264         assertThat(value.getMQTTpublishValue(null), is("13,20,229"));
265         assertThat(value.getMQTTpublishValue("%3$d,%2$d,%1$d"), is("229,20,13"));
266     }
267
268     @Test
269     public void receiveHSBColorTest() {
270         ColorValue value = new ColorValue(ColorMode.HSB, "FON", "FOFF", 10);
271         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
272         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
273
274         c.processMessage("state", "ON".getBytes()); // Normal on state
275         assertThat(value.getChannelState().toString(), is("0,0,10"));
276         assertThat(value.getMQTTpublishValue(null), is("0,0,10"));
277
278         c.processMessage("state", "FOFF".getBytes()); // Custom off state
279         assertThat(value.getChannelState().toString(), is("0,0,0"));
280         assertThat(value.getMQTTpublishValue(null), is("0,0,0"));
281
282         c.processMessage("state", "10".getBytes()); // Brightness only
283         assertThat(value.getChannelState().toString(), is("0,0,10"));
284         assertThat(value.getMQTTpublishValue(null), is("0,0,10"));
285
286         c.processMessage("state", "12,18,100".getBytes());
287         assertThat(value.getChannelState().toString(), is("12,18,100"));
288         assertThat(value.getMQTTpublishValue(null), is("12,18,100"));
289     }
290
291     @Test
292     public void receiveXYYColorTest() {
293         ColorValue value = new ColorValue(ColorMode.XYY, "FON", "FOFF", 10);
294         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
295         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
296
297         c.processMessage("state", "ON".getBytes()); // Normal on state
298         assertThat(value.getChannelState().toString(), is("0,0,10"));
299         assertThat(value.getMQTTpublishValue(null), is("0.312716,0.329002,10.00"));
300
301         c.processMessage("state", "FOFF".getBytes()); // Custom off state
302         assertThat(value.getChannelState().toString(), is("0,0,0"));
303         assertThat(value.getMQTTpublishValue(null), is("0.312716,0.329002,0.00"));
304
305         c.processMessage("state", "10".getBytes()); // Brightness only
306         assertThat(value.getChannelState().toString(), is("0,0,10"));
307         assertThat(value.getMQTTpublishValue(null), is("0.312716,0.329002,10.00"));
308
309         HSBType t = HSBType.fromXY(0.3f, 0.6f);
310
311         c.processMessage("state", "0.3,0.6,100".getBytes());
312         assertThat(value.getChannelState(), is(t)); // HSB
313         assertThat(value.getMQTTpublishValue(null), is("0.300000,0.600000,100.00"));
314         assertThat(value.getMQTTpublishValue("%3$.1f,%2$.4f,%1$.4f"), is("100.0,0.6000,0.3000"));
315     }
316
317     @Test
318     public void receiveLocationTest() {
319         LocationValue value = new LocationValue();
320         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
321         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
322
323         c.processMessage("state", "46.833974, 7.108433".getBytes());
324         assertThat(value.getChannelState().toString(), is("46.833974,7.108433"));
325         assertThat(value.getMQTTpublishValue(null), is("46.833974,7.108433"));
326     }
327
328     @Test
329     public void receiveDateTimeTest() {
330         DateTimeValue value = new DateTimeValue();
331         ChannelState subject = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
332         subject.start(connectionMock, mock(ScheduledExecutorService.class), 100);
333
334         ZonedDateTime zd = ZonedDateTime.now();
335         String datetime = zd.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
336
337         subject.processMessage("state", datetime.getBytes());
338
339         String channelState = value.getChannelState().toString();
340         assertTrue(channelState.startsWith(datetime),
341                 "Expected '" + channelState + "' to start with '" + datetime + "'");
342         assertThat(value.getMQTTpublishValue(null), is(datetime));
343     }
344
345     @Test
346     public void receiveImageTest() {
347         ImageValue value = new ImageValue();
348         ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
349         c.start(connectionMock, mock(ScheduledExecutorService.class), 100);
350
351         byte[] payload = { (byte) 0xFF, (byte) 0xD8, 0x01, 0x02, (byte) 0xFF, (byte) 0xD9 };
352         c.processMessage("state", payload);
353         assertThat(value.getChannelState(), is(instanceOf(RawType.class)));
354         assertThat(((RawType) value.getChannelState()).getMimeType(), is("image/jpeg"));
355     }
356 }