Esempio n. 1
0
class SensorGroup(SensorPassive):

    sensordesc = "Select a sensor to be included in this group."
    sensor01 = Property.Sensor("Sensor 1", description=sensordesc)
    sensor02 = Property.Sensor("Sensor 2", description=sensordesc)
    sensor03 = Property.Sensor("Sensor 3", description=sensordesc)
    sensor04 = Property.Sensor("Sensor 4", description=sensordesc)
    sensor05 = Property.Sensor("Sensor 5", description=sensordesc)
    sensor06 = Property.Sensor("Sensor 6", description=sensordesc)
    sensor07 = Property.Sensor("Sensor 7", description=sensordesc)
    sensor08 = Property.Sensor("Sensor 8", description=sensordesc)

    value_type = Property.Select(
        "Value",
        options=["Average", "Minimum", "Maximum"],
        description="Select what data to return from the group.")

    def init(self):
        self.sensors = []

        if isinstance(self.sensor01, unicode) and self.sensor01:
            self.sensors.append(int(self.sensor01))
        if isinstance(self.sensor02, unicode) and self.sensor02:
            self.sensors.append(int(self.sensor02))
        if isinstance(self.sensor03, unicode) and self.sensor03:
            self.sensors.append(int(self.sensor03))
        if isinstance(self.sensor04, unicode) and self.sensor04:
            self.sensors.append(int(self.sensor04))
        if isinstance(self.sensor05, unicode) and self.sensor05:
            self.sensors.append(int(self.sensor05))
        if isinstance(self.sensor06, unicode) and self.sensor06:
            self.sensors.append(int(self.sensor06))
        if isinstance(self.sensor07, unicode) and self.sensor07:
            self.sensors.append(int(self.sensor07))
        if isinstance(self.sensor08, unicode) and self.sensor08:
            self.sensors.append(int(self.sensor08))

    def stop(self):
        pass

    def read(self):
        values = [
            float(cbpi.cache.get("sensors")[sensor].instance.last_value)
            for sensor in self.sensors
        ]
        if self.value_type == "Minimum":
            temp = min(values)
        elif self.value_type == "Maximum":
            temp = max(values)
        else:
            temp = sum(values) / len(values)
        self.data_received(round(temp, 2))

    def get_unit(self):
        if len(self.sensors) > 0:
            return cbpi.cache.get("sensors")[
                self.sensors[0]].instance.get_unit()
        else:
            return super(SensorBase, self).get_unit()
Esempio n. 2
0
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
Esempio n. 3
0
class SensorGroup(SensorPassive):

    sensordesc = "Select a sensor to be averaged with the other sensors in this group."
    sensor01 = Property.Sensor("Sensor 1", description=sensordesc)
    sensor02 = Property.Sensor("Sensor 2", description=sensordesc)
    sensor03 = Property.Sensor("Sensor 3", description=sensordesc)
    sensor04 = Property.Sensor("Sensor 4", description=sensordesc)
    sensor05 = Property.Sensor("Sensor 5", description=sensordesc)
    sensor06 = Property.Sensor("Sensor 6", description=sensordesc)
    sensor07 = Property.Sensor("Sensor 7", description=sensordesc)
    sensor08 = Property.Sensor("Sensor 8", description=sensordesc)

    def init(self):
        self.sensors = []

        if isinstance(self.sensor01, unicode) and self.sensor01:
            self.sensors.append(int(self.sensor01))
        if isinstance(self.sensor02, unicode) and self.sensor02:
            self.sensors.append(int(self.sensor02))
        if isinstance(self.sensor03, unicode) and self.sensor03:
            self.sensors.append(int(self.sensor03))
        if isinstance(self.sensor04, unicode) and self.sensor04:
            self.sensors.append(int(self.sensor04))
        if isinstance(self.sensor05, unicode) and self.sensor05:
            self.sensors.append(int(self.sensor05))
        if isinstance(self.sensor06, unicode) and self.sensor06:
            self.sensors.append(int(self.sensor06))
        if isinstance(self.sensor07, unicode) and self.sensor07:
            self.sensors.append(int(self.sensor07))
        if isinstance(self.sensor08, unicode) and self.sensor08:
            self.sensors.append(int(self.sensor08))

    def stop(self):
        pass

    def read(self):
        tempsum = float(0)
        for sensor in self.sensors:
            tempsum += float(
                cbpi.cache.get("sensors")[sensor].instance.last_value)
        self.data_received(round(tempsum / len(self.sensors), 2))

    def get_unit(self):
        if len(self.sensors) > 0:
            return cbpi.cache.get("sensors")[
                self.sensors[0]].instance.get_unit()
        else:
            return super(SensorBase, self).get_unit()
