class TrailingAverageSensor(SensorPassive): sensor_prop = Property.Sensor("Sensor", description="Select a sensor to average readings of.") count_prop = Property.Number("Count", configurable=True, default_value=12, description="Number of readings to average.") decimals_prop = Property.Number("Decimals", configurable=True, default_value=1, description="How many decimals to round the average to.") #------------------------------------------------------------------------------- def init(self): self.values = list() self.sensor_id = int(self.sensor_prop) self.count = int(self.count_prop) self.weight = 1.0/self.count self.decimals = int(self.decimals_prop) #------------------------------------------------------------------------------- def read(self): self.values.append(float(cbpi.cache.get("sensors")[int(self.sensor_id)].instance.last_value)) while len(self.values) > self.count: self.values.pop(0) numerator = 0.0 denominator = 0.0 weight = 1.0 for value in reversed(self.values): numerator += value * weight denominator += weight weight = weight - self.weight self.data_received(round(numerator/denominator, self.decimals)) #------------------------------------------------------------------------------- def get_unit(self): return cbpi.cache.get("sensors")[int(self.sensor_id)].instance.get_unit() #------------------------------------------------------------------------------- def stop(self): pass
class Hysteresis(KettleController): # Custom Properties on = Property.Number("Offset On", True, 0, description="Offset below target temp when heater should switched on. Should be bigger then Offset Off") off = Property.Number("Offset Off", True, 0, description="Offset below target temp when heater should switched off. Should be smaller then Offset Off") def stop(self): ''' Invoked when the automatic is stopped. Normally you switch off the actors and clean up everything :return: None ''' super(KettleController, self).stop() self.heater_off() def run(self): ''' Each controller is exectuted in its own thread. The run method is the entry point :return: ''' while self.is_running(): if self.get_temp() < self.get_target_temp() - float(self.on): self.heater_on(100) elif self.get_temp() >= self.get_target_temp() - float(self.off): self.heater_off() else: self.heater_off() self.sleep(1)
class Hysteresis(FermenterController): heater_offset_min = Property.Number("Heater Offset min", True, 0) heater_offset_max = Property.Number("Heater Offset max", True, 0) cooler_offset_min = Property.Number("Cooler Offset min", True, 0) cooler_offset_max = Property.Number("Cooler Offset max", True, 0) def stop(self): super(FermenterController, self).stop() self.heater_off() self.cooler_off() def run(self): while self.is_running(): target_temp = self.get_target_temp() temp = self.get_temp() if temp + int(self.heater_offset_min) < target_temp: self.heater_on(100) if temp + int(self.heater_offset_max) > target_temp: self.heater_off() if temp > target_temp + int(self.cooler_offset_min): self.cooler_on(100) if temp < target_temp + int(self.cooler_offset_max): self.cooler_off() self.sleep(1)
class PIDPWM(KettleController): a_p = Property.Number("P", True, 0) b_i = Property.Number("I", True, 0) c_d = Property.Number("D", True, 0) d_max_out = Property.Number("max. output %", True, 100) def run(self): sampleTime = 5 p = float(self.a_p) i = float(self.b_i) d = float(self.c_d) maxout = float(self.d_max_out) pid = PIDArduino(sampleTime, p, i, d, 0, maxout) self.heater_on(0) while self.is_running(): heat_percent = pid.calc(self.get_temp(), self.get_target_temp()) print(heat_percent) self.actor_power(round(heat_percent)) self.sleep(sampleTime) def stop(self): super(KettleController, self).stop() self.heater_off()
class SimpleBoilLogic(KettleController): ramp_power = Property.Number("Ramp Up Power %", True, 100) boil_power = Property.Number("Boil Power %", True, 70) def stop(self): super(KettleController, self).stop() self.heater_off() def run(self): r_power = int(self.ramp_power) b_power = int(self.boil_power) while self.is_running(): temp = self.get_temp() r_limit = self.get_target_temp() if temp < r_limit and r_limit > 0: self.heater_on(r_power) self.actor_power(r_power) if temp >= r_limit and r_limit > 0: self.heater_on(b_power) self.actor_power(b_power) self.sleep(1)
class HeartsStep(StepBase, BaseColletingStep): temperatureSensor = StepProperty.Sensor( "Датчик температуры", description="Датчик температуры в кубе") initialCollecting = Property.Number("Стартовая скорость отбора, мл/ч", configurable=True, default_value=1000) endTemp = Property.Number("Температура завершения отбора", configurable=True, default_value=93) collectingSpeed = 0.0 temperature = 0 def finish(self): self.actor_off(int(self.collectingActor)) self.notify("", "Отбор тела завершен", type="success", timeout=2000) def execute(self): self.updateAndCheckTemperature() self.recountCollecting() self.notifySensor() self.updateMaxCollectingSpeed() self.calculateActorPower() self.manageActor() def recountCollecting(self): self.collectingSpeed = int( self.initialCollecting) * (6.04 - 0.06 * float(self.temperature)) def updateAndCheckTemperature(self): self.temperature = float( self.get_sensor_value(int(self.temperatureSensor))) if self.temperature >= int(self.endTemp): next(self)
class Hysteresis(KettleController): # Custom Properties on = Property.Number("Offset On", True, 0) off = Property.Number("Offset Off", True, 0) def stop(self): ''' Invoked when the automatic is stopped. Normally you switch off the actors and clean up everything :return: None ''' super(KettleController, self).stop() self.heater_off() def run(self): ''' Each controller is exectuted in its own thread. The run method is the entry point :return: ''' while self.is_running(): self.actor_power(50) if self.get_temp() < self.get_target_temp() - int(self.on): self.heater_on(100) elif self.get_temp() >= self.get_target_temp() - int(self.off): self.heater_off() else: self.heater_off() self.sleep(1)
class PIDHendi(KettleController): P = Property.Number("P", configurable=True, default_value=40, unit="") I = Property.Number("I", configurable=True, default_value=140, unit="") D = Property.Number("D", configurable=True, default_value=0, unit="") Pmax = Property.Number("Max Power", configurable=True, default_value=100, unit="%") def run(self): p = float(self.P) i = float(self.I) d = float(self.D) pmax = int(self.Pmax) ts = 5 print((p, i, d, pmax)) pid = PID(ts, p, i, d, pmax) while self.is_running(): heat_percent = pid.calc(self.get_sensor_value(), self.get_target_temp()) if heat_percent == 0: self.actor_power(heat_percent) self.heater_off() cbpi.log_action("PIDHendi OFF {}") else: self.actor_power(heat_percent) self.heater_on(power=heat_percent) cbpi.log_action("PIDHendi calling heater_on(power={})".format(heat_percent)) self.sleep(ts) self.heater_off()
class HeadsStep(StepBase, BaseColletingStep): collectingSpeed = Property.Number("Скорость отбора, мл/ч", configurable=True, default_value=100) headsTotal = Property.Number("Объем голов для отбора, мл", configurable=True, default_value=100) total = 0 time = datetime.utcnow() def finish(self): self.actor_off(int(self.collectingActor)) self.notify("", "Отбор голов завершен", type="success", timeout=2000) def execute(self): self.updateMaxCollectingSpeed() self.calculateActorPower() self.manageActor() self.checkTotalCollecting() self.notifySensor() def checkTotalCollecting(self): time = datetime.utcnow() if (time - self.time).total_seconds() >= 10: self.time = time self.total += float(self.collectingSpeed) / 360.0 if self.total >= int(self.headsTotal): next(self)
class LauteringAutomation(KettleController): minSensorDistance = Property.Number( "Minimum Distance to Sensor (High Fill Level)", True, 0) maxSensorDistance = Property.Number( "Maximum Distance to Sensor (Low Fill Level)", True, 0) pumping = False currentFillLevel = 0 def stop(self): super(KettleController, self).stop() self.heater_off() def run(self): self.sleep(1) while self.is_running(): self.currentFillLevel = self.get_temp() if bool(self.pumping): if self.currentFillLevel >= float(self.maxSensorDistance): self.heater_off() self.pumping = False else: self.heater_on(100) else: if self.currentFillLevel <= float(self.minSensorDistance): self.heater_on(100) self.pumping = True else: self.heater_off() self.sleep(1)
class PIDArduino(KettleController): a_p = Property.Number("P", True, 0) b_i = Property.Number("I", True, 0) c_d = Property.Number("D", True, 0) d_max_out = Property.Number("max. output %", True, 100) def run(self): sampleTime = 5 wait_time = 5 p = float(self.a_p) i = float(self.b_i) d = float(self.c_d) maxout = float(self.d_max_out) pid = PIDArduino(sampleTime, p, i, d, 0, maxout) while self.is_running(): heat_percent = pid.calc(self.get_temp(), self.get_target_temp()) heating_time = sampleTime * heat_percent / 100 wait_time = sampleTime - heating_time if heating_time == sampleTime: self.heater_on(100) self.sleep(heating_time) elif wait_time == sampleTime: self.heater_off() self.sleep(wait_time) else: self.heater_on(100) self.sleep(heating_time) self.heater_off() self.sleep(wait_time)
class Hysteresis(FermenterController): heater_offset_min = Property.Number("Heater Offset ON", True, 0, description="Offset as decimal number when the heater is switched on. Should be greater then 'Heater Offset OFF'. For example a value of 2 switches on the heater if the current temperature is 2 degrees below the target temperature") heater_offset_max = Property.Number("Heater Offset OFF", True, 0, description="Offset as decimal number when the heater is switched off. Should be smaller then 'Heater Offset ON'. For example a value of 1 switches off the heater if the current temperature is 1 degree below the target temperature") cooler_offset_min = Property.Number("Cooler Offset ON", True, 0, description="Offset as decimal number when the cooler is switched on. Should be greater then 'Cooler Offset OFF'. For example a value of 2 switches on the cooler if the current temperature is 2 degrees above the target temperature") cooler_offset_max = Property.Number("Cooler Offset OFF", True, 0, description="Offset as decimal number when the cooler is switched off. Should be less then 'Cooler Offset ON'. For example a value of 1 switches off the cooler if the current temperature is 1 degree above the target temperature") def stop(self): super(FermenterController, self).stop() self.heater_off() self.cooler_off() def run(self): while self.is_running(): target_temp = self.get_target_temp() temp = self.get_temp() if temp + float(self.heater_offset_min) <= target_temp: self.heater_on(100) if temp + float(self.heater_offset_max) >= target_temp: self.heater_off() if temp >= target_temp + float(self.cooler_offset_min): self.cooler_on(100) if temp <= target_temp + float(self.cooler_offset_max): self.cooler_off() self.sleep(1)
class MashStep(StepBase): ''' Just put the decorator @cbpi.step on top of a method ''' # Properties temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step") kettle = StepProperty.Kettle( "Kettle", description="Kettle in which the mashing takes place") timer = Property.Number( "Timer in Minutes", configurable=True, description="Timer is started when the target temperature is reached") def init(self): ''' Initialize Step. This method is called once at the beginning of the step :return: ''' # set target tep self.set_target_temp(self.temp, self.kettle) @cbpi.action("Start Timer Now") def start(self): ''' Custom Action which can be execute form the brewing dashboard. All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface :return: ''' if self.is_timer_finished() is None: self.start_timer(int(self.timer) * 60) def reset(self): self.stop_timer() self.set_target_temp(self.temp, self.kettle) def finish(self): self.set_target_temp(0, self.kettle) def execute(self): ''' This method is execute in an interval :return: ''' # Check if Target Temp is reached if self.get_kettle_temp(self.kettle) >= float(self.temp): # Check if Timer is Running if self.is_timer_finished() is None: self.start_timer(int(self.timer) * 60) # Check if timer finished and go to next step if self.is_timer_finished() == True: self.notify("Mash Step Completed!", "Starting the next step", timeout=None) # if you dont want a beep sound comment out like : # cbpi.MashStepEndBeep() cbpi.MashStepEndBeep() self.next()
class GPIOinput(SensorActive): gpio = Property.Select("GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]) input_type = Property.Select("Input Type", options=["Momentary","Latch Rise", "Latch Fall"], description="Momentary - input high = high val. Latched - pulse on, pulse off") pud_type = Property.Select("Pull Up/Down", options=["Pull Up","Pull Down","Off"], description="Pull Up or down ressitor on input") on_val = Property.Number("High Value", configurable=True, default_value="0", description="Read value when input is high or Latch True(3.3V)") off_val = Property.Number("Low Value", configurable=True, default_value="100", description="Read value when sensor is low or Latch False (0V)") out_val = [0,1] input_on = False latch_val = False def init(self): self.input_on = False self.trigger_val = None try: GPIO.setup(int(self.gpio), GPIO.IN , pull_up_down = PUD_map[self.pud_type]) GPIO.remove_event_detect(int(self.gpio)) GPIO.add_event_detect(int(self.gpio), GPIO.BOTH, callback=self.IO_trigger, bouncetime=20) self.out_val = [self.off_val,self.on_val] self.latch_val = trig_level[self.input_type] super(GPIOinput,self).init() print "Init Complete" self.data_received(self.out_val[self.input_on]) except Exception as e: print e def get_unit(self): unit = "NA" return unit def IO_trigger(self, channel): self.sleep(0.0005) self.trigger_val = GPIO.input(int(self.gpio)) if self.input_type[0] == "L": if self.trigger_val != self.latch_val: self.trigger_val = None def execute(self): while self.is_running(): self.api.socketio.sleep(.1) #if GPIO.event_detected(int(self.gpio)): if self.trigger_val is not None: # if we're here, an edge was recognized #self.sleep(0.01) # debounce if self.input_type[0] == "M": self.input_on = GPIO.input(int(self.gpio)) else: #Latch if self.trigger_val == self.latch_val: self.input_on = not self.input_on self.data_received(self.out_val[self.input_on]) self.trigger_val = None def stop(self): self.__running = False GPIO.cleanup([int(self.gpio)]) GPIO.remove_event_detect(int(self.gpio))
class Cooling(StepBase): ''' Just put the decorator @cbpi.step on top of a method ''' # Properties temp = Property.Number("Temperature", configurable=True, description="Target Temperature") kettle = StepProperty.Kettle( "Kettle", description="Kettle in which the Chilling takes place") timer = Property.Number( "Timer in Seconds", configurable=True, description="Timer is started when the target temperature is reached") def init(self): ''' Initialize Step. This method is called once at the beginning of the step :return: ''' # set target tep self.set_target_temp(self.temp, self.kettle) @cbpi.action("Start Timer Now") def start(self): ''' Custom Action which can be execute form the brewing dashboard. All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface :return: ''' if self.is_timer_finished() is None: self.start_timer(int(self.timer)) def reset(self): self.stop_timer() self.set_target_temp(self.temp, self.kettle) def finish(self): self.set_target_temp(0, self.kettle) def execute(self): ''' This method is execute in an interval :return: ''' # Check if Target Temp is reached if self.get_kettle_temp(self.kettle) <= float(self.temp): # Check if Timer is Running if self.is_timer_finished() is None: self.start_timer(int(self.timer)) # Check if timer finished and go to next step if self.is_timer_finished() == True: self.notify( "Cooling Step Completed! Target temp is: %s C" % (self.temp), "Current temp is: %s C" % (self.get_kettle_temp(self.kettle)), timeout=10000) self.next()
class Timer(object): timer_end = Property.Number("TIMER_END", configurable=False) stopwatch_started = Property.Number("STOPWATCH_START", configurable=False) def start_stopwatch(self): if self.stopwatch_started is not None: return self.stopwatch_started = time.time() # cbpi.app.logger.info("Stopwatch: Started %s" % str(self.stopwatch_started)) def stop_stopwatch(self): if self.stopwatch_started is not None: end_time = time.time() elapsed_time = str(end_time - self.stopwatch_started) cbpi.app.logger.info("Stopwatch: Stopped, Elapsed Time: %s" % (elapsed_time)) self.stopwatch_started = None return elapsed_time else: return 0 def is_stopwatch_running(self): # cbpi.app.logger.info("Stopwatch: Running") if self.stopwatch_started is not None: return True else: return False def start_timer(self, timer): cbpi.app.logger.info("Started Timer ...") if self.timer_end is not None: return self.timer_end = int(time.time()) + timer def stop_timer(self): if self.timer_end is not None: self.timer_end = None def is_timer_running(self): if self.timer_end is not None: return True else: return False def timer_remaining(self): if self.timer_end is not None: return self.timer_end - int(time.time()) else: return None def is_timer_finished(self): if self.timer_end is None: return None if self.timer_end <= int(time.time()): return True else: return False
class PIDPWMAutoTune(KettleController): a_outstep = Property.Number("output step %", True, 100, description="Default: 100. Sets the output when stepping up/down.") b_maxout = Property.Number("max. output %", True, 100, description="Default: 100. Sets the max power output.") c_lookback = Property.Number("lookback seconds", True, 30, description="Default: 30. How far back to look for min/max temps.") def autoOff(self): cbpi.cache.get("kettle")[self.kettle_id].state = False super(KettleController, self).stop() cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(self.kettle_id)) def stop(self): if self.is_running(): self.notify("AutoTune Interrupted", "AutoTune has been interrupted and was not able to finish", type="danger", timeout=None) super(KettleController, self).stop() def run(self): self.notify("AutoTune In Progress", "Do not turn off Auto mode until AutoTuning is complete", type="success", timeout=None) sampleTime = 5 wait_time = 5 outstep = float(self.a_outstep) outmax = float(self.b_maxout) lookbackSec = float(self.c_lookback) setpoint = self.get_target_temp() try: atune = AutoTuner(setpoint, outstep, sampleTime, lookbackSec, 0, outmax) except Exception as e: self.notify("AutoTune Error", str(e), type="danger", timeout=None) atune.log(str(e)) self.autoOff() atune.log("AutoTune will now begin") self.heater_on(0) while self.is_running() and not atune.run(self.get_temp()): heat_percent = atune.output print(heat_percent) self.actor_power(round(heat_percent)) self.sleep(sampleTime) self.autoOff() if atune.state == atune.STATE_SUCCEEDED: atune.log("AutoTune has succeeded") self.notify("AutoTune Complete", "PID AutoTune was successful", type="success", timeout=None) for rule in atune.tuningRules: params = atune.getPIDParameters(rule) atune.log('rule: {0}'.format(rule)) atune.log('P: {0}'.format(params.Kp)) atune.log('I: {0}'.format(params.Ki)) atune.log('D: {0}'.format(params.Kd)) if rule == "brewing": self.notify("AutoTune P Value", str(params.Kp), type="info", timeout=None) self.notify("AutoTune I Value", str(params.Ki), type="info", timeout=None) self.notify("AutoTune D Value", str(params.Kd), type="info", timeout=None) elif atune.state == atune.STATE_FAILED: atune.log("AutoTune has failed") self.notify("AutoTune Failed", "PID AutoTune has failed", type="danger", timeout=None)
class PT100X(SensorPassive): # CONFIG PARAMETER & PROPERTIES csPin = Property.Select("csPin", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], description="GPIO Pin connected to the CS Pin of the MAX31865 - For MISO, MOSI, CLK no choice by default it's PIN 9, 10, 11") ResSens = Property.Select("Sensor Type", options=[100,1000],description="Select 100 for PT100 or 1000 for PT1000") RefRest = Property.Number("Reference Resistor", configurable=True, description="Reference Resistor of the MAX31865 board (it's written on the resistor: 430 or 4300,....)") offset = Property.Number("Offset", True, 0, description="Offset which is added to the received sensor data. Positive and negative values are both allowed.") ignore_below = Property.Number(ifelse_celcius("Low value filter threshold (°C)", "Low value filter threshold (°F)"), True, ifelse_celcius(0,32), description="Readings below this value will be ignored") ignore_above = Property.Number(ifelse_celcius("High value filter threshold (°C)", "High value filter threshold (°F)"), True,ifelse_celcius(100,212), description="Readings above this value will be ignored") misoPin = 9 mosiPin = 10 clkPin = 11 ConfigText = Property.Select("Conversion Mode & Wires", options=["[0xB2] - 3 Wires Manual","[0xD2] - 3 Wires Auto","[0xA2] - 2 or 4 Wires Manual","[0xC2] - 2 or 4 Wires Auto"], description="Choose beetween 2, 3 or 4 wire PT100 & the Conversion mode at 60 Hz beetween Manual or Continuous Auto") # # Config Register # --------------- # bit 7: Vbias -> 1 (ON), 0 (OFF) # bit 6: Conversion Mode -> 0 (MANUAL), 1 (AUTO) !!don't change the noch fequency 60Hz when auto # bit5: 1-shot ->1 (ON) # bit4: 3-wire select -> 1 (3 wires config), 0 (2 or 4 wires) # bits 3-2: fault detection cycle -> 0 (none) # bit 1: fault status clear -> 1 (clear any fault) # bit 0: 50/60 Hz filter select -> 0 (60Hz - Faster converson), 1 (50Hz) # # 0b10110010 = 0xB2 (Manual conversion, 3 wires at 60Hz) # 0b10100010 = 0xA2 (Manual conversion, 2 or 4 wires at 60Hz) # 0b11010010 = 0xD2 (Continuous auto conversion, 3 wires at 60 Hz) # 0b11000010 = 0xC2 (Continuous auto conversion, 2 or 4 wires at 60 Hz) # def init(self): # INIT SENSOR self.ConfigReg = self.ConfigText[1:5] self.max = max31865.max31865(int(self.csPin),int(self.misoPin), int(self.mosiPin), int(self.clkPin), int(self.ResSens), int(self.RefRest), int(self.ConfigReg,16)) # low_filter = float(self.ignore_below) # high_filter = float(self.ignore_above) # READ SENSOR def read(self): low_filter = float(self.ignore_below) high_filter = float(self.ignore_above) value = self.max.readTemp() if value < low_filter or value > high_filter: return if self.get_config_parameter("unit", "C") == "C": self.data_received(round(value + self.offset_value(), 2)) else: self.data_received(round(9.0 / 5.0 * value + 32 + self.offset_value(), 2)) @cbpi.try_catch(0) def offset_value(self): return float(self.offset)
class Rast(StepBase): ''' Just put the decorator @cbpi.step on top of a method ''' # Properties temp = Property.Number("Temperatur", configurable=True, description="Zieltemperatur des Schritts") kettle = StepProperty.Kettle("Kessel", description="Auswahl des Braukessels") timer = Property.Number( "Timer in Minuten", configurable=True, description="Timer startet, wenn die Zieltemperatur erreicht wurde") def init(self): ''' Initialize Step. This method is called once at the beginning of the step :return: ''' # set target tep self.set_target_temp(self.temp, self.kettle) # @cbpi.action("Timer manuell starten") # def start(self): # if self.is_timer_finished() is None: # self.start_timer(int(self.timer) * 60) def reset(self): self.stop_timer() self.set_target_temp(self.temp, self.kettle) def finish(self): pass def execute(self): ''' This method is execute in an interval :return: ''' # Check if Target Temp is reached if self.get_kettle_temp(self.kettle) >= float(self.temp): # Check if Timer is Running if self.is_timer_finished() is None: self.start_timer(int(self.timer) * 60) # Check if timer finished and go to next step if self.is_timer_finished() == True: self.notify("Rast beendet!", "Beginne nächsten Schritt", timeout=None) self.next()
class StartStopStep(StepBase, BaseColletingStep): temperatureSensor = StepProperty.Sensor( "Датчик температуры", description="Датчик температуры в царге") initialCollecting = Property.Number("Стартовая скорость отбора, мл/ч", configurable=True, default_value=1000) deltaTemp = Property.Number("Разрешенный залет температуры, °C", configurable=True, default_value=0.3) decrement = Property.Number("Уменьшение отбора при залете температуры, %", configurable=True, default_value=10) endTemp = Property.Number("Температура завершения отбора", configurable=True, default_value=93) initialTemp = None currentCollecting = None collectingSpeed = 0.0 temperature = 0 stopped = False def finish(self): self.actor_off(int(self.collectingActor)) self.notify("", "Отбор тела завершен", type="success", timeout=2000) def execute(self): self.updateAndCheckTemperature() self.recountCollecting() self.notifySensor() self.updateMaxCollectingSpeed() self.calculateActorPower() self.manageActor() def recountCollecting(self): if not self.currentCollecting: self.currentCollecting = int(initialCollecting) if not self.initialTemp: self.initialTemp = float(self.temperature) excess = float(self.temperature) - self.initialTemp > float( self.deltaTemp) if excess and not self.stopped: self.currentCollecting = self.currentCollecting * ( 100 - int(decrement)) / 100 self.stopped = excess self.collectingSpeed = 0 if self.stopped else self.currentCollecting def updateAndCheckTemperature(self): self.temperature = float( self.get_sensor_value(int(self.temperatureSensor))) if self.temperature >= int(self.endTemp): next(self)
class BoilStep(StepBase): ''' Just put the decorator @cbpi.step on top of a method ''' # Properties power = Property.Number("Power", configurable=True) kettle = StepProperty.Kettle("Kettle") timer = Property.Number("Timer in Minutes", configurable=True) def init(self): ''' Initialize Step. This method is called once at the beginning of the step :return: ''' # set target tep print(("class BoilStep(StepBase): power = {}".format(self.kettle))) self.actor_power(1, self.power) #self.set_target_temp(self.power, self.kettle) @cbpi.action("Start Timer Now") def start(self): ''' Custom Action which can be execute form the brewing dashboard. All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface :return: ''' if self.is_timer_finished() is None: self.start_timer(int(self.timer) * 60) def reset(self): self.stop_timer() self.actor_power(0, self.power) #self.set_target_temp(self.temp, self.kettle) def finish(self): self.actor_power(0, 0) self.set_target_temp(0, self.kettle) def execute(self): ''' This method is execute in an interval :return: ''' # Check if Timer is Running if self.is_timer_finished() is None: self.start_timer(int(self.timer) * 60) # Check if timer finished and go to next step if self.is_timer_finished() == True: next(self)
class Mod_PWM_Logic(KettleController): TempDiff = Property.Number("Degrees from target to start Reduction", True, 2) PowDiff = Property.Number("Percent of power deduction", True, 50) RampUp = Property.Number("Percent of power increase per 1/10 sec", True, 2) Checking = Property.Select( "This logic only works with heater running Mod_PWM", options=["OK"]) def stop(self): self.actor_power(int(Mod_PWM.power)) super(KettleController, self).stop() self.heater_off() def run(self): x = Mod_PWM() top = x.power ramp = 0 if self.TempDiff is None: self.TempDiff = 2 if self.PowDiff is None: self.PowDiff = 50 if self.RampUp is None: self.RampUp = 2 while self.is_running(): if self.get_temp() >= self.get_target_temp(): ramp = 0 self.heater_off() elif self.get_temp() < self.get_target_temp() - int(self.TempDiff): self.heater_on(0) while int(ramp) < int(top): self.actor_power(int(ramp)) ramp = ramp + int(self.RampUp) self.sleep(.1) self.actor_power(int(top)) else: self.heater_on(0) while int(ramp) < int(top - (top * int(self.PowDiff) / 100)): self.actor_power(int(ramp)) ramp = ramp + int(self.RampUp) self.sleep(.1) self.actor_power(int(top - (top * int(self.PowDiff) / 100))) ramp = x.power Mod_PWM.power = top self.sleep(1) Ntop = x.power if int(Ntop) <> int(top): top = Ntop self.heater_off()
class HendiControl(ActorBase): power_pin = Property.Select("Power control GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], ) onoff_pin = Property.Select("On/Off control GPIO", options=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]) freq = Property.Number("PWM frequency", configurable=True) Pmax = Property.Number("Max Power", configurable=True, default_value=100, unit="%") power = 0 pwm = None stopped = True def init(self): GPIO.setmode(GPIO.BCM) # setup pins for power control GPIO.setup(int(self.power_pin), GPIO.OUT) # setup pins for on/off control GPIO.setup(int(self.onoff_pin), GPIO.OUT) GPIO.output(int(self.onoff_pin), 0) HendiControl.power = int(self.Pmax) def on(self, power=None): HendiControl.stopped = False if HendiControl.pwm is None: if HendiControl.freq is None: HendiControl.freq = 100 HendiControl.pwm = GPIO.PWM(int(self.power_pin), int(self.freq)) HendiControl.pwm.start(int(HendiControl.power)) if(0 == HendiControl.power): GPIO.output(int(self.onoff_pin), 0) else: GPIO.output(int(self.onoff_pin), 1) HendiControl.pwm.start(1) HendiControl.pwm.ChangeDutyCycle(int(HendiControl.power)) cbpi.log_action("ON, Set power {}".format(HendiControl.power)) def set_power(self, power): HendiControl.power = min(int(power), int(self.Pmax)) cbpi.log_action("Set power {}".format(HendiControl.power)) self.pwm.ChangeDutyCycle(HendiControl.power) def off(self): cbpi.log_action("off") self.stopped = True self.pwm.ChangeDutyCycle(0) self.pwm.stop() GPIO.output(int(self.onoff_pin), 0)
class GlycolChillerController(FermenterController): chiller_offset_min = Property.Number( "Cooler Offset ON", True, 0, description= "Offset as decimal number when the cooler is switched on. Should be greater then 'Cooler Offset OFF'. For example a value of 2 switches on the cooler if the current temperature is 2 degrees above the target temperature" ) chiller_offset_max = Property.Number( "Cooler Offset OFF", True, 0, description= "Offset as decimal number when the cooler is switched off. Should be less then 'Cooler Offset ON'. For example a value of 1 switches off the cooler if the current temperature is 1 degree above the target temperature" ) def stop(self): ''' switch the chiller off when controller stops :return: ''' super(FermenterController, self).stop() self.cooler_off() def run(self): ''' controller logic :return: ''' while self.is_running(): # Read current target temp target_temp = self.get_target_temp() # Read current temp of the fermenter (sensor1) temp = self.get_temp() # offset 1 - cast to float value offset1 = float(self.chiller_offset_min) # offset 2 - cast to float value offset2 = float(self.chiller_offset_max) if temp >= target_temp + offset1: self.cooler_on() if temp <= target_temp + offset2: self.cooler_off() # wait for 1 second self.sleep(1)
class UARTSimple(ActorBase): maxRPM = Property.Number("Max RPM", configurable=True, description="Maximale Rührgeschwindigkeit") stdRPM = Property.Number("Standard RPM", configurable=True, description="Standard Geschwindigkeit") power = 10 #stdRPM state = 0 ser = serial.Serial("/dev/serial0", 1000000) def init(self): #start and reset sensor pwr = float(self.stdRPM) * 100.0 / float(self.maxRPM) self.set_power(int(pwr)) try: ser.write('s\n') except: cbpi.notify("Rührwerk Error", "Unable to open serial connection", type="danger", timeout=None) def on(self, power=None): rpm = int(self.power) * int(self.maxRPM) / 100.0 try: ser.write(str(rpm) + '\n') self.state = 1 except: cbpi.notify("Rührwerk Error", "Unable to write to UART", type="danger", timeout=None) def set_power(self, power): ''' Optional: Set the power of your actor :param power: int value between 0 - maxRPM :return: ''' if power is not None: self.power = power rpm = int(self.power) * int(self.maxRPM) / 100.0 if self.state == 1: ser.write(str(rpm) + '\n') def off(self): rpm = 0 self.state = 0 ser.write(str(0) + '\n')
class KettleVolumeStep(StepBase): ''' Just put the decorator @cbpi.step on top of a method. The class name must be unique in the system ''' # Properties temp = Property.Number("Temperature", configurable=True) kettle = StepProperty.Kettle("Kettle") timer = Property.Number("Timer in Minutes", configurable=True) sensor = StepProperty.Sensor("Sensor") volume = Property.Number("Volume", configurable=True) def init(self): ''' Initialize Step. This method is called once at the beginning of the step :return: ''' # set target tep #self.set_target_temp(self.temp, self.kettle) def finish(self): self.set_target_temp(0, self.kettle) def execute(self): ''' This method is execute in an interval :return: ''' for key, value in cbpi.cache.get("sensors").iteritems(): if key == int(self.sensor): sensorValue = value.instance.last_value # Check if timer finished and go to next step if float(sensorValue) <= float(self.volume): self.set_target_temp(self.temp, self.kettle) kettle_state = cbpi.cache.get("kettle")[int(self.kettle)].state if kettle_state is True: Kettle2View().toggle(int(self.kettle)) self.notify("Kettle Update", "Auto is off. Timer started.", timeout=None) if self.is_timer_finished() is None: self.start_timer(int(self.timer) * 60) if self.is_timer_finished() == True: self.notify("Mash-in Complete!", "Starting the next step.", timeout=None) self.next()
class Timer(object): timer_end = Property.Number("TIMER_END", configurable=False) def start_timer(self, timer): if self.timer_end is not None: return self.timer_end = int(time.time()) + timer def stop_timer(self): if self.timer_end is not None: self.timer_end = None def is_timer_running(self): if self.timer_end is not None: return True else: return False def timer_remaining(self): if self.timer_end is not None: return self.timer_end - int(time.time()) else: return None def is_timer_finished(self): if self.timer_end is None: return None if self.timer_end <= int(time.time()): return True else: return False
class MashInStep(StepBase): ''' Just put the decorator @cbpi.step on top of a method ''' # Properties temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step") kettle = StepProperty.Kettle("Kettle", description="Kettle in which the mashing takes place") s = False @cbpi.action("Change Power") def change_power(self): self.actor_power(1, 50) def init(self): ''' Initialize Step. This method is called once at the beginning of the step :return: ''' # set target step self.s = False self.set_target_temp(self.temp, self.kettle) def execute(self): ''' This method is execute in an interval :return: ''' # Check if Target Temp is reached if self.get_kettle_temp(self.kettle) >= float(self.temp) and self.s is False: self.s = True #DBK_added self.actor_off(1) self.notify("Step Temp Reached!", "Please press the next button to continue", timeout=None)
class PumpStep(StepBase): pump = StepProperty.Actor("Pump", description="Pump actor gets toogled") timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately") @cbpi.action("Start Timer") def start(self): if self.is_timer_finished() is None: self.start_timer(int(self.timer) * 60) #self.start_stopwatch() def reset(self): self.stop_timer() #self.stop_stopwatch() def finish(self): self.actor_off(int(self.pump)) #self.stop_stopwatch() def init(self): self.actor_on(int(self.pump)) def execute(self): if self.is_timer_finished() is None: self.start_timer(int(self.timer) * 60) if self.is_timer_finished() == True: self.next()
class Mod_PWM(ActorBase): gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]) frequency = Property.Number("Cycles Per Second", configurable=True) power = 100 p = None stopped = True def init(self): GPIO.setup(int(self.gpio), GPIO.OUT) GPIO.output(int(self.gpio), 0) def on(self, power): self.stopped = False if self.p is None: if self.frequency is None: self.frequency = 50 self.p = GPIO.PWM(int(self.gpio), int(self.frequency)) self.p.start(int(Mod_PWM.power)) if power is not None: self.p.ChangeDutyCycle(int(power)) else: self.p.ChangeDutyCycle(int(Mod_PWM.power)) def set_power(self, power): if power is not None: Mod_PWM.power = power if self.stopped is False: self.p.ChangeDutyCycle(int(Mod_PWM.power)) def off(self): self.stopped = True self.p.ChangeDutyCycle(0)