Skip to content

Commit 972ea8e

Browse files
committed
Add ramp minimum duty
1 parent 1eddb05 commit 972ea8e

4 files changed

Lines changed: 42 additions & 13 deletions

File tree

src/heater.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct APB::Heater::Private {
1919
APB::Heater *q;
2020
Heater::Mode mode{Heater::Mode::off};
2121
float maxDuty;
22+
float minDuty = 0;
2223
std::optional<float> temperature;
2324
float targetTemperature;
2425
float dewpointOffset;
@@ -126,20 +127,21 @@ uint8_t APB::Heater::index() const {
126127
}
127128

128129

129-
bool APB::Heater::setTemperature(float targetTemperature, float maxDuty, float rampOffset) {
130+
bool APB::Heater::setTemperature(float targetTemperature, float maxDuty, float minDuty, float rampOffset) {
130131
if(!this->temperature().has_value()) {
131132
Log.warningln(TEMPERATURE_NOT_FOUND_WARNING_LOG, d->log_scope);
132133
return false;
133134
}
134135
d->targetTemperature = targetTemperature;
135136
d->maxDuty = maxDuty;
137+
d->minDuty = minDuty;
136138
d->mode = Heater::Mode::target_temperature;
137139
d->rampOffset = rampOffset >= 0 ? rampOffset : 0;
138140
d->loop();
139141
return true;
140142
}
141143

142-
bool APB::Heater::setDewpoint(float offset, float maxDuty, float rampOffset) {
144+
bool APB::Heater::setDewpoint(float offset, float maxDuty, float minDuty, float rampOffset) {
143145
if(!this->temperature().has_value()) {
144146
Log.warningln(TEMPERATURE_NOT_FOUND_WARNING_LOG, d->log_scope);
145147
return false;
@@ -150,6 +152,7 @@ bool APB::Heater::setDewpoint(float offset, float maxDuty, float rampOffset) {
150152
}
151153
d->dewpointOffset = offset;
152154
d->rampOffset = rampOffset >= 0 ? rampOffset : 0;
155+
d->minDuty = minDuty;
153156
d->maxDuty = maxDuty;
154157
d->mode = Heater::Mode::dewpoint;
155158
d->loop();
@@ -178,6 +181,13 @@ std::optional<float> APB::Heater::rampOffset() const {
178181
return {d->rampOffset};
179182
}
180183

184+
std::optional<float> APB::Heater::minDuty() const {
185+
if(d->mode != Mode::dewpoint && d->mode != Mode::target_temperature) {
186+
return {};
187+
}
188+
return {d->minDuty};
189+
}
190+
181191
std::optional<float> APB::Heater::temperature() const {
182192
return d->temperature;
183193
}
@@ -234,12 +244,13 @@ void APB::Heater::Private::loop()
234244
Log.traceln("%s current temperature=`%F`", log_scope, currentTemperature);
235245
if(currentTemperature < dynamicTargetTemperature) {
236246
float rampFactor = rampOffset > 0 ? (dynamicTargetTemperature - currentTemperature)/rampOffset : 1;
237-
float targetPWM = std::max(0.f, std::min(1.f, rampFactor * maxDuty));
238-
Log.infoln("%s - temperature `%F` lower than target temperature `%F`, ramp=`%F` and max PWM is `%F`, ramp factor=`%F`, setting PWM to `%F`",
247+
float targetPWM = std::max(0.f, std::min(1.f, rampFactor * (maxDuty-minDuty) + minDuty));
248+
Log.infoln("%s - temperature `%F` lower than target temperature `%F`, ramp=`%F` and PWM range is `%F-%F`, ramp factor=`%F`, setting PWM to `%F`",
239249
log_scope,
240250
currentTemperature,
241251
dynamicTargetTemperature,
242252
rampOffset,
253+
minDuty,
243254
maxDuty,
244255
rampFactor,
245256
targetPWM
@@ -289,7 +300,7 @@ float APB::Heater::Private::getDuty() const {
289300
}
290301

291302
void APB::Heater::Private::writePinDuty(float pwm) {
292-
int16_t newPWMValue = MAX_PWM * pwm;
303+
int16_t newPWMValue = std::max(int16_t{0}, static_cast<int16_t>(std::min(MAX_PWM, MAX_PWM * pwm)));
293304
if(newPWMValue != pwmValue) {
294305
pwmValue = newPWMValue;
295306
Log.traceln("%s setting PWM=%d for pin %d", log_scope, pwmValue, pinout->pwm);

src/heater.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ class Heater {
3030
float maxDuty() const;
3131
float duty() const;
3232
void setDuty(float duty);
33-
bool setTemperature(float targetTemperature, float maxDuty=1, float rampOffset=0);
34-
bool setDewpoint(float offset, float maxDuty=1, float rampOffset=0);
33+
bool setTemperature(float targetTemperature, float maxDuty=1, float minDuty=0, float rampOffset=0);
34+
bool setDewpoint(float offset, float maxDuty=1, float minDuty=0, float rampOffset=0);
3535
std::optional<float> temperature() const;
3636
std::optional<float> targetTemperature() const;
3737
std::optional<float> dewpointOffset() const;
3838
std::optional<float> rampOffset() const;
39+
std::optional<float> minDuty() const;
3940
bool active() const;
4041

4142
Mode mode() const;

src/webserver.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,12 @@ void APB::WebServer::onGetHeaters(AsyncWebServerRequest *request) {
153153
void APB::WebServer::populateHeatersStatus(JsonArray heatersStatus) {
154154
std::for_each(Heaters::Instance.begin(), Heaters::Instance.end(), [heatersStatus](Heater &heater) {
155155
heatersStatus[heater.index()]["mode"] = heater.modeAsString(),
156-
heatersStatus[heater.index()]["maxDuty"] = heater.maxDuty();
156+
heatersStatus[heater.index()]["max_duty"] = heater.maxDuty();
157157
heatersStatus[heater.index()]["duty"] = heater.duty();
158158
heatersStatus[heater.index()]["active"] = heater.active();
159159
heatersStatus[heater.index()]["has_temperature"] = heater.temperature().has_value();
160160
optional::if_present(heater.rampOffset(), [&](float v){ heatersStatus[heater.index()]["ramp_offset"] = v; });
161+
optional::if_present(heater.minDuty(), [&](float v){ heatersStatus[heater.index()]["min_duty"] = v; });
161162
optional::if_present(heater.temperature(), [&](float v){ heatersStatus[heater.index()]["temperature"] = v; });
162163
optional::if_present(heater.targetTemperature(), [&](float v){ heatersStatus[heater.index()]["target_temperature"] = v; });
163164
optional::if_present(heater.dewpointOffset(), [&](float v){ heatersStatus[heater.index()]["dewpoint_offset"] = v; });
@@ -308,12 +309,14 @@ void APB::WebServer::onPostSetHeater(AsyncWebServerRequest *request, JsonVariant
308309
if(validation
309310
.range("dewpoint_offset", {-30}, {30})
310311
.required<float>("dewpoint_offset")
312+
.range("min_duty", 0, 1)
311313
.range("ramp_offset", 0, 20)
312314
.invalid()
313315
) return;
314316
float dewpointOffset = json["dewpoint_offset"];
315-
float rampOffset = json["ramp_offset"].is<float>() ? json["ramp_offset"] : 0;
316-
if(!heater.setDewpoint(dewpointOffset, duty, rampOffset)) {
317+
float minDuty = json["min_duty"].is<float>() ? json["min_duty"] : 0.f;
318+
float rampOffset = json["ramp_offset"].is<float>() ? json["ramp_offset"] : 0.f;
319+
if(!heater.setDewpoint(dewpointOffset, duty, minDuty, rampOffset)) {
317320
JsonResponse::error(500, dewpointTemperatureErrorMessage, request);
318321
return;
319322
}
@@ -322,12 +325,14 @@ void APB::WebServer::onPostSetHeater(AsyncWebServerRequest *request, JsonVariant
322325
if(validation
323326
.range("target_temperature", {-50}, {50})
324327
.required<float>("target_temperature")
328+
.range("min_duty", 0, 1)
325329
.range("ramp_offset", 0, 20)
326330
.invalid()
327331
) return;
328332
float targetTemperature = json["target_temperature"];
329-
float rampOffset = json["ramp_offset"].is<float>() ? json["ramp_offset"] : 0;
330-
if(!heater.setTemperature(targetTemperature, duty, rampOffset)) {
333+
float rampOffset = json["ramp_offset"].is<float>() ? json["ramp_offset"] : 0.f;
334+
float minDuty = json["min_duty"].is<float>() ? json["min_duty"] : 0.f;
335+
if(!heater.setTemperature(targetTemperature, duty, minDuty, rampOffset)) {
331336
JsonResponse::error(500, temperatureErrorMessage, request);
332337
return;
333338
}

web/src/features/sensors/heaters/Heaters.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,27 @@ const SetHeaterModal = ({heater: originalHeater, show, onClose, index}) => {
134134
<Form.Group className='mb-3'>
135135
<Form.Label>Ramp Offset</Form.Label>
136136
<Badge className='float-end'><Number value={heater.ramp_offset || 0} unit='°C' /></Badge>
137-
<Form.Range min={0} max={20} value={heater.ramp_offset || 0} onChange={updateHeater('ramp_offset', parseFloat)}/>
137+
<Form.Range min={0} max={20} step={.1} value={heater.ramp_offset || 0} onChange={updateHeater('ramp_offset', parseFloat)}/>
138138
<Form.Text>
139139
Set this to a number greater than <code>0</code> to start ramping down the duty proportionally to the difference with the target temperature.
140140
For instance, if set to <code>3°C</code>, with a target temperature of <code>25°C</code>, a current temperature of <code>24°C</code>
141141
and a maximum duty of <code>100%</code>, the actual duty will be <code>(25-24)/3 = 33%</code>.
142142
</Form.Text>
143143
</Form.Group>
144+
</Collapse>
145+
<Collapse in={heater.ramp_offset>0 && ['dewpoint', 'target_temperature'].includes(heater.mode) && originalHeater.has_temperature}>
146+
<Form.Group className='mb-3'>
147+
<Form.Label>Mimimum duty</Form.Label>
148+
<Badge className='float-end'><Number value={heater.min_duty*100|| 0} unit='°%' /></Badge>
149+
<Form.Range min={0.0} max={1.0} step={.01} value={heater.min_duty|| 0} onChange={updateHeater('min_duty', parseFloat)}/>
150+
<Form.Text>
151+
Minimum duty for PWM ramping.<br />
152+
Note: the heater will set to <code>0</code> regardless of minimum duty when target temperature is reached.
153+
</Form.Text>
154+
</Form.Group>
144155

145156
</Collapse>
157+
146158
</Form>
147159
</Modal.Body>
148160
<Modal.Footer>

0 commit comments

Comments
 (0)