Esempio n. 4
0
class SimpleCascadeHysteresis(KettleController):
    """
    This hysteresis controls MashTun temp. It creates hysteresis on HLT temp not allowing it to reach
    much higher values than desired mash tun temp (target). It allows to set offset to target MT temp
    and temp is held in these values so there is not so much overshooting HLT temp
    In other words target temp is set in mash tun but is regulated with hystersis in HLT
    There is also a "safety check" which is the temp of coil/tube in Herms/Rims breweries, which is often
    much higher than desired target temp. In this plugin, this temp is also switching off the heater with
    adjustable offset.
    """
    pos_off_desc = "Positive value indicating possibility to go above target temp with actor still switched on. If target is 55 and offset is 1, heater will switch off when reaching 56."
    neg_off_desc = "Positive value indicating possibility to go below target temp with actor still switched off. If target is 55 and offset is 1, heater will switch back on when reaching 54."
    coil_sensor_desc = "Safety measurement for preventing overheating in Herms coil or rims tube. Leave blank if you don't have sensor after coil/tube."
    coil_off_desc = "Positive value indicating, when the heater will switch off if the temp at the end of coil/tube is above the target by this value or more. This helps to prevent rising the temp in HLT too much."
    a_hyst_sensor = Property.Sensor(label="HLT sensor")
    b_hysteresis_positive_offset = Property.Number(
        "Positive offset for hysteresis", True, 1, description=pos_off_desc)
    c_hysteresis_negative_offset = Property.Number(
        "Negative offset for hysteresis", True, 0, description=neg_off_desc)
    d_on_min = Property.Number("Hysteresis Minimum Time On (s)", True, 60)
    e_off_min = Property.Number("Hysteresis Minimum Time Off (s)", True, 60)
    f_coil_tube_sensor = Property.Sensor(
        label="Sensor after the HERMS coil or RIMS tube",
        description=coil_sensor_desc)
    g_coil_positive_offset = Property.Number("Positive offset for coil/tube",
                                             True,
                                             1.5,
                                             description=coil_off_desc)

    def stop(self):
        self.heater_off()
        super(KettleController, self).stop()

    def run(self):
        on_min = abs(float(self.d_on_min))
        off_min = abs(float(self.e_off_min))
        hyst_pos_offset = abs(float(self.b_hysteresis_positive_offset))
        hyst_neg_offset = abs(float(self.c_hysteresis_negative_offset))
        coil_pos_offset = abs(float(self.g_coil_positive_offset))

        hyst_sensor = int(self.a_hyst_sensor)
        if not self.f_coil_tube_sensor:
            coil_sensor = None
            coil_pos_offset = None
        else:
            coil_sensor = int(self.f_coil_tube_sensor)

        h = HysteresisWithTimeChecksAndSafetySwitch(
            True,
            hyst_pos_offset,
            hyst_neg_offset,
            on_min,
            off_min,
            safety_switch_offset=coil_pos_offset)
        heater_on = False
        while self.is_running():
            waketime = time.time() + 3
            target = self.get_target_temp()
            current = self.get_temp()

            # target reached in MT we can switch off no matter what
            if current >= target:
                self.heater_off()
                cbpi.app.logger.info("[%s] Target temp reached" % (waketime))
                self.sleep(waketime - time.time())
                continue

            # get control switch temp only if we have control switch
            control = None
            if coil_sensor is not None:
                control = float(
                    cbpi.cache.get("sensors")[coil_sensor].instance.last_value)
            hyst_temp = float(
                cbpi.cache.get("sensors")[hyst_sensor].instance.last_value)

            # Update the hysteresis controller
            try:
                heater_on = h.run(hyst_temp, target, control)
            except TimeIntervalNotPassed as e:
                self.notify("Hysteresis warning",
                            e.message,
                            type="warning",
                            timeout=1500)
            if heater_on:
                self.heater_on(100)
                cbpi.app.logger.info("[%s] Actor stays ON" % (waketime))
            else:
                self.heater_off()
                cbpi.app.logger.info("[%s] Actor stays OFF" % (waketime))

            # Sleep until update required again
            if waketime <= time.time() + 0.25:
                self.notify("Hysteresis Error",
                            "Update interval is too short",
                            type="warning")
                cbpi.app.logger.info(
                    "Hysteresis - Update interval is too short")
            else:
                self.sleep(waketime - time.time())
