diff --git a/CODEOWNERS b/CODEOWNERS index ca38e94d9a3e5..f9b28129999e5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -9,6 +9,7 @@ /bundles/org.openhab.automation.jsscripting/ @jpg0 /bundles/org.openhab.automation.jythonscripting/ @openhab/add-ons-maintainers /bundles/org.openhab.automation.pidcontroller/ @fwolter +/bundles/org.openhab.automation.pwm/ @fwolter /bundles/org.openhab.binding.adorne/ @theiding /bundles/org.openhab.binding.ahawastecollection/ @soenkekueper /bundles/org.openhab.binding.airq/ @aurelio1 diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index b183a255f0598..defb5cbeded19 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -36,6 +36,11 @@ org.openhab.automation.pidcontroller ${project.version} + + org.openhab.addons.bundles + org.openhab.automation.pwm + ${project.version} + org.openhab.addons.bundles org.openhab.binding.adorne diff --git a/bundles/org.openhab.automation.pwm/NOTICE b/bundles/org.openhab.automation.pwm/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.automation.pwm/README.md b/bundles/org.openhab.automation.pwm/README.md new file mode 100644 index 0000000000000..840b19df8715a --- /dev/null +++ b/bundles/org.openhab.automation.pwm/README.md @@ -0,0 +1,62 @@ +# Pulse Width Modulation (PWM) Automation + +This automation module implements [Pulse Width Modulation (PWM)](https://en.wikipedia.org/wiki/Pulse-width_modulation). + +PWM can be used to control actuators continuously from 0 to 100% that only support ON/OFF commands. +E.g. valves or heating burners. +It accomplishes that by switching the actuator on and off with a fixed interval. +The higher the control percentage (duty cycle), the longer the ON phase. + +Example: If you have an interval of 10 sec and the duty cycle is 30%, the output is ON for 3 sec and OFF for 7 sec. + +This module is **unsuitable** for controlling LED lights as the high PWM frequency can't be met. + +> Note: The module starts to work only if the duty cycle has been updated at least once. + +## Modules + +The PWM module can be used in openHAB's [rule engine](https://www.openhab.org/docs/configuration/rules-dsl.html). + +This automation provides a trigger module ("PWM triggers") with one input Item: `dutycycleItem` (0-100%). +The module calculates the ON/OFF state and returns it. +The return value is used to feed the Action module "Item Action" aka "send a command", which controls the actuator. + +To configure a rule, you need to add a Trigger ("PWM triggers") and an Action ("Item Action"). +Select the Item you like to control in the "Item Action" and leave the command empty. + +### Trigger + +| Name | Type | Description | Required | +|-----------------|---------|----------------------------------------------------------------------------------------------|----------| +| `dutycycleItem` | Item | The Item (PercentType) to read the duty cycle from | Yes | +| `interval` | Decimal | The constant interval in which the output is switch ON and OFF again in sec. | Yes | +| `minDutyCycle` | Decimal | Any duty cycle below this value will be increased to this value | No | +| `maxDutycycle` | Decimal | Any duty cycle above this value will be decreased to this value | No | +| `deadManSwitch` | Decimal | The output will be switched off, when the duty cycle is not updated within this time (in ms) | No | + +The duty cycle can be limited via the parameters `minDutycycle` and `maxDutyCycle`. +This is helpful if you need to maintain a minimum time between the switching of the output. +This is necessary for example for heating burners, which may not be switched on for very short times. +The on time is than increased to `minDutycycle`. +In this case one should also set a max duty cycle to prevent short off times. +It makes sense to apply these symmetrically e.g. 10%/90% or 20%/80%. + +If the duty cycle is 0% or 100%, the min/max parameters are ignored and the output is switched ON or OFF continuously. + +If the duty cycle Item is not updated within the dead-man switch timeout, the output is switched off, regardless of the current duty cycle. +The function can be used to save energy if the source of the duty cycle died for whatever reason and doesn't update the value anymore. +When the duty cycle is updated again, the module returns to normal operation. + +> Note: The min/max ON/OFF times set via `minDutycycle` and `maxDutycycle` are not met if the dead-man switch triggers and recovers fast. + +## Control Algorithm + +This module is designed to respond fast to duty cycle changes, but at the same time maintain a constant interval and also the min/max ON/OFF parameters. +For that reason, the module might seem to act peculiarly in some cases: + +- When the output is ON and the duty cycle is decreased, the output might switch off immediately, if applicable. +Example: The interval is 10 sec and the current duty cycle is 80%. +When the duty cycle is decreased to 20%, the output would switch off immediately, if it has been already ON for more than 2 sec. +- When the duty cycle is 0% for a short interval and then increased again, the output will only switch on when the new interval starts. +- When the duty cycle is 0% or 100% for more than a whole interval, a new interval will start as soon as the duty cycle is updated to a value other than 0%, respective 100%. +- The module starts to work only if the duty cycle Item has been updated at least once. diff --git a/bundles/org.openhab.automation.pwm/doc/statemachine.odg b/bundles/org.openhab.automation.pwm/doc/statemachine.odg new file mode 100644 index 0000000000000..be28e78e32ba2 Binary files /dev/null and b/bundles/org.openhab.automation.pwm/doc/statemachine.odg differ diff --git a/bundles/org.openhab.automation.pwm/doc/statemachine.png b/bundles/org.openhab.automation.pwm/doc/statemachine.png new file mode 100644 index 0000000000000..aa99d12bd492f Binary files /dev/null and b/bundles/org.openhab.automation.pwm/doc/statemachine.png differ diff --git a/bundles/org.openhab.automation.pwm/pom.xml b/bundles/org.openhab.automation.pwm/pom.xml new file mode 100644 index 0000000000000..d972adb157105 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.1.0-SNAPSHOT + + + org.openhab.automation.pwm + + openHAB Add-ons :: Bundles :: Automation :: PWM + + diff --git a/bundles/org.openhab.automation.pwm/src/main/feature/feature.xml b/bundles/org.openhab.automation.pwm/src/main/feature/feature.xml new file mode 100644 index 0000000000000..212e8c27b981d --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.automation.pwm/${project.version} + + diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/PWMConstants.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/PWMConstants.java new file mode 100644 index 0000000000000..e2072322a7939 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/PWMConstants.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Constants for the PWM automation module. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class PWMConstants { + public static final String AUTOMATION_NAME = "pwm"; + + public static final String CONFIG_DUTY_CYCLE_ITEM = "dutycycleItem"; + public static final String CONFIG_PERIOD = "interval"; + public static final String CONFIG_MIN_DUTYCYCLE = "minDutycycle"; + public static final String CONFIG_MAX_DUTYCYCLE = "maxDutycycle"; + public static final String CONFIG_COMMAND_ITEM = "command"; + public static final String CONFIG_DEAD_MAN_SWITCH = "deadManSwitch"; + public static final String CONFIG_OUTPUT_ITEM = "outputItem"; + public static final String INPUT = "input"; + public static final String OUTPUT = "command"; +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/PWMException.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/PWMException.java new file mode 100644 index 0000000000000..8b2f86b90a5ed --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/PWMException.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Common exception for the PWM automation module. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class PWMException extends Exception { + private static final long serialVersionUID = -3029834022610530982L; + + public PWMException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/factory/PWMModuleHandlerFactory.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/factory/PWMModuleHandlerFactory.java new file mode 100644 index 0000000000000..87e54e0bb8693 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/factory/PWMModuleHandlerFactory.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.factory; + +import java.util.Collection; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.automation.pwm.internal.handler.PWMTriggerHandler; +import org.openhab.core.automation.Module; +import org.openhab.core.automation.Trigger; +import org.openhab.core.automation.handler.BaseModuleHandlerFactory; +import org.openhab.core.automation.handler.ModuleHandler; +import org.openhab.core.automation.handler.ModuleHandlerFactory; +import org.openhab.core.items.ItemRegistry; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * Factory for the PWM automation module. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +@Component(service = ModuleHandlerFactory.class, configurationPid = "automation.pwm") +public class PWMModuleHandlerFactory extends BaseModuleHandlerFactory { + private static final Collection TYPES = Set.of(PWMTriggerHandler.MODULE_TYPE_ID); + private ItemRegistry itemRegistry; + private BundleContext bundleContext; + + @Activate + public PWMModuleHandlerFactory(@Reference ItemRegistry itemRegistry, BundleContext bundleContext) { + this.itemRegistry = itemRegistry; + this.bundleContext = bundleContext; + } + + @Override + public Collection getTypes() { + return TYPES; + } + + @Override + protected @Nullable ModuleHandler internalCreate(Module module, String ruleUID) { + switch (module.getTypeUID()) { + case PWMTriggerHandler.MODULE_TYPE_ID: + return new PWMTriggerHandler((Trigger) module, itemRegistry, bundleContext); + } + + return null; + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/PWMTriggerHandler.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/PWMTriggerHandler.java new file mode 100644 index 0000000000000..f5c1619841d64 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/PWMTriggerHandler.java @@ -0,0 +1,240 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.handler; + +import static org.openhab.automation.pwm.internal.PWMConstants.*; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.automation.pwm.internal.PWMException; +import org.openhab.automation.pwm.internal.handler.state.StateMachine; +import org.openhab.core.automation.ModuleHandlerCallback; +import org.openhab.core.automation.Trigger; +import org.openhab.core.automation.handler.BaseTriggerModuleHandler; +import org.openhab.core.automation.handler.TriggerHandlerCallback; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.events.Event; +import org.openhab.core.events.EventFilter; +import org.openhab.core.events.EventSubscriber; +import org.openhab.core.items.Item; +import org.openhab.core.items.ItemNotFoundException; +import org.openhab.core.items.ItemRegistry; +import org.openhab.core.items.events.ItemStateEvent; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents a Trigger module in the rules engine. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class PWMTriggerHandler extends BaseTriggerModuleHandler implements EventSubscriber { + public static final String MODULE_TYPE_ID = AUTOMATION_NAME + ".trigger"; + private static final Set SUBSCRIBED_EVENT_TYPES = Set.of(ItemStateEvent.TYPE); + private final Logger logger = LoggerFactory.getLogger(PWMTriggerHandler.class); + private final BundleContext bundleContext; + private final EventFilter eventFilter; + private final Optional minDutyCycle; + private final Optional maxDutyCycle; + private final Optional deadManSwitchTimeoutMs; + private final Item dutyCycleItem; + private @Nullable ServiceRegistration eventSubscriberRegistration; + private @Nullable ScheduledFuture deadMeanSwitchTimer; + private @Nullable StateMachine stateMachine; + + public PWMTriggerHandler(Trigger module, ItemRegistry itemRegistry, BundleContext bundleContext) { + super(module); + this.bundleContext = bundleContext; + + Configuration config = module.getConfiguration(); + + String dutycycleItemName = (String) Objects.requireNonNull(config.get(CONFIG_DUTY_CYCLE_ITEM), + "DutyCycle item is not set"); + + minDutyCycle = getOptionalDoubleFromConfig(config, CONFIG_MIN_DUTYCYCLE); + maxDutyCycle = getOptionalDoubleFromConfig(config, CONFIG_MAX_DUTYCYCLE); + deadManSwitchTimeoutMs = getOptionalDoubleFromConfig(config, CONFIG_DEAD_MAN_SWITCH); + + try { + dutyCycleItem = itemRegistry.getItem(dutycycleItemName); + } catch (ItemNotFoundException e) { + throw new IllegalArgumentException("Dutycycle item not found: " + dutycycleItemName, e); + } + + eventFilter = event -> event.getTopic().equals("openhab/items/" + dutycycleItemName + "/state"); + } + + @Override + public void setCallback(ModuleHandlerCallback callback) { + super.setCallback(callback); + + double periodSec = getDoubleFromConfig(module.getConfiguration(), CONFIG_PERIOD); + stateMachine = new StateMachine(getCallback().getScheduler(), this::setOutput, (long) (periodSec * 1000)); + + eventSubscriberRegistration = bundleContext.registerService(EventSubscriber.class.getName(), this, null); + } + + private double getDoubleFromConfig(Configuration config, String key) { + return ((BigDecimal) Objects.requireNonNull(config.get(key), key + " is not set")).doubleValue(); + } + + private Optional getOptionalDoubleFromConfig(Configuration config, String key) { + Object o = config.get(key); + + if (o instanceof BigDecimal) { + return Optional.of(((BigDecimal) o).doubleValue()); + } + + return Optional.empty(); + } + + @Override + public void receive(Event event) { + if (!(event instanceof ItemStateEvent)) { + return; + } + + ItemStateEvent changedEvent = (ItemStateEvent) event; + synchronized (this) { + try { + double newDutycycle = getDutyCycleValueInPercent(changedEvent.getItemState()); + double newDutycycleBeforeLimit = newDutycycle; + + restartDeadManSwitchTimer(); + + // set duty cycle to min duty cycle if it is smaller than min duty cycle + // set duty cycle to 0% if it is 0%, regardless of the min duty cycle + final double newDutyCycleFinal1 = newDutycycle; + newDutycycle = minDutyCycle.map(minDutycycle -> { + if (Math.round(newDutyCycleFinal1) <= 0) { + return 0d; + } else { + return Math.max(minDutycycle, newDutyCycleFinal1); + } + }).orElse(newDutycycle); + + // set duty cycle to 100% if the current duty cycle is larger than the max duty cycle + final double newDutyCycleFinal2 = newDutycycle; + newDutycycle = maxDutyCycle.map(maxDutycycle -> { + if (Math.round(newDutyCycleFinal2) >= maxDutycycle) { + return 100d; + } else { + return newDutyCycleFinal2; + } + }).orElse(newDutycycle); + + logger.debug("Received new duty cycle: {} {}", newDutycycleBeforeLimit, + newDutycycle != newDutycycleBeforeLimit ? "Limited to: " + newDutycycle : ""); + + StateMachine localStateMachine = stateMachine; + if (localStateMachine != null) { + localStateMachine.setDutycycle(newDutycycle); + } else { + logger.debug("Initialization not finished"); + } + } catch (PWMException e) { + logger.warn("{}", e.getMessage()); + } + } + } + + private void restartDeadManSwitchTimer() { + ScheduledFuture timer = deadMeanSwitchTimer; + if (timer != null) { + timer.cancel(true); + } + + deadManSwitchTimeoutMs.ifPresent(timeout -> { + deadMeanSwitchTimer = getCallback().getScheduler().schedule(this::activateDeadManSwitch, + timeout.longValue(), TimeUnit.MILLISECONDS); + }); + } + + private void activateDeadManSwitch() { + logger.warn("Dead-man switch activated. Disabling output"); + + StateMachine localStateMachine = stateMachine; + if (localStateMachine != null) { + localStateMachine.stop(); + } + } + + private void setOutput(boolean enable) { + getCallback().triggered(module, Collections.singletonMap(OUTPUT, OnOffType.from(enable))); + } + + private TriggerHandlerCallback getCallback() { + ModuleHandlerCallback localCallback = callback; + if (localCallback != null && localCallback instanceof TriggerHandlerCallback) { + return (TriggerHandlerCallback) localCallback; + } + + throw new IllegalStateException(); + } + + private double getDutyCycleValueInPercent(State state) throws PWMException { + if (state instanceof DecimalType) { + return ((DecimalType) state).doubleValue(); + } else if (state instanceof StringType) { + try { + return Integer.parseInt(state.toString()); + } catch (NumberFormatException e) { + // nothing + } + } else if (state instanceof UnDefType) { + throw new PWMException("Duty cycle item '" + dutyCycleItem.getName() + "' has no valid value"); + } + throw new PWMException("Duty cycle item not of type DecimalType: " + state.getClass().getSimpleName()); + } + + @Override + public Set getSubscribedEventTypes() { + return SUBSCRIBED_EVENT_TYPES; + } + + @Override + public @Nullable EventFilter getEventFilter() { + return eventFilter; + } + + @Override + public void dispose() { + ServiceRegistration localEventSubscriberRegistration = eventSubscriberRegistration; + if (localEventSubscriberRegistration != null) { + localEventSubscriberRegistration.unregister(); + } + + StateMachine localStateMachine = stateMachine; + if (localStateMachine != null) { + localStateMachine.stop(); + } + + super.dispose(); + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/AlwaysOffState.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/AlwaysOffState.java new file mode 100644 index 0000000000000..e8e21be7935c2 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/AlwaysOffState.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.handler.state; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Active when, the duty cycle is 0% for at least a whole period. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class AlwaysOffState extends State { + public AlwaysOffState(StateMachine context) { + super(context); + + controlOutput(false); + } + + @Override + public void dutyCycleChanged() { + if (Math.round(context.getDutycycle()) >= 100) { + nextState(DutycycleHundredState::new); + } else { + nextState(OnState::new); + } + } + + @Override + protected void dutyCycleUpdated() { + // in case we came here by the dead-man switch + if (Math.round(context.getDutycycle()) > 0) { + nextState(OnState::new); + } + } + + @Override + public void dispose() { + // nothing + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/AlwaysOnState.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/AlwaysOnState.java new file mode 100644 index 0000000000000..53d49c0947561 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/AlwaysOnState.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.handler.state; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Active when, the duty cycle is 100% for at least a whole period. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class AlwaysOnState extends State { + public AlwaysOnState(StateMachine context) { + super(context); + + controlOutput(true); + } + + @Override + public void dutyCycleChanged() { + nextState(OffState::new); + } + + @Override + protected void dutyCycleUpdated() { + // nothing + } + + @Override + public void dispose() { + // nothing + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/DutycycleHundredState.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/DutycycleHundredState.java new file mode 100644 index 0000000000000..121549c42c711 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/DutycycleHundredState.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.handler.state; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Active when, the PWM period ended with a duty cycle set to 100%. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class DutycycleHundredState extends State { + private ScheduledFuture periodTimer; + private @Nullable ScheduledFuture offTimer; + private Instant enabledAt = Instant.now(); + private boolean dutyCycleChanged; + + public DutycycleHundredState(StateMachine context) { + super(context); + + controlOutput(true); + + periodTimer = scheduler.schedule(this::periodEnded, context.getPeriodMs(), TimeUnit.MILLISECONDS); + } + + private void periodEnded() { + long dutycycleRounded = Math.round(context.getDutycycle()); + + if (!dutyCycleChanged && dutycycleRounded <= 0) { + nextState(AlwaysOffState::new); + } else if (!dutyCycleChanged && dutycycleRounded >= 100) { + nextState(AlwaysOnState::new); + } else { + nextState(OnState::new); + } + } + + @Override + public void dutyCycleChanged() { + dutyCycleChanged = true; + + long newOnTimeMs = calculateOnTimeMs(context.getDutycycle()); + long elapsedMs = enabledAt.until(Instant.now(), ChronoUnit.MILLIS); + + if (elapsedMs - newOnTimeMs > 0) { + controlOutput(false); + } else { + ScheduledFuture timer = offTimer; + if (timer != null) { + timer.cancel(false); + } + offTimer = scheduler.schedule(() -> controlOutput(false), newOnTimeMs - elapsedMs, TimeUnit.MILLISECONDS); + } + } + + @Override + protected void dutyCycleUpdated() { + // nothing + } + + @Override + public void dispose() { + periodTimer.cancel(false); + + ScheduledFuture timer = offTimer; + if (timer != null) { + timer.cancel(false); + } + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/DutycycleZeroState.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/DutycycleZeroState.java new file mode 100644 index 0000000000000..59e3a12508a09 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/DutycycleZeroState.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.handler.state; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Active when, the PWM period ended with a duty cycle set to 0%. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class DutycycleZeroState extends State { + private ScheduledFuture periodTimer; + + public DutycycleZeroState(StateMachine context) { + super(context); + + controlOutput(false); + + periodTimer = scheduler.schedule(this::periodEnded, context.getPeriodMs(), TimeUnit.MILLISECONDS); + } + + private void periodEnded() { + long dutycycleRounded = Math.round(context.getDutycycle()); + + if (dutycycleRounded <= 0) { + nextState(AlwaysOffState::new); + } else if (dutycycleRounded >= 100) { + nextState(DutycycleHundredState::new); + } else { + nextState(OnState::new); + } + } + + @Override + public void dutyCycleChanged() { + // nothing + } + + @Override + protected void dutyCycleUpdated() { + // nothing + } + + @Override + public void dispose() { + periodTimer.cancel(false); + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/OffState.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/OffState.java new file mode 100644 index 0000000000000..0762d2da6cda7 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/OffState.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.handler.state; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Active when, the output is currently OFF and the duty cycle is between 0% and 100% (exclusively). + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class OffState extends State { + ScheduledFuture offTimer; + + public OffState(StateMachine context) { + super(context); + + controlOutput(false); + + long offTimeMs = context.getPeriodMs() - calculateOnTimeMs(context.getDutycycle()); + offTimer = scheduler.schedule(this::periodEnded, offTimeMs, TimeUnit.MILLISECONDS); + } + + private void periodEnded() { + long dutycycleRounded = Math.round(context.getDutycycle()); + + if (dutycycleRounded <= 0) { + nextState(DutycycleZeroState::new); + } else if (dutycycleRounded >= 100) { + nextState(DutycycleHundredState::new); + } else { + nextState(OnState::new); + } + } + + @Override + public void dutyCycleChanged() { + // nothing + } + + @Override + protected void dutyCycleUpdated() { + // nothing + } + + @Override + public void dispose() { + offTimer.cancel(false); + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/OnState.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/OnState.java new file mode 100644 index 0000000000000..e1c22c24cd30a --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/OnState.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.handler.state; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Active when, the output is currently ON and the duty cycle is between 0% and 100% (exclusively). + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class OnState extends State { + private @NonNullByDefault({}) ScheduledFuture offTimer; + private Instant enabledAt = Instant.now(); + + public OnState(StateMachine context) { + super(context); + + context.controlOutput(true); + + startOnTimer(calculateOnTimeMs(context.getDutycycle())); + } + + private void startOnTimer(long timeMs) { + offTimer = scheduler.schedule(() -> { + if (Math.round(context.getDutycycle()) >= 100) { + nextState(DutycycleHundredState::new); + } else { + nextState(OffState::new); + } + }, timeMs, TimeUnit.MILLISECONDS); + } + + @Override + public void dutyCycleChanged() { + // end current ON phase prematurely or extend it if the new duty cycle demands it + offTimer.cancel(false); + + long newOnTimeMs = calculateOnTimeMs(context.getDutycycle()); + long elapsedMs = enabledAt.until(Instant.now(), ChronoUnit.MILLIS); + + if (elapsedMs - newOnTimeMs > 0) { + nextState(OffState::new); + } else { + startOnTimer(newOnTimeMs - elapsedMs); + } + } + + @Override + protected void dutyCycleUpdated() { + // nothing + } + + @Override + public void dispose() { + offTimer.cancel(false); + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/State.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/State.java new file mode 100644 index 0000000000000..2bf490b5e9aab --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/State.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.handler.state; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The base class of all states. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public abstract class State { + private final Logger logger = LoggerFactory.getLogger(State.class); + protected StateMachine context; + protected ScheduledExecutorService scheduler; + + public State(StateMachine context) { + this.context = context; + this.scheduler = context.getScheduler(); + } + + /** + * Invoked when the duty cycle updated and changed. + */ + public abstract void dutyCycleChanged(); + + /** + * Invoked when the duty cycle updated. + */ + protected abstract void dutyCycleUpdated(); + + public abstract void dispose(); + + /** + * Sets a new state in the state machine. + */ + public synchronized void nextState(Function nextState) { + if (context.getState() != this) { // compare identity + return; + } + + context.getState().dispose(); + State newState = nextState.apply(context); + + logger.trace("{} -> {}", context.getState().getClass().getSimpleName(), newState.getClass().getSimpleName()); + + context.setState(newState); + } + + /** + * Calculates the ON duration by the duty cycle. + * + * @param dutyCycleInPercent the duty cycle in percent + * @return the ON duration in ms + */ + protected long calculateOnTimeMs(double dutyCycleInPercent) { + return (long) (context.getPeriodMs() / 100 * dutyCycleInPercent); + } + + /** + * Switches the output on or off. + * + * @param on true, if the output shall be switched on. + */ + protected void controlOutput(boolean on) { + context.controlOutput(on); + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/StateMachine.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/StateMachine.java new file mode 100644 index 0000000000000..47c8454e5dfe8 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/StateMachine.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.handler.state; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The context of all states. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class StateMachine { + private ScheduledExecutorService scheduler; + private Consumer controlOutput; + private State state; + private long periodMs; + private double dutycycle; + + public StateMachine(ScheduledExecutorService scheduler, Consumer controlOutput, long periodMs) { + this.scheduler = scheduler; + this.controlOutput = controlOutput; + this.periodMs = periodMs; + this.state = new AlwaysOffState(this); + } + + public ScheduledExecutorService getScheduler() { + return scheduler; + } + + public void setDutycycle(double newDutycycle) { + if (dutycycle != newDutycycle) { + this.dutycycle = newDutycycle; + state.dutyCycleChanged(); + } + + state.dutyCycleUpdated(); + } + + public double getDutycycle() { + return dutycycle; + } + + public long getPeriodMs() { + return periodMs; + } + + public State getState() { + return state; + } + + public void setState(State current) { + this.state = current; + } + + public void controlOutput(boolean on) { + controlOutput.accept(on); + } + + public void reset() { + state.nextState(OnState::new); + } + + public void stop() { + state.nextState(AlwaysOffState::new); + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/template/PWMRuleTemplate.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/template/PWMRuleTemplate.java new file mode 100644 index 0000000000000..cf715d72c64aa --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/template/PWMRuleTemplate.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.template; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.automation.pwm.internal.PWMConstants; +import org.openhab.automation.pwm.internal.type.PWMTriggerType; +import org.openhab.core.automation.Action; +import org.openhab.core.automation.Condition; +import org.openhab.core.automation.Trigger; +import org.openhab.core.automation.Visibility; +import org.openhab.core.automation.template.RuleTemplate; +import org.openhab.core.automation.util.ModuleBuilder; +import org.openhab.core.config.core.ConfigDescriptionParameter; + +/** + * Rule template for the PWM automation module. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class PWMRuleTemplate extends RuleTemplate { + public static final String UID = "PWMRuleTemplate"; + + public static PWMRuleTemplate initialize() { + final String triggerId = UUID.randomUUID().toString(); + + final List triggers = Collections.singletonList(ModuleBuilder.createTrigger().withId(triggerId) + .withTypeUID(PWMTriggerType.UID).withLabel("PWM Trigger").build()); + + final Map actionInputs = new HashMap(); + actionInputs.put(PWMConstants.INPUT, triggerId + "." + PWMConstants.OUTPUT); + + Set tags = new HashSet(); + tags.add("PWM"); + + return new PWMRuleTemplate(tags, triggers, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList()); + } + + public PWMRuleTemplate(Set tags, List triggers, List conditions, List actions, + List configDescriptions) { + super(UID, "PWM", "Template for a PWM rule", tags, triggers, conditions, actions, configDescriptions, + Visibility.VISIBLE); + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/template/PWMTemplateProvider.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/template/PWMTemplateProvider.java new file mode 100644 index 0000000000000..87fc455d9f1a4 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/template/PWMTemplateProvider.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.template; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.automation.template.RuleTemplate; +import org.openhab.core.automation.template.RuleTemplateProvider; +import org.openhab.core.common.registry.ProviderChangeListener; +import org.osgi.service.component.annotations.Component; + +/** + * Rule template provider for the PWM automation module. + * + * @author Fabian Wolter - Initial Contribution + */ +@Component +@NonNullByDefault +public class PWMTemplateProvider implements RuleTemplateProvider { + private final Map providedRuleTemplates = new HashMap(); + + public PWMTemplateProvider() { + providedRuleTemplates.put(PWMRuleTemplate.UID, PWMRuleTemplate.initialize()); + } + + @Override + @Nullable + public RuleTemplate getTemplate(String UID, @Nullable Locale locale) { + return providedRuleTemplates.get(UID); + } + + @Override + public Collection getTemplates(@Nullable Locale locale) { + return providedRuleTemplates.values(); + } + + @Override + public void addProviderChangeListener(ProviderChangeListener listener) { + // does nothing because this provider does not change + } + + @Override + public Collection getAll() { + return Collections.unmodifiableCollection(providedRuleTemplates.values()); + } + + @Override + public void removeProviderChangeListener(ProviderChangeListener listener) { + // does nothing because this provider does not change + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/type/PWMModuleTypeProvider.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/type/PWMModuleTypeProvider.java new file mode 100644 index 0000000000000..2db14925d489d --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/type/PWMModuleTypeProvider.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.type; + +import java.util.Collection; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.automation.pwm.internal.handler.PWMTriggerHandler; +import org.openhab.core.automation.type.ModuleType; +import org.openhab.core.automation.type.ModuleTypeProvider; +import org.openhab.core.common.registry.ProviderChangeListener; +import org.osgi.service.component.annotations.Component; + +/** + * Provides the module types for the rules engine. + * + * @author Fabian Wolter - Initial Contribution + */ +@Component +@NonNullByDefault +public class PWMModuleTypeProvider implements ModuleTypeProvider { + private static final Map PROVIDED_MODULE_TYPES = Map.of(PWMTriggerHandler.MODULE_TYPE_ID, + PWMTriggerType.initialize()); + + @SuppressWarnings("unchecked") + @Override + public T getModuleType(@Nullable String UID, @Nullable Locale locale) { + return (T) PROVIDED_MODULE_TYPES.get(UID); + } + + @SuppressWarnings("unchecked") + @Override + public Collection getModuleTypes(@Nullable Locale locale) { + return (Collection) PROVIDED_MODULE_TYPES.values(); + } + + @Override + public void addProviderChangeListener(ProviderChangeListener listener) { + // does nothing because this provider does not change + } + + @Override + public Collection getAll() { + return Collections.unmodifiableCollection(PROVIDED_MODULE_TYPES.values()); + } + + @Override + public void removeProviderChangeListener(ProviderChangeListener listener) { + // does nothing because this provider does not change + } +} diff --git a/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/type/PWMTriggerType.java b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/type/PWMTriggerType.java new file mode 100644 index 0000000000000..f0859328d6229 --- /dev/null +++ b/bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/type/PWMTriggerType.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2021 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.automation.pwm.internal.type; + +import static org.openhab.automation.pwm.internal.PWMConstants.*; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.automation.pwm.internal.handler.PWMTriggerHandler; +import org.openhab.core.automation.Visibility; +import org.openhab.core.automation.type.Output; +import org.openhab.core.automation.type.TriggerType; +import org.openhab.core.config.core.ConfigDescriptionParameter; +import org.openhab.core.config.core.ConfigDescriptionParameter.Type; +import org.openhab.core.config.core.ConfigDescriptionParameterBuilder; +import org.openhab.core.library.types.OnOffType; + +/** + * Creates the configuration for the Trigger module in the rules engine. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class PWMTriggerType extends TriggerType { + public static final String UID = PWMTriggerHandler.MODULE_TYPE_ID; + + public static PWMTriggerType initialize() { + List configDescriptions = new ArrayList<>(); + configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_DUTY_CYCLE_ITEM, Type.TEXT) // + .withRequired(true) // + .withMultiple(false) // + .withContext("item") // + .withLabel("Dutycycle Item").withDescription("Item to read the current dutycycle from (PercentType)") + .build()); + configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_PERIOD, Type.DECIMAL) // + .withRequired(true) // + .withMultiple(false) // + .withDefault("600") // + .withLabel("PWM Interval") // + .withUnit("s") // + .withDescription("Duration of the PWM interval in sec.").build()); + configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_MIN_DUTYCYCLE, Type.DECIMAL) // + .withRequired(false) // + .withMultiple(false) // + .withMinimum(BigDecimal.ZERO) // + .withMaximum(BigDecimal.valueOf(100)) // + .withDefault("0") // + .withLabel("Min Dutycycle") // + .withUnit("%") // + .withDescription("The dutycycle will be min this value").build()); + configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_MAX_DUTYCYCLE, Type.DECIMAL) // + .withRequired(false) // + .withMultiple(false) // + .withMinimum(BigDecimal.ZERO) // + .withMaximum(BigDecimal.valueOf(100)) // + .withDefault("100") // + .withUnit("%") // + .withLabel("Max Dutycycle") // + .withDescription("The dutycycle will be max this value").build()); + configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_DEAD_MAN_SWITCH, Type.DECIMAL) // + .withRequired(false) // + .withMultiple(false) // + .withMinimum(BigDecimal.ZERO) // + .withDefault("") // + .withLabel("Dead Man Switch") // + .withUnit("ms") // + .withDescription( + "If the duty cycle Item is not updated within this time (in ms), the output is switched off") + .build()); + + List outputs = Collections.singletonList(new Output(OUTPUT, OnOffType.class.getName(), "Output", + "Output value of the PWM module", Set.of("command"), null, null)); + + return new PWMTriggerType(configDescriptions, outputs); + } + + public PWMTriggerType(List configDescriptions, List outputs) { + super(UID, configDescriptions, "PWM triggers", null, null, Visibility.VISIBLE, outputs); + } +} diff --git a/bundles/pom.xml b/bundles/pom.xml index fef76662048d3..47f28d1587ffc 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -22,6 +22,7 @@ org.openhab.automation.jsscripting org.openhab.automation.jythonscripting org.openhab.automation.pidcontroller + org.openhab.automation.pwm org.openhab.io.homekit org.openhab.io.hueemulation