brewcontrol.cpp 11 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* Copyright (c) 2017 Philippe Kalaf, MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
18 19


20 21 22
// brew worker to run 10 times per second
#define BREW_WORKER_PERIOD 0.1

23
// Boiler PID worker to run 1 times per second
24 25 26 27 28 29
#define PID_WORKER_PERIOD 1

// PWM period
// 0.8333 for 60Hz, 1 for 50Hz for 1% resolution
#define BOILER_PWM_PERIOD 0.8333

Philippe Kalaf's avatar
Philippe Kalaf committed
30 31 32
// Soft stop time
#define SOFT_STOP_TIME_S 7.0

33 34 35 36 37 38
// Steam timeout in seconds
#define STEAM_TIMEOUT 300.0

// Steam temperature
#define STEAM_TEMPERATURE 140.0

39 40 41 42 43 44 45 46 47
// Manage different brew modes and timings
#include "brewcontrol.h"

BrewControl::BrewControl(   PinName brew_pin, 
                            PinName flow_sensor_pin,
                            PinName zcd_input_pin,
                            PinName pump_control_pin,
                            PinName pressure_sensor_pin,
                            PinName temp_sensor_pin,
48 49 50
#ifdef TEMP2
			    PinName temp2_sensor_pin,
#endif
51 52 53 54 55 56 57
                            PinName boiler_pwm_pin
                            ) : 
                            _brew_switch(brew_pin, 0), 
                            _flow_sensor(flow_sensor_pin), 
                            _pump_control(zcd_input_pin, pump_control_pin),
                            _pressure_sensor(pressure_sensor_pin),
                            _temp_sensor(temp_sensor_pin),
58 59 60
#ifdef TEMP2
			    _temp2_sensor(temp2_sensor_pin),
#endif
61 62 63 64 65 66 67 68 69 70 71 72
                            _boiler_pwm(boiler_pwm_pin)
{
    _preinfuse_time = 0;
    _brew_switch = 0;
    
    // at 60Hz, we got 120 zero-crosses per sec, we want to capture 100 of 
    // those within each PWM period
    _boiler_pwm.period(BOILER_PWM_PERIOD);
    
    // let's start at 93 C
    _target_shot_temperature = 93;

73 74 75
    // 9 bars is default
    _target_shot_pressure = 9;

76 77 78
    // this is used for steam mode to return to prev target temp when done
    _prev_temp = 0;

79 80 81
    _boiler_pid.setPIDGains(0.075, 0.1, 0.9);
    _boiler_pid.setIntegratorLimits(0, 1);

82 83 84 85
    // Boiler is on by default
    enable_boiler();
}

86
float BrewControl::get_current_temperature_side()
87 88 89 90
{
    return _temp_sensor.read();
}

91 92
float BrewControl::get_current_temperature_top()
{
93 94 95
#ifdef TEMP2
    return _temp2_sensor.read();
#endif
96 97 98 99 100 101 102 103 104
    return 0;
}

float BrewControl::get_current_temperature()
{
    //return (_temp_sensor.read() + _temp2_sensor.read())/2;
    return get_current_temperature_side();
}

105 106 107 108
void BrewControl::set_shot_temperature(float shot_temp)
{
    if (shot_temp <= 20)
        _target_shot_temperature = 20;
Philippe Kalaf's avatar
Philippe Kalaf committed
109 110
    else if (shot_temp >= 150)
        _target_shot_temperature = 150;
111 112 113 114 115 116 117 118 119
    else
        _target_shot_temperature = shot_temp;
}

float BrewControl::get_shot_temperature()
{
    return _target_shot_temperature;
}

120 121
void BrewControl::set_shot_pressure(float pressure)
{
122 123 124 125 126 127
    if (pressure <= 0)
        _target_shot_pressure = 0;
    else if (pressure >= 12)
        _target_shot_pressure = 12;
    else
        _target_shot_pressure = pressure;
128
}
129 130 131 132

// Return pressure in bars
float BrewControl::get_current_pressure()
{
133
    return _pressure_sensor.read_bars();
134 135 136 137 138 139 140 141
}

int BrewControl::get_shot_volume()
{
    return _target_shot_volume;
}


142
void BrewControl::_boiler_pid_worker()
143 144
{
    // take temperature measurement
145
    float latestTemp = get_current_temperature(); 
146
        
147
    float power = 0.0;
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165

    // if the temperature is near zero, we assume there's an error
    if ( latestTemp > 0.5 ) {
        // calculate PID update
        power = _boiler_pid.update(_target_shot_temperature - latestTemp,
                                    latestTemp);
    }

    // Validate number
    if ( power > 1 )
        power = 1;
    else if ( power < 0 )
        power = 0;

    // let's set new power on boiler
    _boiler_pwm = power;

    // store the latest temperature reading
166
    _latest_temp = latestTemp;
167 168 169 170 171
}