Esempio n. 5
0
class CascadePID(KettleController):
    a_inner_sensor = Property.Sensor(label="Inner loop sensor")
    b_inner_kp = Property.Number("Inner loop proportional term", True, 5.0, description=kp_description)
    c_inner_ki = Property.Number("Inner loop integral term", True, 0.25, description=ki_description)
    d_inner_kd = Property.Number("Inner loop derivative term", True, 0.0, description=kd_description)
    e_inner_integrator_max = Property.Number("Inner loop integrator max", True, 15.0, description=integrator_max_description)
    e_inner_integrator_initial = Property.Number("Inner loop integrator initial value", True, 0.0)
    f_outer_kp = Property.Number("Outer loop proportional term", True, 0.0, description=kp_description)
    g_outer_ki = Property.Number("Outer loop integral term", True, 2.0, description=ki_description)
    h_outer_kd = Property.Number("Outer loop derivative term", True, 1.0, description=kd_description)
    i_outer_integrator_max = Property.Number("Outer loop integrator max", True, 15.0, description=integrator_max_description)
    i_outer_integrator_initial = Property.Number("Outer loop integrator initial value", True, 0.0)
    j_update_interval = Property.Number("Update interval", True, 2.5, description=update_interval_description)
    k_notification_timeout = Property.Number("Notification duration", True, 5000, description=notification_timeout_description)

    def stop(self):
        self.heater_off()
        super(KettleController, self).stop()

    def run(self):
        if not isinstance(self.a_inner_sensor, unicode):
            self.notify("PID Error", "An inner sensor must be selected", timeout=None, type="danger")
            raise UserWarning("PID - An inner sensor must be selected")

        # Get inner sensor as an integer
        inner_sensor = int(self.a_inner_sensor)

        # Ensure all numerical properties are floats
        inner_kp = float(self.b_inner_kp)
        inner_ki = float(self.c_inner_ki)
        inner_kd = float(self.d_inner_kd)
        inner_integrator_max = float(self.e_inner_integrator_max)
        inner_integrator_initial = float(self.e_inner_integrator_initial)
        outer_kp = float(self.f_outer_kp)
        outer_ki = float(self.g_outer_ki)
        outer_kd = float(self.h_outer_kd)
        outer_integrator_max = float(self.i_outer_integrator_max)
        outer_integrator_initial = float(self.i_outer_integrator_initial)
        update_interval = float(self.j_update_interval)
        notification_timeout = float(self.k_notification_timeout)


        # Error check
        if update_interval <= 0.0:
            self.notify("PID Error", "Update interval must be positive", timeout=None, type="danger")
            raise ValueError("PID - Update interval must be positive")
        elif inner_integrator_max < 0.0:
            self.notify("PID Error", "Inner loop max integrator must be >= 0", timeout=None, type="danger")
            raise ValueError("PID - Inner loop max integrator must be >= 0")
        elif abs(inner_integrator_max) < abs(inner_integrator_initial):
            self.notify("PID Error", "Inner loop integrator initial value must be below the integrator max", timeout=None, type="danger")
            raise ValueError("PID - Inner loop integrator initial value must be below the integrator max")
        elif outer_integrator_max < 0.0:
            self.notify("PID Error", "Outer loop max integrator must be >= 0", timeout=None, type="danger")
            raise ValueError("PID - Outer loop max integrator must be >= 0")
        elif abs(outer_integrator_max) < abs(outer_integrator_initial):
            self.notify("PID Error", "Outer loop integrator initial value must be below the integrator max", timeout=None, type="danger")
            raise ValueError("PID - Outer loop integrator initial value must be below the integrator max")
        elif notification_timeout < 0.0:
            cbpi.notify("OneWire Error", "Notification timeout must be positive", timeout=None, type="danger")
            raise ValueError("OneWire - Notification timeout must be positive")
        else:
            self.heater_on(0.0)

        # Initialize PID cascade
        if cbpi.get_config_parameter("unit", "C") == "C":
            outer_pid = PID(outer_kp, outer_ki, outer_kd, 0.0, 100.0, outer_integrator_max, outer_integrator_initial)
        else:
            outer_pid = PID(outer_kp, outer_ki, outer_kd, 32, 212, outer_integrator_max, outer_integrator_initial)

        inner_pid = PID(inner_kp, inner_ki, inner_kd, 0.0, 100.0, inner_integrator_max, inner_integrator_initial)

        while self.is_running():
            waketime = time.time() + update_interval

            # Get the target temperature
            outer_target_value = self.get_target_temp()

            # Calculate inner target value from outer PID
            outer_current_value = self.get_temp()
            inner_target_value = round(outer_pid.update(outer_current_value, outer_target_value),2)

            # Calculate inner output from inner PID
            inner_current_value = float(cbpi.cache.get("sensors")[inner_sensor].instance.last_value)
            inner_output = round(inner_pid.update(inner_current_value, inner_target_value), 2)

            # Update the heater power
            self.actor_power(inner_output)

            # Print loop details
            cbpi.app.logger.info("[%s] Outer loop PID target/actual/output/integrator: %s/%s/%s/%s" % (waketime, outer_target_value, outer_current_value, inner_target_value, round(outer_pid.integrator, 2)))
            cbpi.app.logger.info("[%s] Inner loop PID target/actual/output/integrator: %s/%s/%s/%s" % (waketime, inner_target_value, inner_current_value, inner_output, round(inner_pid.integrator, 2)))

            # Sleep until update required again
            if waketime <= time.time() + 0.25:
                self.notify("PID Error", "Update interval is too short", timeout=notification_timeout, type="warning")
                cbpi.app.logger.info("PID - Update interval is too short")
            else:
                self.sleep(waketime - time.time())
