mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
Compare commits
5 Commits
7a9c7c2e57
...
d7741ad371
Author | SHA1 | Date | |
---|---|---|---|
|
d7741ad371 | ||
|
98ff656400 | ||
|
adacdebb9f | ||
|
334fffc31f | ||
|
05365effeb |
@ -51,7 +51,6 @@ public class DigiplexBindingConstants {
|
|||||||
public static final String BRIDGE_MESSAGES_SENT = "statistics#messages_sent";
|
public static final String BRIDGE_MESSAGES_SENT = "statistics#messages_sent";
|
||||||
public static final String BRIDGE_RESPONSES_RECEIVED = "statistics#responses_received";
|
public static final String BRIDGE_RESPONSES_RECEIVED = "statistics#responses_received";
|
||||||
public static final String BRIDGE_EVENTS_RECEIVED = "statistics#events_received";
|
public static final String BRIDGE_EVENTS_RECEIVED = "statistics#events_received";
|
||||||
|
|
||||||
public static final String BRIDGE_TLM_TROUBLE = "troubles#tlm_trouble";
|
public static final String BRIDGE_TLM_TROUBLE = "troubles#tlm_trouble";
|
||||||
public static final String BRIDGE_AC_FAILURE = "troubles#ac_failure";
|
public static final String BRIDGE_AC_FAILURE = "troubles#ac_failure";
|
||||||
public static final String BRIDGE_BATTERY_FAILURE = "troubles#battery_failure";
|
public static final String BRIDGE_BATTERY_FAILURE = "troubles#battery_failure";
|
||||||
|
@ -52,6 +52,9 @@ public interface DigiplexMessageHandler {
|
|||||||
default void handleUnknownResponse(UnknownResponse response) {
|
default void handleUnknownResponse(UnknownResponse response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void handleErroneousResponse(ErroneousResponse response) {
|
||||||
|
}
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
default void handleZoneEvent(ZoneEvent event) {
|
default void handleZoneEvent(ZoneEvent event) {
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
package org.openhab.binding.digiplex.internal.communication;
|
package org.openhab.binding.digiplex.internal.communication;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.AreaEvent;
|
import org.openhab.binding.digiplex.internal.communication.events.AreaEvent;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.AreaEventType;
|
import org.openhab.binding.digiplex.internal.communication.events.AreaEventType;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.GenericEvent;
|
import org.openhab.binding.digiplex.internal.communication.events.GenericEvent;
|
||||||
@ -29,21 +30,19 @@ import org.openhab.binding.digiplex.internal.communication.events.ZoneStatusEven
|
|||||||
* Resolves serial messages to appropriate classes
|
* Resolves serial messages to appropriate classes
|
||||||
*
|
*
|
||||||
* @author Robert Michalak - Initial contribution
|
* @author Robert Michalak - Initial contribution
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class DigiplexResponseResolver {
|
public class DigiplexResponseResolver {
|
||||||
|
|
||||||
private static final String OK = "&ok";
|
private static final String OK = "&ok";
|
||||||
// TODO: handle failures
|
|
||||||
private static final String FAIL = "&fail";
|
private static final String FAIL = "&fail";
|
||||||
|
|
||||||
public static DigiplexResponse resolveResponse(String message) {
|
public static DigiplexResponse resolveResponse(String message) {
|
||||||
if (message.length() < 4) { // sanity check: try to filter out malformed responses
|
if (message.length() < 4) { // sanity check: try to filter out malformed responses
|
||||||
return new UnknownResponse(message);
|
return new ErroneousResponse(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
int zoneNo, areaNo;
|
Integer zoneNo, areaNo;
|
||||||
String commandType = message.substring(0, 2);
|
String commandType = message.substring(0, 2);
|
||||||
switch (commandType) {
|
switch (commandType) {
|
||||||
case "CO": // communication status
|
case "CO": // communication status
|
||||||
@ -53,24 +52,36 @@ public class DigiplexResponseResolver {
|
|||||||
return CommunicationStatus.OK;
|
return CommunicationStatus.OK;
|
||||||
}
|
}
|
||||||
case "ZL": // zone label
|
case "ZL": // zone label
|
||||||
zoneNo = Integer.valueOf(message.substring(2, 5));
|
zoneNo = getZoneOrArea(message);
|
||||||
|
if (zoneNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return ZoneLabelResponse.failure(zoneNo);
|
return ZoneLabelResponse.failure(zoneNo);
|
||||||
} else {
|
} else {
|
||||||
return ZoneLabelResponse.success(zoneNo, message.substring(5).trim());
|
return ZoneLabelResponse.success(zoneNo, message.substring(5).trim());
|
||||||
}
|
}
|
||||||
case "AL": // area label
|
case "AL": // area label
|
||||||
areaNo = Integer.valueOf(message.substring(2, 5));
|
areaNo = getZoneOrArea(message);
|
||||||
|
if (areaNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return AreaLabelResponse.failure(areaNo);
|
return AreaLabelResponse.failure(areaNo);
|
||||||
} else {
|
} else {
|
||||||
return AreaLabelResponse.success(areaNo, message.substring(5).trim());
|
return AreaLabelResponse.success(areaNo, message.substring(5).trim());
|
||||||
}
|
}
|
||||||
case "RZ": // zone status
|
case "RZ": // zone status
|
||||||
zoneNo = Integer.valueOf(message.substring(2, 5));
|
zoneNo = getZoneOrArea(message);
|
||||||
|
if (zoneNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return ZoneStatusResponse.failure(zoneNo);
|
return ZoneStatusResponse.failure(zoneNo);
|
||||||
} else {
|
} else {
|
||||||
|
if (message.length() < 10) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
return ZoneStatusResponse.success(zoneNo, // zone number
|
return ZoneStatusResponse.success(zoneNo, // zone number
|
||||||
ZoneStatus.fromMessage(message.charAt(5)), // status
|
ZoneStatus.fromMessage(message.charAt(5)), // status
|
||||||
toBoolean(message.charAt(6)), // alarm
|
toBoolean(message.charAt(6)), // alarm
|
||||||
@ -79,10 +90,16 @@ public class DigiplexResponseResolver {
|
|||||||
toBoolean(message.charAt(9))); // battery low
|
toBoolean(message.charAt(9))); // battery low
|
||||||
}
|
}
|
||||||
case "RA": // area status
|
case "RA": // area status
|
||||||
areaNo = Integer.valueOf(message.substring(2, 5));
|
areaNo = getZoneOrArea(message);
|
||||||
|
if (areaNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return AreaStatusResponse.failure(areaNo);
|
return AreaStatusResponse.failure(areaNo);
|
||||||
} else {
|
} else {
|
||||||
|
if (message.length() < 12) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
return AreaStatusResponse.success(areaNo, // zone number
|
return AreaStatusResponse.success(areaNo, // zone number
|
||||||
AreaStatus.fromMessage(message.charAt(5)), // status
|
AreaStatus.fromMessage(message.charAt(5)), // status
|
||||||
toBoolean(message.charAt(6)), // zone in memory
|
toBoolean(message.charAt(6)), // zone in memory
|
||||||
@ -95,7 +112,10 @@ public class DigiplexResponseResolver {
|
|||||||
case "AA": // area arm
|
case "AA": // area arm
|
||||||
case "AQ": // area quick arm
|
case "AQ": // area quick arm
|
||||||
case "AD": // area disarm
|
case "AD": // area disarm
|
||||||
areaNo = Integer.valueOf(message.substring(2, 5));
|
areaNo = getZoneOrArea(message);
|
||||||
|
if (areaNo == null) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
if (message.contains(FAIL)) {
|
if (message.contains(FAIL)) {
|
||||||
return AreaArmDisarmResponse.failure(areaNo, ArmDisarmType.fromMessage(commandType));
|
return AreaArmDisarmResponse.failure(areaNo, ArmDisarmType.fromMessage(commandType));
|
||||||
} else {
|
} else {
|
||||||
@ -105,21 +125,41 @@ public class DigiplexResponseResolver {
|
|||||||
case "PG": // PGM events
|
case "PG": // PGM events
|
||||||
default:
|
default:
|
||||||
if (message.startsWith("G")) {
|
if (message.startsWith("G")) {
|
||||||
return resolveSystemEvent(message);
|
if (message.length() >= 12) {
|
||||||
|
return resolveSystemEvent(message);
|
||||||
|
} else {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return new UnknownResponse(message);
|
return new UnknownResponse(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @Nullable Integer getZoneOrArea(String message) {
|
||||||
|
if (message.length() < 5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Integer.valueOf(message.substring(2, 5));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean toBoolean(char value) {
|
private static boolean toBoolean(char value) {
|
||||||
return value != 'O';
|
return value != 'O';
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DigiplexResponse resolveSystemEvent(String message) {
|
private static DigiplexResponse resolveSystemEvent(String message) {
|
||||||
int eventGroup = Integer.parseInt(message.substring(1, 4));
|
int eventGroup, eventNumber, areaNumber;
|
||||||
int eventNumber = Integer.parseInt(message.substring(5, 8));
|
try {
|
||||||
int areaNumber = Integer.parseInt(message.substring(9, 12));
|
eventGroup = Integer.parseInt(message.substring(1, 4));
|
||||||
|
eventNumber = Integer.parseInt(message.substring(5, 8));
|
||||||
|
areaNumber = Integer.parseInt(message.substring(9, 12));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return new ErroneousResponse(message);
|
||||||
|
}
|
||||||
switch (eventGroup) {
|
switch (eventGroup) {
|
||||||
case 0:
|
case 0:
|
||||||
return new ZoneStatusEvent(eventNumber, ZoneStatus.CLOSED, areaNumber);
|
return new ZoneStatusEvent(eventNumber, ZoneStatus.CLOSED, areaNumber);
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.digiplex.internal.communication;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erroneous message from PRT3.
|
||||||
|
*
|
||||||
|
* Message that is invalid, which happens sometimes due to communication errors.
|
||||||
|
*
|
||||||
|
* @author Robert Michalak - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ErroneousResponse implements DigiplexResponse {
|
||||||
|
|
||||||
|
public final String message;
|
||||||
|
|
||||||
|
public ErroneousResponse(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(DigiplexMessageHandler visitor) {
|
||||||
|
visitor.handleErroneousResponse(this);
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,9 @@ package org.openhab.binding.digiplex.internal.communication;
|
|||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unknown message from PRT3
|
* Unknown message from PRT3.
|
||||||
|
*
|
||||||
|
* Message that is otherwise valid, but not handled in this binding.
|
||||||
*
|
*
|
||||||
* @author Robert Michalak - Initial contribution
|
* @author Robert Michalak - Initial contribution
|
||||||
*
|
*
|
||||||
|
@ -38,6 +38,7 @@ import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandle
|
|||||||
import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
|
import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
|
||||||
import org.openhab.binding.digiplex.internal.communication.DigiplexResponse;
|
import org.openhab.binding.digiplex.internal.communication.DigiplexResponse;
|
||||||
import org.openhab.binding.digiplex.internal.communication.DigiplexResponseResolver;
|
import org.openhab.binding.digiplex.internal.communication.DigiplexResponseResolver;
|
||||||
|
import org.openhab.binding.digiplex.internal.communication.ErroneousResponse;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.AbstractEvent;
|
import org.openhab.binding.digiplex.internal.communication.events.AbstractEvent;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.TroubleEvent;
|
import org.openhab.binding.digiplex.internal.communication.events.TroubleEvent;
|
||||||
import org.openhab.binding.digiplex.internal.communication.events.TroubleStatus;
|
import org.openhab.binding.digiplex.internal.communication.events.TroubleStatus;
|
||||||
@ -295,6 +296,12 @@ public class DigiplexBridgeHandler extends BaseBridgeHandler implements SerialPo
|
|||||||
updateState(channel, state);
|
updateState(channel, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleErroneousResponse(ErroneousResponse response) {
|
||||||
|
logger.debug("Erroneous response: {}", response.message);
|
||||||
|
handleCommunicationError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DigiplexReceiverThread extends Thread {
|
private class DigiplexReceiverThread extends Thread {
|
||||||
|
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* 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.digiplex.internal.communication;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.openhab.binding.digiplex.internal.communication.events.GenericEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link DigiplexResponseResolver}
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class DigiplexResponseResolverTest {
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsErroneousResponseWhenMessageIsMalformed")
|
||||||
|
void resolveResponseReturnsErroneousResponseWhenMessageIsMalformed(String message) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(ErroneousResponse.class)));
|
||||||
|
if (actual instanceof ErroneousResponse erroneousResponse) {
|
||||||
|
assertThat(erroneousResponse.message, is(equalTo(message)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsErroneousResponseWhenMessageIsMalformed() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("CO&"), Arguments.of("ZL&fail"), Arguments.of("ZL12"), Arguments.of("AL&fail"),
|
||||||
|
Arguments.of("AL12"), Arguments.of("RZZZ3COOOO&fail"), Arguments.of("RZ123C"),
|
||||||
|
Arguments.of("RZ123COOO"), Arguments.of("RA&fail"), Arguments.of("RA123DOOXOO"),
|
||||||
|
Arguments.of("AA&fail"), Arguments.of("GGGGGGGGGGGG"), Arguments.of("G1234567890"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveResponseReturnsCommunicationStatusSuccessWhenWellformed() {
|
||||||
|
String message = "CO&ok";
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(CommunicationStatus.class)));
|
||||||
|
if (actual instanceof CommunicationStatus communicationStatus) {
|
||||||
|
assertThat(communicationStatus.success, is(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveResponseReturnsCommunicationStatusFailureWhenMessageContainsFail() {
|
||||||
|
String message = "CO&fail";
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(CommunicationStatus.class)));
|
||||||
|
if (actual instanceof CommunicationStatus communicationStatus) {
|
||||||
|
assertThat(communicationStatus.success, is(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsZoneLabelResponse")
|
||||||
|
void resolveResponseReturnsZoneLabelResponse(String message, boolean expectedSuccess, int expectedZoneNo,
|
||||||
|
String expectedName) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(ZoneLabelResponse.class)));
|
||||||
|
if (actual instanceof ZoneLabelResponse zoneLabelResponse) {
|
||||||
|
assertThat(zoneLabelResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(zoneLabelResponse.zoneNo, is(expectedZoneNo));
|
||||||
|
assertThat(zoneLabelResponse.zoneName, is(expectedName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsZoneLabelResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("ZL123", true, 123, ""), Arguments.of("ZL123test ", true, 123, "test"),
|
||||||
|
Arguments.of("ZL123&fail", false, 123, null), Arguments.of("ZL123test&fail", false, 123, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsAreaLabelResponse")
|
||||||
|
void resolveResponseReturnsAreaLabelResponse(String message, boolean expectedSuccess, int expectedAreaNo,
|
||||||
|
String expectedName) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(AreaLabelResponse.class)));
|
||||||
|
if (actual instanceof AreaLabelResponse areaLabelResponse) {
|
||||||
|
assertThat(areaLabelResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(areaLabelResponse.areaNo, is(expectedAreaNo));
|
||||||
|
assertThat(areaLabelResponse.areaName, is(expectedName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsAreaLabelResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("AL123", true, 123, ""), Arguments.of("AL123test ", true, 123, "test"),
|
||||||
|
Arguments.of("AL123&fail", false, 123, null), Arguments.of("AL123test&fail", false, 123, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsZoneStatusResponse")
|
||||||
|
void resolveResponseReturnsZoneStatusResponse(String message, boolean expectedSuccess, int expectedZoneNo,
|
||||||
|
ZoneStatus expectedZoneStatus, boolean expectedAlarm, boolean expectedFireAlarm,
|
||||||
|
boolean expectedSupervisionLost, boolean expectedLowBattery) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(ZoneStatusResponse.class)));
|
||||||
|
if (actual instanceof ZoneStatusResponse zoneStatusResponse) {
|
||||||
|
assertThat(zoneStatusResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(zoneStatusResponse.zoneNo, is(expectedZoneNo));
|
||||||
|
assertThat(zoneStatusResponse.status, is(expectedZoneStatus));
|
||||||
|
assertThat(zoneStatusResponse.alarm, is(expectedAlarm));
|
||||||
|
assertThat(zoneStatusResponse.fireAlarm, is(expectedFireAlarm));
|
||||||
|
assertThat(zoneStatusResponse.supervisionLost, is(expectedSupervisionLost));
|
||||||
|
assertThat(zoneStatusResponse.lowBattery, is(expectedLowBattery));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsZoneStatusResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("RZ123COOOO", true, 123, ZoneStatus.CLOSED, false, false, false, false),
|
||||||
|
Arguments.of("RZ123OOOOO", true, 123, ZoneStatus.OPEN, false, false, false, false),
|
||||||
|
Arguments.of("RZ123TOOOO", true, 123, ZoneStatus.TAMPERED, false, false, false, false),
|
||||||
|
Arguments.of("RZ123FOOOO", true, 123, ZoneStatus.FIRE_LOOP_TROUBLE, false, false, false, false),
|
||||||
|
Arguments.of("RZ123uOOOO", true, 123, ZoneStatus.UNKNOWN, false, false, false, false),
|
||||||
|
Arguments.of("RZ123cOOOO", true, 123, ZoneStatus.UNKNOWN, false, false, false, false),
|
||||||
|
Arguments.of("RZ123cXOOO", true, 123, ZoneStatus.UNKNOWN, true, false, false, false),
|
||||||
|
Arguments.of("RZ123cOXOO", true, 123, ZoneStatus.UNKNOWN, false, true, false, false),
|
||||||
|
Arguments.of("RZ123cOOXO", true, 123, ZoneStatus.UNKNOWN, false, false, true, false),
|
||||||
|
Arguments.of("RZ123cOOOX", true, 123, ZoneStatus.UNKNOWN, false, false, false, true),
|
||||||
|
Arguments.of("RZ123&fail", false, 123, null, false, false, false, false),
|
||||||
|
Arguments.of("RZ123COOOO&fail", false, 123, null, false, false, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsAreaStatusResponse")
|
||||||
|
void resolveResponseReturnsAreaStatusResponse(String message, boolean expectedSuccess, int expectedAreaNo,
|
||||||
|
AreaStatus expectedAreaStatus, boolean expectedZoneInMemory, boolean expectedTrouble, boolean expectedReady,
|
||||||
|
boolean expectedInProgramming, boolean expectedAlarm, boolean expectedStrobe) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(AreaStatusResponse.class)));
|
||||||
|
if (actual instanceof AreaStatusResponse areaStatusResponse) {
|
||||||
|
assertThat(areaStatusResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(areaStatusResponse.areaNo, is(expectedAreaNo));
|
||||||
|
assertThat(areaStatusResponse.status, is(expectedAreaStatus));
|
||||||
|
assertThat(areaStatusResponse.zoneInMemory, is(expectedZoneInMemory));
|
||||||
|
assertThat(areaStatusResponse.trouble, is(expectedTrouble));
|
||||||
|
assertThat(areaStatusResponse.ready, is(expectedReady));
|
||||||
|
assertThat(areaStatusResponse.inProgramming, is(expectedInProgramming));
|
||||||
|
assertThat(areaStatusResponse.alarm, is(expectedAlarm));
|
||||||
|
assertThat(areaStatusResponse.strobe, is(expectedStrobe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsAreaStatusResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("RA123DOOXOOO", true, 123, AreaStatus.DISARMED, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123AOOXOOO", true, 123, AreaStatus.ARMED, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123FOOXOOO", true, 123, AreaStatus.ARMED_FORCE, false, false, false, false, false,
|
||||||
|
false),
|
||||||
|
Arguments.of("RA123SOOXOOO", true, 123, AreaStatus.ARMED_STAY, false, false, false, false, false,
|
||||||
|
false),
|
||||||
|
Arguments.of("RA123IOOXOOO", true, 123, AreaStatus.ARMED_INSTANT, false, false, false, false, false,
|
||||||
|
false),
|
||||||
|
Arguments.of("RA123uOOXOOO", true, 123, AreaStatus.UNKNOWN, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123dOOXOOO", true, 123, AreaStatus.UNKNOWN, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123dXOXOOO", true, 123, AreaStatus.UNKNOWN, true, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123dOXxOOO", true, 123, AreaStatus.UNKNOWN, false, true, false, false, false, false),
|
||||||
|
Arguments.of("RA123dOOOOOO", true, 123, AreaStatus.UNKNOWN, false, false, true, false, false, false),
|
||||||
|
Arguments.of("RA123dOOXXOO", true, 123, AreaStatus.UNKNOWN, false, false, false, true, false, false),
|
||||||
|
Arguments.of("RA123dOOXOXO", true, 123, AreaStatus.UNKNOWN, false, false, false, false, true, false),
|
||||||
|
Arguments.of("RA123dOOXOOX", true, 123, AreaStatus.UNKNOWN, false, false, false, false, false, true),
|
||||||
|
Arguments.of("RA123&fail", false, 123, null, false, false, false, false, false, false),
|
||||||
|
Arguments.of("RA123DOOXOOO&fail", false, 123, null, false, false, false, false, false, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideTestCasesForResolveResponseReturnsAreaArmDisarmResponse")
|
||||||
|
void resolveResponseReturnsAreaArmDisarmResponse(String message, boolean expectedSuccess, int expectedAreaNo,
|
||||||
|
ArmDisarmType expectedType) {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse(message);
|
||||||
|
assertThat(actual, is(instanceOf(AreaArmDisarmResponse.class)));
|
||||||
|
if (actual instanceof AreaArmDisarmResponse armDisarmResponse) {
|
||||||
|
assertThat(armDisarmResponse.success, is(expectedSuccess));
|
||||||
|
assertThat(armDisarmResponse.areaNo, is(expectedAreaNo));
|
||||||
|
assertThat(armDisarmResponse.type, is(expectedType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> provideTestCasesForResolveResponseReturnsAreaArmDisarmResponse() {
|
||||||
|
return Stream.of( //
|
||||||
|
Arguments.of("AA123", true, 123, ArmDisarmType.ARM),
|
||||||
|
Arguments.of("AQ123", true, 123, ArmDisarmType.QUICK_ARM),
|
||||||
|
Arguments.of("AD123", true, 123, ArmDisarmType.DISARM),
|
||||||
|
Arguments.of("AA123&fail", false, 123, ArmDisarmType.ARM),
|
||||||
|
Arguments.of("AQ123&fail", false, 123, ArmDisarmType.QUICK_ARM),
|
||||||
|
Arguments.of("AD123&fail", false, 123, ArmDisarmType.DISARM));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveResponseReturnsGenericEventWhenWellformed() {
|
||||||
|
DigiplexResponse actual = DigiplexResponseResolver.resolveResponse("G123 456 789");
|
||||||
|
assertThat(actual, is(instanceOf(GenericEvent.class)));
|
||||||
|
if (actual instanceof GenericEvent genericEvent) {
|
||||||
|
assertThat(genericEvent.getEventGroup(), is(123));
|
||||||
|
assertThat(genericEvent.getEventNumber(), is(456));
|
||||||
|
assertThat(genericEvent.getAreaNo(), is(789));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -96,23 +96,25 @@ org.openhab.homekit:blockUserDeletion=false
|
|||||||
org.openhab.homekit:name=openHAB
|
org.openhab.homekit:name=openHAB
|
||||||
org.openhab.homekit:instances=1
|
org.openhab.homekit:instances=1
|
||||||
org.openhab.homekit:useDummyAccessories=false
|
org.openhab.homekit:useDummyAccessories=false
|
||||||
|
org.openhab.homekit:listenToNetworkChanges=true
|
||||||
```
|
```
|
||||||
|
|
||||||
Some settings are only visible in UI if the checkbox "Show advanced" is activated.
|
Some settings are only visible in UI if the checkbox "Show advanced" is activated.
|
||||||
|
|
||||||
### Overview of all settings
|
### Overview of all settings
|
||||||
|
|
||||||
| Setting | Description | Default value |
|
| Setting | Description | Default value |
|
||||||
|:-------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------|
|
|:--------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|
|
||||||
| networkInterface | IP address or domain name under which the HomeKit bridge can be reached. If no value is configured, the add-on uses the primary IP address configured for openHAB. If unsure, keep it empty | (none) |
|
| networkInterface | IP address or domain name under which the HomeKit bridge can be reached. If no value is configured, the add-on uses the primary IP address configured for openHAB. If unsure, keep it empty | (none) |
|
||||||
| port | Port under which the HomeKit bridge can be reached. | 9123 |
|
| port | Port under which the HomeKit bridge can be reached. | 9123 |
|
||||||
| useOHmDNS | mDNS service is used to advertise openHAB as HomeKit bridge in the network so that HomeKit clients can find it. openHAB has already mDNS service running. This option defines whether the mDNS service of openHAB or a separate service should be used. | false |
|
| useOHmDNS | mDNS service is used to advertise openHAB as HomeKit bridge in the network so that HomeKit clients can find it. openHAB has already mDNS service running. This option defines whether the mDNS service of openHAB or a separate service should be used. | false |
|
||||||
| blockUserDeletion | Blocks HomeKit user deletion in openHAB and as result unpairing of devices. If you experience an issue with accessories becoming non-responsive after some time, try to enable this setting. You can also enable this setting if your HomeKit setup is done and you will not re-pair ios devices. | false |
|
| blockUserDeletion | Blocks HomeKit user deletion in openHAB and as result unpairing of devices. If you experience an issue with accessories becoming non-responsive after some time, try to enable this setting. You can also enable this setting if your HomeKit setup is done and you will not re-pair ios devices. | false |
|
||||||
| pin | Pin code used for pairing with iOS devices. Apparently, pin codes are provided by Apple and represent specific device types, so they cannot be chosen freely. The pin code 031-45-154 is used in sample applications and known to work. | 031-45-154 |
|
| pin | Pin code used for pairing with iOS devices. Apparently, pin codes are provided by Apple and represent specific device types, so they cannot be chosen freely. The pin code 031-45-154 is used in sample applications and known to work. | 031-45-154 |
|
||||||
| useFahrenheitTemperature | Set to true to use Fahrenheit degrees, or false to use Celsius degrees. Note if an item has a QuantityType as its state, this configuration is ignored and it's always converted properly. | false |
|
| useFahrenheitTemperature | Set to true to use Fahrenheit degrees, or false to use Celsius degrees. Note if an item has a QuantityType as its state, this configuration is ignored and it's always converted properly. | false |
|
||||||
| name | Name under which this HomeKit bridge is announced on the network. This is also the name displayed on the iOS device when searching for available bridges. | openHAB |
|
| name | Name under which this HomeKit bridge is announced on the network. This is also the name displayed on the iOS device when searching for available bridges. | openHAB |
|
||||||
| instances | Defines how many bridges to expose. Necessary if you have more than 149 accessories. Accessories must be assigned to additional instances via metadata. Additional bridges will use incrementing port numbers. | 1 |
|
| instances | Defines how many bridges to expose. Necessary if you have more than 149 accessories. Accessories must be assigned to additional instances via metadata. Additional bridges will use incrementing port numbers. | 1 |
|
||||||
| useDummyAccessories | When an accessory is missing, substitute a dummy in its place instead of removing it. See [Dummy Accessories](#dummy-accessories). | false |
|
| useDummyAccessories | When an accessory is missing, substitute a dummy in its place instead of removing it. See [Dummy Accessories](#dummy-accessories). | false |
|
||||||
|
| listenToNetworkChanges | Listen to network changes, e.g. changes of IP addresses, and restart HomeKit bridge. Disable in case of connectivity issues with HomeKit bridge. | true |
|
||||||
|
|
||||||
## Item Configuration
|
## Item Configuration
|
||||||
|
|
||||||
|
@ -412,10 +412,10 @@ public class HomekitImpl implements Homekit, NetworkAddressChangeListener, Ready
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void onChanged(final List<CidrAddress> added, final List<CidrAddress> removed) {
|
public synchronized void onChanged(final List<CidrAddress> added, final List<CidrAddress> removed) {
|
||||||
logger.trace("HomeKit bridge reacting on network interface changes.");
|
if (!started || !settings.listenToNetworkChanges) {
|
||||||
if (!started) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
logger.trace("HomeKit bridge reacting on network interface changes.");
|
||||||
removed.forEach(i -> {
|
removed.forEach(i -> {
|
||||||
logger.trace("removed interface {}", i.getAddress().toString());
|
logger.trace("removed interface {}", i.getAddress().toString());
|
||||||
if (i.getAddress().equals(networkInterface)) {
|
if (i.getAddress().equals(networkInterface)) {
|
||||||
|
@ -34,6 +34,7 @@ public class HomekitSettings {
|
|||||||
public boolean useFahrenheitTemperature = false;
|
public boolean useFahrenheitTemperature = false;
|
||||||
public boolean useOHmDNS = false;
|
public boolean useOHmDNS = false;
|
||||||
public boolean blockUserDeletion = false;
|
public boolean blockUserDeletion = false;
|
||||||
|
public boolean listenToNetworkChanges = true;
|
||||||
public String networkInterface;
|
public String networkInterface;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -69,6 +70,8 @@ public class HomekitSettings {
|
|||||||
return false;
|
return false;
|
||||||
} else if (!blockUserDeletion != other.blockUserDeletion) {
|
} else if (!blockUserDeletion != other.blockUserDeletion) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if (!listenToNetworkChanges != other.listenToNetworkChanges) {
|
||||||
|
return false;
|
||||||
} else if (!pin.equals(other.pin)) {
|
} else if (!pin.equals(other.pin)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (!setupId.equals(other.setupId)) {
|
} else if (!setupId.equals(other.setupId)) {
|
||||||
|
@ -81,5 +81,11 @@
|
|||||||
<default>false</default>
|
<default>false</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="listenToNetworkChanges" type="boolean" required="false" groupName="core">
|
||||||
|
<label>Listen to network changes</label>
|
||||||
|
<description>Listen to changes on the network interface, e.g. changing of IP address, and restart HomeKit bridge.</description>
|
||||||
|
<default>true</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
</config-description:config-descriptions>
|
</config-description:config-descriptions>
|
||||||
|
@ -30,3 +30,5 @@ io.config.homekit.useFahrenheitTemperature.label = Use Fahrenheit Temperature
|
|||||||
io.config.homekit.useFahrenheitTemperature.description = Defines whether or not to direct HomeKit clients to use fahrenheit temperatures instead of celsius.
|
io.config.homekit.useFahrenheitTemperature.description = Defines whether or not to direct HomeKit clients to use fahrenheit temperatures instead of celsius.
|
||||||
io.config.homekit.useOHmDNS.label = Use openHAB mDNS service
|
io.config.homekit.useOHmDNS.label = Use openHAB mDNS service
|
||||||
io.config.homekit.useOHmDNS.description = Defines whether mDNS service of openHAB or a separate instance of mDNS should be used.
|
io.config.homekit.useOHmDNS.description = Defines whether mDNS service of openHAB or a separate instance of mDNS should be used.
|
||||||
|
io.config.homekit.listenToNetworkChanges.label = Listen to network changes
|
||||||
|
io.config.homekit.listenToNetworkChanges.description = Listen to changes on the network interface, e.g. changing of IP address, and restart HomeKit bridge.
|
||||||
|
Loading…
Reference in New Issue
Block a user