mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[pidcontroller] Initial Contribution (#9512)
* [pidcontroller] Initial Contribution * Incorporate review feedback No.1 Signed-off-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
parent
fe5e9b85e8
commit
924eca29f2
@ -7,6 +7,7 @@
|
|||||||
# Add-on maintainers:
|
# Add-on maintainers:
|
||||||
/bundles/org.openhab.automation.groovyscripting/ @wborn
|
/bundles/org.openhab.automation.groovyscripting/ @wborn
|
||||||
/bundles/org.openhab.automation.jythonscripting/ @openhab/add-ons-maintainers
|
/bundles/org.openhab.automation.jythonscripting/ @openhab/add-ons-maintainers
|
||||||
|
/bundles/org.openhab.automation.pidcontroller/ @fwolter
|
||||||
/bundles/org.openhab.binding.adorne/ @theiding
|
/bundles/org.openhab.binding.adorne/ @theiding
|
||||||
/bundles/org.openhab.binding.airquality/ @kubawolanin
|
/bundles/org.openhab.binding.airquality/ @kubawolanin
|
||||||
/bundles/org.openhab.binding.airvisualnode/ @3cky
|
/bundles/org.openhab.binding.airvisualnode/ @3cky
|
||||||
|
@ -26,6 +26,11 @@
|
|||||||
<artifactId>org.openhab.automation.jythonscripting</artifactId>
|
<artifactId>org.openhab.automation.jythonscripting</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.automation.pidcontroller</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.adorne</artifactId>
|
<artifactId>org.openhab.binding.adorne</artifactId>
|
||||||
|
13
bundles/org.openhab.automation.pidcontroller/NOTICE
Normal file
13
bundles/org.openhab.automation.pidcontroller/NOTICE
Normal file
@ -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
|
137
bundles/org.openhab.automation.pidcontroller/README.md
Normal file
137
bundles/org.openhab.automation.pidcontroller/README.md
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# PID Controller Automation
|
||||||
|
|
||||||
|
This automation implements a [PID](https://en.wikipedia.org/wiki/PID_controller)-T1 controller for openHAB.
|
||||||
|
|
||||||
|
A PID controller can be used for closed-loop controls. For example:
|
||||||
|
|
||||||
|
- Heating: A sensor measures the room temperature.
|
||||||
|
The PID controller calculates the heater's valve opening, so that the room temperature is kept at the setpoint.
|
||||||
|
- Lighting: A light sensor measures the room's illuminance.
|
||||||
|
The PID controller controls the dimmer of the room's lighting, so that the illuminance in the room is kept at a constant level.
|
||||||
|
- PV zero export: A meter measures the power at the grid point of the building.
|
||||||
|
The PID controller calculates the amount of power the battery storage system needs to feed-in or charge the battery, so that the building's grid power consumption is around zero,
|
||||||
|
i.e. PV generation, battery storage output power and the building's power consumption are at balance.
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
The PID controller can be used in openHAB's [rule engine](https://www.openhab.org/docs/configuration/rules-dsl.html). This automation provides a trigger and an action module.
|
||||||
|
|
||||||
|
### Trigger
|
||||||
|
|
||||||
|
This module triggers whenever the `input` or the `setpoint` changes or the `loopTime` expires.
|
||||||
|
Every trigger calculates the P, the I and the D part and sums them up to form the `output` value.
|
||||||
|
This is then transferred to the action module.
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
|--------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------|----------|
|
||||||
|
| `input` | Item | Name of the input [Item](https://www.openhab.org/docs/configuration/items.html) (e.g. temperature sensor value) | Y |
|
||||||
|
| `setpoint` | Item | Name of the setpoint Item (e.g. desired room temperature) | Y |
|
||||||
|
| `kp` | Decimal | P: [Proportional Gain](#proportional-p-gain-parameter) Parameter | Y |
|
||||||
|
| `ki` | Decimal | I: [Integral Gain](#integral-i-gain-parameter) Parameter | Y |
|
||||||
|
| `kd` | Decimal | D: [Derivative Gain](#derivative-d-gain-parameter) Parameter | Y |
|
||||||
|
| `kdTimeConstant` | Decimal | D-T1: [Derivative Gain Time Constant](#derivative-time-constant-d-t1-parameter) in sec. | Y |
|
||||||
|
| `outputLowerLimit` | Decimal | The output of the PID controller will be max this value | Y |
|
||||||
|
| `outputUpperLimit` | Decimal | The output of the PID controller will be min this value | Y |
|
||||||
|
| `loopTime` | Decimal | The interval the output value will be updated in milliseconds. Note: the output will also be updated when the input value or the setpoint changes. | Y |
|
||||||
|
|
||||||
|
The purpose of the limit parameters are to keep the output value and the integral value in a reasonable range, if the regulation cannot meet its setpoint.
|
||||||
|
E.g. the window is open and the heater doesn't manage to heat up the room.
|
||||||
|
|
||||||
|
The `loopTime` should be max a tenth of the system response.
|
||||||
|
E.g. the heating needs 10 min to heat up the room, the loop time should be max 1 min.
|
||||||
|
Lower values won't harm, but need more calculation resources.
|
||||||
|
|
||||||
|
### Action
|
||||||
|
|
||||||
|
This module writes the PID controller's output value into the `output` Item and provides debugging abilities.
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
|--------------|------|----------------------------------------------------------------------|----------|
|
||||||
|
| `output` | Item | Name of the output Item (e.g. the valve actuator 0-100%) | Y |
|
||||||
|
| `pInspector` | Item | Name of the debug Item for the current P part | N |
|
||||||
|
| `iInspector` | Item | Name of the debug Item for the current I part | N |
|
||||||
|
| `dInspector` | Item | Name of the debug Item for the current D part | N |
|
||||||
|
| `eInspector` | Item | Name of the debug Item for the current regulation difference (error) | N |
|
||||||
|
|
||||||
|
You can view the internal P, I and D parts of the controller with the inspector Items.
|
||||||
|
These values are useful when tuning the controller.
|
||||||
|
They are updated everytime the output is updated.
|
||||||
|
|
||||||
|
## Proportional (P) Gain Parameter
|
||||||
|
|
||||||
|
Parameter: `kp`
|
||||||
|
|
||||||
|
A value of 0 disables the P part.
|
||||||
|
|
||||||
|
A value of 1 sets the output to the current setpoint deviation (error).
|
||||||
|
E.g. the setpoint is 25°C and the measured value is 20°C, the output will be set to 5.
|
||||||
|
If the output is the opening of a valve in %, you might want to set this parameter to higher values (`kp=10` would result in 50%).
|
||||||
|
|
||||||
|
## Integral (I) Gain Parameter
|
||||||
|
|
||||||
|
Parameter: `ki`
|
||||||
|
|
||||||
|
The purpose of this parameter is to let the output drift towards the setpoint.
|
||||||
|
The bigger this parameter, the faster the drifting.
|
||||||
|
|
||||||
|
A value of 0 disables the I part.
|
||||||
|
|
||||||
|
A value of 1 adds the current setpoint deviation (error) to the output each second.
|
||||||
|
E.g. the setpoint is 25°C and the measured value is 20°C, the output will be set to 5 after 1 sec.
|
||||||
|
After 2 sec the output will be 10.
|
||||||
|
If the output is the opening of a valve in %, you might want to set this parameter to a lower value (`ki=0.1` would result in 30% after 60 sec: 5\*0.1\*60=30).
|
||||||
|
|
||||||
|
## Derivative (D) Gain Parameter
|
||||||
|
|
||||||
|
Parameter: `kd`
|
||||||
|
|
||||||
|
The purpose of this parameter is to react to sudden changes (e.g. an opened window) and also to damp the regulation.
|
||||||
|
This makes the regulation more resilient against oscillations, i.e. bigger `kp` and `ki` values can be set.
|
||||||
|
|
||||||
|
A value of 0 disables the D part.
|
||||||
|
|
||||||
|
A value of 1 sets the output to the difference between the last setpoint deviation (error) and the current.
|
||||||
|
E.g. the setpoint is 25°C and the measured value is 20°C (error=5°C).
|
||||||
|
When the temperature drops to 10°C due to an opened window (error=15°C), the output is set to 15°C - 5°C = 10.
|
||||||
|
|
||||||
|
## Derivative Time Constant (D-T1) Parameter
|
||||||
|
|
||||||
|
Parameter: `kdTimeConstant`
|
||||||
|
|
||||||
|
The purpose of this parameter is to slow down the impact of the D part.
|
||||||
|
|
||||||
|
This parameter behaves like a [low-pass](https://en.wikipedia.org/wiki/Low-pass_filter) filter.
|
||||||
|
The D part will become 63% of its actual value after `kdTimeConstant` seconds and 99% after 5 times `kdTimeConstant`. E.g. `kdTimeConstant` is set to 10s, the D part will become 99% after 50s.
|
||||||
|
|
||||||
|
Higher values lead to a longer lasting impact of the D part (stretching) after a change in the setpoint deviation (error).
|
||||||
|
The "stretching" also results in a lower amplitude, i.e. if you increase this value, you might want to also increase `kd` to keep the height of the D part at the same level.
|
||||||
|
|
||||||
|
## Tuning
|
||||||
|
|
||||||
|
Tuning the `Kp`, `Ki` and `Kd` parameters can be done by applying science.
|
||||||
|
It can also be done by heuristic methods like the [Ziegler–Nichols method](https://en.wikipedia.org/wiki/Ziegler%E2%80%93Nichols_method).
|
||||||
|
But it can also be done by trial and error.
|
||||||
|
This results in quite reasonable working systems in most cases.
|
||||||
|
So, this will be described in the following.
|
||||||
|
|
||||||
|
To be able to proceed with this method, you need to visualize the input and the output value of the PID controller over time.
|
||||||
|
It's also good to visualize the individual P, I and D parts (these are forming the output value) via the inspector Items.
|
||||||
|
The visualization can be done by the analyze function in Main UI or by adding a persistence and use Grafana for example.
|
||||||
|
|
||||||
|
After you added a [Rule](https://www.openhab.org/docs/configuration/rules-dsl.html) with above trigger and action module and configured those, proceed with the following steps:
|
||||||
|
|
||||||
|
> *Notice:* A good starting point for the derivative time constant `kdTimeConstant` is the response time of the control loop.
|
||||||
|
E.g. the time it takes from opening the heater valve and seeing an effect of the measured temperature.
|
||||||
|
|
||||||
|
1. Set `kp`, `ki` and `kd` to 0
|
||||||
|
2. Increase `kp` until the system starts to oscillate (continuous over- and undershoot)
|
||||||
|
3. Decrease `kp` a bit, that the system doesn't oscillate anymore
|
||||||
|
4. Repeat the two steps for the `ki` parameter (keep `kp` set)
|
||||||
|
5. Repeat the two steps for the `kd` parameter (keep `kp` and `ki` set)
|
||||||
|
6. As the D part acts as a damper, you should now be able to increase `kp` and `ki` further without resulting in oscillations
|
||||||
|
|
||||||
|
After each modification of above parameters, test the system response by introducing a setpoint deviation (error).
|
||||||
|
This can be done either by changing the setpoint (e.g. 20°C -> 25°C) or by forcing the measured value to change (e.g. by opening a window).
|
||||||
|
|
||||||
|
This process can take some time with slow responding control loops like heating systems.
|
||||||
|
You will get faster results with constant lighting or PV zero export applications.
|
17
bundles/org.openhab.automation.pidcontroller/pom.xml
Normal file
17
bundles/org.openhab.automation.pidcontroller/pom.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||||
|
<version>3.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.automation.pidcontroller</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Add-ons :: Bundles :: Automation :: PID Controller</name>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<features name="org.openhab.automation.pidcontroller-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||||
|
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||||
|
|
||||||
|
<feature name="openhab-automation-pidcontroller" description="PID Controller" version="${project.version}">
|
||||||
|
<feature>openhab-runtime-base</feature>
|
||||||
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.automation.pidcontroller/${project.version}</bundle>
|
||||||
|
</feature>
|
||||||
|
</features>
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realizes an first-order FIR low pass filter. To keep code complexity low, it is implemented as moving average (all
|
||||||
|
* FIR coefficients are set to normalized ones).
|
||||||
|
*
|
||||||
|
* The exponential decaying function is used for the calculation (see https://en.wikipedia.org/wiki/Time_constant). That
|
||||||
|
* means the output value is approx. 63% of the input value after one time constant and approx. 99% after 5 time
|
||||||
|
* constants.
|
||||||
|
*
|
||||||
|
* @author Fabian Wolter - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class LowpassFilter {
|
||||||
|
/**
|
||||||
|
* Executes one low pass filter step.
|
||||||
|
*
|
||||||
|
* @param lastOutput the current filter value (result of the last invocation)
|
||||||
|
* @param newValue the just sampled value
|
||||||
|
* @param timeQuotient quotient of the current time and the time constant
|
||||||
|
* @return the new filter value
|
||||||
|
*/
|
||||||
|
public static double calculate(double lastOutput, double newValue, double timeQuotient) {
|
||||||
|
double output = lastOutput * Math.exp(-timeQuotient);
|
||||||
|
output += newValue * (1 - Math.exp(-timeQuotient));
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Constants for PID controller.
|
||||||
|
*
|
||||||
|
* @author Fabian Wolter - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDControllerConstants {
|
||||||
|
public static final String AUTOMATION_NAME = "pidcontroller";
|
||||||
|
public static final String CONFIG_INPUT_ITEM = "input";
|
||||||
|
public static final String CONFIG_SETPOINT_ITEM = "setpoint";
|
||||||
|
public static final String CONFIG_OUTPUT_LOWER_LIMIT = "outputLowerLimit";
|
||||||
|
public static final String CONFIG_OUTPUT_UPPER_LIMIT = "outputUpperLimit";
|
||||||
|
public static final String CONFIG_LOOP_TIME = "loopTime";
|
||||||
|
public static final String CONFIG_KP_GAIN = "kp";
|
||||||
|
public static final String CONFIG_KI_GAIN = "ki";
|
||||||
|
public static final String CONFIG_KD_GAIN = "kd";
|
||||||
|
public static final String CONFIG_KD_TIMECONSTANT = "kdTimeConstant";
|
||||||
|
public static final String P_INSPECTOR = "pInspector";
|
||||||
|
public static final String I_INSPECTOR = "iInspector";
|
||||||
|
public static final String D_INSPECTOR = "dInspector";
|
||||||
|
public static final String E_INSPECTOR = "eInspector";
|
||||||
|
public static final String OUTPUT = "output";
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Common Exception for PID controller.
|
||||||
|
*
|
||||||
|
* @author Fabian Wolter - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDException extends Exception {
|
||||||
|
private static final long serialVersionUID = -3029834022610530982L;
|
||||||
|
|
||||||
|
public PIDException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.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.pidcontroller.internal.handler.PIDControllerActionHandler;
|
||||||
|
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerTriggerHandler;
|
||||||
|
import org.openhab.core.automation.Action;
|
||||||
|
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.events.EventPublisher;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Hilbrand Bouwkamp - Initial Contribution
|
||||||
|
*/
|
||||||
|
@Component(service = ModuleHandlerFactory.class, configurationPid = "action.pidcontroller")
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDControllerModuleHandlerFactory extends BaseModuleHandlerFactory {
|
||||||
|
private static final Collection<String> TYPES = Set.of(PIDControllerTriggerHandler.MODULE_TYPE_ID,
|
||||||
|
PIDControllerActionHandler.MODULE_TYPE_ID);
|
||||||
|
private ItemRegistry itemRegistry;
|
||||||
|
private EventPublisher eventPublisher;
|
||||||
|
private BundleContext bundleContext;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public PIDControllerModuleHandlerFactory(@Reference ItemRegistry itemRegistry,
|
||||||
|
@Reference EventPublisher eventPublisher, BundleContext bundleContext) {
|
||||||
|
this.itemRegistry = itemRegistry;
|
||||||
|
this.eventPublisher = eventPublisher;
|
||||||
|
this.bundleContext = bundleContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getTypes() {
|
||||||
|
return TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @Nullable ModuleHandler internalCreate(Module module, String ruleUID) {
|
||||||
|
switch (module.getTypeUID()) {
|
||||||
|
case PIDControllerTriggerHandler.MODULE_TYPE_ID:
|
||||||
|
return new PIDControllerTriggerHandler((Trigger) module, itemRegistry, eventPublisher, bundleContext);
|
||||||
|
case PIDControllerActionHandler.MODULE_TYPE_ID:
|
||||||
|
return new PIDControllerActionHandler((Action) module, itemRegistry, eventPublisher);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal.handler;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.automation.pidcontroller.internal.LowpassFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link PIDController} provides the necessary methods for retrieving part(s) of the PID calculations
|
||||||
|
* and it provides the method for the overall PID calculations. It also resets the PID controller
|
||||||
|
*
|
||||||
|
* @author George Erhan - Initial contribution
|
||||||
|
* @author Hilbrand Bouwkamp - Adapted for new rule engine
|
||||||
|
* @author Fabian Wolter - Add T1 to D part, add debugging ability for PID values
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
class PIDController {
|
||||||
|
private final double outputLowerLimit;
|
||||||
|
private final double outputUpperLimit;
|
||||||
|
|
||||||
|
private double integralResult;
|
||||||
|
private double derivativeResult;
|
||||||
|
private double previousError;
|
||||||
|
private double output;
|
||||||
|
|
||||||
|
private double kp;
|
||||||
|
private double ki;
|
||||||
|
private double kd;
|
||||||
|
private double derivativeTimeConstantSec;
|
||||||
|
|
||||||
|
public PIDController(double outputLowerLimit, double outputUpperLimit, double kpAdjuster, double kiAdjuster,
|
||||||
|
double kdAdjuster, double derivativeTimeConstantSec) {
|
||||||
|
this.outputLowerLimit = outputLowerLimit;
|
||||||
|
this.outputUpperLimit = outputUpperLimit;
|
||||||
|
this.kp = kpAdjuster;
|
||||||
|
this.ki = kiAdjuster;
|
||||||
|
this.kd = kdAdjuster;
|
||||||
|
this.derivativeTimeConstantSec = derivativeTimeConstantSec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PIDOutputDTO calculate(double input, double setpoint, long lastInvocationMs) {
|
||||||
|
final double lastInvocationSec = lastInvocationMs / 1000d;
|
||||||
|
final double error = setpoint - input;
|
||||||
|
|
||||||
|
// derivative T1 calculation
|
||||||
|
final double timeQuotient = lastInvocationSec / derivativeTimeConstantSec;
|
||||||
|
if (derivativeTimeConstantSec != 0) {
|
||||||
|
derivativeResult = LowpassFilter.calculate(derivativeResult, error - previousError, timeQuotient);
|
||||||
|
previousError = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// integral calculation
|
||||||
|
integralResult += error * lastInvocationSec;
|
||||||
|
// limit to output limits
|
||||||
|
if (ki != 0) {
|
||||||
|
final double maxIntegral = outputUpperLimit / ki;
|
||||||
|
final double minIntegral = outputLowerLimit / ki;
|
||||||
|
integralResult = Math.min(maxIntegral, Math.max(minIntegral, integralResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate parts
|
||||||
|
final double proportionalPart = kp * error;
|
||||||
|
final double integralPart = ki * integralResult;
|
||||||
|
final double derivativePart = kd * derivativeResult;
|
||||||
|
output = proportionalPart + integralPart + derivativePart;
|
||||||
|
|
||||||
|
// limit output value
|
||||||
|
output = Math.min(outputUpperLimit, Math.max(outputLowerLimit, output));
|
||||||
|
|
||||||
|
return new PIDOutputDTO(output, proportionalPart, integralPart, derivativePart, error);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.automation.pidcontroller.internal.PIDControllerConstants.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.automation.Action;
|
||||||
|
import org.openhab.core.automation.handler.ActionHandler;
|
||||||
|
import org.openhab.core.automation.handler.BaseModuleHandler;
|
||||||
|
import org.openhab.core.events.EventPublisher;
|
||||||
|
import org.openhab.core.items.ItemRegistry;
|
||||||
|
import org.openhab.core.items.events.ItemCommandEvent;
|
||||||
|
import org.openhab.core.items.events.ItemEventFactory;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Hilbrand Bouwkamp - Initial Contribution
|
||||||
|
* @author Fabian Wolter - Add PID debugging items
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDControllerActionHandler extends BaseModuleHandler<Action> implements ActionHandler {
|
||||||
|
public static final String MODULE_TYPE_ID = AUTOMATION_NAME + ".action";
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(PIDControllerActionHandler.class);
|
||||||
|
|
||||||
|
private ItemRegistry itemRegistry;
|
||||||
|
private EventPublisher eventPublisher;
|
||||||
|
|
||||||
|
public PIDControllerActionHandler(Action module, ItemRegistry itemRegistry, EventPublisher eventPublisher) {
|
||||||
|
super(module);
|
||||||
|
this.itemRegistry = itemRegistry;
|
||||||
|
this.eventPublisher = eventPublisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Map<String, Object> execute(Map<String, Object> context) {
|
||||||
|
Stream.of(OUTPUT, P_INSPECTOR, I_INSPECTOR, D_INSPECTOR, E_INSPECTOR).forEach(arg -> {
|
||||||
|
final String itemName = (String) module.getConfiguration().get(arg);
|
||||||
|
|
||||||
|
if (itemName == null || itemName.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final BigDecimal command = (BigDecimal) context.get("1." + arg);
|
||||||
|
|
||||||
|
if (command != null) {
|
||||||
|
final DecimalType outputValue = new DecimalType(command);
|
||||||
|
final ItemCommandEvent itemCommandEvent = ItemEventFactory.createCommandEvent(itemName, outputValue);
|
||||||
|
|
||||||
|
eventPublisher.post(itemCommandEvent);
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
"Command was not posted because either the configuration was not correct or a service was missing: ItemName: {}, Command: {}, eventPublisher: {}, ItemRegistry: {}",
|
||||||
|
itemName, command, eventPublisher, itemRegistry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,226 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.automation.pidcontroller.internal.PIDControllerConstants.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
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.pidcontroller.internal.PIDException;
|
||||||
|
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.common.NamedThreadFactory;
|
||||||
|
import org.openhab.core.config.core.Configuration;
|
||||||
|
import org.openhab.core.events.Event;
|
||||||
|
import org.openhab.core.events.EventFilter;
|
||||||
|
import org.openhab.core.events.EventPublisher;
|
||||||
|
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.ItemEventFactory;
|
||||||
|
import org.openhab.core.items.events.ItemStateChangedEvent;
|
||||||
|
import org.openhab.core.items.events.ItemStateEvent;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.types.RefreshType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.osgi.framework.BundleContext;
|
||||||
|
import org.osgi.framework.ServiceRegistration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Hilbrand Bouwkamp - Initial Contribution
|
||||||
|
* @author Fabian Wolter - Add PID debug output values
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDControllerTriggerHandler extends BaseTriggerModuleHandler implements EventSubscriber {
|
||||||
|
public static final String MODULE_TYPE_ID = AUTOMATION_NAME + ".trigger";
|
||||||
|
private static final Set<String> SUBSCRIBED_EVENT_TYPES = Set.of(ItemStateEvent.TYPE, ItemStateChangedEvent.TYPE);
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(PIDControllerTriggerHandler.class);
|
||||||
|
private final ScheduledExecutorService scheduler = Executors
|
||||||
|
.newSingleThreadScheduledExecutor(new NamedThreadFactory("OH-automation-" + AUTOMATION_NAME, true));
|
||||||
|
private final ServiceRegistration<?> eventSubscriberRegistration;
|
||||||
|
private final PIDController controller;
|
||||||
|
private final int loopTimeMs;
|
||||||
|
private @Nullable ScheduledFuture<?> controllerjob;
|
||||||
|
private long previousTimeMs = System.currentTimeMillis();
|
||||||
|
private Item inputItem;
|
||||||
|
private Item setpointItem;
|
||||||
|
private EventFilter eventFilter;
|
||||||
|
|
||||||
|
public PIDControllerTriggerHandler(Trigger module, ItemRegistry itemRegistry, EventPublisher eventPublisher,
|
||||||
|
BundleContext bundleContext) {
|
||||||
|
super(module);
|
||||||
|
|
||||||
|
Configuration config = module.getConfiguration();
|
||||||
|
|
||||||
|
String inputItemName = (String) requireNonNull(config.get(CONFIG_INPUT_ITEM), "Input item is not set");
|
||||||
|
String setpointItemName = (String) requireNonNull(config.get(CONFIG_SETPOINT_ITEM), "Setpoint item is not set");
|
||||||
|
|
||||||
|
try {
|
||||||
|
inputItem = itemRegistry.getItem(inputItemName);
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
throw new IllegalArgumentException("Configured input item not found: " + inputItemName, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setpointItem = itemRegistry.getItem(setpointItemName);
|
||||||
|
} catch (ItemNotFoundException e) {
|
||||||
|
throw new IllegalArgumentException("Configured setpoint item not found: " + setpointItemName, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
double outputLowerLimit = getDoubleFromConfig(config, CONFIG_OUTPUT_LOWER_LIMIT);
|
||||||
|
double outputUpperLimit = getDoubleFromConfig(config, CONFIG_OUTPUT_UPPER_LIMIT);
|
||||||
|
double kpAdjuster = getDoubleFromConfig(config, CONFIG_KP_GAIN);
|
||||||
|
double kiAdjuster = getDoubleFromConfig(config, CONFIG_KI_GAIN);
|
||||||
|
double kdAdjuster = getDoubleFromConfig(config, CONFIG_KD_GAIN);
|
||||||
|
double kdTimeConstant = getDoubleFromConfig(config, CONFIG_KD_TIMECONSTANT);
|
||||||
|
|
||||||
|
loopTimeMs = ((BigDecimal) requireNonNull(config.get(CONFIG_LOOP_TIME), CONFIG_LOOP_TIME + " is not set"))
|
||||||
|
.intValue();
|
||||||
|
|
||||||
|
controller = new PIDController(outputLowerLimit, outputUpperLimit, kpAdjuster, kiAdjuster, kdAdjuster,
|
||||||
|
kdTimeConstant);
|
||||||
|
|
||||||
|
eventFilter = event -> {
|
||||||
|
String topic = event.getTopic();
|
||||||
|
|
||||||
|
return topic.equals("openhab/items/" + inputItemName + "/state")
|
||||||
|
|| topic.equals("openhab/items/" + inputItemName + "/statechanged")
|
||||||
|
|| topic.equals("openhab/items/" + setpointItemName + "/statechanged");
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSubscriberRegistration = bundleContext.registerService(EventSubscriber.class.getName(), this, null);
|
||||||
|
|
||||||
|
eventPublisher.post(ItemEventFactory.createCommandEvent(inputItemName, RefreshType.REFRESH));
|
||||||
|
|
||||||
|
controllerjob = scheduler.scheduleWithFixedDelay(this::calculate, 0, loopTimeMs, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T requireNonNull(T obj, String message) {
|
||||||
|
if (obj == null) {
|
||||||
|
throw new IllegalArgumentException(message);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getDoubleFromConfig(Configuration config, String key) {
|
||||||
|
return ((BigDecimal) Objects.requireNonNull(config.get(key), key + " is not set")).doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculate() {
|
||||||
|
double input;
|
||||||
|
double setpoint;
|
||||||
|
|
||||||
|
try {
|
||||||
|
input = getItemValueAsNumber(inputItem);
|
||||||
|
} catch (PIDException e) {
|
||||||
|
logger.warn("Input item: {}", e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setpoint = getItemValueAsNumber(setpointItem);
|
||||||
|
} catch (PIDException e) {
|
||||||
|
logger.warn("Setpoint item: {}", e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
PIDOutputDTO output = controller.calculate(input, setpoint, now - previousTimeMs);
|
||||||
|
previousTimeMs = now;
|
||||||
|
|
||||||
|
Map<String, BigDecimal> outputs = new HashMap<>();
|
||||||
|
|
||||||
|
putBigDecimal(outputs, OUTPUT, output.getOutput());
|
||||||
|
putBigDecimal(outputs, P_INSPECTOR, output.getProportionalPart());
|
||||||
|
putBigDecimal(outputs, I_INSPECTOR, output.getIntegralPart());
|
||||||
|
putBigDecimal(outputs, D_INSPECTOR, output.getDerivativePart());
|
||||||
|
putBigDecimal(outputs, E_INSPECTOR, output.getError());
|
||||||
|
|
||||||
|
ModuleHandlerCallback localCallback = callback;
|
||||||
|
if (localCallback != null && localCallback instanceof TriggerHandlerCallback) {
|
||||||
|
((TriggerHandlerCallback) localCallback).triggered(module, outputs);
|
||||||
|
} else {
|
||||||
|
logger.warn("No callback set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putBigDecimal(Map<String, BigDecimal> map, String key, double value) {
|
||||||
|
map.put(key, BigDecimal.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getItemValueAsNumber(Item item) throws PIDException {
|
||||||
|
State setpointState = item.getState();
|
||||||
|
|
||||||
|
if (setpointState instanceof Number) {
|
||||||
|
double doubleValue = ((Number) setpointState).doubleValue();
|
||||||
|
|
||||||
|
if (Double.isFinite(doubleValue)) {
|
||||||
|
return doubleValue;
|
||||||
|
}
|
||||||
|
} else if (setpointState instanceof StringType) {
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(setpointState.toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new PIDException(
|
||||||
|
"Item type is not a number: " + setpointState.getClass().getSimpleName() + ": " + setpointState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(Event event) {
|
||||||
|
if (event instanceof ItemStateChangedEvent) {
|
||||||
|
calculate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getSubscribedEventTypes() {
|
||||||
|
return SUBSCRIBED_EVENT_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable EventFilter getEventFilter() {
|
||||||
|
return eventFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
eventSubscriberRegistration.unregister();
|
||||||
|
|
||||||
|
ScheduledFuture<?> localControllerjob = controllerjob;
|
||||||
|
if (localControllerjob != null) {
|
||||||
|
localControllerjob.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal.handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Fabian Wolter - Initial Contribution
|
||||||
|
*/
|
||||||
|
public class PIDOutputDTO {
|
||||||
|
private double output;
|
||||||
|
private double proportionalPart;
|
||||||
|
private double integralPart;
|
||||||
|
private double derivativePart;
|
||||||
|
private double error;
|
||||||
|
|
||||||
|
public PIDOutputDTO(double output, double proportionalPart, double integralPart, double derivativePart,
|
||||||
|
double error) {
|
||||||
|
this.output = output;
|
||||||
|
this.proportionalPart = proportionalPart;
|
||||||
|
this.integralPart = integralPart;
|
||||||
|
this.derivativePart = derivativePart;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getOutput() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getProportionalPart() {
|
||||||
|
return proportionalPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getIntegralPart() {
|
||||||
|
return integralPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDerivativePart() {
|
||||||
|
return derivativePart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal.template;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
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.pidcontroller.internal.PIDControllerConstants;
|
||||||
|
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerActionHandler;
|
||||||
|
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerTriggerHandler;
|
||||||
|
import org.openhab.automation.pidcontroller.internal.type.PIDControllerActionType;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Hilbrand Bouwkamp - Initial Contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDControllerRuleTemplate extends RuleTemplate {
|
||||||
|
public static final String UID = "PIDControllerRuleTemplate";
|
||||||
|
|
||||||
|
public static PIDControllerRuleTemplate initialize() {
|
||||||
|
final String triggerId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
final List<Trigger> triggers = List.of(ModuleBuilder.createTrigger().withId(triggerId)
|
||||||
|
.withTypeUID(PIDControllerTriggerHandler.MODULE_TYPE_ID).withLabel("PID Controller Trigger").build());
|
||||||
|
|
||||||
|
final Map<String, String> actionInputs = Map.of(PIDControllerActionType.INPUT,
|
||||||
|
triggerId + "." + PIDControllerConstants.OUTPUT);
|
||||||
|
|
||||||
|
final List<Action> actions = List.of(ModuleBuilder.createAction().withId(UUID.randomUUID().toString())
|
||||||
|
.withTypeUID(PIDControllerActionHandler.MODULE_TYPE_ID).withLabel("PID Controller Action")
|
||||||
|
.withInputs(actionInputs).build());
|
||||||
|
|
||||||
|
return new PIDControllerRuleTemplate(Set.of("PID Controller"), triggers, Collections.emptyList(), actions,
|
||||||
|
Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PIDControllerRuleTemplate(Set<String> tags, List<Trigger> triggers, List<Condition> conditions,
|
||||||
|
List<Action> actions, List<ConfigDescriptionParameter> configDescriptions) {
|
||||||
|
super(UID, "PID Controller", "Template for a PID controlled rule", tags, triggers, conditions, actions,
|
||||||
|
configDescriptions, Visibility.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal.template;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Hilbrand Bouwkamp - Initial Contribution
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDControllerTemplateProvider implements RuleTemplateProvider {
|
||||||
|
private static final RuleTemplate PROVIDED_RULE_TEMPLATE = PIDControllerRuleTemplate.initialize();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable RuleTemplate getTemplate(String uid, @Nullable Locale locale) {
|
||||||
|
return uid.equals(PIDControllerRuleTemplate.UID) ? PROVIDED_RULE_TEMPLATE : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<RuleTemplate> getTemplates(@Nullable Locale locale) {
|
||||||
|
return Set.of(PROVIDED_RULE_TEMPLATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addProviderChangeListener(ProviderChangeListener<RuleTemplate> listener) {
|
||||||
|
// does nothing because this provider does not change
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<RuleTemplate> getAll() {
|
||||||
|
return Set.of(PROVIDED_RULE_TEMPLATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeProviderChangeListener(ProviderChangeListener<RuleTemplate> listener) {
|
||||||
|
// does nothing because this provider does not change
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal.type;
|
||||||
|
|
||||||
|
import static org.openhab.automation.pidcontroller.internal.PIDControllerConstants.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerActionHandler;
|
||||||
|
import org.openhab.core.automation.Visibility;
|
||||||
|
import org.openhab.core.automation.type.ActionType;
|
||||||
|
import org.openhab.core.automation.type.Input;
|
||||||
|
import org.openhab.core.config.core.ConfigDescriptionParameter;
|
||||||
|
import org.openhab.core.config.core.ConfigDescriptionParameter.Type;
|
||||||
|
import org.openhab.core.config.core.ConfigDescriptionParameterBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Hilbrand Bouwkamp - Initial Contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDControllerActionType extends ActionType {
|
||||||
|
public static final String INPUT = "input";
|
||||||
|
|
||||||
|
public static PIDControllerActionType initialize() {
|
||||||
|
final ConfigDescriptionParameter outputItem = ConfigDescriptionParameterBuilder.create(OUTPUT, Type.TEXT)
|
||||||
|
.withRequired(true).withMultiple(false).withContext("item").withLabel("Output Item")
|
||||||
|
.withDescription("Item to send output").build();
|
||||||
|
final ConfigDescriptionParameter pInspectorItem = ConfigDescriptionParameterBuilder
|
||||||
|
.create(P_INSPECTOR, Type.TEXT).withRequired(false).withMultiple(false).withContext("item")
|
||||||
|
.withLabel("P Inspector Item").withDescription("Item for debugging the P part").build();
|
||||||
|
final ConfigDescriptionParameter iInspectorItem = ConfigDescriptionParameterBuilder
|
||||||
|
.create(I_INSPECTOR, Type.TEXT).withRequired(false).withMultiple(false).withContext("item")
|
||||||
|
.withLabel("I Inspector Item").withDescription("Item for debugging the I part").build();
|
||||||
|
final ConfigDescriptionParameter dInspectorItem = ConfigDescriptionParameterBuilder
|
||||||
|
.create(D_INSPECTOR, Type.TEXT).withRequired(false).withMultiple(false).withContext("item")
|
||||||
|
.withLabel("D Inspector Item").withDescription("Item for debugging the D part").build();
|
||||||
|
final ConfigDescriptionParameter eInspectorItem = ConfigDescriptionParameterBuilder
|
||||||
|
.create(E_INSPECTOR, Type.TEXT).withRequired(false).withMultiple(false).withContext("item")
|
||||||
|
.withLabel("Error Inspector Item").withDescription("Item for debugging the error value").build();
|
||||||
|
|
||||||
|
List<ConfigDescriptionParameter> config = List.of(outputItem, pInspectorItem, iInspectorItem, dInspectorItem,
|
||||||
|
eInspectorItem);
|
||||||
|
|
||||||
|
List<Input> inputs = List.of(createInput(INPUT), createInput(P_INSPECTOR), createInput(I_INSPECTOR),
|
||||||
|
createInput(D_INSPECTOR), createInput(E_INSPECTOR));
|
||||||
|
|
||||||
|
return new PIDControllerActionType(config, inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Input createInput(String name) {
|
||||||
|
return new Input(name, BigDecimal.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PIDControllerActionType(List<ConfigDescriptionParameter> configDescriptions, List<Input> inputs) {
|
||||||
|
super(PIDControllerActionHandler.MODULE_TYPE_ID, configDescriptions, "calculate PID output", null, null,
|
||||||
|
Visibility.VISIBLE, inputs, null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.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.pidcontroller.internal.handler.PIDControllerActionHandler;
|
||||||
|
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerTriggerHandler;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Hilbrand Bouwkamp - Initial Contribution
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDControllerModuleTypeProvider implements ModuleTypeProvider {
|
||||||
|
private static final Map<String, ModuleType> PROVIDED_MODULE_TYPES = Map.of(
|
||||||
|
PIDControllerActionHandler.MODULE_TYPE_ID, PIDControllerActionType.initialize(),
|
||||||
|
PIDControllerTriggerHandler.MODULE_TYPE_ID, PIDControllerTriggerType.initialize());
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T extends ModuleType> T getModuleType(@Nullable String UID, @Nullable Locale locale) {
|
||||||
|
return (T) PROVIDED_MODULE_TYPES.get(UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T extends ModuleType> Collection<T> getModuleTypes(@Nullable Locale locale) {
|
||||||
|
return (Collection<T>) PROVIDED_MODULE_TYPES.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
|
||||||
|
// does nothing because this provider does not change
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<ModuleType> getAll() {
|
||||||
|
return Collections.unmodifiableCollection(PROVIDED_MODULE_TYPES.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeProviderChangeListener(ProviderChangeListener<ModuleType> listener) {
|
||||||
|
// does nothing because this provider does not change
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal.type;
|
||||||
|
|
||||||
|
import static org.openhab.automation.pidcontroller.internal.PIDControllerConstants.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.automation.pidcontroller.internal.handler.PIDControllerTriggerHandler;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Hilbrand Bouwkamp - Initial Contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class PIDControllerTriggerType extends TriggerType {
|
||||||
|
private static final String DEFAULT_LOOPTIME_MS = "1000";
|
||||||
|
|
||||||
|
public static PIDControllerTriggerType initialize() {
|
||||||
|
List<ConfigDescriptionParameter> configDescriptions = new ArrayList<>();
|
||||||
|
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_INPUT_ITEM, Type.TEXT).withRequired(true)
|
||||||
|
.withReadOnly(true).withMultiple(false).withContext("item").withLabel("Input Item")
|
||||||
|
.withDescription("Item to monitor").build());
|
||||||
|
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_SETPOINT_ITEM, Type.TEXT)
|
||||||
|
.withRequired(true).withReadOnly(true).withMultiple(false).withContext("item").withLabel("Setpoint")
|
||||||
|
.withDescription("Targeted setpoint").build());
|
||||||
|
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_KP_GAIN, Type.DECIMAL).withRequired(true)
|
||||||
|
.withMultiple(false).withDefault("1.0").withLabel("Proportional Gain (Kp)")
|
||||||
|
.withDescription("Change to output propertional to current error value.").build());
|
||||||
|
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_KI_GAIN, Type.DECIMAL).withRequired(true)
|
||||||
|
.withMultiple(false).withDefault("1.0").withLabel("Integral Gain (Ki)")
|
||||||
|
.withDescription("Accelerate movement towards the setpoint.").build());
|
||||||
|
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_KD_GAIN, Type.DECIMAL).withRequired(true)
|
||||||
|
.withMultiple(false).withDefault("1.0").withLabel("Derivative Gain (Kd)")
|
||||||
|
.withDescription("Slows the rate of change of the output.").build());
|
||||||
|
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_KD_TIMECONSTANT, Type.DECIMAL)
|
||||||
|
.withRequired(true).withMultiple(false).withDefault("1.0").withLabel("Derivative Time Constant")
|
||||||
|
.withDescription("Slows the rate of change of the D Part (T1) in seconds.").withUnit("s").build());
|
||||||
|
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_OUTPUT_LOWER_LIMIT, Type.DECIMAL)
|
||||||
|
.withRequired(true).withMultiple(false).withDefault("0").withLabel("Output Lower Limit")
|
||||||
|
.withDescription("The output of the PID controller will be min this value").build());
|
||||||
|
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_OUTPUT_UPPER_LIMIT, Type.DECIMAL)
|
||||||
|
.withRequired(true).withMultiple(false).withDefault("100").withLabel("Output Upper Limit")
|
||||||
|
.withDescription("The output of the PID controller will be max this value").build());
|
||||||
|
configDescriptions.add(ConfigDescriptionParameterBuilder.create(CONFIG_LOOP_TIME, Type.DECIMAL)
|
||||||
|
.withRequired(true).withMultiple(false).withDefault(DEFAULT_LOOPTIME_MS).withLabel("Loop Time")
|
||||||
|
.withDescription("The interval the output value is updated in ms").withUnit("ms").build());
|
||||||
|
|
||||||
|
Output output = new Output(OUTPUT, BigDecimal.class.getName(), "Output", "Output value of the PID Controller",
|
||||||
|
null, null, null);
|
||||||
|
Output pInspector = new Output(P_INSPECTOR, BigDecimal.class.getName(), "P Inspector",
|
||||||
|
"Current P value of the pid controller", null, null, null);
|
||||||
|
Output iInspector = new Output(I_INSPECTOR, BigDecimal.class.getName(), "I Inspector",
|
||||||
|
"Current I value of the pid controller", null, null, null);
|
||||||
|
Output dInspector = new Output(D_INSPECTOR, BigDecimal.class.getName(), "D Inspector",
|
||||||
|
"Current D value of the pid controller", null, null, null);
|
||||||
|
Output eInspector = new Output(E_INSPECTOR, BigDecimal.class.getName(), "Error Value Inspector",
|
||||||
|
"Current error value of the pid controller", null, null, null);
|
||||||
|
|
||||||
|
List<Output> outputs = List.of(output, pInspector, iInspector, dInspector, eInspector);
|
||||||
|
|
||||||
|
return new PIDControllerTriggerType(configDescriptions, outputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PIDControllerTriggerType(List<ConfigDescriptionParameter> configDescriptions, List<Output> outputs) {
|
||||||
|
super(PIDControllerTriggerHandler.MODULE_TYPE_ID, configDescriptions, "PID controller triggers", null, null,
|
||||||
|
Visibility.VISIBLE, outputs);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 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.pidcontroller.internal;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for LowpassFilter.
|
||||||
|
*
|
||||||
|
* @author Fabian Wolter - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
class LowpassFilterTest {
|
||||||
|
@Test
|
||||||
|
void test0to1after1tau() {
|
||||||
|
double output = LowpassFilter.calculate(0, 1, 1);
|
||||||
|
assertEquals(0.63, output, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test0to1after2tau() {
|
||||||
|
double output = LowpassFilter.calculate(0, 1, 1);
|
||||||
|
output = LowpassFilter.calculate(output, 1, 1);
|
||||||
|
assertEquals(0.86, output, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test0to1after5tau() {
|
||||||
|
double output = LowpassFilter.calculate(0, 1, 1);
|
||||||
|
output = LowpassFilter.calculate(output, 1, 1);
|
||||||
|
output = LowpassFilter.calculate(output, 1, 1);
|
||||||
|
output = LowpassFilter.calculate(output, 1, 1);
|
||||||
|
output = LowpassFilter.calculate(output, 1, 1);
|
||||||
|
assertEquals(0.99, output, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test0to1after1tau2timeConstant() {
|
||||||
|
double output = LowpassFilter.calculate(0, 1, 2);
|
||||||
|
assertEquals(0.86, output, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test0to1after0_1tau() {
|
||||||
|
double output = LowpassFilter.calculate(0, 1, 0.1);
|
||||||
|
assertEquals(0.095162582, output, 0.000000001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test1to0after1tau() {
|
||||||
|
double output = LowpassFilter.calculate(1, 0, 1);
|
||||||
|
assertEquals(0.36, output, 0.01);
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@
|
|||||||
<!-- automation -->
|
<!-- automation -->
|
||||||
<module>org.openhab.automation.groovyscripting</module>
|
<module>org.openhab.automation.groovyscripting</module>
|
||||||
<module>org.openhab.automation.jythonscripting</module>
|
<module>org.openhab.automation.jythonscripting</module>
|
||||||
|
<module>org.openhab.automation.pidcontroller</module>
|
||||||
<!-- io -->
|
<!-- io -->
|
||||||
<module>org.openhab.io.homekit</module>
|
<module>org.openhab.io.homekit</module>
|
||||||
<module>org.openhab.io.hueemulation</module>
|
<module>org.openhab.io.hueemulation</module>
|
||||||
|
Loading…
Reference in New Issue
Block a user