Esempio n. 6
0
class FunctionActor(ActorBase):
    global function_actor_ids

    #Properties
    a_output_actor = Property.Actor(
        "Slave Actor", description="Select an Actor to be controlled")
    b_on_delay = Property.Number("On Delay",
                                 configurable=True,
                                 default_value=0,
                                 description="On wait time in seconds")
    c_off_delay = Property.Number("Off Delay",
                                  configurable=True,
                                  default_value=0,
                                  description="Off wait time in seconds")
    d_cycle_delay = Property.Number(
        "Cycle Delay",
        configurable=True,
        default_value=0,
        description="Minimum time before next turn on in seconds")
    h_control_word = Property.Text(
        "Control Func",
        configurable=True,
        default_value="",
        description=
        "Control function executed when actor switches on, see Readme for more info"
    )

    trigger_sensor_a = Property.Sensor(
        "Trigger Sensor 1 (S1 or sensor)",
        description="Select a Sensor to be used as a trigger")
    trigger_sensor_b = Property.Sensor(
        "Trigger Sensor 2 (S2)",
        description="Select a Sensor to be used as a trigger")
    trigger_text = Property.Text(
        "Trigger Rule",
        configurable=True,
        default_value="True",
        description="Trigger eqation, use sensor as key word, eg sensor > 25")

    def init(self):
        try:
            cbpi.app.logger.info("Func Actor init")

            #guards and stored vals
            self.out = dict.fromkeys(
                [
                    "on", "req", "active", "im_on", "im_off", "no_force",
                    "last_on"
                ], False
            )  #on: slave state, active: actor state trig: actor is triggered req: slave on is requested
            self.power = 100

            #output timers
            time_now = datetime.utcnow()
            self.times = dict.fromkeys(["onoff", "cycle"], time_now)
            self.delay = {}
            self.delay["on"] = timedelta(seconds=tryfloat(self.b_on_delay))
            self.delay["off"] = timedelta(seconds=tryfloat(self.c_off_delay))
            self.delay["cycle"] = timedelta(
                seconds=tryfloat(self.d_cycle_delay))

            self.pulse = {
                "on_list": [],
                "off_list": [],
                "next": [],
                "loop": False
            }

            #remove 'ordering' letter
            self.control_word = self.h_control_word
            self.output_actor = self.a_output_actor

            #check for sensor and trigger config
            if (((isinstance(self.trigger_sensor_a, unicode)
                  and self.trigger_sensor_a) or
                 (isinstance(self.trigger_sensor_b, unicode)
                  and self.trigger_sensor_b))
                    and isinstance(self.trigger_text, unicode)
                    and self.trigger_text):

                self.trig = {"s1": None, "s2": None, "text": self.trigger_text}
                if isinstance(self.trigger_sensor_a,
                              unicode) and self.trigger_sensor_a:
                    self.trig["s1"] = self.trigger_sensor_a
                if isinstance(self.trigger_sensor_b,
                              unicode) and self.trigger_sensor_b:
                    self.trig["s2"] = self.trigger_sensor_b

                self.trig.update(
                    dict.fromkeys(["last", "im_on", "im_off", "type"], False))
            else:
                self.trig = None

            #check for control word config
            if isinstance(self.control_word, unicode) and self.control_word:
                self.control_word = self.decode_control_word()
            else:
                self.control_word = None

            #add actor to list to execute
            if not int(self.id) in function_actor_ids:
                function_actor_ids.append(int(self.id))
            self.api.switch_actor_off(int(self.output_actor))
            self.display_power(0)

        except Exception as e:
            print "Function init fail"
            traceback.print_exc()
            e.throw

    def decode_control_word(self):

        try:
            #pulse vars

            word_temp = self.control_word.replace("_", "")
            word_temp = word_temp.replace("(", "[")
            word_temp = word_temp.replace(")", "]")
            words_temp = word_temp.split()

            for word in words_temp:
                if word[0] == "P":  #Pulse command
                    print "On Pulse command"
                    self.pulse["on_list"] = tuple(eval(word[1:]))
                    self.pulse["loop"] = False
                elif word[0] == "p":  #Pulse command
                    print "Off Pulse command"
                    self.pulse["off_list"] = tuple(eval(word[1:]))
                elif word[0] == "L":  #Pulse command
                    print "Loop command"
                    self.pulse["on_list"] = tuple(eval(word[1:]))
                    self.pulse["loop"] = True
                elif word[0] == "R":  #Ramp command
                    print "On Ramp command not implemented"
                elif word[0] == "r":  #Ramp command
                    print "Off Ramp command not implemented"
                elif word == "trigSwitchActor":
                    self.trig["type"] = "Sw"
                elif word == "trigToggleActor":
                    self.trig["type"] = "Tog"
                elif word == "trigImOn":
                    self.trig["im_on"] = True
                elif word == "trigImOff":
                    self.trig["im_off"] = True
                elif word == "UiImOn":
                    self.out["im_on"] = True
                elif word == "UiImOff":
                    self.out["im_off"] = True
                elif word == "noForce":
                    self.out["no_force"] = True
                else:
                    print word
                    raise ValueError("Control word not valid")
        except Exception as e:
            print e
            cbpi.app.logger.error("Control Word not valid")
            return False
        return True

    def trigger_eval(self):
        #print "trigger"
        if self.trig["s1"]:
            sensor = tryfloat(cbpi.get_sensor_value(tryint(self.trig["s1"])))
        else:
            sensor = 0
        s1 = sensor
        if self.trig["s2"]:
            s2 = tryfloat(cbpi.get_sensor_value(tryint(self.trig["s2"])))
        else:
            s2 = 0
        on = self.out["active"]
        state = self.out["on"]
        off = not self.out["active"]
        trig_sig = bool(eval(self.trig["text"]))

        #based on trigger and immediate settings, enable or disable actor
        if trig_sig == True:
            if self.trig["last"] is False:
                if self.trig["type"] == "Tog":
                    pass  #toggle actor
                    self.update_self(self.power, "Tog")
                elif self.trig["type"] == "Sw":
                    pass  # switch actor on
                    self.update_self(self.power, True)
                if not self.trig["im_on"]:
                    self.times["onoff"] = datetime.utcnow() + self.delay["on"]
                self.trig["last"] = True
            return self.out["req"]
        else:
            if self.trig["last"] is True:
                if self.trig["type"] == "NTog":
                    pass  #toggle actor on negative switch
                elif self.trig["type"] == "Sw":
                    pass  # switch actor off
                    self.update_self(self.power, False)
                if not self.trig["im_off"]:
                    self.times["onoff"] = datetime.utcnow() + self.delay["off"]
                self.trig["last"] = False
            if self.trig["type"] is False:
                return False
            else:
                return self.out["req"]

        #should not get here
        print "Failure: should not be here"
        return False

    def execute_func(self):

        #evaluate trigger if setup was valid
        if self.trig is not None:
            trigger = self.trigger_eval()
        else:
            trigger = self.out["req"]

        #evalute active condition
        right_now = datetime.utcnow()
        if trigger != self.out["active"]:
            if (right_now >= self.times["onoff"]) and (
                (right_now >= self.times["cycle"]) or not trigger):
                self.out["active"] = trigger
                self.out["on"] = trigger
                if trigger:
                    self.pulse["next"] = list(self.pulse["on_list"])
                else:
                    self.pulse["next"] = list(self.pulse["off_list"])
                if len(self.pulse["next"]) > 0:
                    self.times["pulse"] = right_now + timedelta(
                        seconds=tryfloat(self.pulse["next"][0]))

        #set output_actor and change func actor power displayed
        if ((cbpi.cache.get("actors").get(int(self.output_actor)).state !=
             self.out["on"]) or (self.out["no_force"] == True)):
            if ((self.out["no_force"] == False)
                    or (self.out["on"] != self.out["last_on"])):
                self.out["last_on"] = self.out["on"]
                if self.out["on"] == True:
                    self.api.switch_actor_on(int(self.output_actor),
                                             power=self.power)
                    self.display_power(self.power)
                else:
                    self.api.switch_actor_off(int(self.output_actor))
                    self.display_power(0)

        # overwrite displayed power as Zero if slave actor is off
        if self.out["on"] == False and cbpi.cache.get("actors").get(
                int(self.id)).power != 0:
            self.display_power(0)

        #evalute output pulsing
        if len(self.pulse["next"]) > 0:
            if right_now > self.times["pulse"]:
                self.out["on"] = not self.out["on"]
                del self.pulse["next"][0]
                if len(self.pulse["next"]) == 0:
                    if self.pulse["loop"] and self.out["active"]:
                        self.pulse["next"] = list(self.pulse["on_list"])
                if len(self.pulse["next"]) > 0:
                    self.times["pulse"] = right_now + timedelta(
                        seconds=tryfloat(self.pulse["next"][0]))

        else:
            if not self.out["active"]:
                self.out["on"] = False
                if self.out["last_on"]:
                    self.times["cycle"] = right_now + self.delay["cycle"]

    def on(self, power=None):
        if power is not None:
            self.power = power

        if self.out["req"] is False:
            self.out["req"] = True
            if self.out["im_on"] is False:
                self.times["onoff"] = datetime.utcnow() + self.delay["on"]

    def off(self):
        if self.out["req"] == True:
            self.out["req"] = False
            if self.out["im_off"] is False:
                self.times["onoff"] = datetime.utcnow() + self.delay["off"]

    def set_power(self, power=None):
        if power is not None:
            self.power = power
        self.api.actor_power(int(self.output_actor), power=self.power)

    def update_self(self, pwr, state):
        actor = cbpi.cache.get("actors").get(int(self.id))
        actor.power = pwr
        if state == "Tog":
            state = not actor.state
        if actor.state != state:
            actor.state = state
            self.out["req"] = state
            cbpi.emit("SWITCH_ACTOR", actor)

    def display_power(self, pwr):
        actor = cbpi.cache.get("actors").get(int(self.id))
        actor.power = pwr
        cbpi.emit("SWITCH_ACTOR", actor)
