From 9d903c240e7ea18934c179e530dfac43fa253d31 Mon Sep 17 00:00:00 2001 From: Fabian Wolter Date: Tue, 1 Jun 2021 20:23:33 +0200 Subject: [PATCH] [pwm] Initial Contribution (#10205) Signed-off-by: Fabian Wolter --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.automation.pwm/NOTICE | 13 + bundles/org.openhab.automation.pwm/README.md | 62 +++++ .../doc/statemachine.odg | Bin 0 -> 14133 bytes .../doc/statemachine.png | Bin 0 -> 75283 bytes bundles/org.openhab.automation.pwm/pom.xml | 17 ++ .../src/main/feature/feature.xml | 9 + .../automation/pwm/internal/PWMConstants.java | 35 +++ .../automation/pwm/internal/PWMException.java | 29 +++ .../factory/PWMModuleHandlerFactory.java | 64 +++++ .../internal/handler/PWMTriggerHandler.java | 240 ++++++++++++++++++ .../handler/state/AlwaysOffState.java | 51 ++++ .../internal/handler/state/AlwaysOnState.java | 44 ++++ .../handler/state/DutycycleHundredState.java | 87 +++++++ .../handler/state/DutycycleZeroState.java | 63 +++++ .../pwm/internal/handler/state/OffState.java | 64 +++++ .../pwm/internal/handler/state/OnState.java | 74 ++++++ .../pwm/internal/handler/state/State.java | 84 ++++++ .../internal/handler/state/StateMachine.java | 80 ++++++ .../internal/template/PWMRuleTemplate.java | 64 +++++ .../template/PWMTemplateProvider.java | 67 +++++ .../internal/type/PWMModuleTypeProvider.java | 65 +++++ .../pwm/internal/type/PWMTriggerType.java | 95 +++++++ bundles/pom.xml | 1 + 25 files changed, 1314 insertions(+) create mode 100644 bundles/org.openhab.automation.pwm/NOTICE create mode 100644 bundles/org.openhab.automation.pwm/README.md create mode 100644 bundles/org.openhab.automation.pwm/doc/statemachine.odg create mode 100644 bundles/org.openhab.automation.pwm/doc/statemachine.png create mode 100644 bundles/org.openhab.automation.pwm/pom.xml create mode 100644 bundles/org.openhab.automation.pwm/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/PWMConstants.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/PWMException.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/factory/PWMModuleHandlerFactory.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/PWMTriggerHandler.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/AlwaysOffState.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/AlwaysOnState.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/DutycycleHundredState.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/DutycycleZeroState.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/OffState.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/OnState.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/State.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/handler/state/StateMachine.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/template/PWMRuleTemplate.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/template/PWMTemplateProvider.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/type/PWMModuleTypeProvider.java create mode 100644 bundles/org.openhab.automation.pwm/src/main/java/org/openhab/automation/pwm/internal/type/PWMTriggerType.java diff --git a/CODEOWNERS b/CODEOWNERS index ca38e94d9a3..f9b28129999 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 b183a255f05..defb5cbeded 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 00000000000..38d625e3492 --- /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 00000000000..840b19df871 --- /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 0000000000000000000000000000000000000000..be28e78e32ba233373a9f114025662d9c1521be3 GIT binary patch literal 14133 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 literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.automation.pwm/pom.xml b/bundles/org.openhab.automation.pwm/pom.xml new file mode 100644 index 00000000000..d972adb1571 --- /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 00000000000..212e8c27b98 --- /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 00000000000..e2072322a79 --- /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 00000000000..8b2f86b90a5 --- /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 00000000000..87e54e0bb86 --- /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 00000000000..f5c1619841d --- /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 00000000000..e8e21be7935 --- /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 00000000000..53d49c09475 --- /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 00000000000..121549c42c7 --- /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 00000000000..59e3a12508a --- /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 00000000000..0762d2da6cd --- /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 00000000000..e1c22c24cd3 --- /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 00000000000..2bf490b5e9a --- /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 00000000000..47c8454e5df --- /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 00000000000..cf715d72c64 --- /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 00000000000..87fc455d9f1 --- /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 00000000000..2db14925d48 --- /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 00000000000..f0859328d62 --- /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 fef76662048..47f28d1587f 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