# ***************************************************************************** # # This program 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 2 of the License, or (at your option) any later # version. # # This program 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 # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Module authors: # Markus Zolliker # # ***************************************************************************** import time from frappy.core import Attached, Readable, Writable, Parameter, Command, \ IDLE, BUSY, DISABLED, ERROR from frappy.datatypes import FloatRange, StatusType, TupleOf, EnumType from frappy.states import HasStates, Retry, status_code class AutoFill(HasStates, Readable): level = Attached(Readable) valve = Attached(Writable) status = Parameter(datatype=StatusType(Readable, 'BUSY')) mode = Parameter('auto mode', EnumType(disabled=0, auto=30), readonly=False) fill_level = Parameter('low threshold triggering start filling', FloatRange(unit='%'), readonly=False) full_level = Parameter('high threshold triggering stop filling', FloatRange(unit='%'), readonly=False) fill_minutes_range = Parameter('range of possible fill rate', TupleOf(FloatRange(unit='min'), FloatRange(unit='min')), readonly=False) hold_hours_range = Parameter('range of possible consumption rate', TupleOf(FloatRange(unit='h'), FloatRange(unit='h')), readonly=False) fill_delay = Parameter('delay for cooling the transfer line', FloatRange(unit='min'), readonly=False) def read_status(self): if self.mode == 'DISABLED': return DISABLED, '' vstatus = self.valve.status if vstatus[0] // 100 != IDLE // 100: self.stop_machine(vstatus) return vstatus status = self.level.read_status(self) if status[0] // 100 == IDLE // 100: return HasStates.read_status(self) self.stop_machine(status) return status def write_mode(self, mode): if mode == 'DISABLED': self.stop_machine((DISABLED, '')) elif mode == 'AUTO': self.start_machine(self.watching) return mode @status_code(BUSY) def watching(self, state): if state.init: self.valve.write_target(0) delta = state.delta(10) raw = self.level.value if raw > self.value: self.value -= delta / (3600 * self.hold_hours_range[1]) elif raw < self.value: self.value -= delta / (3600 * self.hold_hours_range[0]) else: self.value = raw if self.value < self.fill_level: return self.precooling return Retry @status_code(BUSY) def precooling(self, state): if state.init: state.fillstart = state.now self.valve.write_target(1) delta = state.delta(1) raw = self.level.value if raw > self.value: self.value += delta / (60 * self.fill_minutes_range[0]) elif raw < self.value: self.value -= delta / (60 * self.fill_minutes_range[0]) else: self.value = raw if self.value > self.full_level: return self.watching if state.now > state.fillstart + self.fill_delay * 60: return self.filling return Retry @status_code(BUSY) def filling(self, state): delta = state.delta(1) raw = self.level.value if raw > self.value: self.value += delta / (60 * self.fill_minutes_range[0]) elif raw < self.value: self.value += delta / (60 * self.fill_minutes_range[1]) else: self.value = raw if self.value > self.full_level: return self.watching return Retry def on_cleanup(self, state): try: self.valve.write_target(0) except Exception: pass super().on_cleanup() @Command() def fill(self): self.mode = 'AUTO' self.start_machine(self.precooling, fillstart=time.time()) @Command() def stop(self): self.start_machine(self.watching)