- Battery
- Filter Maintenance
- Television
+- Irrigation System
## Quick start
- valve with timer:
```xtend
-Group gValve "Valve Group" {homekit="Valve" [homekitValveType="Irrigation"]}
+Group gValve "Valve Group" {homekit="Valve" [ValveType="Irrigation"]}
Switch valve_active "Valve active" (gValve) {homekit = "Valve.ActiveStatus, Valve.InUseStatus"}
Number valve_duration "Valve duration" (gValve) {homekit = "Valve.Duration"}
Number valve_remaining_duration "Valve remaining duration" (gValve) {homekit = "Valve.RemainingDuration"}
- valve without timer (no item for remaining duration required)
```xtend
-Group gValve "Valve Group" {homekit="Valve" [homekitValveType="Irrigation", homekitTimer="true"]}
+Group gValve "Valve Group" {homekit="Valve" [ValveType="Irrigation", homekitTimer="true"]}
Switch valve_active "Valve active" (gValve) {homekit = "Valve.ActiveStatus, Valve.InUseStatus"}
Number valve_duration "Valve duration" (gValve) {homekit = "Valve.Duration" [homekitDefaultDuration = 1800]}
```
+### Irrigation System
+
+An irrigation system is an accessory composed of multiple valves.
+You just need to link multiple valves within an irrigation system's group.
+When part of an irrigation system, valves are required to have Duration and RemainingDuration characteristics, as well as a ServiceIndex.
+The valve's types will also automatically be set to IRRIGATION.
+
+```java
+Group gIrrigationSystem "Irrigation System" { homekit="IrrigationSystem" }
+String irrigationSystemProgramMode (gIrrigationSystem) { homekit="ProgramMode" }
+Switch irrigationSystemEnabled (gIrrigationSystem) { homekit="Active" }
+Switch irrigationSystemInUse (gIrrigationSystem) { homekit="InUseStatus" }
+Group irrigationSystemTotalRemaining (gIrrigationSystem) { homekit="RemainingDuration" }
+
+Group gValve1 "Valve 1" (gIrrigationSystem) { homekit="Valve"[ServiceIndex=1] }
+Switch valve1Active (gValve1) { homekit="ActiveStatus" }
+Switch valve1InUse (gValve1) { homekit="InUseStatus" }
+Number valve1SetDuration (gValve1) { homekit="Duration" }
+Number valve1RemainingDuration (gValve1) { homekit="RemainingDuration" }
+
+Group gValve2 "Valve 2" (gIrrigationSystem) { homekit="Valve"[ServiceIndex=2] }
+Switch valve2Active (gValve2) { homekit="ActiveStatus" }
+Switch valve2InUse (gValve2) { homekit="InUseStatus" }
+Number valve2SetDuration (gValve2) { homekit="Duration" }
+Number valve2RemainingDuration (gValve2) { homekit="RemainingDuration" }
+```
+
### Sensors
Sensors have typically one mandatory characteristic, e.g. temperature or lead trigger, and several optional characteristics which are typically used for battery powered sensors and/or wireless sensors.
| | LockCurrentState | | Switch, Number | Current state of lock mechanism (1/ON=SECURED, 0/OFF=UNSECURED, 2=JAMMED, 3=UNKNOWN) |
| | LockTargetState | | Switch | Target state of lock mechanism (ON=SECURED, OFF=UNSECURED) |
| | | Name | String | Name of the lock |
-| Valve | | | | Valve. additional configuration: homekitValveType = ["Generic", "Irrigation", "Shower", "Faucet"] |
+| Valve | | | | Valve. additional configuration: ValveType = ["Generic", "Irrigation", "Shower", "Faucet"] |
| | ActiveStatus | | Switch, Dimmer | Accessory current working status. A value of "ON"/"OPEN" indicates that the accessory is active and is functioning without any errors. |
| | InUseStatus | | Switch, Dimmer | Indicates whether fluid flowing through the valve. A value of "ON"/"OPEN" indicates that fluid is flowing. |
| | | Duration | Number | Defines how long a valve should be set to ʼIn Useʼ in second. You can define the default duration via configuration homekitDefaultDuration = <default duration in seconds> |
| | | Volume | Dimmer, Number | Current volume. min/max/step can configured at item level, e.g. minValue=10.5, maxValue=50, step=2] |
| | | VolumeSelector | Dimmer, String | If linked do a dimmer item, will send INCREASE/DECREASE commands. If linked to a string item, will send INCREMENT and DECREMENT. |
| | | VolumeControlType | String | The type of control available. This will default to infer based on what other items are linked. NONE = status only, no control; RELATIVE = INCREMENT/DECREMENT only, no status; RELATIVE_WITH_CURRENT = INCREMENT/DECREMENT only with status; ABSOLUTE = direct status and control. Can also be configured via metadata, e.g. [VolumeControlType="ABSOLUTE"]. |
+| IrrigationSystem | | | | An accessory that represents multiple water valves and accommodates a programmed scheduled. |
+| | Active | | Switch | If the irrigation system as a whole is enabled. This must be ON if any of the valves are also enabled. |
+| | InUseStatus | | Switch | If the irrigation system as a whole is running. This must be ON if any of the valves are ON. |
+| | ProgramMode | | String | The current program mode of the irrigation system. Possible values (NO_SCHEDULED - no programs scheduled, SCHEDULED - program scheduled, SCHEDULED_MANUAL - program scheduled, currently overriden to manual mode). |
+| | | RemainingDuration | Number | The remaining duration for all scheduled valves in the current program in seconds. |
+| | | FaultStatus | Switch, Contact | Accessory fault status. "ON"/"OPEN" value indicates that the accessory has experienced a fault that may be interfering with its intended functionality. A value of "OFF"/"CLOSED" indicates that there is no fault. |
+
### Examples
INPUT_SOURCE("InputSource"),
TELEVISION_SPEAKER("TelevisionSpeaker"),
ACCESSORY_GROUP("AccessoryGroup"),
+ IRRIGATION_SYSTEM("IrrigationSystem"),
DUMMY("Dummy");
private static final Map<String, HomekitAccessoryType> TAG_MAP = new HashMap<>();
TARGET_VISIBILITY_STATE("TargetVisibilityState"),
VOLUME_SELECTOR("VolumeSelector"),
- VOLUME_CONTROL_TYPE("VolumeControlType");
+ VOLUME_CONTROL_TYPE("VolumeControlType"),
+
+ PROGRAM_MODE("ProgramMode"),
+ SERVICE_LABEL("ServiceLabel"),
+ SERVICE_INDEX("ServiceIndex");
private static final Map<String, HomekitCharacteristicType> TAG_MAP = new HashMap<>();
put(TELEVISION, new HomekitCharacteristicType[] { ACTIVE });
put(INPUT_SOURCE, new HomekitCharacteristicType[] {});
put(TELEVISION_SPEAKER, new HomekitCharacteristicType[] { MUTE });
+ put(IRRIGATION_SYSTEM, new HomekitCharacteristicType[] { ACTIVE, INUSE_STATUS, PROGRAM_MODE });
}
};
put(TELEVISION, HomekitTelevisionImpl.class);
put(INPUT_SOURCE, HomekitInputSourceImpl.class);
put(TELEVISION_SPEAKER, HomekitTelevisionSpeakerImpl.class);
+ put(IRRIGATION_SYSTEM, HomekitIrrigationSystemImpl.class);
}
};
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.io.homekit.internal.accessories;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
+import org.openhab.io.homekit.internal.HomekitCharacteristicType;
+import org.openhab.io.homekit.internal.HomekitSettings;
+import org.openhab.io.homekit.internal.HomekitTaggedItem;
+
+import io.github.hapjava.accessories.IrrigationSystemAccessory;
+import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
+import io.github.hapjava.characteristics.impl.common.ActiveEnum;
+import io.github.hapjava.characteristics.impl.common.InUseEnum;
+import io.github.hapjava.characteristics.impl.common.ProgramModeEnum;
+import io.github.hapjava.characteristics.impl.common.ServiceLabelNamespaceCharacteristic;
+import io.github.hapjava.characteristics.impl.common.ServiceLabelNamespaceEnum;
+import io.github.hapjava.services.impl.IrrigationSystemService;
+import io.github.hapjava.services.impl.ServiceLabelService;
+
+/**
+ * Implements an Irrigation System accessory.
+ *
+ * To be a complete accessory, the user must configure individual valves linked
+ * to this primary service. This class also adds the ServiceLabelService
+ * automatically.
+ *
+ * @author Cody Cutrer - Initial contribution
+ */
+@NonNullByDefault({})
+public class HomekitIrrigationSystemImpl extends AbstractHomekitAccessoryImpl implements IrrigationSystemAccessory {
+ private BooleanItemReader inUseReader;
+ private Map<ProgramModeEnum, String> programModeMap;
+ private static final String SERVICE_LABEL = "ServiceLabel";
+
+ public HomekitIrrigationSystemImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
+ HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
+ super(taggedItem, mandatoryCharacteristics, updater, settings);
+ inUseReader = createBooleanReader(HomekitCharacteristicType.INUSE_STATUS);
+ programModeMap = HomekitCharacteristicFactory
+ .createMapping(getCharacteristic(HomekitCharacteristicType.PROGRAM_MODE).get(), ProgramModeEnum.class);
+ getServices().add(new IrrigationSystemService(this));
+ }
+
+ @Override
+ public void init() {
+ String serviceLabelNamespaceConfig = getAccessoryConfiguration(SERVICE_LABEL, "ARABIC_NUMERALS");
+ ServiceLabelNamespaceEnum serviceLabelEnum;
+
+ try {
+ serviceLabelEnum = ServiceLabelNamespaceEnum.valueOf(serviceLabelNamespaceConfig.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ serviceLabelEnum = ServiceLabelNamespaceEnum.ARABIC_NUMERALS;
+ }
+ final var finalEnum = serviceLabelEnum;
+ var serviceLabelNamespace = getCharacteristic(ServiceLabelNamespaceCharacteristic.class).orElseGet(
+ () -> new ServiceLabelNamespaceCharacteristic(() -> CompletableFuture.completedFuture(finalEnum)));
+ getServices().add(new ServiceLabelService(serviceLabelNamespace));
+ }
+
+ @Override
+ public CompletableFuture<ActiveEnum> getActive() {
+ OnOffType state = getStateAs(HomekitCharacteristicType.ACTIVE, OnOffType.class);
+ return CompletableFuture.completedFuture(state == OnOffType.ON ? ActiveEnum.ACTIVE : ActiveEnum.INACTIVE);
+ }
+
+ @Override
+ public CompletableFuture<Void> setActive(ActiveEnum value) {
+ getCharacteristic(HomekitCharacteristicType.ACTIVE).ifPresent(tItem -> {
+ tItem.send(value == ActiveEnum.ACTIVE ? OnOffType.ON : OnOffType.OFF);
+ });
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public CompletableFuture<InUseEnum> getInUse() {
+ return CompletableFuture.completedFuture(inUseReader.getValue() ? InUseEnum.IN_USE : InUseEnum.NOT_IN_USE);
+ }
+
+ @Override
+ public CompletableFuture<ProgramModeEnum> getProgramMode() {
+ return CompletableFuture.completedFuture(getKeyFromMapping(HomekitCharacteristicType.PROGRAM_MODE,
+ programModeMap, ProgramModeEnum.NO_SCHEDULED));
+ }
+
+ @Override
+ public void subscribeActive(HomekitCharacteristicChangeCallback callback) {
+ subscribe(HomekitCharacteristicType.ACTIVE, callback);
+ }
+
+ @Override
+ public void unsubscribeActive() {
+ unsubscribe(HomekitCharacteristicType.ACTIVE);
+ }
+
+ @Override
+ public void subscribeInUse(HomekitCharacteristicChangeCallback callback) {
+ subscribe(HomekitCharacteristicType.INUSE_STATUS, callback);
+ }
+
+ @Override
+ public void unsubscribeInUse() {
+ unsubscribe(HomekitCharacteristicType.INUSE_STATUS);
+ }
+
+ @Override
+ public void subscribeProgramMode(HomekitCharacteristicChangeCallback callback) {
+ subscribe(HomekitCharacteristicType.PROGRAM_MODE, callback);
+ }
+
+ @Override
+ public void unsubscribeProgramMode() {
+ unsubscribe(HomekitCharacteristicType.PROGRAM_MODE);
+ }
+}
import io.github.hapjava.characteristics.impl.common.IsConfiguredCharacteristic;
import io.github.hapjava.characteristics.impl.common.IsConfiguredEnum;
import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
+import io.github.hapjava.characteristics.impl.common.ServiceLabelIndexCharacteristic;
import io.github.hapjava.characteristics.impl.heatercooler.CurrentHeaterCoolerStateCharacteristic;
import io.github.hapjava.characteristics.impl.heatercooler.CurrentHeaterCoolerStateEnum;
import io.github.hapjava.characteristics.impl.heatercooler.TargetHeaterCoolerStateCharacteristic;
put(INPUT_SOURCE_TYPE, HomekitMetadataCharacteristicFactory::createInputSourceTypeCharacteristic);
put(NAME, HomekitMetadataCharacteristicFactory::createNameCharacteristic);
put(PICTURE_MODE, HomekitMetadataCharacteristicFactory::createPictureModeCharacteristic);
+ put(SERVICE_INDEX, HomekitMetadataCharacteristicFactory::createServiceIndexCharacteristic);
put(SLEEP_DISCOVERY_MODE, HomekitMetadataCharacteristicFactory::createSleepDiscoveryModeCharacteristic);
put(TARGET_HEATER_COOLER_STATE,
HomekitMetadataCharacteristicFactory::createTargetHeaterCoolerStateCharacteristic);
});
}
+ private static Characteristic createServiceIndexCharacteristic(Object value) {
+ return new ServiceLabelIndexCharacteristic(getInteger(value));
+ }
+
private static Characteristic createSleepDiscoveryModeCharacteristic(Object value) {
return new SleepDiscoveryModeCharacteristic(getEnum(value, SleepDiscoveryModeEnum.class,
SleepDiscoveryModeEnum.ALWAYS_DISCOVERABLE, SleepDiscoveryModeEnum.NOT_DISCOVERABLE), v -> {
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import io.github.hapjava.accessories.HomekitAccessory;
import io.github.hapjava.accessories.ValveAccessory;
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
import io.github.hapjava.characteristics.impl.common.ActiveEnum;
*/
public class HomekitValveImpl extends AbstractHomekitAccessoryImpl implements ValveAccessory {
private final Logger logger = LoggerFactory.getLogger(HomekitValveImpl.class);
- private static final String CONFIG_VALVE_TYPE = "homekitValveType";
+ private static final String CONFIG_VALVE_TYPE = "ValveType";
+ private static final String CONFIG_VALVE_TYPE_DEPRECATED = "homekitValveType";
public static final String CONFIG_DEFAULT_DURATION = "homekitDefaultDuration";
private static final String CONFIG_TIMER = "homekitTimer";
private final ScheduledExecutorService timerService = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> valveTimer;
private final boolean homekitTimer;
+ private ValveTypeEnum valveType;
public HomekitValveImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
if (homekitTimer) {
addRemainingDurationCharacteristic(taggedItem, updater, service);
}
+ String valveTypeConfig = getAccessoryConfiguration(CONFIG_VALVE_TYPE, "GENERIC");
+ valveTypeConfig = getAccessoryConfiguration(CONFIG_VALVE_TYPE_DEPRECATED, valveTypeConfig);
+ var valveType = CONFIG_VALVE_TYPE_MAPPING.get(valveTypeConfig.toUpperCase());
+ this.valveType = valveType != null ? valveType : ValveTypeEnum.GENERIC;
}
private void addRemainingDurationCharacteristic(HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater,
@Override
public CompletableFuture<ValveTypeEnum> getValveType() {
- final String valveType = getAccessoryConfiguration(CONFIG_VALVE_TYPE, "GENERIC");
- ValveTypeEnum type = CONFIG_VALVE_TYPE_MAPPING.get(valveType.toUpperCase());
- return CompletableFuture.completedFuture(type != null ? type : ValveTypeEnum.GENERIC);
+ return CompletableFuture.completedFuture(valveType);
}
@Override
public void unsubscribeValveType() {
// nothing changes here
}
+
+ @Override
+ public boolean isLinkable(HomekitAccessory parentAccessory) {
+ // When part of an irrigation system, the valve type _must_ be irrigation.
+ if (parentAccessory instanceof HomekitIrrigationSystemImpl) {
+ valveType = ValveTypeEnum.IRRIGATION;
+ return true;
+ }
+ return false;
+ }
}