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 ca38e94d9a..f9b2812999 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 b183a255f0..defb5cbede 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 0000000000..38d625e349 --- /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 0000000000..840b19df87 --- /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/doc/statemachine.png b/bundles/org.openhab.automation.pwm/doc/statemachine.png new file mode 100644 index 0000000000000000000000000000000000000000..aa99d12bd492fc841163ce8575eb71284f88fad7 GIT binary patch literal 75283 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 + + + 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 0000000000..212e8c27b9 --- /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 0000000000..e2072322a7 --- /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 0000000000..8b2f86b90a --- /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 0000000000..87e54e0bb8 --- /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 0000000000..f5c1619841 --- /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 0000000000..e8e21be793 --- /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 0000000000..53d49c0947 --- /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 0000000000..121549c42c --- /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 0000000000..59e3a12508 --- /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 0000000000..0762d2da6c --- /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 0000000000..e1c22c24cd --- /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 0000000000..2bf490b5e9 --- /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 0000000000..47c8454e5d --- /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 0000000000..cf715d72c6 --- /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 0000000000..87fc455d9f --- /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 0000000000..2db14925d4 --- /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 0000000000..f0859328d6 --- /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 fef7666204..47f28d1587 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 -- 2.47.3