mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-31 13:34:22 +02:00
[satel] Discovery fix for roller shutter things, other minor changes (#18444)
Signed-off-by: Krzysztof Goworek <krzysztof.goworek@gmail.com>
This commit is contained in:
+24
-45
@@ -15,6 +15,7 @@ package org.openhab.binding.satel.internal.discovery;
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -50,19 +51,19 @@ import org.slf4j.LoggerFactory;
|
||||
public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
|
||||
.of(DEVICE_THING_TYPES_UIDS, VIRTUAL_THING_TYPES_UIDS).flatMap(uids -> uids.stream())
|
||||
.of(DEVICE_THING_TYPES_UIDS, VIRTUAL_THING_TYPES_UIDS).flatMap(Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
private static final int OUTPUT_FUNCTION_SHUTTER = 105;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SatelDeviceDiscoveryService.class);
|
||||
|
||||
private SatelBridgeHandler bridgeHandler;
|
||||
private Function<ThingTypeUID, ThingType> thingTypeProvider;
|
||||
private final SatelBridgeHandler bridgeHandler;
|
||||
private final Function<ThingTypeUID, ThingType> thingTypeProvider;
|
||||
private volatile boolean scanStopped;
|
||||
|
||||
public SatelDeviceDiscoveryService(SatelBridgeHandler bridgeHandler,
|
||||
Function<ThingTypeUID, ThingType> thingTypeProvider) {
|
||||
super(SUPPORTED_THING_TYPES, 60, true);
|
||||
super(SUPPORTED_THING_TYPES, 60, false);
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
this.thingTypeProvider = thingTypeProvider;
|
||||
}
|
||||
@@ -74,20 +75,14 @@ public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
// add virtual things by default
|
||||
for (ThingTypeUID thingTypeUID : VIRTUAL_THING_TYPES_UIDS) {
|
||||
ThingType thingType = thingTypeProvider.apply(thingTypeUID);
|
||||
addThing(thingTypeUID, null, thingType.getLabel(), Collections.emptyMap());
|
||||
addThing(thingTypeUID, toCamelCase(thingTypeUID.getId()), thingType.getLabel(), Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.KEYPAD, 8);
|
||||
scanForDevices(DeviceType.PARTITION, bridgeHandler.getIntegraType().getPartitions());
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.EXPANDER, 64);
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.PARTITION_WITH_OBJECT, bridgeHandler.getIntegraType().getPartitions());
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.ZONE_WITH_PARTITION, bridgeHandler.getIntegraType().getZones());
|
||||
scanForDevices(DeviceType.ZONE, bridgeHandler.getIntegraType().getZones());
|
||||
}
|
||||
if (!scanStopped) {
|
||||
scanForDevices(DeviceType.OUTPUT, bridgeHandler.getIntegraType().getZones());
|
||||
@@ -109,9 +104,8 @@ public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
if (bridgeHandler.sendCommand(cmd, false)) {
|
||||
String name = cmd.getName(encoding);
|
||||
int deviceKind = cmd.getDeviceKind();
|
||||
int info = cmd.getAdditionalInfo();
|
||||
logger.debug("Found device: type={}, id={}, name={}, kind/function={}, info={}", deviceType.name(), i,
|
||||
name, deviceKind, info);
|
||||
name, deviceKind, cmd.getAdditionalInfo());
|
||||
if (isDeviceAvailable(deviceType, deviceKind)) {
|
||||
addDevice(deviceType, deviceKind, i, name);
|
||||
}
|
||||
@@ -119,7 +113,7 @@ public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
// serious failure, disconnection or so
|
||||
scanStopped = true;
|
||||
logger.error("Unexpected failure during scan for {} using {}", deviceType.name(),
|
||||
bridgeHandler.getThing().getUID().toString());
|
||||
bridgeHandler.getThing().getUID());
|
||||
}
|
||||
}
|
||||
if (scanStopped) {
|
||||
@@ -135,7 +129,7 @@ public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
if (thingTypeUID == null) {
|
||||
logger.warn("Unknown device found: type={}, kind={}, name={}", deviceType.name(), deviceKind, deviceName);
|
||||
} else if (!getSupportedThingTypes().contains(thingTypeUID)) {
|
||||
logger.warn("Unsupported device: {}", thingTypeUID.toString());
|
||||
logger.warn("Unsupported device: {}", thingTypeUID);
|
||||
} else {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
@@ -150,44 +144,29 @@ public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
}
|
||||
}
|
||||
|
||||
private void addThing(ThingTypeUID thingTypeUID, @Nullable String deviceId, String label,
|
||||
Map<String, Object> properties) {
|
||||
private void addThing(ThingTypeUID thingTypeUID, String deviceId, String label, Map<String, Object> properties) {
|
||||
final ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
final ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID,
|
||||
deviceId == null ? toCamelCase(thingTypeUID.getId()) : deviceId);
|
||||
final ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, deviceId);
|
||||
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
|
||||
.withBridge(bridgeUID).withLabel(label).withProperties(properties).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
|
||||
private static @Nullable ThingTypeUID getThingTypeUID(DeviceType deviceType, int deviceKind) {
|
||||
switch (deviceType) {
|
||||
case OUTPUT:
|
||||
return (deviceKind == OUTPUT_FUNCTION_SHUTTER) ? THING_TYPE_SHUTTER : THING_TYPE_OUTPUT;
|
||||
case PARTITION:
|
||||
case PARTITION_WITH_OBJECT:
|
||||
return THING_TYPE_PARTITION;
|
||||
case ZONE:
|
||||
case ZONE_WITH_PARTITION:
|
||||
return THING_TYPE_ZONE;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return switch (deviceType) {
|
||||
case OUTPUT -> (deviceKind == OUTPUT_FUNCTION_SHUTTER) ? THING_TYPE_SHUTTER : THING_TYPE_OUTPUT;
|
||||
case PARTITION -> THING_TYPE_PARTITION;
|
||||
case ZONE -> THING_TYPE_ZONE;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isDeviceAvailable(DeviceType deviceType, int deviceKind) {
|
||||
switch (deviceType) {
|
||||
case OUTPUT:
|
||||
return deviceKind != 0 && deviceKind != OUTPUT_FUNCTION_SHUTTER
|
||||
&& (deviceKind != OUTPUT_FUNCTION_SHUTTER + 1);
|
||||
case PARTITION:
|
||||
case PARTITION_WITH_OBJECT:
|
||||
case ZONE:
|
||||
case ZONE_WITH_PARTITION:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return switch (deviceType) {
|
||||
case OUTPUT -> deviceKind != 0 && deviceKind != (OUTPUT_FUNCTION_SHUTTER + 1);
|
||||
case PARTITION, ZONE -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private static String toCamelCase(String s) {
|
||||
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.satel.internal.discovery;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
|
||||
import static org.openhab.binding.satel.internal.command.SatelCommand.State.FAILED;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand;
|
||||
import org.openhab.binding.satel.internal.event.EventDispatcher;
|
||||
import org.openhab.binding.satel.internal.handler.SatelBridgeHandler;
|
||||
import org.openhab.binding.satel.internal.protocol.SatelMessage;
|
||||
import org.openhab.binding.satel.internal.types.IntegraType;
|
||||
import org.openhab.core.config.discovery.DiscoveryListener;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.internal.BridgeImpl;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
|
||||
/**
|
||||
* @author Krzysztof Goworek - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SatelDeviceDiscoveryServiceTest {
|
||||
|
||||
private static final Charset bridgeEncoding = StandardCharsets.US_ASCII;
|
||||
|
||||
@Mock
|
||||
private SatelBridgeHandler bridgeHandler;
|
||||
|
||||
@Mock
|
||||
private Function<ThingTypeUID, ThingType> thingTypeProvider;
|
||||
|
||||
@Mock
|
||||
private EventDispatcher eventDispatcher;
|
||||
|
||||
@Mock
|
||||
private DiscoveryListener listener;
|
||||
|
||||
@InjectMocks
|
||||
private SatelDeviceDiscoveryService testSubject;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(bridgeHandler.getIntegraType()).thenReturn(IntegraType.I24);
|
||||
when(bridgeHandler.getEncoding()).thenReturn(bridgeEncoding);
|
||||
testSubject.addDiscoveryListener(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startScanShouldNotAddAnyThingWhenBridgeIsNotInitialized() {
|
||||
when(bridgeHandler.getThing()).thenReturn(new BridgeImpl(THING_TYPE_ETHM1, "bridgeId"));
|
||||
|
||||
testSubject.startScan();
|
||||
|
||||
verifyNoInteractions(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startScanShouldAddVirtualThingsWhenBridgeIsInitialized() {
|
||||
ThingType thingType = mock(ThingType.class);
|
||||
when(thingTypeProvider.apply(any())).thenReturn(thingType);
|
||||
when(bridgeHandler.isInitialized()).thenReturn(true);
|
||||
when(bridgeHandler.getThing()).thenReturn(new BridgeImpl(THING_TYPE_ETHM1, "bridgeId"));
|
||||
|
||||
testSubject.startScan();
|
||||
|
||||
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
|
||||
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
|
||||
List<DiscoveryResult> results = resultCaptor.getAllValues();
|
||||
|
||||
assertEquals(2, results.size());
|
||||
assertEquals(THING_TYPE_SYSTEM, results.get(0).getThingTypeUID());
|
||||
assertEquals(THING_TYPE_EVENTLOG, results.get(1).getThingTypeUID());
|
||||
}
|
||||
|
||||
@Test
|
||||
void startScanShouldContinueWhenFailureOccurred() {
|
||||
setUpCommandFailure();
|
||||
|
||||
testSubject.startScan();
|
||||
|
||||
verifyNoInteractions(listener);
|
||||
verify(bridgeHandler, times(52)).sendCommand(any(), eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void startScanShouldAddAllDevices() {
|
||||
BridgeImpl bridge = new BridgeImpl(THING_TYPE_ETHM1, "bridgeId");
|
||||
when(bridgeHandler.getThing()).thenReturn(bridge);
|
||||
setUpCommandResponse(1);
|
||||
|
||||
testSubject.startScan();
|
||||
|
||||
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
|
||||
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
|
||||
List<DiscoveryResult> results = resultCaptor.getAllValues();
|
||||
assertEquals(4,
|
||||
results.stream().filter(result -> THING_TYPE_PARTITION.equals(result.getThingTypeUID())).count());
|
||||
assertEquals(24, results.stream().filter(result -> THING_TYPE_ZONE.equals(result.getThingTypeUID())).count());
|
||||
assertEquals(24, results.stream().filter(result -> THING_TYPE_OUTPUT.equals(result.getThingTypeUID())).count());
|
||||
assertTrue(results.stream().allMatch(r -> "Device".equals(r.getLabel())));
|
||||
for (DiscoveryResult result : results) {
|
||||
assertEquals("Device", result.getLabel());
|
||||
assertEquals(bridge.getUID(), result.getBridgeUID());
|
||||
assertEquals(1, result.getProperties().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void startScanShouldAddShutters() {
|
||||
BridgeImpl bridge = new BridgeImpl(THING_TYPE_ETHM1, "bridgeId");
|
||||
when(bridgeHandler.getThing()).thenReturn(bridge);
|
||||
setUpCommandResponse(105);
|
||||
|
||||
testSubject.startScan();
|
||||
|
||||
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
|
||||
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
|
||||
List<DiscoveryResult> results = resultCaptor.getAllValues().stream()
|
||||
.filter(result -> THING_TYPE_SHUTTER.equals(result.getThingTypeUID())).toList();
|
||||
assertEquals(24, results.size());
|
||||
for (DiscoveryResult result : results) {
|
||||
assertEquals("Device", result.getLabel());
|
||||
assertEquals(bridge.getUID(), result.getBridgeUID());
|
||||
assertEquals(2, result.getProperties().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void startScanShouldSkipUnusedOutput() {
|
||||
when(bridgeHandler.getThing()).thenReturn(new BridgeImpl(THING_TYPE_ETHM1, "bridgeId"));
|
||||
setUpCommandResponse(0);
|
||||
|
||||
testSubject.startScan();
|
||||
|
||||
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
|
||||
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
|
||||
List<DiscoveryResult> results = resultCaptor.getAllValues();
|
||||
assertEquals(0, results.stream().filter(result -> THING_TYPE_OUTPUT.equals(result.getThingTypeUID())).count());
|
||||
assertEquals(0, results.stream().filter(result -> THING_TYPE_SHUTTER.equals(result.getThingTypeUID())).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
void startScanShouldSkipSecondShutterOutput() {
|
||||
when(bridgeHandler.getThing()).thenReturn(new BridgeImpl(THING_TYPE_ETHM1, "bridgeId"));
|
||||
setUpCommandResponse(106);
|
||||
|
||||
testSubject.startScan();
|
||||
|
||||
ArgumentCaptor<DiscoveryResult> resultCaptor = ArgumentCaptor.forClass(DiscoveryResult.class);
|
||||
verify(listener, atLeastOnce()).thingDiscovered(any(), resultCaptor.capture());
|
||||
List<DiscoveryResult> results = resultCaptor.getAllValues();
|
||||
assertEquals(0, results.stream().filter(result -> THING_TYPE_OUTPUT.equals(result.getThingTypeUID())).count());
|
||||
assertEquals(0, results.stream().filter(result -> THING_TYPE_SHUTTER.equals(result.getThingTypeUID())).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stopScanShouldSkipDiscovery() throws InterruptedException {
|
||||
CountDownLatch startLatch = new CountDownLatch(1);
|
||||
CountDownLatch stopLatch = new CountDownLatch(1);
|
||||
Thread thread = new Thread(() -> {
|
||||
reset(bridgeHandler);
|
||||
when(bridgeHandler.isInitialized()).thenAnswer(invocationOnMock -> {
|
||||
startLatch.countDown();
|
||||
stopLatch.await();
|
||||
return false;
|
||||
});
|
||||
testSubject.startScan();
|
||||
});
|
||||
thread.start();
|
||||
startLatch.await();
|
||||
|
||||
testSubject.stopScan();
|
||||
stopLatch.countDown();
|
||||
thread.join();
|
||||
|
||||
verifyNoMoreInteractions(bridgeHandler);
|
||||
verifyNoInteractions(listener);
|
||||
}
|
||||
|
||||
private void setUpCommandResponse(int deviceKind) {
|
||||
when(bridgeHandler.sendCommand(isA(ReadDeviceInfoCommand.class), eq(false))).thenAnswer(invocationOnMock -> {
|
||||
ReadDeviceInfoCommand cmd = invocationOnMock.getArgument(0);
|
||||
byte[] payload = new byte[19];
|
||||
byte[] nameBytes = "Device".getBytes(bridgeEncoding);
|
||||
System.arraycopy(nameBytes, 0, payload, 3, nameBytes.length);
|
||||
payload[2] = (byte) deviceKind;
|
||||
cmd.handleResponse(eventDispatcher, new SatelMessage(ReadDeviceInfoCommand.COMMAND_CODE, payload));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpCommandFailure() {
|
||||
when(bridgeHandler.sendCommand(isA(ReadDeviceInfoCommand.class), eq(false))).thenAnswer(invocationOnMock -> {
|
||||
ReadDeviceInfoCommand cmd = invocationOnMock.getArgument(0);
|
||||
cmd.setState(FAILED);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
-4
@@ -145,8 +145,4 @@ class SatelEventLogHandlerTest {
|
||||
assertEquals(1, result.size());
|
||||
assertTrue(result.contains(SatelEventLogActions.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readEvent() {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user