Compare commits

...

5 Commits

Author SHA1 Message Date
eugen
d7741ad371
Merge 334fffc31f into 98ff656400 2025-01-08 20:08:30 -07:00
Jacob Laursen
98ff656400
Fix headers (#18070)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
2025-01-08 23:25:39 +01:00
Robert Michalak
adacdebb9f
[digiplex] Handle erroneous responses and restart the bridge (#18035)
Signed-off-by: Robert Michalak <rbrt.michalak@gmail.com>
2025-01-08 22:21:07 +01:00
Eugen Freiter
334fffc31f add new line to properties file
Signed-off-by: Eugen Freiter <freiter@gmx.de>
2024-12-25 14:20:04 +01:00
Eugen Freiter
05365effeb add listen to network changes switch
Signed-off-by: Eugen Freiter <freiter@gmx.de>
2024-12-25 14:00:47 +01:00
12 changed files with 351 additions and 28 deletions

View File

@ -51,7 +51,6 @@ public class DigiplexBindingConstants {
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_EVENTS_RECEIVED = "statistics#events_received";
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_BATTERY_FAILURE = "troubles#battery_failure";

View File

@ -52,6 +52,9 @@ public interface DigiplexMessageHandler {
default void handleUnknownResponse(UnknownResponse response) {
}
default void handleErroneousResponse(ErroneousResponse response) {
}
// Events
default void handleZoneEvent(ZoneEvent event) {
}

View File

@ -13,6 +13,7 @@
package org.openhab.binding.digiplex.internal.communication;
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.AreaEventType;
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
*
* @author Robert Michalak - Initial contribution
*
*/
@NonNullByDefault
public class DigiplexResponseResolver {
private static final String OK = "&ok";
// TODO: handle failures
private static final String FAIL = "&fail";
public static DigiplexResponse resolveResponse(String message) {
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);
switch (commandType) {
case "CO": // communication status
@ -53,24 +52,36 @@ public class DigiplexResponseResolver {
return CommunicationStatus.OK;
}
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)) {
return ZoneLabelResponse.failure(zoneNo);
} else {
return ZoneLabelResponse.success(zoneNo, message.substring(5).trim());
}
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)) {
return AreaLabelResponse.failure(areaNo);
} else {
return AreaLabelResponse.success(areaNo, message.substring(5).trim());
}
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)) {
return ZoneStatusResponse.failure(zoneNo);
} else {
if (message.length() < 10) {
return new ErroneousResponse(message);
}
return ZoneStatusResponse.success(zoneNo, // zone number
ZoneStatus.fromMessage(message.charAt(5)), // status
toBoolean(message.charAt(6)), // alarm
@ -79,10 +90,16 @@ public class DigiplexResponseResolver {
toBoolean(message.charAt(9))); // battery low
}
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)) {
return AreaStatusResponse.failure(areaNo);
} else {
if (message.length() < 12) {
return new ErroneousResponse(message);
}
return AreaStatusResponse.success(areaNo, // zone number
AreaStatus.fromMessage(message.charAt(5)), // status
toBoolean(message.charAt(6)), // zone in memory
@ -95,7 +112,10 @@ public class DigiplexResponseResolver {
case "AA": // area arm
case "AQ": // area quick arm
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)) {
return AreaArmDisarmResponse.failure(areaNo, ArmDisarmType.fromMessage(commandType));
} else {
@ -105,21 +125,41 @@ public class DigiplexResponseResolver {
case "PG": // PGM events
default:
if (message.startsWith("G")) {
return resolveSystemEvent(message);
if (message.length() >= 12) {
return resolveSystemEvent(message);
} else {
return new ErroneousResponse(message);
}
} else {
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) {
return value != 'O';
}
private static DigiplexResponse resolveSystemEvent(String message) {
int eventGroup = Integer.parseInt(message.substring(1, 4));
int eventNumber = Integer.parseInt(message.substring(5, 8));
int areaNumber = Integer.parseInt(message.substring(9, 12));
int eventGroup, eventNumber, areaNumber;
try {
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) {
case 0:
return new ZoneStatusEvent(eventNumber, ZoneStatus.CLOSED, areaNumber);

View File

@ -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);
}
}

View File

@ -15,7 +15,9 @@ package org.openhab.binding.digiplex.internal.communication;
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
*

View File

@ -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.DigiplexResponse;
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.TroubleEvent;
import org.openhab.binding.digiplex.internal.communication.events.TroubleStatus;
@ -295,6 +296,12 @@ public class DigiplexBridgeHandler extends BaseBridgeHandler implements SerialPo
updateState(channel, state);
}
}
@Override
public void handleErroneousResponse(ErroneousResponse response) {
logger.debug("Erroneous response: {}", response.message);
handleCommunicationError();
}
}
private class DigiplexReceiverThread extends Thread {

View File

@ -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));
}
}
}

View File

@ -96,23 +96,25 @@ org.openhab.homekit:blockUserDeletion=false
org.openhab.homekit:name=openHAB
org.openhab.homekit:instances=1
org.openhab.homekit:useDummyAccessories=false
org.openhab.homekit:listenToNetworkChanges=true
```
Some settings are only visible in UI if the checkbox "Show advanced" is activated.
### Overview of all settings
| 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) |
| 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 |
| 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 |
| 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 |
| 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 |
| 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) |
| 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 |
| 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 |
| 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 |
| 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 |
| 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

View File

@ -412,10 +412,10 @@ public class HomekitImpl implements Homekit, NetworkAddressChangeListener, Ready
@Override
public synchronized void onChanged(final List<CidrAddress> added, final List<CidrAddress> removed) {
logger.trace("HomeKit bridge reacting on network interface changes.");
if (!started) {
if (!started || !settings.listenToNetworkChanges) {
return;
}
logger.trace("HomeKit bridge reacting on network interface changes.");
removed.forEach(i -> {
logger.trace("removed interface {}", i.getAddress().toString());
if (i.getAddress().equals(networkInterface)) {

View File

@ -34,6 +34,7 @@ public class HomekitSettings {
public boolean useFahrenheitTemperature = false;
public boolean useOHmDNS = false;
public boolean blockUserDeletion = false;
public boolean listenToNetworkChanges = true;
public String networkInterface;
@Override
@ -69,6 +70,8 @@ public class HomekitSettings {
return false;
} else if (!blockUserDeletion != other.blockUserDeletion) {
return false;
} else if (!listenToNetworkChanges != other.listenToNetworkChanges) {
return false;
} else if (!pin.equals(other.pin)) {
return false;
} else if (!setupId.equals(other.setupId)) {

View File

@ -81,5 +81,11 @@
<default>false</default>
<advanced>true</advanced>
</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-descriptions>

View File

@ -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.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.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.