<commit_before>/*
 * Copyright 2012 BrewPi/<NAME>.
 *
 * This file is part of BrewPi.
 * 
 * BrewPi is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * BrewPi is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with BrewPi.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Brewpi.h"

#include "Pins.h"
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <limits.h>

#include "TemperatureFormats.h"
#include "TempControl.h"
#include "PiLink.h"
#include "TempSensor.h"
#include "Ticks.h"
#include "TempSensorMock.h"
#include "EepromManager.h"
#include "TempSensorDisconnected.h"

TempControl tempControl;

#if TEMP_CONTROL_STATIC

extern ValueSensor<bool> defaultSensor;
extern ValueActuator defaultActuator;
extern DisconnectedTempSensor defaultTempSensor;

// These sensors are switched out to implement multi-chamber.
TempSensor* TempControl::beerSensor;
TempSensor* TempControl::fridgeSensor;
BasicTempSensor* TempControl::ambientSensor = &defaultTempSensor;


Actuator* TempControl::heater = &defaultActuator;
Actuator* TempControl::cooler = &defaultActuator;
Actuator* TempControl::light = &defaultActuator;
Actuator* TempControl::fan = &defaultActuator;

ValueActuator cameraLightState;		
AutoOffActuator TempControl::cameraLight(600, &cameraLightState);	// timeout 10 min
Sensor<bool>* TempControl::door = &defaultSensor;
	
// Control parameters
ControlConstants TempControl::cc;
ControlSettings TempControl::cs;
ControlVariables TempControl::cv;
	
	// State variables
uint8_t TempControl::state;
bool TempControl::doPosPeakDetect;
bool TempControl::doNegPeakDetect;
	
	// keep track of beer setting stored in EEPROM
fixed7_9 TempControl::storedBeerSetting;
	
	// Timers
unsigned int TempControl::lastIdleTime;
unsigned int TempControl::lastHeatTime;
unsigned int TempControl::lastCoolTime;
#endif

void TempControl::init(void){
	state=STARTUP;		
	cs.mode = MODE_OFF;
	
	if (tempControl.beerSensor==NULL)
		tempControl.beerSensor = new TempSensor(TEMP_SENSOR_TYPE_BEER, &defaultTempSensor);
	if (tempControl.fridgeSensor==NULL)
		tempControl.fridgeSensor = new TempSensor(TEMP_SENSOR_TYPE_FRIDGE, &defaultTempSensor);
		
	beerSensor->init();
	fridgeSensor->init();
	updateTemperatures();
	reset();
}

void TempControl::reset(void){
	doPosPeakDetect=false;
	doNegPeakDetect=false;
}

void TempControl::updateTemperatures(void){
	beerSensor->update();
	if(!beerSensor->isConnected() && (cs.mode == MODE_BEER_CONSTANT || cs.mode == MODE_FRIDGE_CONSTANT)){
		beerSensor->init(); // try to restart the sensor when controlling beer temperature
	}
	fridgeSensor->update();
	if(!fridgeSensor->isConnected()){
		fridgeSensor->init(); // always try to restart the fridge sensor
	}
}


fixed7_9 multiplyFixeda7_9b23_9(fixed7_9 a, fixed23_9 b)
{
	return ((fixed23_9) a * b)>>9;
}

fixed7_9 multiplyFixed7_9(fixed7_9 a, fixed7_9 b) 
{	
	return ((fixed23_9) a * (fixed23_9) b)>>9;	
}


void TempControl::updatePID(void){
	static unsigned char integralUpdateCounter = 0;
	if(cs.mode == MODE_BEER_CONSTANT || cs.mode == MODE_BEER_PROFILE){
		if(cs.beerSetting == INT_MIN){
			// beer setting is not updated yet
			// set fridge to unknown too
			cs.fridgeSetting = INT_MIN;
			return;
		}
		
		// fridge setting is calculated with PID algorithm. Beer temperature error is input to PID
		cv.beerDiff =  cs.beerSetting - beerSensor->readSlowFiltered();
		cv.beerSlope = beerSensor->readSlope();
		fixed7_9 fridgeFastFiltered = fridgeSensor->readFastFiltered();
		if(integralUpdateCounter++ == 60){
			integralUpdateCounter = 0;
			if(abs(cv.beerDiff) < cc.iMaxError){
				//difference is smaller than iMaxError, check 4 conditions to see if integrator should be active
				
				// Actuator is not saturated. Update integrator
				if((cv.beerDiff >= 0) == (cv.diffIntegral >= 0)){
					// beerDiff and integrator have same sign. Integrator action is increased.
					if(timeSinceIdle() > 1800){
						// more than 30 minutes since idle, actuator is probably saturated. Do not increase integrator.
					}
					if(cs.fridgeSetting == cc.tempSettingMax && cs.fridgeSetting == cc.tempSettingMin){
						// actuator is already at max. Increasing actuator will only cause integrator windup.
					}
					else if(cv.beerDiff < 0 && (cs.fridgeSetting +1024) < fridgeFastFiltered){
						// cooling and fridge temp is more than 2 degrees from setting, actuator is saturated.
					}
					else if(cv.beerDiff > 0 && (cs.fridgeSetting -1024) > fridgeFastFiltered){
						// heating and fridge temp is more than 2 degrees from setting, actuator is saturated.
					}
					else{
						// increase integrator action
						cv.diffIntegral = cv.diffIntegral + cv.beerDiff;
					}
				}
				else{
					// integrator action is decreased. Decrease faster than increase.
					cv.diffIntegral = cv.diffIntegral + 4*cv.beerDiff;
				}
			}
			else{
				// decrease integral by 1/8 when not close to end value to prevent integrator windup
				cv.diffIntegral = cv.diffIntegral-(cv.diffIntegral>>3);
			
			}
		}			
		
		// calculate PID parts. Use fixed23_9 to prevent overflow<commit_msg>Refactor of updatePID, still needs testing<commit_after>/*
 * Copyright 2012 BrewPi/<NAME>.
 *
 * This file is part of BrewPi.
 * 
 * BrewPi is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * BrewPi is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with BrewPi.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Brewpi.h"

#include "Pins.h"
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <limits.h>

#include "TemperatureFormats.h"
#include "TempControl.h"
#include "PiLink.h"
#include "TempSensor.h"
#include "Ticks.h"
#include "TempSensorMock.h"
#include "EepromManager.h"
#include "TempSensorDisconnected.h"

TempControl tempControl;

#if TEMP_CONTROL_STATIC

extern ValueSensor<bool> defaultSensor;
extern ValueActuator defaultActuator;
extern DisconnectedTempSensor defaultTempSensor;

// These sensors are switched out to implement multi-chamber.
TempSensor* TempControl::beerSensor;
TempSensor* TempControl::fridgeSensor;
BasicTempSensor* TempControl::ambientSensor = &defaultTempSensor;


Actuator* TempControl::heater = &defaultActuator;
Actuator* TempControl::cooler = &defaultActuator;
Actuator* TempControl::light = &defaultActuator;
Actuator* TempControl::fan = &defaultActuator;

ValueActuator cameraLightState;		
AutoOffActuator TempControl::cameraLight(600, &cameraLightState);	// timeout 10 min
Sensor<bool>* TempControl::door = &defaultSensor;
	
// Control parameters
ControlConstants TempControl::cc;
ControlSettings TempControl::cs;
ControlVariables TempControl::cv;
	
	// State variables
uint8_t TempControl::state;
bool TempControl::doPosPeakDetect;
bool TempControl::doNegPeakDetect;
	
	// keep track of beer setting stored in EEPROM
fixed7_9 TempControl::storedBeerSetting;
	
	// Timers
unsigned int TempControl::lastIdleTime;
unsigned int TempControl::lastHeatTime;
unsigned int TempControl::lastCoolTime;
#endif

void TempControl::init(void){
	state=STARTUP;		
	cs.mode = MODE_OFF;
	
	if (tempControl.beerSensor==NULL)
		tempControl.beerSensor = new TempSensor(TEMP_SENSOR_TYPE_BEER, &defaultTempSensor);
	if (tempControl.fridgeSensor==NULL)
		tempControl.fridgeSensor = new TempSensor(TEMP_SENSOR_TYPE_FRIDGE, &defaultTempSensor);
		
	beerSensor->init();
	fridgeSensor->init();
	updateTemperatures();
	reset();
}

void TempControl::reset(void){
	doPosPeakDetect=false;
	doNegPeakDetect=false;
}

void TempControl::updateTemperatures(void){
	beerSensor->update();
	if(!beerSensor->isConnected() && (cs.mode == MODE_BEER_CONSTANT || cs.mode == MODE_FRIDGE_CONSTANT)){
		beerSensor->init(); // try to restart the sensor when controlling beer temperature
	}
	fridgeSensor->update();
	if(!fridgeSensor->isConnected()){
		fridgeSensor->init(); // always try to restart the fridge sensor
	}
}


fixed7_9 multiplyFixeda7_9b23_9(fixed7_9 a, fixed23_9 b)
{
	return ((fixed23_9) a * b)>>9;
}

fixed7_9 multiplyFixed7_9(fixed7_9 a, fixed7_9 b) 
{	
	return ((fixed23_9) a * (fixed23_9) b)>>9;	
}


void TempControl::updatePID(void){
	static unsigned char integralUpdateCounter = 0;
	if(cs.mode == MODE_BEER_CONSTANT || cs.mode == MODE_BEER_PROFILE){
		if(cs.beerSetting == INT_MIN){
			// beer setting is not updated yet
			// set fridge to unknown too
			cs.fridgeSetting = INT_MIN;
			return;
		}
		
		// fridge setting is calculated with PID algorithm. Beer temperature error is input to PID
		cv.beerDiff =  cs.beerSetting - beerSensor->readSlowFiltered();
		cv.beerSlope = beerSensor->readSlope();
		fixed7_9 fridgeFastFiltered = fridgeSensor->readFastFiltered();
			
		if(integralUpdateCounter++ == 60){
			integralUpdateCounter = 0;
			
			fixed7_9 integratorUpdate = cv.beerDiff;
			if(abs(integratorUpdate) < cc.iMaxError){
				//difference is smaller than iMaxError, check 4 conditions to see if integrator should be active
				bool updateSign = (integratorUpdate > 0); // 1 = positive, 0 = negative
				bool integratorSign = (cv.diffIntegral > 0);		
				
				// Actuator is not saturated. Update integrator
				if(updateSign == integratorSign){
					// beerDiff and integrator have same sign. Integrator would be increased.
					
					// set update to zero when timeSinceIdle > 30 min. Actuator is probably saturated
					integratorUpdate = (timeSinceIdle() > 1800u) ? 0 : integratorUpdate;
					
					// If actuator is already at max increasing actuator will only cause integrator windup.
					integratorUpdate = (cs.fridgeSetting == cc.tempSettingMax) ? 0 : integratorUpdate;
					integratorUpdate = (cs.fridgeSetting == cc.tempSettingMin) ? 0 : integratorUpdate;										
					
					// cooling and fridge temp is more than 2 degrees from setting, actuator is saturated.
					integratorUpdate = (!updateSign && (fridgeFastFiltered > (cs.fridgeSetting +1024))) ? 0 : integratorUpdate;
					
					// heating and fridge temp is more than 2 degrees from setting, actuator is saturated.
					integratorUpdate = (updateSign && (fridgeFastFiltered < (cs.fridgeSetting -1024))) ? 0 : integratorUpdate;
									
				}
				else{
					// integrator action is decreased. Decrease faster than increase.
					integratorUpdate = integratorUpdate*4;
				}
				
			}
			else{
				// decrease integral by 1/8 when not close to end value to prevent integrator windup
				integratorUpdate = -(cv.diffIntegral >> 3);		
			}
			cv.diffIntegral = cv.diffIntegral + integratorUpdate;
		}			
		
		// calculate PID parts. Use fixed23_9 to prevent overflow
{'fraction_non_alphanumeric': 0.10086328400281888, 'fraction_numerical': 0.014358703312191684, 'mean_word_length': 4.3627775153519135, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1595984', 'n_tokens_mistral': 4027, 'n_tokens_neox': 3548, 'n_words': 1019}