def board_host_actions(action="None", cause="None"): if "host_shutdown" in action: Logger.crit("Host is shutdown due to cause %s" % (str(cause), )) return yamp_host_shutdown() Logger.warn("Host needs action '%s' and cause '%s'" % (str(action), str(cause))) pass
def sensor_valid_check(board, sname, check_name, attribute): try: if attribute["type"] == "power_status": with open( "/sys/class/gpio/gpio" + fru_map[board]["gpio"] + "/value", "r") as f: pwr_sts = f.read(1) if pwr_sts[0] == "1": if match(r"soc_dimm", sname) != None: # check DIMM present with open( "/mnt/data/kv_store/sys_config/" + fru_map[board]["name"] + loc_map[sname[8:10]], "rb", ) as f: dimm_sts = f.read(1) if dimm_sts[0] != 1: return 0 return 1 else: return 0 else: Logger.debug( "Sensor corresponding valid check funciton not found!") return -1 except SystemExit: Logger.debug("SystemExit from sensor read") raise except Exception: Logger.warn("Exception with board=%s, sensor_name=%s" % (board, sname)) return -1
def sensor_valid_check(board, sname, check_name, attribute): status = c_uint8(0) try: if attribute["type"] == "power_status": lpal_hndl.pal_get_server_power(int(fru_map[board]["slot_num"]), byref(status)) if (status.value == 1): if match(r"soc_cpu", sname) is not None: return 1 elif match(r"soc_therm", sname) is not None: return 1 elif match(r"soc_dimm", sname) is not None: # check DIMM present file = "/mnt/data/kv_store/sys_config/" + fru_map[board]["name"] + part_name_map[sname[8]] with open(file, "r") as f: dimm_sts = f.readline() if re.search(r"([a-zA-Z0-9])", dimm_sts): return 1 else: return 0 else: return 0 return 0 except SystemExit: Logger.debug("SystemExit from sensor read") raise except Exception: Logger.warn("Exception with board=%s, sensor_name=%s" % (board, sname)) return 0
def board_callout(callout='None', **kwargs): ''' Override this method for defining board specific callouts: - Exmaple chassis intrusion ''' Logger.warn("Need to perform callout action %s" % callout) pass
def sensor_valid_check(board, sname, check_name, attribute): try: if attribute['type'] == "power_status": with open("/sys/class/gpio/gpio"+fru_map[board]['ready']+"/value", "r") as f: bic_ready = f.read(1) if bic_ready[0] == "1": return 0 cmd = "/usr/local/bin/power-util %s status" % board data = Popen(cmd, shell=True, stdout=PIPE).stdout.read().decode() result=data.split(": ") if match(r'ON', result[1]) != None: if match(r'soc_dimm', sname) != None: # check DIMM present with open("/mnt/data/kv_store/sys_config/"+fru_map[board]['name']+loc_map[sname[8:10]], "rb") as f: dimm_sts = f.read(1) if dimm_sts[0] == 0x3f: return 0 return 1 else: return 0 else: Logger.debug("Sensor corresponding valid check funciton not found!") return -1 except SystemExit: Logger.debug("SystemExit from sensor read") raise except Exception: Logger.warn("Exception with board=%s, sensor_name=%s" % (board, sname)) return -1
def board_host_actions(action="None", cause="None"): """ Override the method to define fan specific actions like: - handling host power off - alarming/syslogging criticals """ Logger.warn("Host needs action %s and cause %s" % (str(action), str(cause))) pass
def board_fan_actions(fan, action="None"): """ Override the method to define fan specific actions like: - handling dead fan - handling fan led """ Logger.warn("%s needs action %s" % (fan.label, str(action))) pass
def board_host_actions(action='None', cause='None'): ''' Override the method to define fan specific actions like: - handling host power off - alarming/syslogging criticals ''' Logger.warn("Host needs action %s and cause %s" % (str(action),str(cause),)) pass
def handle_term(signum, frame): global wdfile board_callout(callout='init_fans', boost=DEFAULT_INIT_TRANSITIONAL) Logger.warn("killed by signal %d" % (signum, )) if signum == signal.SIGQUIT and wdfile: Logger.info("Killed with SIGQUIT - stopping watchdog.") stop_watchdog() sys.exit('killed')
def board_fan_actions(fan, action='None'): ''' Override the method to define fan specific actions like: - handling dead fan - handling fan led ''' Logger.warn("%s needs action %s" % (fan.label, str(action),)) pass
def board_fan_actions(fan, action='None'): if "led" in action: yamp_set_fan_led(fan.label, color=action) else: Logger.warn("fscd: %s has no action %s" % ( fan.label, str(action), )) pass
def sensor_valid_check(board, sname, check_name, attribute): status = c_uint8(0) is_valid_check = False try: if attribute["type"] == "power_status": lpal_hndl.pal_get_server_power(int(fru_map[board]["slot_num"]), byref(status)) if (status.value == 1): # power on if match(r"soc_cpu|soc_therm", sname) is not None: is_valid_check = True elif match(r"spe_ssd", sname) is not None: # get SSD present status cmd = '/usr/bin/bic-util slot1 0xe0 0x2 0x9c 0x9c 0x0 0x15 0xe0 0x34 0x9c 0x9c 0x0 0x0 0x3' response = Popen(cmd, shell=True, stdout=PIPE).stdout.read() response = response.decode() # check the completion code if response.split(' ')[6] != '00': return 0 prsnt_bits = response.split(' ')[-3] int_val = int('0x' + prsnt_bits, 16) ssd_id = int(sname[7]) if int_val & (1 << ssd_id): return 1 else: return 0 else: suffix = "" if match(r"1ou_m2", sname) is not None: # 1ou_m2_a_temp. key is at 7 suffix = m2_1ou_name_map[sname[7]] elif match(r"soc_dimm", sname) is not None: # soc_dimma_temp. key is at 8 suffix = dimm_location_name_map[sname[8]] file = "/mnt/data/kv_store/sys_config/" + fru_map[board][ "name"] + suffix if is_dev_prsnt(file) == True: is_valid_check = True if is_valid_check == True: # Check power status again file = "/tmp/cache_store/" + host_ready_map[board] if not os.path.exists(file): return 0 with open(file, "r") as f: flag_status = f.read() if (flag_status == "1"): return 1 return 0 except SystemExit: Logger.debug("SystemExit from sensor read") raise except Exception: Logger.warn("Exception with board=%s, sensor_name=%s" % (board, sname)) return 0
def board_callout(callout="None", **kwargs): if "init_fans" in callout: boost = 100 if "boost" in kwargs: boost = kwargs["boost"] Logger.info("FSC init fans to boost=%s " % str(boost)) return yamp_set_all_pwm(boost) else: Logger.warn("Need to perform callout action %s" % callout) pass
def board_host_actions(action="None", cause="None"): """ Override the method to define fan specific actions like: - handling host power off - alarming/syslogging criticals """ if "host_shutdown" in action: Logger.crit("Host is shutdown due to cause %s" % (str(cause),)) return host_shutdown() Logger.warn("Host needs action '%s' and cause '%s'" % (str(action), str(cause))) pass
def board_callout(callout='None', **kwargs): ''' Override this method for defining board specific callouts: - Exmaple chassis intrusion ''' if 'init_fans' in callout: boost = 100 if 'boost' in kwargs: boost = kwargs['boost'] Logger.info("FSC init fans to boost=%s " % str(boost)) return set_all_pwm(boost) else: Logger.warn("Need to perform callout action %s" % callout) pass
def board_callout(callout='None', **kwargs): ''' Override this method for defining board specific callouts: - Exmaple chassis intrusion ''' if 'chassis_intrusion' in callout: return pal_fan_chassis_intrusion_handle() elif 'init_fans' in callout: boost = 100 # define a boost for the platform or respect fscd override if 'boost' in kwargs: boost = kwargs['boost'] return set_all_pwm(boost) else: Logger.warn("Callout %s not handled" % callout)
def check_if_all_fantrays_ok(): FANTRAY_STATUS = "/sys/class/i2c-adapter/i2c-8/8-0033/fantray_failure" cmd = 'cat ' + FANTRAY_STATUS response = Popen(cmd, shell=True, stdout=PIPE).stdout.read() response = response.decode() response = response.split("\n") if "0x00" not in response[0]: Logger.warn("All fans report failed RPM not consistent with " "Fantray status {}".format(response[0])) return False Logger.warn("All fans report failed RPM consistent with " "Fantray status{}".format(response[0])) return True
def board_callout(callout="None", **kwargs): """ Override this method for defining board specific callouts: - Exmaple chassis intrusion """ if "init_fans" in callout: boost = 100 if "boost" in kwargs: boost = kwargs["boost"] Logger.info("FSC init fans to boost=%s " % str(boost)) return set_all_pwm(boost) else: Logger.warn("Need to perform callout action %s" % callout) pass
def board_callout(callout="None", **kwargs): """ Override this method for defining board specific callouts: - Exmaple chassis intrusion """ if "chassis_intrusion" in callout: return pal_fan_chassis_intrusion_handle() elif "init_fans" in callout: boost = 100 # define a boost for the platform or respect fscd override if "boost" in kwargs: boost = kwargs["boost"] return set_all_pwm(boost) else: Logger.warn("Callout %s not handled" % callout)
def board_fan_actions(fan, action='None'): ''' Override the method to define fan specific actions like: - handling dead fan - handling fan led ''' if "led" in action: set_fan_led(fan.label, color=action) else: Logger.warn("%s needs action %s" % ( fan.label, str(action), )) pass
def board_fan_actions(fan, action="None"): """ Override the method to define fan specific actions like: - handling dead fan - handling fan led """ if action in "dead": return pal_fan_dead_handle(fan.fan_num + 1) elif action in "recover": return pal_fan_recovered_handle(fan.fan_num + 1) elif "led" in action: # No Action Needed. LED controlled in action 'dead' and 'recover'. pass else: Logger.warn("%s needs action %s" % (fan.label, str(action)))
def board_host_actions(action="None", cause="None"): """ Override the method to define fan specific actions like: - handling host power off - alarming/syslogging criticals """ if "host_shutdown" in action: if "All fans are bad" in cause: if not check_if_all_fantrays_ok(): Logger.warn("Host action %s not performed for cause %s" % (str(action), str(cause))) return False Logger.crit("Host is shutdown due to cause %s" % (str(cause), )) return host_shutdown() Logger.warn("Host needs action '%s' and cause '%s'" % (str(action), str(cause))) pass
def board_fan_actions(fan, action='None'): ''' Override the method to define fan specific actions like: - handling dead fan - handling fan led ''' if action in 'dead': # TODO: Why not do return pal_fan_dead_handle(fan) lpal_hndl.pal_fan_dead_handle(fan.fan_num) elif action in 'recover': # TODO: Why not do return pal_fan_recovered_handle(fan) lpal_hndl.pal_fan_recovered_handle(fan.fan_num) elif "led" in action: # No Action Needed. LED controlled in action 'dead' and 'recover'. pass else: Logger.warn("%s needs action %s" % (fan.label, str(action),)) pass
def sensor_valid_check(board, sname, check_name, attribute): try: if attribute['type'] == "power_status": with open( "/sys/class/gpio/gpio" + fru_map[board]['pwr_gpio'] + "/value", "r") as f: pwr_sts = f.read(1) if pwr_sts[0] == "1": slot_id = fru_map[board]['slot_num'] is_slot_server = lpal_hndl.pal_is_slot_server(slot_id) if int(is_slot_server) == 1: with open( "/sys/class/gpio/gpio" + fru_map[board]['bic_ready_gpio'] + "/value", "r") as f: bic_sts = f.read(1) if bic_sts[0] == "0": if match(r'soc_dimm', sname) != None: # check DIMM present with open( "/mnt/data/kv_store/sys_config/" + fru_map[board]['name'] + loc_map[sname[8:10]], "rb") as f: dimm_sts = f.read(1) if dimm_sts[0] != 1: return 0 return 1 else: return 1 return 0 else: Logger.debug( "Sensor corresponding valid check funciton not found!") return 0 except SystemExit: Logger.debug("SystemExit from sensor read") raise except Exception: Logger.warn("Exception with board=%s, sensor_name=%s" % (board, sname)) return 0
def update_dead_fans(self, dead_fans): ''' Check for dead and recovered fans ''' last_dead_fans = dead_fans.copy() speeds = self.machine.read_fans(self.fans) print("\x1b[2J\x1b[H") sys.stdout.flush() for fan, rpms in list(speeds.items()): Logger.info("%s speed: %d RPM" % (fan.label, rpms)) if rpms < self.fsc_config['min_rpm']: dead_fans.add(fan) self.fsc_fan_action(fan, action='dead') else: dead_fans.discard(fan) recovered_fans = last_dead_fans - dead_fans newly_dead_fans = dead_fans - last_dead_fans if len(newly_dead_fans) > 0: if self.fanpower: Logger.warn("%d fans failed" % (len(dead_fans), )) else: Logger.crit("%d fans failed" % (len(dead_fans), )) for dead_fan in dead_fans: if self.fanpower: Logger.warn("%s dead, %d RPM" % (dead_fan.label, speeds[dead_fan])) else: Logger.crit("%s dead, %d RPM" % (dead_fan.label, speeds[dead_fan])) Logger.usbdbg("%s fail" % (dead_fan.label)) fan_fail_record_path = FAN_FAIL_RECORD_DIR + '%s' % ( dead_fan.label) if not os.path.isfile(fan_fail_record_path): fan_fail_record = open(fan_fail_record_path, 'w') fan_fail_record.close() for fan in recovered_fans: if self.fanpower: Logger.warn("%s has recovered" % (fan.label, )) else: Logger.crit("%s has recovered" % (fan.label, )) Logger.usbdbg("%s recovered" % (fan.label)) self.fsc_fan_action(fan, action='recover') fan_fail_record_path = FAN_FAIL_RECORD_DIR + '%s' % (fan.label) if os.path.isfile(fan_fail_record_path): os.remove(fan_fail_record_path) return dead_fans
def board_callout(callout="None", **kwargs): """ Override this method for defining board specific callouts: - Exmaple chassis intrusion """ if "init_fans" in callout: boost = 100 if "boost" in kwargs: boost = kwargs["boost"] Logger.info("FSC init fans to boost=%s " % str(boost)) return set_all_pwm(boost) elif "chassis_intrusion" in callout: # fan present cmd = "presence_util.sh fan" lines = Popen(cmd, shell=True, stdout=PIPE).stdout.read().decode() fan_presence = 0 psu_presence = 0 tray_pull_out = 0 for line in lines.split("\n"): m = re.match(r"fan.*\s:\s+(\d+)", line) if m is not None: if int(m.group(1)) == 1: fan_presence += 1 # psu present cmd = "presence_util.sh psu" lines = Popen(cmd, shell=True, stdout=PIPE).stdout.read().decode() for line in lines.split("\n"): m = re.match(r"psu.*\s:\s+(\d+)", line) if m is not None: if int(m.group(1)) == 1: psu_presence += 1 if fan_presence < 4: Logger.warn("chassis_intrusion Found Fan absent (%d/4)" % (fan_presence)) tray_pull_out = 1 if psu_presence < 2: Logger.warn("chassis_intrusion Found PSU absent (%d/2)" % (psu_presence)) return tray_pull_out else: Logger.warn("Need to perform callout action %s" % callout) pass
def update_dead_fans(self, dead_fans): ''' Check for dead and recovered fans ''' last_dead_fans = dead_fans.copy() speeds = self.machine.read_fans(self.fans) print("\x1b[2J\x1b[H") sys.stdout.flush() for fan, rpms in speeds.items(): Logger.info("%s speed: %d RPM" % (fan.label, rpms)) if rpms < self.fsc_config['min_rpm']: dead_fans.add(fan) self.fsc_fan_action(fan, action='dead') else: dead_fans.discard(fan) recovered_fans = last_dead_fans - dead_fans newly_dead_fans = dead_fans - last_dead_fans if len(newly_dead_fans) > 0: if self.fanpower: Logger.warn("%d fans failed" % (len(dead_fans), )) else: Logger.crit("%d fans failed" % (len(dead_fans), )) for dead_fan in dead_fans: if self.fanpower: Logger.warn("%s dead, %d RPM" % (dead_fan.label, speeds[dead_fan])) else: Logger.crit("%s dead, %d RPM" % (dead_fan.label, speeds[dead_fan])) Logger.usbdbg("%s fail" % (dead_fan.label)) for fan in recovered_fans: if self.fanpower: Logger.warn("%s has recovered" % (fan.label, )) else: Logger.crit("%s has recovered" % (fan.label, )) Logger.usbdbg("%s recovered" % (fan.label)) self.fsc_fan_action(fan, action='recover') return dead_fans
def sensor_valid_check(board, sname, check_name, attribute): global fscd_counter global board_for_counter global sname_for_counter if not board_for_counter: board_for_counter = board sname_for_counter = sname if str(board) == str(board_for_counter): if str(sname) == str(sname_for_counter): fscd_counter = lpal_hndl.pal_get_fscd_counter() fscd_counter = fscd_counter+1 lpal_hndl.pal_set_fscd_counter(fscd_counter) if match(r'soc_temp_diode', sname) != None: return rc_stby_sensor_check(board) try: if attribute['type'] == "power_status": with open("/sys/class/gpio/gpio"+fru_map[board]['pwr_gpio']+"/value", "r") as f: pwr_sts = f.read(1) if pwr_sts[0] == "1": slot_id = fru_map[board]['slot_num'] is_slot_server = lpal_hndl.pal_is_slot_support_update(slot_id) if int(is_slot_server) == 1: with open("/sys/class/gpio/gpio"+fru_map[board]['bic_ready_gpio']+"/value", "r") as f: bic_sts = f.read(1) if bic_sts[0] == "0": if match(r'soc_dimm', sname) != None: server_type = lpal_hndl.pal_get_server_type(slot_id) if int(server_type) == 1: #RC Server # check DIMM present with open("/mnt/data/kv_store/sys_config/"+fru_map[board]['name']+loc_map_rc[sname[9:10]], "rb") as f: dimm_sts = f.read(1) if dimm_sts[0] != 1: return 0 else: return 1 elif int(server_type) == 2: #EP Server # check DIMM present with open("/mnt/data/kv_store/sys_config/"+fru_map[board]['name']+loc_map_ep[sname[8:9]], "rb") as f: dimm_sts = f.read(1) if dimm_sts[0] != 1: return 0 else: return 1 else: #TL Server # check DIMM present with open("/mnt/data/kv_store/sys_config/"+fru_map[board]['name']+loc_map[sname[8:10]], "rb") as f: dimm_sts = f.read(1) if dimm_sts[0] != 1: return 0 else: return 1 else: fru_name = c_char_p(board.encode('utf-8')) snr_name = c_char_p(sname.encode('utf-8')) is_m2_prsnt = lpal_hndl.pal_is_m2_prsnt(fru_name, snr_name) return int(is_m2_prsnt) else: if match(r'dc_nvme', sname) != None: #GPv1 fru_name = c_char_p(board.encode('utf-8')) snr_name = c_char_p(sname.encode('utf-8')) is_m2_prsnt = lpal_hndl.pal_is_m2_prsnt(fru_name, snr_name) return int(is_m2_prsnt) return 1 return 0 else: Logger.debug("Sensor corresponding valid check funciton not found!") return 0 except SystemExit: Logger.debug("SystemExit from sensor read") raise except Exception: Logger.warn("Exception with board=%s, sensor_name=%s" % (board, sname)) return 0
def sensor_valid_check(board, sname, check_name, attribute): global fscd_counter global board_for_counter global sname_for_counter if not board_for_counter: board_for_counter = board sname_for_counter = sname if str(board) == str(board_for_counter): if str(sname) == str(sname_for_counter): fscd_counter = lpal_hndl.pal_get_fscd_counter() fscd_counter = fscd_counter + 1 lpal_hndl.pal_set_fscd_counter(fscd_counter) if str(board) == "all": sdata = sname.split("_") board = sdata[0] sname = sname.replace(board + "_", "") Logger.debug("board=%s sname=%s" % (board, sname)) if match(r"soc_temp_diode", sname) is not None: return rc_stby_sensor_check(board) try: if attribute["type"] == "power_status": with open(gpio_path + fru_map[board]["pwr_gpio"] + "/value", "r") as f: pwr_sts = f.read(1) if pwr_sts[0] == "1": slot_id = fru_map[board]["slot_num"] is_slot_server = lpal_hndl.pal_is_slot_support_update(slot_id) if int(is_slot_server) == 1: with open( gpio_path + fru_map[board]["bic_ready_gpio"] + "/value", "r", ) as f: bic_sts = f.read(1) if bic_sts[0] == "0": if match(r"soc_dimm", sname) is not None: server_type = lpal_hndl.pal_get_server_type( slot_id) if int(server_type) == 1: # RC Server # check DIMM present with open( "/mnt/data/kv_store/sys_config/" + fru_map[board]["name"] + loc_map_rc[sname[9:10]], "rb", ) as f: dimm_sts = f.read(1) if dimm_sts[0] != 1: return 0 else: return 1 elif int(server_type) == 2: # EP Server # check DIMM present with open( "/mnt/data/kv_store/sys_config/" + fru_map[board]["name"] + loc_map_ep[sname[8:9]], "rb", ) as f: dimm_sts = f.read(1) if dimm_sts[0] != 1: return 0 else: return 1 elif int(server_type) == 4: # ND Server # check DIMM present with open( "/mnt/data/kv_store/sys_config/" + fru_map[board]["name"] + loc_map_nd[sname[8:9]], "rb", ) as f: dimm_sts = f.read(1) if dimm_sts[0] != 1: return 0 else: return 1 else: # TL Server # check DIMM present with open( "/mnt/data/kv_store/sys_config/" + fru_map[board]["name"] + loc_map[sname[8:10]], "rb", ) as f: dimm_sts = f.read(1) if dimm_sts[0] != 1: return 0 else: return 1 else: fru_name = c_char_p(board.encode("utf-8")) snr_name = c_char_p(sname.encode("utf-8")) is_m2_prsnt = lpal_hndl.pal_is_m2_prsnt( fru_name, snr_name) return int(is_m2_prsnt) else: if match(r"dc_nvme", sname) is not None: # GPv1 fru_name = c_char_p(board.encode("utf-8")) snr_name = c_char_p(sname.encode("utf-8")) is_m2_prsnt = lpal_hndl.pal_is_m2_prsnt( fru_name, snr_name) return int(is_m2_prsnt) return 1 return 0 else: Logger.debug( "Sensor corresponding valid check funciton not found!") return 0 except SystemExit: Logger.debug("SystemExit from sensor read") raise except Exception: Logger.warn("Exception with board=%s, sensor_name=%s" % (board, sname)) return 0
def update_zones(self, dead_fans, time_difference): """ TODO: Need to change logic here. # Platforms with chassis_intrusion mode enabled if chassis_intrusion: set the chassis_intrusion_boost_flag to 0 and then do necessary checks to set flag to 1 if chassis_intrusion_boost_flag: run boost mode else: run normal mode else # Platforms WITHOUT chassis_intrusion mode run normal mode """ sensors_tuples = self.machine.read_sensors(self.sensors) self.fsc_safe_guards(sensors_tuples) for zone in self.zones: Logger.info("PWM: %s" % (json.dumps(zone.pwm_output))) chassis_intrusion_boost_flag = 0 if self.chassis_intrusion: self_tray_pull_out = board_callout( callout='chassis_intrusion') if self_tray_pull_out == 1: chassis_intrusion_boost_flag = 1 if chassis_intrusion_boost_flag == 0: pwmval = zone.run(sensors=sensors_tuples, dt=time_difference) else: pwmval = self.boost if self.fan_fail: if self.boost_type == 'progressive' and self.fan_dead_boost: # Cases where we want to progressively bump PWMs dead = len(dead_fans) if dead > 0: Logger.info("Progressive mode: Failed fans: %s" % (', '.join([str(i.label) for i in dead_fans],))) for fan_count, rate in self.fan_dead_boost["data"]: if dead <= fan_count: pwmval = clamp(pwmval + (dead * rate), 0, 100) break else: pwmval = self.boost else: if dead_fans: # If not progressive ,when there is 1 fan failed, boost all fans Logger.info("Failed fans: %s" % ( ', '.join([str(i.label) for i in dead_fans],))) pwmval = self.boost if self.fan_dead_boost: # If all the fans failed take action after a few cycles if len(dead_fans) == len(self.fans): self.all_fan_fail_counter = self.all_fan_fail_counter + 1 Logger.warn("Currently all fans failed for {} cycles".format(self.all_fan_fail_counter)) if self.fan_dead_boost["threshold"] and self.fan_dead_boost["action"]: if self.all_fan_fail_counter >= self.fan_dead_boost["threshold"]: self.fsc_host_action( action=self.fan_dead_boost["action"], cause="All fans are bad for more than " + str(self.fan_dead_boost["threshold"]) + " cycles" ) else: # If atleast 1 fan is working reset the counter self.all_fan_fail_counter = 0 if abs(zone.last_pwm - pwmval) > self.ramp_rate: if pwmval < zone.last_pwm: pwmval = zone.last_pwm - self.ramp_rate else: pwmval = zone.last_pwm + self.ramp_rate zone.last_pwm = pwmval if hasattr(zone.pwm_output, '__iter__'): for output in zone.pwm_output: self.machine.set_pwm(self.fans.get( str(output)), pwmval) else: self.machine.set_pwm(self.fans[zone.pwm_output], pwmval)