Skip to content

Commit

Permalink
Merge pull request #4 from philippemezzadri/dev
Browse files Browse the repository at this point in the history
Alternate heating curve, code cleanup, better documentation
  • Loading branch information
philippemezzadri committed Mar 10, 2024
2 parents 27bd0b2 + 62dd707 commit cc94d0b
Show file tree
Hide file tree
Showing 17 changed files with 910 additions and 149 deletions.
137 changes: 137 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
Language: Cpp
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: DontAlign
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 2000
PointerAlignment: Right
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 2
UseTab: Never
92 changes: 63 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ The same changes are applicable to the component [actions](<https://esphome.io/g

This work is strongly inspired from:

- <https://antoinegrall.wordpress.com/decodage-frisquet-ers/>
- <http://wiki.kainhofer.com/hardware/vaillantvrt340f>
- <https://github.com/etimou/frisquet-arduino>
- [Décodage du signal Frisquet Eco Radio System](<https://antoinegrall.wordpress.com/decodage-frisquet-ers/>) (French)
- [Decoding the wireless heating control Vaillant CalorMatic 340f](<http://wiki.kainhofer.com/hardware/vaillantvrt340f>)
- [frisquet-arduino](<https://github.com/etimou/frisquet-arduino>)

and from the discussions made in this thread:
and from the discussions held in this thread:

- <https://easydomoticz.com/forum/viewtopic.php?f=17&t=1486sid=d2f41ac68e5bab18fd412a192a21c2c4> (French)
- [Régulation d'une chaudière Frisquet ECO radio System](<https://easydomoticz.com/forum/viewtopic.php?f=17&t=1486sid=d2f41ac68e5bab18fd412a192a21c2c4>) (French)

## Wiring

Expand All @@ -41,14 +41,14 @@ The ESPHome replaces the original Eco Radio System HF receiver and is conneted t

**Micro-fit 4 pin out:**

![Micro-fit 4 pinout drawing](doc/connector_4pin1_80px.png)
![Micro-fit 4 pinout drawing](images/connector_4pin1_80px.png)

Defined viewing direction for the connector pin out:

- Receptable - _rear view_
- Header - _front view_
- Receptable - *rear view*
- Header - *front view*

_Note_: It has been observed that the current supplied by the boiler main board is not sufficent to power the ESP32.
*Note*: It has been observed that the current supplied by the boiler main board is not sufficent to power the ESP32.

## Installation

Expand Down Expand Up @@ -109,6 +109,8 @@ The output value received by the component is any rational value between 0 and 1
your boiler to receive the messages from the ESP. This ID can be retrieved by connecting the radio receiver signal wire to an Arduino.
See [here](https://github.com/etimou/frisquet-arduino) for more details.

*Note:* The ``frisquet_boiler`` component will send commands to the boiler right after an update of the ``output``value and then every 4 minutes. The component must receive regularly updates from the Climate component. To prevent overheating of the boiler, it will stop sending commands to the boiler if the ``output`` value is not updated during 15 minutes. In such case, the boiler will put itself in safe mode.

## Heating Curve Climate

In addition, a [Climate](<https://esphome.io/components/climate/index.html>) component is necessary to control the output. The [PID Climate](https://esphome.io/components/climate/pid.html?highlight=pid) could be used but it does not provide
Expand Down Expand Up @@ -147,36 +149,34 @@ Configuration variables:
- **outdoor_sensor** (**Required**, [ID](<https://esphome.io/guides/configuration-types.html#config-id>)): The sensor that is used to measure the outside temperature.
- **default_target_temperature** (**Required**, float): The default target temperature (setpoint) for the control algorithm. This can be dynamically set in the frontend later.
- **output** (**Required**, [ID](<https://esphome.io/guides/configuration-types.html#config-id>)): The ID of a float output that increases the current temperature.
- **control_parameters** (_Optional_): Control parameters of the controller (see [below](<#heating-curve-definition>)).
- **slope** (_Optional_, float): The proportional term (slope) of the heating curve. Defaults to 1.5.
- **shift** (_Optional_, float): The parallel shift term of the heating curve. Defaults to 0.
- **kp** (_Optional_, float): The factor for the proportional term of the heating curve. May be useful for accelerating convergence to target temperature. Defaults to 0.
- **ki** (_Optional_, float): The factor for the integral term of the heating curve. May be useful if target temperature can't be reached. Use with caution when the house has a lot of thermal inertia. Defaults to 0.
- **output_parameters** (_Optional_): Output parameters of the controller (see [below](<#setpoint-calibration-factors>)).
- **rounded** (_Optional_, boolean): Forces rounding of the output value to two digits. This is recommended if used in conjunction with the `friquet_boiler` output. Defaults to false.
- **minimum_output** (_Optional_, float): Output value below which output value is set to zero. Defaults to 0.1.
- **maximum_output** (_Optional_, float): Output value above which output value won't go (cap). Defaults to 1.
- **heat_required_output** (_Optional_, float): Minimum output value to be considered when the [_Heat Required_ switch](#heat_curve_climate-switch) is on. Defaults to 0.1.
- **output_factor** (_Optional_, float): Calibration factor of the output. Defaults to 1.
- **output_offset** (_Optional_, float): Calibration offset of the output. Defaults to 0.
- **control_parameters** (*Optional*): Control parameters of the controller (see [below](<#heating-curve-definition>)).
- **alt_curve** (*Optional*, boolean): Set to `true` to use an alternate heating curve. Defaults to `false`.
- **slope** (*Optional*, float): The proportional term (slope) of the heating curve. Defaults to `1.5`.
- **shift** (*Optional*, float): The parallel shift term of the heating curve. Defaults to `0`.
- **kp** (*Optional*, float): The factor for the proportional term of the heating curve. May be useful for accelerating convergence to target temperature. Defaults to `0`.
- **ki** (*Optional*, float): The factor for the integral term of the heating curve. May be useful if target temperature can't be reached. Use with caution when the house has a lot of thermal inertia. Defaults to `0`.
- **output_parameters** (*Optional*): Output parameters of the controller (see [below](<#setpoint-calibration-factors>)).
- **rounded** (*Optional*, boolean): Forces rounding of the output value to two digits. This is recommended if used in conjunction with the `friquet_boiler` output. Defaults to `false`.
- **minimum_output** (*Optional*, float): Output value below which output value is set to zero. Defaults to `0.1`.
- **maximum_output** (*Optional*, float): Output value above which output value won't go (cap). Defaults to `1`.
- **heat_required_output** (*Optional*, float): Minimum output value to be considered when the [*Heat Required* switch](#heat_curve_climate-switch) is on. Defaults to `0.1`.
- **output_factor** (*Optional*, float): Calibration factor of the output. Defaults to `1`.
- **output_offset** (*Optional*, float): Calibration offset of the output. Defaults to `0`.
- All other options from [Climate](<https://esphome.io/components/climate/index.html#config-climate>)

### Heating curve definition

The boiler flow temperature is calculated from the outdoor temperature:

`WATERTEMP` = `slope` \* `DELTA` + `target temperature` + `shift` + `ERROR`* `kp` + `INTEGRAL_TERM`
`WATERTEMP` = `slope` \* `DELTA` + `target temperature` + `shift`

where :

- `WATERTEMP` is the temperature setpoint for the water circulating in the heating circuit.
- `DELTA` is the temperature difference between the target and the outdoor,
- `ERROR` is the calculated error (target - current)
- `INTEGRAL_TERM` is the cumulative sum of `ki` \* `ERROR` \* `dt`
- `slope`, `shift`, `kp` and `ki` are defined in the Climate `control_parameters`.
- `dt` is the time difference in seconds between two calculations.
- `slope` and `shift` are defined in the Climate `control_parameters`.

![heat curve example graph](doc/heat_curve_graph.webp)
![heat curve example graph](images/heat_curve_graph.png)

In this example, heating curves are given for an ambiant temperature (target) of 20°C with no shift. The `shift`parameter allows you to move up and down the curves by a few degrees.

Expand All @@ -198,6 +198,35 @@ control_parameters:
kp: 2
```

**Alternate heating curve:**

If you struggle in finding the good `slope`and `shift`, you can try to set `alt_curve` to `true`. You can do it especially if you can't find settings that work for both cold winter and spring. The alternate heating curve is not linear like the standard curve but is polynomial and is designed to show a reduced slope for high delta between the outdoor and target temperatures.

![Graph of alternate heating curve](images/alternate_heating_curve.png)

In the above example, both curves have the same `slope` parameter.

### Proportionnal and integral terms

If needed, proportionnal and integral terms can be added to the heating curve:

`WATERTEMP` = `HEATING_CURVE_TEMP` + `ERROR`* `kp` + `INTEGRAL_TERM`

where :

- `WATERTEMP` is the temperature setpoint for the water circulating in the heating circuit.
- `HEATING_CURVE_TEMP`is the heating curve temperature calculated above.
- `ERROR` is the calculated error (target - current)
- `INTEGRAL_TERM` is the cumulative sum of `ki` \* `ERROR` \* `dt`
- `dt` is the time difference in seconds between two calculations.
- `kp` and `ki` are defined in the Climate `control_parameters`.

**Warning:**

Setting a proportionnal factor `kp` can be useful to accelerate the convergence when the target temperature is changed. The value of `kp` should remain low to maintain the stability of the system and avoid overshoots.

However, setting an integral factor `ki`can be tricky to use and depends on many factors such as the house thermal inertia. We do not recommend to use it unless you know what you are doing.

### Setpoint calibration factors

The boiler `SETPOINT` (integer in the `[0 - 100]` range) and the water flow temperature (`WATERTEMP`) are linked by the following formula:
Expand Down Expand Up @@ -236,11 +265,16 @@ sensor:
unit_of_measurement: "°C"
filters:
- filter_out: nan
- exponential_moving_average:
alpha: 0.3
send_every: 1
- heartbeat: 60s
```

If you are not using Home Assistant, you can use any local temperature sensor connected to the ESP or retrieve other sensor data using [`mqtt_subscribe`](<https://esphome.io/components/sensor/mqtt_subscribe.html>) sensors.

*Note:* Sensors should have a regular update interval as the heat curve update frequency is tied to the update interval of the sensors. We recommend putting a filter on the sensors to filter out the noise to ensure better stability of the output.

## `heat_curve_climate` Switch

On some occasions, external temperature conditions or high values of the Proportional and Integral factors may cause the boiler to enter idle mode. This can be undesirable as heat may be required by radiators in other rooms of the house.
Expand Down Expand Up @@ -310,8 +344,8 @@ Configuration variables:
- **id** (**Required**, [ID](<https://esphome.io/guides/configuration-types.html#config-id>)): ID of the Heating Curve Climate.
- **heat_factor** (**Required**, float): The proportional term (slope) of the heating curve.
- **offset** (**Required**, float): The offset term of the heating curve.
- **kp** (_Optional_, float): The factor for the proportional term of the controller. Defaults to 0.
- **ki** (_Optional_, float): The factor for the integral term of the controller. Defaults to 0.
- **kp** (*Optional*, float): The factor for the proportional term of the controller. Defaults to 0.
- **ki** (*Optional*, float): The factor for the integral term of the controller. Defaults to 0.

## `climate.pid.reset_integral_term` Action

Expand Down
5 changes: 0 additions & 5 deletions automations/boiler.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,25 @@ input_number:
name: Boiler Slope
min: 0
max: 3
initial: 1.4
step: 0.01
mode: box
boiler_shift:
name: Boiler Shift
min: 0
max: 30
initial: 7
step: 0.5
mode: box
boiler_kp:
name: Boiler Kp
min: 0
max: 10
step: 0.5
initial: 5
mode: box
boiler_ki:
name: Boiler Ki
min: 0
max: 1
step: 0.001
initial: 0.0005
mode: box

automation:
Expand Down Expand Up @@ -108,4 +104,3 @@ automation:
- delay: "00:01:30"
mode: single
max_exceeded: silent

Loading

0 comments on commit cc94d0b

Please sign in to comment.