void BrewControl::enable_boiler()
{
    _enable_boiler = 1;
172
    _boiler_pid_ticker.attach(callback(this, &BrewControl::_boiler_pid_worker),
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
                                PID_WORKER_PERIOD);
}

void BrewControl::disable_boiler()
{
    _enable_boiler = 0;
    _boiler_pid_ticker.detach();
    _boiler_pwm = 0;
}

bool BrewControl::toggle_boiler()
{
    if (_enable_boiler)
        disable_boiler();
    else
        enable_boiler();
    
    return _enable_boiler;
}

193
void BrewControl::pressure_up(uint8_t value)
194
{
195 196 197
    // limit to 12 bars
    if (get_current_pressure() <= 11.5)
        _pump_control.level_up(value);
198 199
}

200
void BrewControl::pressure_down(uint8_t value)
201
{
202
    _pump_control.level_down(value);
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
}

// pre-infuse time in seconds
// set to 0 to disable
void BrewControl::set_preinfuse_time(int time)
{
    if (time > 0)
        _preinfuse_time = time;
    else
        _preinfuse_time = 0;
}

int BrewControl::get_preinfuse_time()
{
    return _preinfuse_time;
}

Philippe Kalaf's avatar
Philippe Kalaf committed
220 221 222 223 224
void BrewControl::stop_preinfuse_now()
{
    _stop_preinfuse = 1;
}

225 226 227 228 229 230 231 232 233 234 235 236 237 238
uint8_t BrewControl::get_pump_level()
{
    return _pump_control.get_level();
}

// This is to set the wanted shot time
void BrewControl::set_shot_time(int time)
{
    _target_shot_time = time;
}

// This is to set the wanted shot volume
void BrewControl::set_shot_volume(int volume)
{
239
    if (volume >= 0)
240 241 242
        _target_shot_volume = volume;
}

243 244 245 246 247 248
// This is to set the wanted flow rate (ml/s)
void BrewControl::set_shot_flow_rate(float flow_rate)
{
    _target_flow_rate = flow_rate;
}

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
// return current shot_clock in seconds
float BrewControl::get_current_time()
{
    return _shot_clock;
}

// return current volume in ml
float BrewControl::get_current_volume()
{
    return _flow_sensor.read();
}

// return the current flow rate
float BrewControl::get_current_flow_rate()
{
    return _flow_sensor.get_flow_rate();
}

// read brew on/off state
268
uint8_t BrewControl::get_state()
269 270 271 272
{
    return _state;
}

273
void BrewControl::_brew_worker()
274 275
{
    _shot_clock = _shot_clock + BREW_WORKER_PERIOD;
276

277
    if (_state == PRE_INFUSING)
278 279
    {
        // First let's fill up the portafilter
Philippe Kalaf's avatar
Philippe Kalaf committed
280
        if (_flow_sensor.read() <= PORTAFILTER_VOLUME && !_stop_preinfuse)
281 282 283
            return;
        
        // It's full, let's stop the pump for set pre-infuse time
284
        if (_pump_control.get_level() != 0)
285
        {
286
            _pump_control.set_level(0);
287 288 289 290 291 292 293 294 295 296
            _shot_clock = 0;
        }
        
        // Once pre-infuse time runs out, set brew mode
        if (_shot_clock >= _preinfuse_time)
        {
            _mode = _prev_mode;
            _pump_control.set_level(_prev_pressure);
            _shot_clock = 0;
            _flow_sensor.reset_count();
297
	    _state = BREWING;
Philippe Kalaf's avatar
Philippe Kalaf committed
298
	    _stop_preinfuse = 0;
299
	    _pressure_sensor.start_count();
300
        }
301
	return;
302
    }
303 304
    
    if(_mode == MODE_TIME)
305
    {
306 307 308 309 310 311 312
	// Auto-adjust pressure to target
	float error = _target_shot_pressure - get_current_pressure();
	if(error < -0.25)
	    pressure_down();
	else if(error > 0.25) 
	    pressure_up();

313
        if(_shot_clock >= _target_shot_time)
Philippe Kalaf's avatar
Philippe Kalaf committed
314
            soft_stop();
Philippe Kalaf's avatar
Philippe Kalaf committed
315
    }
316
    else if(_mode == MODE_YIELD)
317
    {
318 319 320 321 322 323 324 325
	// Auto-adjust pressure to target
	float error = _target_shot_pressure - get_current_pressure();
        if(error < -0.25)
	    pressure_down();
	else if(error > 0.25)
	    pressure_up();

	if(_flow_sensor.read() >= _target_shot_volume)
Philippe Kalaf's avatar
Philippe Kalaf committed
326
            soft_stop();
327
    }
328
    else if(_mode == MODE_TIME_YIELD)
329
    {
330 331 332 333 334 335 336 337 338 339 340 341
	// Re-calculate target flowrate
	_target_flow_rate = (_target_shot_volume - _flow_sensor.read())
	    / (_target_shot_time - _shot_clock);
	// Auto-adjust flow-rate
	if(_target_flow_rate - _flow_sensor.get_flow_rate() < 0)
	    pressure_down();
	else
	    pressure_up();

	// Stop when target shot volume is reached
	if(_flow_sensor.read() >= _target_shot_volume)
            soft_stop();
342
    }
343 344 345
    else if(_mode == MODE_MANUAL)
    {
    }
346 347 348 349 350 351
    else if(_mode == MODE_STEAM)
    {
	// automatically stop steaming after STEAM_TIMEOUT
	if (_shot_clock >= STEAM_TIMEOUT)
	    _stop();
    }
352 353
}

