brewcontrol.cpp 11.6 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
// return current shot_clock in seconds
float BrewControl::get_current_time()
{
    return _shot_clock;
}

// return current volume in ml
float BrewControl::get_current_volume()
{
258
    return _flow_sensor.get_volume();
259 260 261 262 263 264 265 266 267
}

// 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 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
#ifdef LOG
void BrewControl::_log_brew_params()
{
   if (_log_file)
    {
        fprintf(_log_file, "%f,%f,%f,%f,%f,%d\n",
                get_current_time(),
                get_current_volume(),
                get_current_temperature(),
                get_current_pressure(),
                get_current_flow_rate(),
                _state);
    }
}
#endif

289
void BrewControl::_brew_worker()
290 291
{
    _shot_clock = _shot_clock + BREW_WORKER_PERIOD;
292

293 294 295 296
#ifdef LOG
    _log_brew_params();
#endif

297
    if (_state == PRE_INFUSING)
298 299
    {
        // First let's fill up the portafilter
300
        if (_flow_sensor.get_volume() <= PORTAFILTER_VOLUME && !_stop_preinfuse)
301 302 303
            return;
        
        // It's full, let's stop the pump for set pre-infuse time
304
        if (_pump_control.get_level() != 0)
305
        {
306
            _pump_control.set_level(0);
307 308 309 310 311 312 313 314 315 316
            _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();
317
	    _state = BREWING;
Philippe Kalaf's avatar
Philippe Kalaf committed
318
	    _stop_preinfuse = 0;
319
	    _pressure_sensor.start_count();
320
        }
321
	return;
322
    }
323 324
    
    if(_mode == MODE_TIME)
325
    {
326 327 328 329 330 331 332
	// 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();

333
        if(_shot_clock >= _target_shot_time)
Philippe Kalaf's avatar
Philippe Kalaf committed
334
            soft_stop();
Philippe Kalaf's avatar
Philippe Kalaf committed
335
    }
336
    else if(_mode == MODE_YIELD)
337
    {
338 339 340 341 342 343 344
	// 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();

345
	if(_flow_sensor.get_volume() >= _target_shot_volume)
Philippe Kalaf's avatar
Philippe Kalaf committed
346
            soft_stop();
347
    }
348
    else if(_mode == MODE_TIME_YIELD)
349
    {
350 351 352
	// Re-calculate target flowrate =
        // remaining volume / remaining time
	_target_flow_rate = (_target_shot_volume - _flow_sensor.get_volume())
353 354
	    / (_target_shot_time - _shot_clock);
	// Auto-adjust flow-rate
355 356 357 358
        if(_target_flow_rate < 0)
            // oops! we have run out of time! go full power!
            _pump_control.set_level(100);
        else if(_target_flow_rate - _flow_sensor.get_flow_rate() < 0)
359 360 361 362 363
	    pressure_down();
	else
	    pressure_up();

	// Stop when target shot volume is reached
364
	if(_flow_sensor.get_volume() >= _target_shot_volume)
365
            soft_stop();
366
    }
367 368 369
    else if(_mode == MODE_MANUAL)
    {
    }
370 371 372 373 374 375
    else if(_mode == MODE_STEAM)
    {
	// automatically stop steaming after STEAM_TIMEOUT
	if (_shot_clock >= STEAM_TIMEOUT)
	    _stop();
    }
376 377
}

378
uint8_t BrewControl::start(uint8_t mode)
379
{
380
    // We are already brewing, return brewing mode
381 382
    if(_state) return _mode;
    
383 384
    _mode = mode;

385 386 387 388
    // reset shot clock and flow sensor
    _flow_sensor.reset_count();
    _shot_clock = 0;
    _brew_ticker.detach();
389 390

    _state = BREWING;
391 392
    
    // Let's save settings before we set pre-infuse mode
393 394
    // only pre-infuse if set to > 0s and not in manual or steam mode
    if (_preinfuse_time && _mode != MODE_MANUAL && _mode != MODE_STEAM)
395 396
    {
        _prev_mode = _mode;
397
	_prev_pressure = get_pump_level();
398
	// set pre-infuse mode
399 400 401
	_state = PRE_INFUSING;

	// we pre-infuse at low pressure
402
        _pump_control.set_level(60);
403
    }
404 405 406 407 408 409 410 411 412 413

    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;
414 415
    
    // Run worker at defined period
416 417
    _brew_ticker.attach(callback(this, &BrewControl::_brew_worker),
		    BREW_WORKER_PERIOD);
418 419 420 421

    return _mode;
}

422 423 424 425 426 427 428 429
void BrewControl::toggle_solenoid()
{
    if(_brew_switch)
	_brew_switch = 0;
    else
	_brew_switch = 1;
}

430
void BrewControl::_stop()
431
{
432
    _state = STOPPED;
433
    _brew_switch = 0;
434
    _shot_clock = 0;
435 436 437
    set_shot_volume(0);
    set_shot_time(0);
    _flow_sensor.reset_count();
438 439 440 441 442
    if(_prev_temp)
    {
	set_shot_temperature(_prev_temp);
	_prev_temp = 0;
    }
443 444
}

Philippe Kalaf's avatar
Philippe Kalaf committed
445 446
void BrewControl::soft_stop()
{
447 448 449 450 451
    if (_mode == MODE_STEAM)
    {
	_stop();
	return;
    }
Philippe Kalaf's avatar
Philippe Kalaf committed
452
    _pump_control.set_level(0);
453
    _brew_ticker.detach();
454
    _soft_stop_timer.attach(callback(this, &BrewControl::_stop),
455
			SOFT_STOP_TIME_S);
456
    _average_pressure = _pressure_sensor.stop_count();
457
    _state = SOFT_STOPPING;
Philippe Kalaf's avatar
Philippe Kalaf committed
458 459
}

460 461 462 463 464
float BrewControl::get_average_pressure()
{
    return _average_pressure;
}

465
uint8_t BrewControl::toggle(uint8_t mode)
466
{
467
    if(_state == BREWING || _state == PRE_INFUSING)
468
	soft_stop();
469
    else if(_state == STOPPED)
470 471
	start(mode);

472 473 474 475 476 477 478
    return _state;
}

PhaseControl *BrewControl::get_pump_control_ptr()
{
    return &_pump_control;
}
479 480 481 482 483 484 485 486 487 488

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();
489 490
#else
    return 0;
491 492
#endif
}