/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.lightwaverf.internal; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.util.Calendar; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.openhab.core.events.EventPublisher; import org.openhab.core.items.Item; import org.openhab.core.library.items.DateTimeItem; import org.openhab.core.library.items.DimmerItem; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.StringItem; import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.StringType; import org.openhab.core.types.Command; import org.openhab.core.types.State; public class LightwaveRfBindingFunctionalTest { private static final String CONTEXT = ""; private static final String WIFILINK_IP = "127.0.0.1"; private static final int TRANSMIT_PORT = 9760; private static final int TIME_BETWEEN_COMMANDS_MS = 100; private static final int TIMEOUT_OK_MESSAGES_MS = 1000; @Mock EventPublisher mockEventPublisher; @Mock DatagramSocket mockReceiveSocket; @Mock DatagramSocket mockReceiveSocket2; @Mock DatagramSocket mockTransmitSocket; LightwaveRfBinding binding; LightwaveRfGenericBindingProvider bindingProvider; LightwaverfConvertor messageConvertor; LightwaveRfWifiLink wifiLink; @Before public void init() throws Exception { MockitoAnnotations.initMocks(this); binding = new LightwaveRfBinding(); bindingProvider = new LightwaveRfGenericBindingProvider(); messageConvertor = new LightwaverfConvertor(); wifiLink = new LightwaveRfWifiLink(WIFILINK_IP, TRANSMIT_PORT, mockReceiveSocket, mockReceiveSocket2, mockTransmitSocket, messageConvertor, TIME_BETWEEN_COMMANDS_MS, TIMEOUT_OK_MESSAGES_MS); binding.setLightwaveRfConvertor(messageConvertor); binding.setWifiLink(wifiLink); binding.setEventPublisher(mockEventPublisher); } @Test public void testDimmingCommandZero() throws Exception { testSendingACommandAndVerify(new DimmerItem("MyDimmer"), "room=1,device=2,type=DIMMER", new PercentType("00"), "200,!R1D2F0\n"); } @Test public void testDimmingCommandSend() throws Exception { testSendingACommandAndVerify(new DimmerItem("MyDimmer"), "room=1,device=2,type=DIMMER", new PercentType("50"), "200,!R1D2FdP16\n"); } @Test public void testDimmingCommandReceive() throws Exception { testReceivingACommandAndVerify(new DimmerItem("MyDimmer"), "room=1,device=2,type=DIMMER", "200,!R1D2FdP16\n", new PercentType("50")); } @Test public void testOnCommandSend() throws Exception { testSendingACommandAndVerify(new SwitchItem("MySwitch"), "room=1,device=2,type=SWITCH", OnOffType.ON, "200,!R1D2F1\n"); } @Test public void testOnCommandReceived() throws Exception { testReceivingACommandAndVerify(new SwitchItem("MySwitch"), "room=1,device=2,type=SWITCH", "200,!R1D2F1\n", OnOffType.ON); } @Test public void testOffCommandSend() throws Exception { testSendingACommandAndVerify(new SwitchItem("MySwitch"), "room=1,device=2,type=SWITCH", OnOffType.OFF, "200,!R1D2F0\n"); } @Test public void testOffCommandReceived() throws Exception { testReceivingACommandAndVerify(new SwitchItem("MySwitch"), "room=1,device=2,type=SWITCH", "200,!R1D2F0\n", OnOffType.OFF); } @Test public void testRelayCommandOpenSend() throws Exception { testSendingACommandAndVerify(new NumberItem("MyRelay"), "room=1,device=2,type=RELAY", new DecimalType("1"), "200,!R1D2F)\n"); } @Test public void testRelayCommandCloseSend() throws Exception { testSendingACommandAndVerify(new NumberItem("MyRelay"), "room=1,device=2,type=RELAY", new DecimalType("-1"), "200,!R1D2F(\n"); } @Test public void testRelayCommandStopSend() throws Exception { testSendingACommandAndVerify(new NumberItem("MyRelay"), "room=1,device=2,type=RELAY", new DecimalType("0"), "200,!R1D2F^\n"); } @Test public void testHeatingInfoResponseReceived() throws Exception { String message = "*!{\"trans\":1506,\"mac\":\"03:02:71\",\"time\":1423850746,\"prod\":\"valve\",\"serial\":\"064402\",\"signal\":54,\"type\":\"temp\",\"batt\":2.99,\"ver\":56,\"state\":\"boost\",\"cTemp\":22.3,\"cTarg\":24.0,\"output\":100,\"nTarg\":20.0,\"nSlot\":\"18:45\",\"prof\":5}"; List<ItemConfigAndExpectedState> itemConfigAndExpectedStates = new LinkedList<ItemConfigAndExpectedState>(); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new NumberItem("BATTERY"), "serial=064402,type=HEATING_BATTERY", new DecimalType("2.99"))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new NumberItem("CURRENT_TEMP"), "serial=064402,type=HEATING_CURRENT_TEMP", new DecimalType("22.3"))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new StringItem("MODE"), "serial=064402,type=HEATING_MODE", new StringType("boost"))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new StringItem("VERSION"), "serial=064402,type=VERSION", new StringType("56"))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new NumberItem("SIGNAL"), "serial=064402,type=SIGNAL", new DecimalType("54"))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new NumberItem("SET_TEMP"), "serial=064402,type=HEATING_SET_TEMP", new DecimalType("24.0"))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new NumberItem("OUTPUT"), "serial=064402,type=HEATING_OUTPUT", new DecimalType("100"))); Calendar cal = Calendar.getInstance(); cal.setTime(new Date(1423850746000L)); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new DateTimeItem("TIME"), "serial=064402,type=UPDATETIME", new DateTimeType(cal))); testReceivingACommandAndVerify(itemConfigAndExpectedStates, message); } @Test public void testWifiLinkStatusReceived() throws Exception { String message = "*!{\"trans\":452,\"mac\":\"ab:cd:ef\",\"time\":1447712274,\"type\":\"hub\",\"prod\":\"wfl\",\"fw\":\"U2.91Y\"," + "\"uptime\":1386309,\"timeZone\":0,\"lat\":52.48,\"long\":-87.89,\"duskTime\":1447690400," + "\"dawnTime\":1447659083,\"tmrs\":0,\"evns\":1,\"run\":0,\"macs\":8,\"ip\":\"192.168.0.1\",\"devs\":0}"; List<ItemConfigAndExpectedState> itemConfigAndExpectedStates = new LinkedList<ItemConfigAndExpectedState>(); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new StringItem("WIFILINK_IP"), "serial=wifilink,type=WIFILINK_IP", new StringType("192.168.0.1"))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new StringItem("WIFILINK_FIRMWARE"), "serial=wifilink,type=WIFILINK_FIRMWARE", new StringType("U2.91Y"))); Calendar duskCal = Calendar.getInstance(); duskCal.setTime(new Date(1447690400000L)); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new DateTimeItem("WIFILINK_DUSK_TIME"), "serial=wifilink,type=WIFILINK_DUSK_TIME", new DateTimeType(duskCal))); Calendar dawnCal = Calendar.getInstance(); dawnCal.setTime(new Date(1447659083000L)); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new DateTimeItem("WIFILINK_DAWN_TIME"), "serial=wifilink,type=WIFILINK_DAWN_TIME", new DateTimeType(dawnCal))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new NumberItem("WIFILINK_UPTIME"), "serial=wifilink,type=WIFILINK_UPTIME", new DecimalType("1386309"))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new StringItem("WIFILINK_LONGITUDE"), "serial=wifilink,type=WIFILINK_LONGITUDE", new StringType("-87.89"))); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new StringItem("WIFILINK_LATITUDE"), "serial=wifilink,type=WIFILINK_LATITUDE", new StringType("52.48"))); Calendar cal = Calendar.getInstance(); cal.setTime(new Date(1447712274000L)); itemConfigAndExpectedStates.add(new ItemConfigAndExpectedState(new NumberItem("TIME"), "serial=wifilink,type=UPDATETIME", new DateTimeType(cal))); testReceivingACommandAndVerify(itemConfigAndExpectedStates, message); } @Test public void testMoodCommandReceive() throws Exception { testReceivingACommandAndVerify(new NumberItem("MyMood"), "room=1,type=MOOD", "200,!R1FmP2\n", new DecimalType("2")); } @Test public void testMoodCommandSend() throws Exception { testSendingACommandAndVerify(new NumberItem("MyMood"), "room=1,type=MOOD", new DecimalType("1"), "200,!R1FmP1\n"); } @Test public void testAllOffCommandSend() throws Exception { testSendingACommandAndVerify(new SwitchItem("MyAllOff"), "room=2,type=ALL_OFF", OnOffType.OFF, "200,!R2Fa\n"); } @Test public void testAllOffCommandReceive() throws Exception { testReceivingACommandAndVerify(new SwitchItem("MyAllOff"), "room=2,type=ALL_OFF", "200,!R2Fa\n", OnOffType.OFF); } @Test public void testUnknownCommand() throws Exception { String message = "{\"trans\":213454,\"mac\":\"03:02:71\",\"cmd\":\"get_duskdawn\",\"lat\":51.52,\"long\":-0.08,\"offset\":0}"; testReceivingACommandAndVerify(new LinkedList<ItemConfigAndExpectedState>(), message); } @Test public void testOffMessageSentByAndriodApp() throws Exception { String message = "030271,102,!R3D1F0|Living Room|Side Light 1 Off"; testReceivingACommandAndVerify(new DimmerItem("LivingRoom"), "room=3,device=1,type=DIMMER", message, OnOffType.OFF); } @Test public void testDimMessageSentByAndriodApp() throws Exception { String message = "030271,101,!R3D2FdP13|Living Room|Side Light 2 40%"; testReceivingACommandAndVerify(new DimmerItem("LivingRoom"), "room=3,device=2,type=DIMMER", message, new PercentType("41")); } @Test public void testInOnlyMessageReceived() throws Exception { String message = "030271,101,!R3D2FdP13|Living Room|Side Light 2 40%"; testReceivingACommandAndVerify(new DimmerItem("LivingRoom"), "<room=3,device=2,type=DIMMER", message, new PercentType("41")); } @Test public void testOutOnlyMessageReceived() throws Exception { String message = "030271,101,!R3D2FdP13|Living Room|Side Light 2 40%"; testReceivingACommandAndVerifyNoInteractions(new DimmerItem("LivingRoom"), ">room=3,device=2,type=DIMMER", message, new PercentType("41")); } @Test public void testInOnlyCommandSend() throws Exception { testSendingACommandAndVerifyNoInteractions(new SwitchItem("MySwitch"), "<room=1,device=2,type=SWITCH", OnOffType.ON, "200,!R1D2F1\n"); } @Test public void testOutOnlyCommandSend() throws Exception { testSendingACommandAndVerify(new SwitchItem("MySwitch"), ">room=1,device=2,type=SWITCH", OnOffType.ON, "200,!R1D2F1\n"); } private void testReceivingACommandAndVerify(Item item, String itemConfig, String messageToReceive, State expectedState) throws Exception { List<ItemConfigAndExpectedState> listOfItemsAndExpectedStates = new LinkedList<ItemConfigAndExpectedState>(); listOfItemsAndExpectedStates.add(new ItemConfigAndExpectedState(item, itemConfig, expectedState)); testReceivingACommandAndVerify(listOfItemsAndExpectedStates, messageToReceive); } private void testReceivingACommandAndVerify(List<ItemConfigAndExpectedState> itemConfigAndExpectedStates, String messageToReceive) throws Exception { receiveACommand(itemConfigAndExpectedStates, messageToReceive); verifyReceivedCommands(itemConfigAndExpectedStates); } private void testReceivingACommandAndVerifyNoInteractions(Item item, String itemConfig, String messageToReceive, State expectedState) throws Exception { List<ItemConfigAndExpectedState> listOfItemsAndExpectedStates = new LinkedList<ItemConfigAndExpectedState>(); listOfItemsAndExpectedStates.add(new ItemConfigAndExpectedState(item, itemConfig, expectedState)); testReceivingACommandAndVerifyNoInteractions(listOfItemsAndExpectedStates, messageToReceive); } private void testReceivingACommandAndVerifyNoInteractions( List<ItemConfigAndExpectedState> itemConfigAndExpectedStates, String messageToReceive) throws Exception { receiveACommand(itemConfigAndExpectedStates, messageToReceive); verifyNoMoreInteractions(mockEventPublisher); } private void receiveACommand(List<ItemConfigAndExpectedState> itemConfigAndExpectedStates, String messageToReceive) throws Exception { // Set up sockets for testing CyclicBarrier sendMessageBarrier = new CyclicBarrier(2); CountDownLatch messageProcessedCountdownLatch = new CountDownLatch(itemConfigAndExpectedStates.size()); doAnswer(sendMessageOnceUnlatched(messageToReceive, sendMessageBarrier)).when(mockReceiveSocket) .receive(any(DatagramPacket.class)); doAnswer(waitIndefinitely()).when(mockReceiveSocket2).receive(any(DatagramPacket.class)); doAnswer(waitIndefinitely()).when(mockTransmitSocket).send(any(DatagramPacket.class)); for (ItemConfigAndExpectedState t : itemConfigAndExpectedStates) { doAnswer(unlatchWhenEventReceivedOrTimeout(messageProcessedCountdownLatch)).when(mockEventPublisher) .postUpdate(t.getItem().getName(), t.getExpectedState()); } // Setup Item config for (ItemConfigAndExpectedState t : itemConfigAndExpectedStates) { bindingProvider.processBindingConfiguration(CONTEXT, t.getItem(), t.getItemConfig()); } binding.addBindingProvider(bindingProvider); // Activate the binding ready for the test binding.activateForTesting(); // Receive the command sendMessageBarrier.await(); // Wait till the receive has been processes we use a timeout in case we don't receive anything messageProcessedCountdownLatch.await(1000, TimeUnit.MILLISECONDS); } private void verifyReceivedCommands(List<ItemConfigAndExpectedState> itemConfigAndExpectedStates) { // Validate what we sent on the event bus for (ItemConfigAndExpectedState t : itemConfigAndExpectedStates) { verify(mockEventPublisher).postUpdate(t.getItem().getName(), t.getExpectedState()); } verifyNoMoreInteractions(mockEventPublisher); } private void testSendingACommandAndVerify(Item item, String itemConfig, Command command, String expectedCommand) throws Exception { DatagramPacket sentPacket = sendCommand(item, itemConfig, command); // Validate what we sent verifyCommandSent(expectedCommand, sentPacket); } private void testSendingACommandAndVerifyNoInteractions(Item item, String itemConfig, Command command, String expectedCommand) throws Exception { DatagramPacket sentPacket = null; try { sentPacket = sendCommand(item, itemConfig, command); } catch (TimeoutException e) { assertEquals(null, sentPacket); } } private DatagramPacket sendCommand(Item item, String itemConfig, Command command) throws Exception { // Set up sockets for testing DatagramPacket sentPacket = new DatagramPacket(new byte[1024], 1024); CyclicBarrier barrier = new CyclicBarrier(2); doAnswer(waitIndefinitely()).when(mockReceiveSocket).receive(any(DatagramPacket.class)); doAnswer(waitIndefinitely()).when(mockReceiveSocket2).receive(any(DatagramPacket.class)); doAnswer(transmitAnswer(sentPacket, barrier)).when(mockTransmitSocket).send(any(DatagramPacket.class)); // Setup Item config bindingProvider.processBindingConfiguration(CONTEXT, item, itemConfig); binding.addBindingProvider(bindingProvider); // Activate the binding ready for the test binding.activateForTesting(); // Send the command binding.internalReceiveCommand(item.getName(), command); // Wait till the socket has sent the command barrier.await(1000, TimeUnit.MILLISECONDS); return sentPacket; } private void verifyCommandSent(String expectedCommand, DatagramPacket sentPacket) throws Exception { assertEquals(expectedCommand, getReceivedStringFromPacket(sentPacket)); verify(mockTransmitSocket, times(1)).send(any(DatagramPacket.class)); verifyNoMoreInteractions(mockTransmitSocket); } private Answer<DatagramPacket> waitIndefinitely() { return new Answer<DatagramPacket>() { @Override public DatagramPacket answer(InvocationOnMock invocation) throws InterruptedException { // Wait on a latch that won't be decremented to wait indefinitely. new CountDownLatch(1).await(); return null; } }; } private Answer<DatagramPacket> sendMessageOnceUnlatched(final String data, final CyclicBarrier latch) { return new Answer<DatagramPacket>() { @Override public DatagramPacket answer(InvocationOnMock invocation) throws InterruptedException, BrokenBarrierException { Object[] args = invocation.getArguments(); ((DatagramPacket) args[0]).setData(data.getBytes()); latch.await(); latch.reset(); return null; } }; } private Answer<DatagramPacket> transmitAnswer(final DatagramPacket packet, final CyclicBarrier latch) { return new Answer<DatagramPacket>() { @Override public DatagramPacket answer(InvocationOnMock invocation) throws InterruptedException, BrokenBarrierException { Object[] args = invocation.getArguments(); byte[] sentData = ((DatagramPacket) args[0]).getData(); String sentAsString = new String(sentData, 0, sentData.length); packet.setData(sentAsString.getBytes()); latch.await(); return null; } }; } private Answer<String> unlatchWhenEventReceivedOrTimeout(final CountDownLatch latch) { return new Answer<String>() { @Override public String answer(InvocationOnMock invocation) throws InterruptedException, BrokenBarrierException { latch.countDown(); return null; } }; } private String getReceivedStringFromPacket(DatagramPacket packet) { return new String(packet.getData(), 0, packet.getData().length); } private final class ItemConfigAndExpectedState { private final Item item; private final String itemConfig; private final State expectedState; public ItemConfigAndExpectedState(Item item, String itemConfig, State expectedState) { this.item = item; this.itemConfig = itemConfig; this.expectedState = expectedState; } public Item getItem() { return item; } public String getItemConfig() { return itemConfig; } public State getExpectedState() { return expectedState; } } }