2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.broadlink.internal.handler;
15 import static org.junit.Assert.fail;
16 import static org.junit.jupiter.api.Assertions.assertEquals;
17 import static org.mockito.Mockito.*;
18 import static org.openhab.binding.broadlink.internal.BroadlinkBindingConstants.*;
20 import java.io.IOException;
21 import java.util.List;
23 import javax.measure.quantity.Dimensionless;
24 import javax.measure.quantity.Temperature;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.junit.jupiter.api.BeforeEach;
28 import org.junit.jupiter.api.Test;
29 import org.mockito.ArgumentCaptor;
30 import org.mockito.ArgumentMatchers;
31 import org.mockito.Mockito;
32 import org.mockito.MockitoAnnotations;
33 import org.openhab.binding.broadlink.internal.BroadlinkBindingConstants;
34 import org.openhab.core.library.types.QuantityType;
35 import org.openhab.core.test.storage.VolatileStorageService;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.types.State;
40 * Tests the Remote Model 4 handler.
42 * @author John Marshall - Initial contribution
45 public class BroadlinkRemoteModel4HandlerTest extends AbstractBroadlinkThingHandlerTest {
47 private byte[] response = { (byte) 0x5a, (byte) 0xa5, (byte) 0xaa, (byte) 0x55, (byte) 0x5a, (byte) 0xa5,
48 (byte) 0xaa, (byte) 0x55, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
49 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
50 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
51 (byte) 0x00, (byte) 0x00, (byte) 0xf6, (byte) 0xcd, (byte) 0x00, (byte) 0x00, (byte) 0x14, (byte) 0x27,
52 (byte) 0x6a, (byte) 0x00, (byte) 0x63, (byte) 0x00, (byte) 0x11, (byte) 0x22, (byte) 0x11, (byte) 0x22,
53 (byte) 0x11, (byte) 0x22, (byte) 0x11, (byte) 0x22, (byte) 0x11, (byte) 0x22, (byte) 0x00, (byte) 0xc0,
54 (byte) 0x00, (byte) 0x00, (byte) 0x09, (byte) 0x9a, (byte) 0x60, (byte) 0xfb, (byte) 0x72, (byte) 0x07,
55 (byte) 0x4f, (byte) 0x89, (byte) 0xf8, (byte) 0xb4, (byte) 0xdb, (byte) 0xb0, (byte) 0x72, (byte) 0xe7,
56 (byte) 0x1f, (byte) 0x86, };
60 public void setUp() throws Exception {
61 configureUnderlyingThing(BroadlinkBindingConstants.THING_TYPE_RM4_MINI, "rm4-test");
62 MockitoAnnotations.openMocks(this).close();
63 Mockito.when(mockSocket.sendAndReceive(ArgumentMatchers.any(byte[].class), ArgumentMatchers.anyString()))
64 .thenReturn(response);
68 public void sendsExpectedBytesWhenGettingDeviceStatus() {
69 VolatileStorageService storageService = new VolatileStorageService();
70 ArgumentCaptor<Byte> commandCaptor = ArgumentCaptor.forClass(Byte.class);
71 ArgumentCaptor<byte[]> byteCaptor = ArgumentCaptor.forClass(byte[].class);
72 BroadlinkRemoteHandler model4 = new BroadlinkRemoteModel4MiniHandler(thing, commandDescriptionProvider,
74 setMocksForTesting(model4);
75 reset(trafficObserver);
77 model4.getStatusFromDevice();
78 } catch (IOException | BroadlinkException e) {
79 fail("Unexpected exception: " + e.getClass().getCanonicalName());
82 verify(trafficObserver).onCommandSent(commandCaptor.capture());
83 assertEquals(0x6a, commandCaptor.getValue().byteValue());
85 verify(trafficObserver).onBytesSent(byteCaptor.capture());
87 byte[] sentBytes = byteCaptor.getValue();
88 assertEquals(16, sentBytes.length);
90 assertEquals(0x04, sentBytes[0]);
91 assertEquals(0x00, sentBytes[1]);
92 assertEquals(0x24, sentBytes[2]);
96 public void sendsExpectedBytesWhenSendingCode() throws IOException {
97 VolatileStorageService storageService = new VolatileStorageService();
98 ArgumentCaptor<Byte> commandCaptor = ArgumentCaptor.forClass(Byte.class);
99 ArgumentCaptor<byte[]> byteCaptor = ArgumentCaptor.forClass(byte[].class);
100 BroadlinkRemoteHandler model4 = new BroadlinkRemoteModel4MiniHandler(thing, commandDescriptionProvider,
102 setMocksForTesting(model4);
103 // Note the length is 10 so as to not require padding (6 byte preamble)
104 byte[] code = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a };
106 reset(trafficObserver);
107 model4.sendCode(code);
109 verify(trafficObserver).onCommandSent(commandCaptor.capture());
110 assertEquals(0x6a, commandCaptor.getValue().byteValue());
112 verify(trafficObserver).onBytesSent(byteCaptor.capture());
114 byte[] sentBytes = byteCaptor.getValue();
115 assertEquals(16, sentBytes.length);
117 assertEquals((byte) 0x0e, sentBytes[0]);
118 assertEquals(0x00, sentBytes[1]);
120 assertEquals(0x02, sentBytes[2]);
121 assertEquals(0x00, sentBytes[3]);
122 assertEquals(0x00, sentBytes[4]);
123 assertEquals(0x00, sentBytes[5]);
125 assertEquals(0x01, sentBytes[6]);
126 assertEquals(0x02, sentBytes[7]);
127 assertEquals(0x03, sentBytes[8]);
128 assertEquals(0x04, sentBytes[9]);
129 assertEquals(0x05, sentBytes[10]);
130 assertEquals(0x06, sentBytes[11]);
131 assertEquals(0x07, sentBytes[12]);
132 assertEquals(0x08, sentBytes[13]);
133 assertEquals(0x09, sentBytes[14]);
134 assertEquals(0x0a, sentBytes[15]);
138 public void sendsExpectedBytesWhenSendingCodeIncludingPadding() throws IOException {
139 VolatileStorageService storageService = new VolatileStorageService();
140 ArgumentCaptor<Byte> commandCaptor = ArgumentCaptor.forClass(Byte.class);
141 ArgumentCaptor<byte[]> byteCaptor = ArgumentCaptor.forClass(byte[].class);
142 BroadlinkRemoteHandler model4 = new BroadlinkRemoteModel4MiniHandler(thing, commandDescriptionProvider,
144 setMocksForTesting(model4);
145 // Note the length is such that padding up to the next multiple of 16 will be needed
146 byte[] code = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b };
147 reset(trafficObserver);
148 model4.sendCode(code);
150 verify(trafficObserver).onCommandSent(commandCaptor.capture());
151 assertEquals(0x6a, commandCaptor.getValue().byteValue());
153 verify(trafficObserver).onBytesSent(byteCaptor.capture());
155 byte[] sentBytes = byteCaptor.getValue();
156 assertEquals(32, sentBytes.length);
158 // Calculated length field is len(data) + 4 ===> 11 + 4 = 15 = 0x0f
160 assertEquals((byte) 0x0f, sentBytes[0]);
161 assertEquals(0x00, sentBytes[1]);
163 assertEquals(0x02, sentBytes[2]); // The "send code" command
165 assertEquals(0x01, sentBytes[6]); // The payload
166 assertEquals(0x02, sentBytes[7]);
167 assertEquals(0x03, sentBytes[8]);
168 assertEquals(0x04, sentBytes[9]);
169 assertEquals(0x05, sentBytes[10]);
170 assertEquals(0x06, sentBytes[11]);
171 assertEquals(0x07, sentBytes[12]);
172 assertEquals(0x08, sentBytes[13]);
173 assertEquals(0x09, sentBytes[14]);
174 assertEquals(0x0a, sentBytes[15]);
175 assertEquals(0x0b, sentBytes[16]);
176 assertEquals(0x00, sentBytes[17]);
180 public void setsTheTemperatureAndHumidityChannelsAfterGettingStatus() {
181 VolatileStorageService storageService = new VolatileStorageService();
182 BroadlinkRemoteHandler model4 = new BroadlinkRemoteModel4MiniHandler(thing, commandDescriptionProvider,
184 setMocksForTesting(model4);
188 model4.getStatusFromDevice();
189 } catch (IOException | BroadlinkException e) {
190 fail("Unexpected exception: " + e.getClass().getCanonicalName());
193 ArgumentCaptor<ChannelUID> channelCaptor = ArgumentCaptor.forClass(ChannelUID.class);
194 ArgumentCaptor<State> stateCaptor = ArgumentCaptor.forClass(State.class);
195 verify(mockCallback, times(2)).stateUpdated(channelCaptor.capture(), stateCaptor.capture());
197 List<ChannelUID> channelCaptures = channelCaptor.getAllValues();
198 List<State> stateCaptures = stateCaptor.getAllValues();
200 ChannelUID expectedTemperatureChannel = new ChannelUID(thing.getUID(), TEMPERATURE_CHANNEL);
201 assertEquals(expectedTemperatureChannel, channelCaptures.get(0));
203 QuantityType<Temperature> expectedTemperature = new QuantityType<>(21.22,
204 BroadlinkBindingConstants.BROADLINK_TEMPERATURE_UNIT);
205 assertEquals(expectedTemperature, stateCaptures.get(0));
207 ChannelUID expectedHumidityChannel = new ChannelUID(thing.getUID(), HUMIDITY_CHANNEL);
208 assertEquals(expectedHumidityChannel, channelCaptures.get(1));
210 QuantityType<Dimensionless> expectedHumidity = new QuantityType<>(39.4,
211 BroadlinkBindingConstants.BROADLINK_HUMIDITY_UNIT);
212 assertEquals(expectedHumidity, stateCaptures.get(1));
216 public void sendsExpectedBytesWhenEnteringLearnMode() throws IOException {
217 VolatileStorageService storageService = new VolatileStorageService();
218 ArgumentCaptor<Byte> commandCaptor = ArgumentCaptor.forClass(Byte.class);
219 ArgumentCaptor<byte[]> byteCaptor = ArgumentCaptor.forClass(byte[].class);
220 BroadlinkRemoteHandler model4 = new BroadlinkRemoteModel4MiniHandler(thing, commandDescriptionProvider,
222 setMocksForTesting(model4);
224 reset(trafficObserver);
225 model4.handleLearningCommand(LEARNING_CONTROL_COMMAND_LEARN);
227 verify(trafficObserver).onCommandSent(commandCaptor.capture());
228 assertEquals(0x6a, commandCaptor.getValue().byteValue());
230 verify(trafficObserver).onBytesSent(byteCaptor.capture());
232 byte[] sentBytes = byteCaptor.getValue();
233 assertEquals(16, sentBytes.length);
236 // PLl, PLh, <commandByte> 0 0 0 then padding for the rest up to 16
237 // Where PL = length(data) + 4 - so in this case, 4
238 assertEquals(0x04, sentBytes[0]); // Low length byte
239 assertEquals(0x00, sentBytes[1]); // High length byte
240 assertEquals(0x03, sentBytes[2]);
241 assertEquals(0x00, sentBytes[3]);
242 assertEquals(0x00, sentBytes[4]);
243 assertEquals(0x00, sentBytes[5]);
244 assertEquals(0x00, sentBytes[6]);
245 assertEquals(0x00, sentBytes[7]);
246 assertEquals(0x00, sentBytes[8]);
247 assertEquals(0x00, sentBytes[9]);
248 assertEquals(0x00, sentBytes[10]);
249 assertEquals(0x00, sentBytes[11]);
250 assertEquals(0x00, sentBytes[12]);
251 assertEquals(0x00, sentBytes[13]);
252 assertEquals(0x00, sentBytes[14]);
253 assertEquals(0x00, sentBytes[15]);