354
uint8_t BrewControl::start(uint8_t mode)
355
{
356
    // We are already brewing, return brewing mode
357 358
    if(_state) return _mode;
    
359 360
    _mode = mode;

361 362 363 364
    // reset shot clock and flow sensor
    _flow_sensor.reset_count();
    _shot_clock = 0;
    _brew_ticker.detach();
365 366

    _state = BREWING;
367 368
    
    // Let's save settings before we set pre-infuse mode
369 370
    // only pre-infuse if set to > 0s and not in manual or steam mode
    if (_preinfuse_time && _mode != MODE_MANUAL && _mode != MODE_STEAM)
371 372
    {
        _prev_mode = _mode;
373
	_prev_pressure = get_pump_level();
374
	// set pre-infuse mode
375 376 377
	_state = PRE_INFUSING;

	// we pre-infuse at low pressure
378
        _pump_control.set_level(60);
379
    }
380 381 382 383 384 385 386 387 388 389

    if(_mode == MODE_STEAM)
    {
 	// save currently set temperature to return to it after steaming
	_prev_temp = get_shot_temperature();
	set_shot_temperature(STEAM_TEMPERATURE);
    }
    else
	// Turn on brewing circuits
	_brew_switch = 1;
390 391
    
    // Run worker at defined period
392 393
    _brew_ticker.attach(callback(this, &BrewControl::_brew_worker),
		    BREW_WORKER_PERIOD);
394 395 396 397

    return _mode;
}

398 399 400 401 402 403 404 405
void BrewControl::toggle_solenoid()
{
    if(_brew_switch)
	_brew_switch = 0;
    else
	_brew_switch = 1;
}

406
void BrewControl::_stop()
407
{
408
    _state = STOPPED;
409
    _brew_switch = 0;
410
    _shot_clock = 0;
411 412 413
    set_shot_volume(0);
    set_shot_time(0);
    _flow_sensor.reset_count();
414 415 416 417 418
    if(_prev_temp)
    {
	set_shot_temperature(_prev_temp);
	_prev_temp = 0;
    }
419 420
}

Philippe Kalaf's avatar
Philippe Kalaf committed
421 422
void BrewControl::soft_stop()
{
423 424 425 426 427
    if (_mode == MODE_STEAM)
    {
	_stop();
	return;
    }
Philippe Kalaf's avatar
Philippe Kalaf committed
428
    _pump_control.set_level(0);
429
    _brew_ticker.detach();
430
    _soft_stop_timer.attach(callback(this, &BrewControl::_stop),
431
			SOFT_STOP_TIME_S);
432
    _average_pressure = _pressure_sensor.stop_count();
433
    _state = SOFT_STOPPING;
Philippe Kalaf's avatar
Philippe Kalaf committed
434 435
}

436 437 438 439 440
float BrewControl::get_average_pressure()
{
    return _average_pressure;
}

441
uint8_t BrewControl::toggle(uint8_t mode)
442
{
443
    if(_state == BREWING || _state == PRE_INFUSING)
444
	soft_stop();
445
    else if(_state == STOPPED)
446 447
	start(mode);

448 449 450 451 452 453 454
    return _state;
}

PhaseControl *BrewControl::get_pump_control_ptr()
{
    return &_pump_control;
}
455 456 457 458 459 460 461 462 463 464

uint16_t BrewControl::get_last_pulse_count_side()
{
    return _temp_sensor.get_last_pulse_count();
}

uint16_t BrewControl::get_last_pulse_count_top()
{
#ifdef TEMP2
    return _temp2_sensor.get_last_pulse_count();
465 466
#else
    return 0;
467 468
#endif
}