Esempio n. 7
0
class CascadeHysteresis(KettleController):
    aa_kp = Property.Number("Proportional term",
                            True,
                            10.0,
                            description=kp_description)
    ab_ki = Property.Number("Integral term",
                            True,
                            2.0,
                            description=ki_description)
    ac_kd = Property.Number("Derivative term",
                            True,
                            1.0,
                            description=kd_description)
    ad_integrator_initial = Property.Number("Integrator initial value", True,
                                            0.0)
    if cbpi.get_config_parameter("unit", "C") == "C":
        ae_maxset = Property.Number("Max hysteresis target (°C)",
                                    True,
                                    75,
                                    description=maxset_description)
    else:
        ae_maxset = Property.Number("Max hysteresis target (°F)",
                                    True,
                                    168,
                                    description=maxset_description)
    ba_inner_sensor = Property.Sensor(label="Inner (hysteresis) sensor")
    bb_action = Property.Select(label="Hysteresis Action Type",
                                options=["Positive", "Negative"],
                                description=action_description)
    bc_on_min = Property.Number("Hysteresis Minimum Time On (s)", True, 45)
    bd_on_max = Property.Number("Hysteresis Maximum Time On (s)", True, 1800)
    be_off_min = Property.Number("Hysteresis Minimum Time Off (s)", True, 90)
    c_update_interval = Property.Number(
        "Update interval (s)",
        True,
        2.5,
        description=update_interval_description)
    d_notification_timeout = Property.Number(
        "Notification duration (ms)",
        True,
        5000,
        description=notification_timeout_description)

    def stop(self):
        self.heater_off()
        super(KettleController, self).stop()

    def run(self):
        # Get inner sensor as an integer
        inner_sensor = int(self.ba_inner_sensor)

        # Outer PID settings
        kp = float(self.aa_kp)
        ki = float(self.ab_ki)
        kd = float(self.ac_kd)
        integrator_initial = float(self.ad_integrator_initial)
        maxset = float(self.ae_maxset)

        # Inner hysteresis settings
        positive = self.bb_action == "Positive"
        on_min = float(self.bc_on_min)
        on_max = float(self.bd_on_max)
        off_min = float(self.be_off_min)

        # General settings
        update_interval = float(self.c_update_interval)
        notification_timeout = float(self.d_notification_timeout)

        # Error check
        if on_min <= 0.0:
            self.notify("Hysteresis Error",
                        "Minimum 'on time' must be positive",
                        timeout=None,
                        type="danger")
            raise ValueError("Hysteresis - Minimum 'on time' must be positive")
        if on_max <= 0.0:
            self.notify("Hysteresis Error",
                        "Maximum 'on time' must be positive",
                        timeout=None,
                        type="danger")
            raise ValueError("Hysteresis - Maximum 'on time' must be positive")
        if on_min >= on_max:
            self.notify(
                "Hysteresis Error",
                "Maximum 'on time' must be greater than the minimum 'on time'",
                timeout=None,
                type="danger")
            raise ValueError(
                "Hysteresis - Maximum 'on time' must be greater than the minimum 'on time'"
            )
        if off_min <= 0.0:
            self.notify("Hysteresis Error",
                        "Minimum 'off time' must be positive",
                        timeout=None,
                        type="danger")
            raise ValueError(
                "Hysteresis - Minimum 'off time' must be positive")
        if update_interval <= 0.0:
            self.notify("Hysteresis Error",
                        "Update interval must be positive",
                        timeout=None,
                        type="danger")
            raise ValueError("Hysteresis - Update interval must be positive")
        elif notification_timeout <= 0.0:
            cbpi.notify("Hysteresis Error",
                        "Notification timeout must be positive",
                        timeout=None,
                        type="danger")
            raise ValueError(
                "Hysteresis - Notification timeout must be positive")
        else:
            # Initialize outer PID
            if cbpi.get_config_parameter("unit", "C") == "C":
                outer_pid = PID(kp, ki, kd, 0.0, maxset, 1.0,
                                integrator_initial)
            else:
                outer_pid = PID(kp, ki, kd, 32, maxset, 1.8,
                                integrator_initial)

            # Initialize hysteresis
            inner_hysteresis = Hysteresis(positive, on_min, on_max, off_min)

            while self.is_running():
                waketime = time.time() + update_interval

                # Get the target temperature
                outer_target_value = self.get_target_temp()

                # Calculate inner target value from outer PID
                outer_current_value = self.get_temp()
                inner_target_value = round(
                    outer_pid.update(outer_current_value, outer_target_value),
                    2)
                inner_current_value = float(
                    cbpi.cache.get("sensors")
                    [inner_sensor].instance.last_value)

                # Update the hysteresis controller
                if inner_hysteresis.update(inner_current_value,
                                           inner_target_value):
                    self.heater_on(100)
                    cbpi.app.logger.info(
                        "[%s] Inner hysteresis actor stays ON" % (waketime))
                    print(
                        ("[%s] Innner hysteresis actor stays ON" % (waketime)))
                else:
                    self.heater_off()
                    cbpi.app.logger.info(
                        "[%s] Inner hysteresis actor stays OFF" % (waketime))
                    print(("[%s] Innner hysteresis actor stays OFF" %
                           (waketime)))

                # Print loop details
                cbpi.app.logger.info(
                    "[%s] Outer loop PID target/actual/output/integrator: %s/%s/%s/%s"
                    % (waketime, outer_target_value, outer_current_value,
                       inner_target_value, round(outer_pid.integrator, 2)))
                print((
                    "[%s] Outer loop PID target/actual/output/integrator: %s/%s/%s/%s"
                    % (waketime, outer_target_value, outer_current_value,
                       inner_target_value, round(outer_pid.integrator, 2))))

                # Sleep until update required again
                if waketime <= time.time() + 0.25:
                    self.notify("Hysteresis Error",
                                "Update interval is too short",
                                timeout=notification_timeout,
                                type="warning")
                    cbpi.app.logger.info(
                        "Hysteresis - Update interval is too short")
                    print("Hysteresis - Update interval is too short")
                else:
                    self.sleep(waketime - time.time())