def stcpr_aircx(self, current_time, stcpr_stpt_data, stcpr_data, zn_dmpr_data, low_sf_cond, high_sf_cond): """ Check duct static pressure AIRCx pre-requisites and manage analysis data set. :param current_time: :param stcpr_stpt_data: :param stcpr_data: :param zn_dmpr_data: :param low_sf_cond: :param high_sf_cond: :return: """ if common.check_date(current_time, self.timestamp_array): common.pre_conditions(self.publish_results, INCONSISTENT_DATE, DX_LIST, current_time) self.reinitialize() run_status = common.check_run_status(self.timestamp_array, current_time, self.no_req_data, self.data_window) if run_status is None: _log.info( "{} - Insufficient data to produce a valid diagnostic result.". format(current_time)) common.pre_conditions(self.publish_results, INSUFFICIENT_DATA, DX_LIST, current_time) self.reinitialize() if run_status: avg_stcpr_stpt, dx_string, dx_msg = common.setpoint_control_check( self.stcpr_stpt_array, self.stcpr_array, self.stpt_deviation_thr, DUCT_STC_RCX) self.publish_results(current_time, dx_string, dx_msg) self.low_stcpr_aircx(avg_stcpr_stpt) self.high_stcpr_aircx(avg_stcpr_stpt) self.reinitialize() self.stcpr_array.append(mean(stcpr_data)) if stcpr_stpt_data: self.stcpr_stpt_array.append(mean(stcpr_stpt_data)) zn_dmpr_data.sort(reverse=False) self.ls_dmpr_low_avg.extend( zn_dmpr_data[:int(math.ceil(len(zn_dmpr_data) * 0.5) ) if len(zn_dmpr_data) != 1 else 1]) self.ls_dmpr_high_avg.extend( zn_dmpr_data[int(math.ceil(len(zn_dmpr_data) * 0.5)) - 1 if len(zn_dmpr_data) != 1 else 0:]) zn_dmpr_data.sort(reverse=True) self.hs_dmpr_high_avg.extend( zn_dmpr_data[:int(math.ceil(len(zn_dmpr_data) * 0.5) ) if len(zn_dmpr_data) != 1 else 1]) self.low_sf_condition.append( low_sf_cond if low_sf_cond is not None else 0) self.high_sf_condition.append( high_sf_cond if high_sf_cond is not None else 0) self.timestamp_array.append(current_time)
def aggregate_power(self, peer, sender, bus, topic, headers, message): """ Power measurements for devices are aggregated. :param peer: :param sender: :param bus: :param topic: :param headers: :param message: :return: """ _log.debug("{}: received topic for power aggregation: {}".format(self.agent_name, topic)) data = message[0] if not self.sim_flag: current_time = parser.parse(headers["Date"]) to_zone = dateutil.tz.gettz(TIMEZONE) current_time = current_time.astimezone(to_zone) else: current_time = parser.parse(headers["Date"]) self.current_datetime = current_time current_hour = current_time.hour try: current_points = self.demand_aggregation_working.pop(topic) except KeyError: if self.power_aggregation: self.current_power = sum(self.power_aggregation) else: self.current_power = 0. self.demand_aggregation_working = self.demand_aggregation_master.copy() conversion = current_points.get("conversion") point_list = [] points = [] for point in current_points.get("points", []): point_list.append((point, data[point])) points.append(point) if conversion is not None: value = float(self.conversion_handler(conversion, points, point_list)) else: value = float(data[point]) _log.debug("uncontrol - topic {} value {}".format(topic, value)) self.power_aggregation.append(value) if not self.demand_aggregation_working: if self.power_aggregation: self.uc_load_array.append(sum(self.power_aggregation)) self.normalize_to_hour += 1.0 _log.debug("Current ts uncontrollable load: {}".format(sum(self.power_aggregation))) else: self.current_power = 0. self.power_aggregation = [] self.demand_aggregation_working = self.demand_aggregation_master.copy() if self.current_hour is not None and current_hour != self.current_hour: self.q_uc[self.current_hour] = max(- mean(self.uc_load_array)*self.normalize_to_hour/60.0, 10.0) _log.debug("Current hour uncontrollable load: {}".format(mean(self.uc_load_array)*self.normalize_to_hour/60.0)) self.uc_load_array = [] self.normalize_to_hour = 0 self.current_hour = current_hour
def not_economizing_when_needed(self): """If the detected problems(s) are consistent then generate a fault message(s). No return """ oaf = [(m - r) / (o - r) for o, r, m in zip(self.oat_values, self.rat_values, self.mat_values)] avg_oaf = max(0.0, min(100.0, mean(oaf) * 100.0)) avg_damper_signal = mean(self.oad_values) diagnostic_msg = {} energy_impact = {} thresholds = zip(self.open_damper_threshold.items(), self.oaf_economizing_threshold.items()) for (key, damper_thr), (key2, oaf_thr) in thresholds: if avg_damper_signal < damper_thr: msg = "{} - {}: {}".format(constants.ECON2, key, self.alg_result_messages[0]) result = 11.1 energy = self.energy_impact_calculation() else: if avg_oaf < oaf_thr: msg = "{} - {}: {} - OAF={}".format(constants.ECON2, key, self.alg_result_messages[2], avg_oaf) result = 12.1 energy = self.energy_impact_calculation() else: msg = "{} - {}: {}".format(constants.ECON2, key, self.alg_result_messages[1]) result = 10.0 energy = 0.0 _log.info(msg) diagnostic_msg.update({key: result}) energy_impact.update({key: energy}) _log.info(constants.table_log_format(self.analysis_name, self.timestamp[-1], (constants.ECON2 + constants.DX + ":" + str(diagnostic_msg)))) _log.info(constants.table_log_format(self.analysis_name, self.timestamp[-1], (constants.ECON2 + constants.EI + ":" + str(energy_impact)))) self.results_publish.append(constants.table_publish_format(self.analysis_name, self.timestamp[-1], (constants.ECON2 + constants.DX), diagnostic_msg)) self.results_publish.append(constants.table_publish_format(self.analysis_name, self.timestamp[-1], (constants.ECON2 + constants.EI), energy_impact)) self.clear_data()
def setpoint_reset_aircx(self, current_time, current_fan_status, stcpr_stpt_data, sat_stpt_data, dx_result): """ Main function for set point reset AIRCx - manages data arrays checks AIRCx run status. :param current_time: :param current_fan_status: :param stcpr_stpt_data: :param sat_stpt_data: :param dx_result: :return: """ stcpr_run_status = check_run_status( self.timestamp_array, current_time, self.no_req_data, run_schedule="daily", minimum_point_array=self.stcpr_stpt_array) if not self.timestamp_array: return dx_result self.reset_table_key = reset_name = create_table_key( self.analysis, self.timestamp_array[0]) if stcpr_run_status is None: dx_result.log("{} - Insufficient data to produce - {}".format( current_time, DUCT_STC_RCX3)) dx_result = pre_conditions(INSUFFICIENT_DATA, [DUCT_STC_RCX3], reset_name, current_time, dx_result) self.stcpr_stpt_array = [] elif stcpr_run_status: dx_result = self.no_static_pr_reset(dx_result) self.stcpr_stpt_array = [] sat_run_status = check_run_status( self.timestamp_array, current_time, self.no_req_data, run_schedule="daily", minimum_point_array=self.sat_stpt_array) if sat_run_status is None: dx_result.log("{} - Insufficient data to produce - {}".format( current_time, SA_TEMP_RCX3)) dx_result = pre_conditions(INSUFFICIENT_DATA, [SA_TEMP_RCX3], reset_name, current_time, dx_result) self.sat_stpt_array = [] self.timestamp_array = [] elif sat_run_status: dx_result = self.no_sat_stpt_reset(dx_result) self.sat_stpt_array = [] self.timestamp_array = [] if current_fan_status: if stcpr_stpt_data: self.stcpr_stpt_array.append(mean(stcpr_stpt_data)) if sat_stpt_data: self.sat_stpt_array.append(mean(sat_stpt_data)) return dx_result
def high_sat(self, dx_result, avg_sat_stpt): """ Diagnostic to identify and correct high supply-air temperature (correction by modifying SAT set point). :param dx_result: :param avg_sat_stpt: :return: """ avg_zones_rht = mean(self.percent_rht) * 100.0 thresholds = zip(self.percent_dmpr_thr.items(), self.percent_rht_thr.items()) diagnostic_msg = {} for (key, percent_dmpr_thr), (key2, percent_rht_thr) in thresholds: avg_zone_dmpr_data = mean(self.percent_dmpr[key]) * 100.0 if avg_zone_dmpr_data > percent_dmpr_thr and avg_zones_rht < percent_rht_thr: if avg_sat_stpt is None: # Create diagnostic message for fault # when supply-air temperature set point # is not available. msg = "{} - The SAT too high but SAT set point data is not available.".format( key) result = 54.1 elif self.auto_correct_flag: aircx_sat_stpt = avg_sat_stpt - self.sat_retuning # Create diagnostic message for fault condition # with auto-correction if aircx_sat_stpt >= self.min_sat_stpt: dx_result.command(self.sat_stpt_cname, aircx_sat_stpt) sat_stpt = "%s" % float("%.2g" % aircx_sat_stpt) msg = "{} - SAT too high. SAT set point decreased to: {}{}F".format( key, self.dgr_sym, sat_stpt) result = 51.1 else: # Create diagnostic message for fault condition # where the maximum SAT has been reached dx_result.command(self.sat_stpt_cname, self.min_sat_stpt) sat_stpt = "%s" % float("%.2g" % self.min_sat_stpt) msg = "{} - SAT too high. Auto-correcting to min SAT set point {}{}F".format( key, self.dgr_sym, sat_stpt) result = 52.1 else: # Create diagnostic message for fault condition # without auto-correction msg = "{} - The SAT too high but auto-correction is not enabled.".format( key) result = 53.1 else: msg = "{} - No problem detected for High SAT diagnostic.".format( key) result = 50.0 diagnostic_msg.update({key: result}) dx_result.log(msg) dx_result.insert_table_row(self.table_key, {SA_TEMP_RCX2 + DX: diagnostic_msg}) return dx_result
def low_stcpr_dx(self, dx_result, avg_stcpr_stpt): """Diagnostic to identify and correct low duct static pressure (correction by modifying duct static pressure set point). """ zn_dmpr = deepcopy(self.zn_dmpr_arr) zn_dmpr.sort(reverse=False) zone_dmpr_lowtemp = zn_dmpr[:int(math.ceil( len(self.zn_dmpr_arr) * 0.5)) if len(self.zn_dmpr_arr) != 1 else 1] zn_dmpr_low_avg = mean(zone_dmpr_lowtemp) zone_dmpr_hightemp = zn_dmpr[int(math. ceil(len(self.zn_dmpr_arr) * 0.5)) - 1 if len(self.zn_dmpr_arr) != 1 else 0:] zn_dmpr_high_avg = mean(zone_dmpr_hightemp) if zn_dmpr_high_avg > self.zone_high_dmpr_threshold and zn_dmpr_low_avg > self.zone_low_dmpr_threshold: if avg_stcpr_stpt is None: # Create diagnostic message for fault # when duct static pressure set point # is not available. msg = ('The duct static pressure set point has been ' 'detected to be too low but but supply-air' 'temperature set point data is not available.') dx_msg = 14.1 elif self.auto_correct_flag: auto_correct_stcpr_stpt = avg_stcpr_stpt + self.stcpr_retuning if auto_correct_stcpr_stpt <= self.max_stcpr_stpt: dx_result.command(self.stcpr_stpt_cname, auto_correct_stcpr_stpt) new_stcpr_stpt = '%s' % float( '%.2g' % auto_correct_stcpr_stpt) new_stcpr_stpt = new_stcpr_stpt + ' in. w.g.' msg = ('The duct static pressure was detected to be ' 'too low. The duct static pressure has been ' 'increased to: {}'.format(new_stcpr_stpt)) dx_msg = 11.1 else: dx_result.command(self.stcpr_stpt_cname, self.max_stcpr_stpt) new_stcpr_stpt = '%s' % float('%.2g' % self.max_stcpr_stpt) new_stcpr_stpt = new_stcpr_stpt + ' in. w.g.' msg = ('The duct static pressure set point is at the ' 'maximum value configured by the building ' 'operator: {})'.format(new_stcpr_stpt)) dx_msg = 12.1 else: msg = ('The duct static pressure set point was detected ' 'to be too low but auto-correction is not enabled.') dx_msg = 13.1 else: msg = ('No re-tuning opportunity was detected during the low duct ' 'static pressure diagnostic.') dx_msg = 10.0 self.dx_table.update({DUCT_STC_RCX1 + DX: dx_msg}) dx_result.log(msg, logging.INFO) return dx_result
def low_stcpr_aircx(self, dx_result, avg_stcpr_stpt, low_sf_condition): """ AIRCx to identify and correct low duct static pressure. :param dx_result: :param avg_stcpr_stpt: :param low_sf_condition: :return: """ zn_dmpr = self.zn_dmpr_array[:] zn_dmpr.sort(reverse=False) dmpr_low_temps = zn_dmpr[:int(math.ceil(len(self.zn_dmpr_array)*0.5)) if len(self.zn_dmpr_array) != 1 else 1] dmpr_low_avg = mean(dmpr_low_temps) dmpr_high_temps = zn_dmpr[int(math.ceil(len(self.zn_dmpr_array)*0.5)) - 1 if len(self.zn_dmpr_array) != 1 else 0:] dmpr_high_avg = mean(dmpr_high_temps) thresholds = zip(self.zn_high_dmpr_thr.items(), self.zn_low_dmpr_thr.items()) diagnostic_msg = {} for (key, zn_high_dmpr_thr), (key2, zn_low_dmpr_thr) in thresholds: if dmpr_high_avg > zn_high_dmpr_thr and dmpr_low_avg > zn_low_dmpr_thr: if low_sf_condition is not None and low_sf_condition: msg = "{} - duct static pressure too low. Supply fan at maximum.".format(key) result = 15.1 elif avg_stcpr_stpt is None: # Create diagnostic message for fault # when duct static pressure set point # is not available. msg = "{} - duct static pressure is too low but set point data is not available.".format(key) result = 14.1 elif self.auto_correct_flag: aircx_stcpr_stpt = avg_stcpr_stpt + self.stcpr_retuning if aircx_stcpr_stpt <= self.max_stcpr_stpt: dx_result.command(self.stcpr_stpt_cname, aircx_stcpr_stpt) stcpr_stpt = "%s" % float("%.2g" % aircx_stcpr_stpt) stcpr_stpt = stcpr_stpt + " in. w.g." msg = "{} - duct static pressure too low. Set point increased to: {}".format(key, stcpr_stpt) result = 11.1 else: dx_result.command(self.stcpr_stpt_cname, self.max_stcpr_stpt) stcpr_stpt = "%s" % float("%.2g" % self.max_stcpr_stpt) stcpr_stpt = stcpr_stpt + " in. w.g." msg = "{} - duct static pressure too low. Set point increased to max {}.".format(key, stcpr_stpt) result = 12.1 else: msg = "{} - duct static pressure is too low but auto-correction is not enabled.".format(key) result = 13.1 else: msg = "{} - no retuning opportunities detected for Low duct static pressure diagnostic.".format(key) result = 10.0 diagnostic_msg.update({key: result}) dx_result.log(msg) dx_result.insert_table_row(self.table_key, {DUCT_STC_RCX1 + DX: diagnostic_msg}) return dx_result
def low_sat(self, avg_sat_stpt): """ Diagnostic to identify and correct low supply-air temperature (correction by modifying SAT set point). :param avg_sat_stpt: :return: """ avg_zones_rht = mean(self.percent_rht) * 100.0 rht_avg = mean(self.rht_array) thresholds = zip(self.rht_valve_thr.items(), self.percent_rht_thr.items()) diagnostic_msg = {} for (key, rht_valve_thr), (key2, percent_rht_thr) in thresholds: if rht_avg > rht_valve_thr and avg_zones_rht > percent_rht_thr: if avg_sat_stpt is None: # Create diagnostic message for fault # when supply-air temperature set point # is not available. msg = "{} - The SAT too low but SAT set point data is not available.".format( key) result = 44.1 elif self.auto_correct_flag and self.auto_correct_flag == key: aircx_sat_stpt = avg_sat_stpt + self.sat_retuning if aircx_sat_stpt <= self.max_sat_stpt: self.send_autocorrect_command(self.sat_stpt_cname, aircx_sat_stpt) sat_stpt = "%s" % float("%.2g" % aircx_sat_stpt) msg = "{} - SAT too low. SAT set point increased to: {}F".format( key, sat_stpt) result = 41.1 else: self.send_autocorrect_command(self.sat_stpt_cname, self.max_sat_stpt) sat_stpt = "%s" % float("%.2g" % self.max_sat_stpt) sat_stpt = str(sat_stpt) msg = "{} - SAT too low. Auto-correcting to max SAT set point {}F".format( key, sat_stpt) result = 42.1 else: msg = "{} - SAT detected to be too low but auto-correction is not enabled.".format( key) result = 43.1 else: msg = "{} - No retuning opportunities detected for Low SAT diagnostic.".format( key) result = 40.0 diagnostic_msg.update({key: result}) _log.info(msg) _log.info( common.table_log_format( self.timestamp_array[-1], (SA_TEMP_RCX1 + DX + ": " + str(diagnostic_msg)))) self.publish_results(self.timestamp_array[-1], SA_TEMP_RCX1 + DX, diagnostic_msg)
def aggregate_data(self): oa_ma = [(x - y) for x, y in zip(self.oat_values, self.mat_values)] ra_ma = [(x - y) for x, y in zip(self.rat_values, self.mat_values)] ma_oa = [(y - x) for x, y in zip(self.oat_values, self.mat_values)] ma_ra = [(y - x) for x, y in zip(self.rat_values, self.mat_values)] avg_oa_ma = mean(oa_ma) avg_ra_ma = mean(ra_ma) avg_ma_oa = mean(ma_oa) avg_ma_ra = mean(ma_ra) return avg_oa_ma, avg_ra_ma, avg_ma_oa, avg_ma_ra
def aggregate_data(self): oa_ma = [(x - y) for x, y in zip(self.oat_values, self.mat_values)] ra_ma = [(x - y) for x, y in zip(self.rat_values, self.mat_values)] ma_oa = [(y - x) for x, y in zip(self.oat_values, self.mat_values)] ma_ra = [(y - x) for x, y in zip(self.rat_values, self.mat_values)] avg_oa_ma = mean(oa_ma) avg_ra_ma = mean(ra_ma) avg_ma_oa = mean(ma_oa) avg_ma_ra = mean(ma_ra) return avg_oa_ma, avg_ra_ma, avg_ma_oa, avg_ma_ra
def duct_static(self, current_time, stcpr_stpt_data, stcpr_data, zn_dmpr_data, low_dx_cond, high_dx_cond, dx_result, validate): """Check duct static pressure RCx pre-requisites and assemble the duct static pressure analysis data set. """ if check_date(current_time, self.timestamp_arr): self.reinitialize() return dx_result if low_dx_cond: dx_result.log(self.low_msg.format(current_time), logging.DEBUG) return dx_result if high_dx_cond: dx_result.log(self.high_msg.format(current_time), logging.DEBUG) return dx_result file_key = create_table_key(VALIDATE_FILE_TOKEN, current_time) data = validation_builder(validate, STCPR_VALIDATE, DATA) run_status = check_run_status(self.timestamp_arr, current_time, self.no_req_data) if run_status is None: dx_result.log('Current analysis data set has insufficient data ' 'to produce a valid diagnostic result.') self.reinitialize() return dx_result if run_status: self.table_key = create_table_key(self.analysis, self.timestamp_arr[-1]) avg_stcpr_stpt, dx_table = setpoint_control_check(self.stcpr_stpt_arr, self.stcpr_arr, self.stpt_allowable_dev, DUCT_STC_RCX, DX, STCPR_NAME, self.token_offset) self.dx_table.update(dx_table) dx_result = self.low_stcpr_dx(dx_result, avg_stcpr_stpt) dx_result = self.high_stcpr_dx(dx_result, avg_stcpr_stpt) dx_result.insert_table_row(self.table_key, self.dx_table) self.data.update({STCPR_VALIDATE + DATA + ST: 1}) dx_result.insert_table_row(self.file_key, self.data) self.reinitialize() self.stcpr_stpt_arr.append(mean(stcpr_data)) self.stcpr_arr.append(mean(stcpr_stpt_data)) self.zn_dmpr_arr.append(mean(zn_dmpr_data)) self.timestamp_arr.append(current_time) if self.data: self.data.update({STCPR_VALIDATE + DATA + ST: 0}) dx_result.insert_table_row(self.file_key, self.data) self.data = data self.file_key = file_key return dx_result
def stcpr_aircx(self, current_time, stcpr_stpt_data, stcpr_data, zn_dmpr_data, low_sf_cond, high_sf_cond, dx_result): """ Check duct static pressure AIRCx pre-requisites and manage analysis data set. :param current_time: :param stcpr_stpt_data: :param stcpr_data: :param zn_dmpr_data: :param low_sf_cond: :param high_sf_cond: :param dx_result: :return: """ try: if check_date(current_time, self.timestamp_array): dx_result = pre_conditions(INCONSISTENT_DATE, DX_LIST, self.analysis, current_time, dx_result) self.reinitialize() return dx_result run_status = check_run_status(self.timestamp_array, current_time, self.no_req_data, self.data_window) if run_status is None: dx_result.log( "{} - Insufficient data to produce a valid diagnostic result." .format(current_time)) dx_result = pre_conditions(INSUFFICIENT_DATA, DX_LIST, self.analysis, current_time, dx_result) self.reinitialize() return dx_result if run_status: self.table_key = create_table_key(self.analysis, self.timestamp_array[-1]) avg_stcpr_stpt, dx_table, dx_result = setpoint_control_check( self.stcpr_stpt_array, self.stcpr_array, self.stpt_deviation_thr, DUCT_STC_RCX, self.dx_offset, dx_result) dx_result.insert_table_row(self.table_key, dx_table) dx_result = self.low_stcpr_aircx(dx_result, avg_stcpr_stpt, low_sf_cond) dx_result = self.high_stcpr_aircx(dx_result, avg_stcpr_stpt, high_sf_cond) self.reinitialize() return dx_result finally: self.stcpr_stpt_array.append(mean(stcpr_data)) self.stcpr_array.append(mean(stcpr_stpt_data)) self.zn_dmpr_array.append(mean(zn_dmpr_data)) self.timestamp_array.append(current_time)
def setpoint_reset_aircx(self, current_time, current_fan_status, stcpr_stpt_data, sat_stpt_data): """ Main function for set point reset AIRCx - manages data arrays checks AIRCx run status. :param current_time: :param current_fan_status: :param stcpr_stpt_data: :param sat_stpt_data: :return: """ stcpr_run_status = common.check_run_status( self.timestamp_array, current_time, self.no_req_data, run_schedule="daily", minimum_point_array=self.stcpr_stpt_array) if not self.timestamp_array: return if stcpr_run_status is None: _log.info("{} - Insufficient data to produce - {}".format( current_time, DUCT_STC_RCX3)) common.pre_conditions(self.publish_results, INSUFFICIENT_DATA, [DUCT_STC_RCX3], current_time) self.stcpr_stpt_array = [] elif stcpr_run_status: self.no_static_pr_reset() self.stcpr_stpt_array = [] sat_run_status = common.check_run_status( self.timestamp_array, current_time, self.no_req_data, run_schedule="daily", minimum_point_array=self.sat_stpt_array) if sat_run_status is None: _log.info("{} - Insufficient data to produce - {}".format( current_time, SA_TEMP_RCX3)) common.pre_conditions(self.publish_results, INSUFFICIENT_DATA, [SA_TEMP_RCX3], current_time) self.sat_stpt_array = [] self.timestamp_array = [] elif sat_run_status: self.no_sat_stpt_reset() self.sat_stpt_array = [] self.timestamp_array = [] if current_fan_status: if stcpr_stpt_data: self.stcpr_stpt_array.append(mean(stcpr_stpt_data)) if sat_stpt_data: self.sat_stpt_array.append(mean(sat_stpt_data))
def sched_rcx_alg(self, current_time, stcpr_data, stcpr_stpt_data, sat_stpt_data, fan_stat_data, dx_result, sched_val): """Check schedule status and unit operational status.""" fan_status = None schedule = self.schedule[current_time.weekday()] run_diagnostic = False start_new_analysis_sat_stpt = None start_new_analysis_stcpr_stpt = None if self.timestamp and self.timestamp[-1].date() != current_time.date(): start_new_analysis_time = current_time run_diagnostic = True if not run_diagnostic: if current_time.time() < schedule[0] or current_time.time() > schedule[1]: self.stcpr_arr.extend(stcpr_data) self.fanstat_values.append((current_time, int(max(fan_stat_data)))) self.sched_time.append(current_time) if int(max(fan_stat_data)): self.stcpr_stpt_arr.append(mean(stcpr_stpt_data)) self.sat_stpt_arr.append(mean(sat_stpt_data)) fan_status = int(max(fan_stat_data)) start_new_analysis_sat_stpt = mean(stcpr_stpt_data) start_new_analysis_stcpr_stpt = mean(sat_stpt_data) self.timestamp.append(current_time) data = validation_builder(sched_val, SCHED_VALIDATE, DATA) reset_key = create_table_key(self.reset_file_name_id, self.timestamp[0]) schedule_key = create_table_key(self.sched_file_name_id, self.timestamp[0]) file_key = create_table_key(VALIDATE_FILE_TOKEN, current_time) if run_diagnostic and len(self.timestamp) >= self.no_req_data: dx_result = self.unocc_fan_operation(dx_result) if len(self.stcpr_stpt_arr) >= self.no_req_data: dx_result = self.no_static_pr_reset(dx_result) if len(self.sat_stpt_arr) >= self.no_req_data: dx_result = self.no_sat_stpt_reset(dx_result) if self.dx_table: dx_result.insert_table_row(reset_key, self.dx_table) data.update({SCHED_VALIDATE + DATA + ST: 1}) self.reinitialize(start_new_analysis_time, start_new_analysis_sat_stpt, start_new_analysis_stcpr_stpt, stcpr_data, fan_status) elif run_diagnostic: dx_msg = 61.2 dx_table = {SCHED_RCX + DX: dx_msg} dx_result.insert_table_row(schedule_key, dx_table) data.update({SCHED_VALIDATE + DATA + ST: 2}) self.reinitialize(start_new_analysis_time, start_new_analysis_sat_stpt, start_new_analysis_stcpr_stpt, stcpr_data, fan_status) else: data.update({SCHED_VALIDATE + DATA + ST: 0}) dx_result.insert_table_row(file_key, data) return dx_result
def low_sat(self, dx_result, avg_sat_stpt): """ Diagnostic to identify and correct low supply-air temperature (correction by modifying SAT set point). :param dx_result: :param avg_sat_stpt: :return: """ avg_zones_rht = mean(self.percent_rht) * 100.0 rht_avg = mean(self.rht_array) thresholds = zip(self.rht_valve_thr.items(), self.percent_rht_thr.items()) diagnostic_msg = {} for (key, rht_valve_thr), (key2, percent_rht_thr) in thresholds: if rht_avg > rht_valve_thr and avg_zones_rht > percent_rht_thr: if avg_sat_stpt is None: # Create diagnostic message for fault # when supply-air temperature set point # is not available. msg = "{} - The SAT too low but SAT set point data is not available.".format( key) result = 44.1 elif self.auto_correct_flag: aircx_sat_stpt = avg_sat_stpt + self.sat_retuning if aircx_sat_stpt <= self.max_sat_stpt: dx_result.command(self.sat_stpt_cname, aircx_sat_stpt) sat_stpt = "%s" % float("%.2g" % aircx_sat_stpt) msg = "{} - SAT too low. SAT set point increased to: {}{}F".format( key, self.dgr_sym, sat_stpt) result = 41.1 else: dx_result.command(self.sat_stpt_cname, self.max_sat_stpt) sat_stpt = "%s" % float("%.2g" % self.max_sat_stpt) sat_stpt = str(sat_stpt) msg = "{} - SAT too low. Auto-correcting to max SAT set point {}{}F".format( key, self.dgr_sym, sat_stpt) result = 42.1 else: msg = "{} - SAT detected to be too low but auto-correction is not enabled.".format( key) result = 43.1 else: msg = "{} - No retuning opportunities detected for Low SAT diagnostic.".format( key) result = 40.0 diagnostic_msg.update({key: result}) dx_result.log(msg) dx_result.insert_table_row(self.table_key, {SA_TEMP_RCX1 + DX: diagnostic_msg}) return dx_result
def low_stcpr_dx(self, dx_result, avg_stcpr_stpt): """Diagnostic to identify and correct low duct static pressure (correction by modifying duct static pressure set point). """ zn_dmpr = deepcopy(self.zn_dmpr_arr) zn_dmpr.sort(reverse=False) zone_dmpr_lowtemp = zn_dmpr[:int(math.ceil(len(self.zn_dmpr_arr)*0.5)) if len(self.zn_dmpr_arr) != 1 else 1] zn_dmpr_low_avg = mean(zone_dmpr_lowtemp) zone_dmpr_hightemp = zn_dmpr[int(math.ceil(len(self.zn_dmpr_arr)*0.5)) - 1 if len(self.zn_dmpr_arr) != 1 else 0:] zn_dmpr_high_avg = mean(zone_dmpr_hightemp) if zn_dmpr_high_avg > self.zone_high_dmpr_threshold and zn_dmpr_low_avg > self.zone_low_dmpr_threshold: if avg_stcpr_stpt is None: # Create diagnostic message for fault # when duct static pressure set point # is not available. msg = ('The duct static pressure set point has been ' 'detected to be too low but but supply-air' 'temperature set point data is not available.') dx_msg = 14.1 elif self.auto_correct_flag: auto_correct_stcpr_stpt = avg_stcpr_stpt + self.stcpr_retuning if auto_correct_stcpr_stpt <= self.max_stcpr_stpt: dx_result.command(self.stcpr_stpt_cname, auto_correct_stcpr_stpt) new_stcpr_stpt = '%s' % float('%.2g' % auto_correct_stcpr_stpt) new_stcpr_stpt = new_stcpr_stpt + ' in. w.g.' msg = ('The duct static pressure was detected to be ' 'too low. The duct static pressure has been ' 'increased to: {}' .format(new_stcpr_stpt)) dx_msg = 11.1 else: dx_result.command(self.stcpr_stpt_cname, self.max_stcpr_stpt) new_stcpr_stpt = '%s' % float('%.2g' % self.max_stcpr_stpt) new_stcpr_stpt = new_stcpr_stpt + ' in. w.g.' msg = ('The duct static pressure set point is at the ' 'maximum value configured by the building ' 'operator: {})'.format(new_stcpr_stpt)) dx_msg = 12.1 else: msg = ('The duct static pressure set point was detected ' 'to be too low but auto-correction is not enabled.') dx_msg = 13.1 else: msg = ('No re-tuning opportunity was detected during the low duct ' 'static pressure diagnostic.') dx_msg = 10.0 self.dx_table.update({DUCT_STC_RCX1 + DX: dx_msg}) dx_result.log(msg, logging.INFO) return dx_result
def high_sat(self, dx_result, avg_sat_stpt): """Diagnostic to identify and correct high supply-air temperature (correction by modifying SAT set point) """ avg_zones_rht = mean(self.percent_rht) * 100 avg_zone_dmpr_data = mean(self.percent_dmpr) * 100 if avg_zone_dmpr_data > self.percent_dmpr_thr and avg_zones_rht < self.percent_rht_thr: if avg_sat_stpt is None: # Create diagnostic message for fault # when supply-air temperature set point # is not available. msg = ('The SAT has been detected to be too high but ' 'but supply-air temperature set point data ' 'is not available.') dx_msg = 54.1 elif self.auto_correct_flag: autocorrect_sat_stpt = avg_sat_stpt - self.sat_retuning # Create diagnostic message for fault condition # with auto-correction if autocorrect_sat_stpt >= self.min_sat_stpt: dx_result.command(self.sat_stpt_cname, autocorrect_sat_stpt) sat_stpt = '%s' % float('%.2g' % autocorrect_sat_stpt) msg = ('The SAT has been detected to be too high. The ' 'SAT set point has been increased to: ' '{}{}F'.format(self.dgr_sym, sat_stpt)) dx_msg = 51.1 else: # Create diagnostic message for fault condition # where the maximum SAT has been reached dx_result.command(self.sat_stpt_cname, self.min_sat_stpt) sat_stpt = '%s' % float('%.2g' % self.min_sat_stpt) msg = ('The SAT was detected to be too high, ' 'auto-correction has increased the SAT to the ' 'minimum configured SAT: {}{}F'.format( self.dgr_sym, sat_stpt)) dx_msg = 52.1 else: # Create diagnostic message for fault condition # without auto-correction msg = ('The SAT has been detected to be too high but ' 'auto-correction is not enabled.') dx_msg = 53.1 else: msg = ('No problem detected for High Supply-air ' 'Temperature diagnostic.') dx_msg = 50.0 self.dx_table.update({SA_TEMP_RCX2 + DX: dx_msg}) dx_result.log(msg, logging.INFO) return dx_result
def high_sat(self, dx_result, avg_sat_stpt): """Diagnostic to identify and correct high supply-air temperature (correction by modifying SAT set point) """ avg_zones_rht = mean(self.percent_rht)*100 avg_zone_dmpr_data = mean(self.percent_dmpr)*100 if avg_zone_dmpr_data > self.percent_dmpr_thr and avg_zones_rht < self.percent_rht_thr: if avg_sat_stpt is None: # Create diagnostic message for fault # when supply-air temperature set point # is not available. msg = ('The SAT has been detected to be too high but ' 'but supply-air temperature set point data ' 'is not available.') dx_msg = 54.1 elif self.auto_correct_flag: autocorrect_sat_stpt = avg_sat_stpt - self.sat_retuning # Create diagnostic message for fault condition # with auto-correction if autocorrect_sat_stpt >= self.min_sat_stpt: dx_result.command(self.sat_stpt_cname, autocorrect_sat_stpt) sat_stpt = '%s' % float('%.2g' % autocorrect_sat_stpt) msg = ('The SAT has been detected to be too high. The ' 'SAT set point has been increased to: ' '{}{}F'.format(self.dgr_sym, sat_stpt)) dx_msg = 51.1 else: # Create diagnostic message for fault condition # where the maximum SAT has been reached dx_result.command(self.sat_stpt_cname, self.min_sat_stpt) sat_stpt = '%s' % float('%.2g' % self.min_sat_stpt) msg = ('The SAT was detected to be too high, ' 'auto-correction has increased the SAT to the ' 'minimum configured SAT: {}{}F' .format(self.dgr_sym, sat_stpt)) dx_msg = 52.1 else: # Create diagnostic message for fault condition # without auto-correction msg = ('The SAT has been detected to be too high but ' 'auto-correction is not enabled.') dx_msg = 53.1 else: msg = ('No problem detected for High Supply-air ' 'Temperature diagnostic.') dx_msg = 50.0 self.dx_table.update({SA_TEMP_RCX2 + DX: dx_msg}) dx_result.log(msg, logging.INFO) return dx_result
def duct_static(self, current_time, stcpr_stpt_data, stcpr_data, zn_dmpr_data, low_dx_cond, high_dx_cond, dx_result): """Check duct static pressure RCx pre-requisites and assemble the duct static pressure analysis data set. """ dx_status = 0 if check_date(current_time, self.timestamp_arr): dx_status = 0 self.reinitialize() return dx_status, dx_result if low_dx_cond: dx_result.log(self.low_msg.format(current_time), logging.DEBUG) return dx_status, dx_result if high_dx_cond: dx_result.log(self.high_msg.format(current_time), logging.DEBUG) return dx_status, dx_result run_status = check_run_status(self.timestamp_arr, current_time, self.no_req_data) if run_status is None: dx_result.log('Current analysis data set has insufficient data ' 'to produce a valid diagnostic result.') self.reinitialize() return dx_status, dx_result dx_status = 1 if run_status: self.table_key = create_table_key(self.analysis, self.timestamp_arr[-1]) avg_stcpr_stpt, dx_table = setpoint_control_check( self.stcpr_stpt_arr, self.stcpr_arr, self.stpt_allowable_dev, DUCT_STC_RCX, DX, STCPR_NAME, self.token_offset) self.dx_table.update(dx_table) dx_result = self.low_stcpr_dx(dx_result, avg_stcpr_stpt) dx_result = self.high_stcpr_dx(dx_result, avg_stcpr_stpt) dx_result.insert_table_row(self.table_key, self.dx_table) dx_status = 2 self.reinitialize() self.stcpr_stpt_arr.append(mean(stcpr_data)) self.stcpr_arr.append(mean(stcpr_stpt_data)) self.zn_dmpr_arr.append(mean(zn_dmpr_data)) self.timestamp_arr.append(current_time) return dx_status, dx_result
def high_stcpr_aircx(self, dx_result, avg_stcpr_stpt, high_sf_condition): """ AIRCx to identify and correct high duct static pressure. :param dx_result: :param avg_stcpr_stpt: :param high_sf_condition: :return: """ zn_dmpr = self.zn_dmpr_array[:] zn_dmpr.sort(reverse=True) zn_dmpr = zn_dmpr[:int(math.ceil(len(self.zn_dmpr_array) * 0.5) ) if len(self.zn_dmpr_array) != 1 else 1] avg_zn_damper = mean(zn_dmpr) diagnostic_msg = {} for key, hdzn_dmpr_thr in self.hdzn_dmpr_thr.items(): if avg_zn_damper <= hdzn_dmpr_thr: if high_sf_condition is not None and high_sf_condition: msg = "{} - duct static pressure too high. Supply fan at minimum.".format( key) result = 25.1 elif avg_stcpr_stpt is None: # Create diagnostic message for fault # when duct static pressure set point # is not available. msg = "{} - duct static pressure is too high but set point data is not available.".format( key) result = 24.1 elif self.auto_correct_flag: aircx_stcpr_stpt = avg_stcpr_stpt - self.stcpr_retuning if aircx_stcpr_stpt >= self.min_stcpr_stpt: dx_result.command(self.stcpr_stpt_cname, aircx_stcpr_stpt) stcpr_stpt = "%s" % float("%.2g" % aircx_stcpr_stpt) stcpr_stpt = stcpr_stpt + " in. w.g." msg = "{} - duct static pressure too high. Set point decreased to: {}".format( key, stcpr_stpt) result = 21.1 else: dx_result.command(self.stcpr_stpt_cname, self.min_stcpr_stpt) stcpr_stpt = "%s" % float("%.2g" % self.min_stcpr_stpt) stcpr_stpt = stcpr_stpt + " in. w.g." msg = "{} - duct static pressure too high. Set point decreased to min {}.".format( key, stcpr_stpt) result = 22.1 else: msg = "{} - duct static pressure is too high but auto-correction is not enabled.".format( key) result = 23.1 else: msg = "{} - No retuning opportunities detected for high duct static pressure diagnostic.".format( key) result = 20.0 diagnostic_msg.update({key: result}) dx_result.log(msg) dx_result.insert_table_row(self.table_key, {DUCT_STC_RCX2 + DX: diagnostic_msg}) return dx_result
def run_diagnostic(self): msg = "" if len(self.oat_values) > self.no_required_data: mat_oat_diff_list = [ abs(x - y) for x, y in zip(self.oat_values, self.mat_values) ] open_damper_check = mean(mat_oat_diff_list) diagnostic_msg = {} for sensitivity, threshold in self.oat_mat_check.items(): if open_damper_check > threshold: msg = "{} - {}: OAT and MAT are inconsistent when OAD is near 100%".format( constants.ECON1, str(sensitivity)) result = 0.1 else: msg = "{} - {}: OAT and MAT are consistent when OAD is near 100%".format( constants.ECON1, str(sensitivity)) result = 0.0 diagnostic_msg.update({sensitivity: result}) _log.info(msg) _log.info( constants.table_log_format(self.analysis_name, self.timestamp[-1], (constants.ECON1 + constants.DX + ":" + str(diagnostic_msg)))) self.results_publish.append( constants.table_publish_format( self.analysis_name, self.timestamp[-1], (constants.ECON1 + constants.DX), diagnostic_msg)) self.clear_data() return True else: self.clear_data() return False
def scrape_ending(self, topic): if not self.scalability_test: return try: self.waiting_to_finish.remove(topic) except KeyError: _log.warning(topic + " published twice before test finished, increase the length of scrape interval and rerun test") if not self.waiting_to_finish: end = datetime.now() delta = end - self.current_test_start delta = delta.total_seconds() self.test_results.append(delta) self.test_iterations += 1 _log.info("publish {} took {} seconds".format(self.test_iterations, delta)) if self.test_iterations >= self.scalability_test_iterations: #Test is now over. Button it up and shutdown. mean = math_utils.mean(self.test_results) stdev = math_utils.stdev(self.test_results) _log.info("Mean total publish time: "+str(mean)) _log.info("Std dev publish time: "+str(stdev)) sys.exit(0)
def insufficient_oa(self): """If the detected problems(s) are consistent then generate a fault message(s). No return """ oaf = [(mat - rat) / (oat - rat) for oat, rat, mat in zip(self.oat_values, self.rat_values, self.mat_values)] avg_oaf = mean(oaf) * 100.0 diagnostic_msg = {} if avg_oaf < 0 or avg_oaf > 125.0: msg = ("{}: Inconclusive result, the OAF calculation led to an " "unexpected value: {}".format(constants.ECON5, avg_oaf)) _log.info(msg) _log.info(constants.table_log_format(self.analysis_name, self.timestamp[-1], (constants.ECON5 + constants.DX + ":" + str(self.invalid_oaf_dict)))) self.results_publish.append(constants.table_publish_format(self.analysis_name, self.timestamp[-1], (constants.ECON5 + constants.DX), self.invalid_oaf_dict)) self.clear_data() return avg_oaf = max(0.0, min(100.0, avg_oaf)) for sensitivity, threshold in self.ventilation_oaf_threshold.items(): if self.desired_oaf - avg_oaf > threshold: msg = "{}: Insufficient OA is being provided for ventilation - sensitivity: {}".format(constants.ECON5, sensitivity) result = 43.1 else: msg = "{}: The calculated OAF was within acceptable limits - sensitivity: {}".format(constants.ECON5, sensitivity) result = 40.0 _log.info(msg) diagnostic_msg.update({sensitivity: result}) _log.info(constants.table_log_format(self.analysis_name, self.timestamp[-1], (constants.ECON5 + constants.DX + ":" + str(diagnostic_msg)))) self.results_publish.append(constants.table_publish_format(self.analysis_name, self.timestamp[-1], (constants.ECON5 + constants.DX), diagnostic_msg)) self.clear_data()
def economizing_when_not_needed(self): """If the detected problems(s) are consistent then generate a fault message(s). No return """ desired_oaf = self.desired_oaf / 100.0 avg_damper = mean(self.oad_values) diagnostic_msg = {} energy_impact = {} for sensitivity, threshold in self.excess_damper_threshold.items(): if avg_damper > threshold: msg = "{} - {}: {}".format(constants.ECON3, sensitivity, self.alg_result_messages[0]) # color_code = "RED" result = 21.1 energy = self.energy_impact_calculation(desired_oaf) else: msg = "{} - {}: {}".format(constants.ECON3, sensitivity, self.alg_result_messages[1]) # color_code = "GREEN" result = 20.0 energy = 0.0 _log.info(msg) diagnostic_msg.update({sensitivity: result}) energy_impact.update({sensitivity: energy}) _log.info(constants.table_log_format(self.analysis_name, self.timestamp[-1], (constants.ECON3 + constants.DX + ":" + str(diagnostic_msg)))) self.results_publish.append(constants.table_publish_format(self.analysis_name, self.timestamp[-1], (constants.ECON3 + constants.DX), diagnostic_msg)) _log.info(constants.table_log_format(self.analysis_name, self.timestamp[-1], (constants.ECON3 + constants.EI + ":" + str(energy_impact)))) self.results_publish.append(constants.table_publish_format(self.analysis_name, self.timestamp[-1], (constants.ECON3 + constants.EI), energy_impact)) self.clear_data()
def check_fan_status(self, current_time): """Check the status and speed of the fan current_time: datetime time delta return int """ if self.fan_status_data: supply_fan_status = int(max(self.fan_status_data)) else: supply_fan_status = None if self.fan_sp_data: self.fan_speed = mean(self.fan_sp_data) else: self.fan_speed = None if supply_fan_status is None: if self.fan_speed > self.low_sf_thr: supply_fan_status = 1 else: supply_fan_status = 0 if not supply_fan_status: if self.unit_status is None: self.unit_status = current_time else: self.unit_status = None return supply_fan_status
def check_prerequisites(self): """Evaluate realtime data to determine if the diagnostic prerequisites are met. If this function returns False the diagnostics in the diagnostic list will not run. :return: bool """ if self.prerequisites_data_required: avg_data = [] prerequisite_eval_list = [] for key, value in self.prerequisites_data_required.items(): try: avg_data.append((key, mean(value))) except ValueError as ex: LOG.warning("Exception prerequisites %s", ex) LOG.warning( "Not enough data to verify " "diagnostic prerequisites: %s", key) return False for condition in self.prerequisites_expr_list: evaluation = condition.subs(avg_data) prerequisite_eval_list.append(evaluation) LOG.debug("Prerequisite %s evaluation: %s", condition, evaluation) if prerequisite_eval_list: if False in prerequisite_eval_list: return False return True return True
def economizing_when_not_needed(self, dx_result, table_key): """ If the detected problems(s) are consistent then generate a fault message(s). :param dx_result: :param table_key: :return: """ desired_oaf = self.desired_oaf / 100.0 avg_damper = mean(self.oad_values) diagnostic_msg = {} energy_impact = {} for key, threshold in self.excess_damper_threshold.items(): if avg_damper - self.min_damper_sp > threshold: msg = "{}: {} - sensitivity: {}".format( ECON3, self.alg_result_messages[0], key) # color_code = "RED" result = 21.1 energy = self.energy_impact_calculation(desired_oaf) else: msg = "{}: {} - sensitivity: {}".format( ECON3, self.alg_result_messages[1], key) # color_code = "GREEN" result = 20.0 energy = 0.0 dx_result.log(msg) diagnostic_msg.update({key: result}) energy_impact.update({key: energy}) dx_table = {ECON3 + DX: diagnostic_msg, ECON3 + EI: energy_impact} dx_result.insert_table_row(table_key, dx_table) self.clear_data() return dx_result
def economizing_when_not_needed(self, dx_result, table_key): """ If the detected problems(s) are consistent then generate a fault message(s). :param dx_result: :param table_key: :return: """ desired_oaf = self.desired_oaf / 100.0 avg_damper = mean(self.oad_values) diagnostic_msg = {} energy_impact = {} for sensitivity, threshold in self.excess_damper_threshold.items(): if avg_damper > threshold: msg = "{} - {}: {}".format(ECON3, sensitivity, self.alg_result_messages[0]) # color_code = "RED" result = 21.1 energy = self.energy_impact_calculation(desired_oaf) else: msg = "{} - {}: {}".format(ECON3, sensitivity, self.alg_result_messages[1]) # color_code = "GREEN" result = 20.0 energy = 0.0 dx_result.log(msg) diagnostic_msg.update({sensitivity: result}) energy_impact.update({sensitivity: energy}) dx_table = { ECON3 + DX: diagnostic_msg, ECON3 + EI: energy_impact } dx_result.insert_table_row(table_key, dx_table) self.clear_data() return dx_result
def scrape_ending(self, topic): if not self.scalability_test: return try: self.waiting_to_finish.remove(topic) except KeyError: _log.warning( topic + " published twice before test finished, increase the length of scrape interval and rerun test" ) if not self.waiting_to_finish: end = datetime.now() delta = end - self.current_test_start delta = delta.total_seconds() self.test_results.append(delta) self.test_iterations += 1 _log.info("publish {} took {} seconds".format( self.test_iterations, delta)) if self.test_iterations >= self.scalability_test_iterations: #Test is now over. Button it up and shutdown. mean = math_utils.mean(self.test_results) stdev = math_utils.stdev(self.test_results) _log.info("Mean total publish time: " + str(mean)) _log.info("Std dev publish time: " + str(stdev)) sys.exit(0)
def low_sat(self, dx_result, avg_sat_stpt): """Diagnostic to identify and correct low supply-air temperature (correction by modifying SAT set point) """ avg_zones_rht = mean(self.percent_rht)*100 rht_avg = mean(self.rht_arr) if rht_avg > self.rht_valve_thr and avg_zones_rht > self.percent_rht_thr: if avg_sat_stpt is None: # Create diagnostic message for fault # when supply-air temperature set point # is not available. msg = ('The SAT has been detected to be too low but ' 'but supply-air temperature set point data ' 'is not available.') dx_msg = 43.1 elif self.auto_correct_flag: autocorrect_sat_stpt = avg_sat_stpt + self.sat_retuning if autocorrect_sat_stpt <= self.max_sat_stpt: dx_result.command(self.sat_stpt_cname, autocorrect_sat_stpt) sat_stpt = '%s' % float('%.2g' % autocorrect_sat_stpt) msg = ('The SAT has been detected to be too low. ' 'The SAT set point has been increased to: ' '{}{}F'.format(self.dgr_sym, sat_stpt)) dx_msg = 41.1 else: dx_result.command(self.sat_stpt_cname, self.max_sat_stpt) sat_stpt = '%s' % float('%.2g' % self.max_sat_stpt) sat_stpt = str(sat_stpt) msg = ( 'The supply-air temperautre was detected to be ' 'too low. Auto-correction has increased the ' 'supply-air temperature set point to the maximum ' 'configured supply-air tempeature set point: ' '{}{}F)'.format(self.dgr_sym, sat_stpt)) dx_msg = 42.1 else: msg = ('The SAT has been detected to be too low but' 'auto-correction is not enabled.') dx_msg = 44.1 else: msg = ('No problem detected for Low Supply-air ' 'Temperature diagnostic.') dx_msg = 40.0 self.dx_table.update({SA_TEMP_RCX1 + DX: dx_msg}) dx_result.log(msg, logging.INFO) return dx_result
def low_sat(self, dx_result, avg_sat_stpt): """Diagnostic to identify and correct low supply-air temperature (correction by modifying SAT set point) """ avg_zones_rht = mean(self.percent_rht) * 100 rht_avg = mean(self.rht_arr) if rht_avg > self.rht_valve_thr and avg_zones_rht > self.percent_rht_thr: if avg_sat_stpt is None: # Create diagnostic message for fault # when supply-air temperature set point # is not available. msg = ('The SAT has been detected to be too low but ' 'but supply-air temperature set point data ' 'is not available.') dx_msg = 43.1 elif self.auto_correct_flag: autocorrect_sat_stpt = avg_sat_stpt + self.sat_retuning if autocorrect_sat_stpt <= self.max_sat_stpt: dx_result.command(self.sat_stpt_cname, autocorrect_sat_stpt) sat_stpt = '%s' % float('%.2g' % autocorrect_sat_stpt) msg = ('The SAT has been detected to be too low. ' 'The SAT set point has been increased to: ' '{}{}F'.format(self.dgr_sym, sat_stpt)) dx_msg = 41.1 else: dx_result.command(self.sat_stpt_cname, self.max_sat_stpt) sat_stpt = '%s' % float('%.2g' % self.max_sat_stpt) sat_stpt = str(sat_stpt) msg = ('The supply-air temperautre was detected to be ' 'too low. Auto-correction has increased the ' 'supply-air temperature set point to the maximum ' 'configured supply-air tempeature set point: ' '{}{}F)'.format(self.dgr_sym, sat_stpt)) dx_msg = 42.1 else: msg = ('The SAT has been detected to be too low but' 'auto-correction is not enabled.') dx_msg = 44.1 else: msg = ('No problem detected for Low Supply-air ' 'Temperature diagnostic.') dx_msg = 40.0 self.dx_table.update({SA_TEMP_RCX1 + DX: dx_msg}) dx_result.log(msg, logging.INFO) return dx_result
def high_stcpr_aircx(self, avg_stcpr_stpt): """ AIRCx to identify and correct high duct static pressure. :param avg_stcpr_stpt:: :return: """ high_sf_condition = True if sum(self.high_sf_condition) / len( self.high_sf_condition) > 0.5 else False dmpr_high_avg = mean(self.hs_dmpr_high_avg) diagnostic_msg = {} for key, hdzn_dmpr_thr in self.hdzn_dmpr_thr.items(): if dmpr_high_avg <= hdzn_dmpr_thr: if high_sf_condition is not None and high_sf_condition: msg = "{} - duct static pressure too high. Supply fan at minimum.".format( key) result = 25.1 elif avg_stcpr_stpt is None: # Create diagnostic message for fault # when duct static pressure set point # is not available. msg = "{} - duct static pressure is too high but set point data is not available.".format( key) result = 24.1 elif self.auto_correct_flag and self.auto_correct_flag == key: aircx_stcpr_stpt = avg_stcpr_stpt - self.stcpr_retuning if aircx_stcpr_stpt >= self.min_stcpr_stpt: self.send_autocorrect_command(self.stcpr_stpt_cname, aircx_stcpr_stpt) stcpr_stpt = "%s" % float("%.2g" % aircx_stcpr_stpt) stcpr_stpt = stcpr_stpt + " in. w.g." msg = "{} - duct static pressure too high. Set point decreased to: {}".format( key, stcpr_stpt) result = 21.1 else: self.send_autocorrect_command(self.stcpr_stpt_cname, self.min_stcpr_stpt) stcpr_stpt = "%s" % float("%.2g" % self.min_stcpr_stpt) stcpr_stpt = stcpr_stpt + " in. w.g." msg = "{} - duct static pressure too high. Set point decreased to min {}.".format( key, stcpr_stpt) result = 22.1 else: msg = "{} - duct static pressure is too high but auto-correction is not enabled.".format( key) result = 23.1 else: msg = "{} - No retuning opportunities detected for high duct static pressure diagnostic.".format( key) result = 20.0 diagnostic_msg.update({key: result}) _log.info(msg) _log.info( common.table_log_format( self.timestamp_array[-1], (DUCT_STC_RCX2 + DX + ": " + str(diagnostic_msg)))) self.publish_results(self.timestamp_array[-1], DUCT_STC_RCX2 + DX, diagnostic_msg)
def aggregate_data(self): """ Calculate averages used for calculations within the class. Needs oat, mat,rat values set in class return avg_oa_ma: float avg_ra_ma: float avg_ma_oa: float avg_ma_ra: float """ oa_ma = [(x - y) for x, y in zip(self.oat_values, self.mat_values)] ra_ma = [(x - y) for x, y in zip(self.rat_values, self.mat_values)] ma_oa = [(y - x) for x, y in zip(self.oat_values, self.mat_values)] ma_ra = [(y - x) for x, y in zip(self.rat_values, self.mat_values)] avg_oa_ma = mean(oa_ma) avg_ra_ma = mean(ra_ma) avg_ma_oa = mean(ma_oa) avg_ma_ra = mean(ma_ra) return avg_oa_ma, avg_ra_ma, avg_ma_oa, avg_ma_ra
def setpoint_reset_aircx(self, current_time, current_fan_status, stcpr_stpt_data, sat_stpt_data, dx_result): """ Main function for set point reset AIRCx - manages data arrays checks AIRCx run status. :param current_time: :param current_fan_status: :param stcpr_stpt_data: :param sat_stpt_data: :param dx_result: :return: """ stcpr_run_status = check_run_status(self.timestamp_array, current_time, self.no_req_data, run_schedule="daily", minimum_point_array=self.stcpr_stpt_array) if not self.timestamp_array: return dx_result self.reset_table_key = reset_name = create_table_key(self.analysis, self.timestamp_array[0]) if stcpr_run_status is None: dx_result.log("{} - Insufficient data to produce - {}".format(current_time, DUCT_STC_RCX3)) dx_result = pre_conditions(INSUFFICIENT_DATA, [DUCT_STC_RCX3], reset_name, current_time, dx_result) self.stcpr_stpt_array = [] elif stcpr_run_status: dx_result = self.no_static_pr_reset(dx_result) self.stcpr_stpt_array = [] sat_run_status = check_run_status(self.timestamp_array, current_time, self.no_req_data, run_schedule="daily", minimum_point_array=self.sat_stpt_array) if sat_run_status is None: dx_result.log("{} - Insufficient data to produce - {}".format(current_time, SA_TEMP_RCX3)) dx_result = pre_conditions(INSUFFICIENT_DATA, [SA_TEMP_RCX3], reset_name, current_time, dx_result) self.sat_stpt_array = [] self.timestamp_array = [] elif sat_run_status: dx_result = self.no_sat_stpt_reset(dx_result) self.sat_stpt_array = [] self.timestamp_array = [] if current_fan_status: if stcpr_stpt_data: self.stcpr_stpt_array.append(mean(stcpr_stpt_data)) if sat_stpt_data: self.sat_stpt_array.append(mean(sat_stpt_data)) return dx_result
def not_economizing_when_needed(self, dx_result, table_key): """ If the detected problems(s) are consistent then generate a fault message(s). :param dx_result: :param table_key: :return: """ oaf = [(m - r) / (o - r) for o, r, m in zip( self.oat_values, self.rat_values, self.mat_values)] avg_oaf = max(0.0, min(100.0, mean(oaf) * 100.0)) avg_damper_signal = mean(self.oad_values) diagnostic_msg = {} energy_impact = {} thresholds = zip(self.open_damper_threshold.items(), self.oaf_economizing_threshold.items()) for (key, damper_thr), (key2, oaf_thr) in thresholds: if avg_damper_signal - self.minimum_damper_setpoint < damper_thr: msg = "{}: {} - sensitivity: {}".format( ECON2, self.alg_result_messages[0], key) # color_code = "RED" result = 11.1 energy = self.energy_impact_calculation() else: if 100.0 - avg_oaf <= oaf_thr: msg = "{}: {} - sensitivity: {}".format( ECON2, self.alg_result_messages[1], key) # color_code = "GREEN" result = 10.0 energy = 0.0 else: msg = "{}: {} --OAF: {} - sensitivity: {}".format( ECON2, self.alg_result_messages[2], avg_oaf, key) # color_code = "RED" result = 12.1 energy = self.energy_impact_calculation() dx_result.log(msg) diagnostic_msg.update({key: result}) energy_impact.update({key: energy}) dx_table = {ECON2 + DX: diagnostic_msg, ECON2 + EI: energy_impact} dx_result.insert_table_row(table_key, dx_table) self.clear_data() return dx_result
def unocc_fan_operation(self, dx_result): """If the AHU/RTU is operating during unoccupied periods inform the building operator. """ avg_duct_stcpr = 0 percent_on = 0 fanstat_on = [(fan[0].hour, fan[1]) for fan in self.fanstat_values if int(fan[1]) == 1] fanstat = [(fan[0].hour, fan[1]) for fan in self.fanstat_values] hourly_counter = [] for counter in range(24): fan_on_count = [fan_status_time[1] for fan_status_time in fanstat_on if fan_status_time[0] == counter] fan_count = [fan_status_time[1] for fan_status_time in fanstat if fan_status_time[0] == counter] if len(fan_count): hourly_counter.append(fan_on_count.count(1)/len(fan_count)*100) else: hourly_counter.append(0) if self.sched_time: if self.fanstat_values: percent_on = (len(fanstat_on)/len(self.fanstat_values)) * 100.0 if self.stcpr_arr: avg_duct_stcpr = mean(self.stcpr_arr) if percent_on > self.unocc_time_threshold: msg = 'Supply fan is on during unoccupied times.' dx_msg = 63.1 else: if avg_duct_stcpr < self.unocc_stp_threshold: msg = 'No problems detected for schedule diagnostic.' dx_msg = 60.0 else: msg = ('Fan status show the fan is off but the duct static ' 'pressure is high, check the functionality of the ' 'pressure sensor.') dx_msg = 64.2 else: msg = 'No problems detected for schedule diagnostic.' dx_msg = 60.0 if dx_msg != 64.2: for _hour in range(24): push_time = self.timestamp[0].date() push_time = datetime.combine(push_time, datetime.min.time()) push_time = push_time.replace(hour=_hour) dx_table = {SCHED_RCX + DX: 60.0} if hourly_counter[_hour] > self.unocc_time_threshold: dx_table = {SCHED_RCX + DX: dx_msg} table_key = create_table_key(self.sched_file_name_id, push_time) dx_result.insert_table_row(table_key, dx_table) else: push_time = self.timestamp[0].date() table_key = create_table_key(self.sched_file_name_id, push_time) dx_result.insert_table_row(table_key, {SCHED_RCX + DX: dx_msg}) dx_result.log(msg, logging.INFO) return dx_result
def high_stcpr_dx(self, dx_result, avg_stcpr_stpt): """Diagnostic to identify and correct high duct static pressure (correction by modifying duct static pressure set point) """ zn_dmpr = deepcopy(self.zn_dmpr_arr) zn_dmpr.sort(reverse=True) zn_dmpr = zn_dmpr[:int(math.ceil(len(self.zn_dmpr_arr) * 0.5) ) if len(self.zn_dmpr_arr) != 1 else 1] avg_zone_damper = mean(zn_dmpr) if avg_zone_damper <= self.hdzn_dmpr_thr: if avg_stcpr_stpt is None: # Create diagnostic message for fault # when duct static pressure set point # is not available. msg = ('The duct static pressure set point has been ' 'detected to be too high but but duct static ' 'pressure set point data is not available.' 'temperature set point data is not available.') dx_msg = 24.1 elif self.auto_correct_flag: auto_correct_stcpr_stpt = avg_stcpr_stpt - self.stcpr_retuning if auto_correct_stcpr_stpt >= self.min_stcpr_stpt: dx_result.command(self.stcpr_stpt_cname, auto_correct_stcpr_stpt) new_stcpr_stpt = '%s' % float( '%.2g' % auto_correct_stcpr_stpt) new_stcpr_stpt = new_stcpr_stpt + ' in. w.g.' msg = ('The duct static pressure was detected to be ' 'too high. The duct static pressure set point ' 'has been reduced to: {}'.format(new_stcpr_stpt)) dx_msg = 21.1 else: dx_result.command(self.stcpr_stpt_cname, self.min_stcpr_stpt) new_stcpr_stpt = '%s' % float('%.2g' % self.min_stcpr_stpt) new_stcpr_stpt = new_stcpr_stpt + ' in. w.g.' msg = ('The duct static pressure set point is at the ' 'minimum value configured by the building ' 'operator: {})'.format(new_stcpr_stpt)) dx_msg = 22.1 else: msg = ('Duct static pressure set point was detected to be ' 'too high but auto-correction is not enabled.') dx_msg = 23.1 else: msg = ( 'No re-tuning opportunity was detected during the high duct ' 'static pressure diagnostic.') dx_msg = 20.0 self.dx_table.update({DUCT_STC_RCX2 + DX: dx_msg}) dx_result.log(msg, logging.INFO) return dx_result
def not_economizing_when_needed(self, dx_result, table_key): """ If the detected problems(s) are consistent then generate a fault message(s). :param dx_result: :param table_key: :return: """ oaf = [(m - r) / (o - r) for o, r, m in zip(self.oat_values, self.rat_values, self.mat_values)] avg_oaf = max(0.0, min(100.0, mean(oaf)*100.0)) avg_damper_signal = mean(self.oad_values) diagnostic_msg = {} energy_impact = {} thresholds = zip(self.open_damper_threshold.items(), self.oaf_economizing_threshold.items()) for (key, damper_thr), (key2, oaf_thr) in thresholds: if avg_damper_signal < damper_thr: msg = "{} - {}: {}".format(ECON2, key, self.alg_result_messages[0]) # color_code = "RED" result = 11.1 energy = self.energy_impact_calculation() else: if avg_oaf < oaf_thr: msg = "{} - {}: {} - OAF={}".format(ECON2, key, self.alg_result_messages[2], avg_oaf) # color_code = "RED" result = 12.1 energy = self.energy_impact_calculation() else: msg = "{} - {}: {}".format(ECON2, key, self.alg_result_messages[1]) # color_code = "GREEN" result = 10.0 energy = 0.0 dx_result.log(msg) diagnostic_msg.update({key: result}) energy_impact.update({key: energy}) dx_table = { ECON2 + DX: diagnostic_msg, ECON2 + EI: energy_impact } dx_result.insert_table_row(table_key, dx_table) self.clear_data() return dx_result
def init_outputs(self, outputs): for output_info in outputs: # Topic to subscribe to for data (currently data format must be # consistent with a MasterDriverAgent all publish) topic = output_info["topic"] # Point name from as published by MasterDriverAgent point = output_info.pop("point") mapped = output_info.pop("mapped") # Options for release are None or default # None assumes BACnet release via priority array # default will safe original value, at start of agent run for control restoration # TODO: Update release value anytime agent has state transition for actuation_enable release = output_info.get("release", None) # Constant offset to apply to apply to determined actuation value offset = output_info.get("offset", 0.0) # VIP identity of Actuator to call via RPC to perform control of device actuator = output_info.get("actuator", "platform.actuator") # This is the flexibility range for the market commodity the # transactive agent will utilize flex = output_info["flexibility_range"] # This is the flexibility of the control point, by default the same as the # market commodity but not necessarily ct_flex = output_info.get("control_flexibility", flex) ct_flex, flex = self.set_control(ct_flex, flex) self.ct_flexibility = ct_flex fallback = output_info.get("fallback", mean(ct_flex)) # TODO: Use condition to determine multiple output scenario condition = output_info.get("condition", True) try: value = self.vip.rpc.call(actuator, 'get_point', topic).get(timeout=10) except (RemoteError, gevent.Timeout, errors.VIPError) as ex: _log.warning("Failed to get {} - ex: {}".format( topic, str(ex))) value = fallback if isinstance(release, str) and release.lower( ) == "default" and value is not None: release_value = value else: release_value = None off_setpoint = output_info.get("off_setpoint", value) self.outputs[mapped] = { "point": point, "topic": topic, "actuator": actuator, "release": release_value, "value": value, "off_setpoint": off_setpoint, "offset": offset, "flex": flex, "ct_flex": ct_flex, "condition": condition }
def high_stcpr_dx(self, dx_result, avg_stcpr_stpt): """Diagnostic to identify and correct high duct static pressure (correction by modifying duct static pressure set point) """ zn_dmpr = deepcopy(self.zn_dmpr_arr) zn_dmpr.sort(reverse=True) zn_dmpr = zn_dmpr[:int(math.ceil(len(self.zn_dmpr_arr)*0.5))if len(self.zn_dmpr_arr) != 1 else 1] avg_zone_damper = mean(zn_dmpr) if avg_zone_damper <= self.hdzn_dmpr_thr: if avg_stcpr_stpt is None: # Create diagnostic message for fault # when duct static pressure set point # is not available. msg = ('The duct static pressure set point has been ' 'detected to be too high but but duct static ' 'pressure set point data is not available.' 'temperature set point data is not available.') dx_msg = 24.1 elif self.auto_correct_flag: auto_correct_stcpr_stpt = avg_stcpr_stpt - self.stcpr_retuning if auto_correct_stcpr_stpt >= self.min_stcpr_stpt: dx_result.command(self.stcpr_stpt_cname, auto_correct_stcpr_stpt) new_stcpr_stpt = '%s' % float('%.2g' % auto_correct_stcpr_stpt) new_stcpr_stpt = new_stcpr_stpt + ' in. w.g.' msg = ('The duct static pressure was detected to be ' 'too high. The duct static pressure set point ' 'has been reduced to: {}' .format(new_stcpr_stpt)) dx_msg = 21.1 else: dx_result.command(self.stcpr_stpt_cname, self.min_stcpr_stpt) new_stcpr_stpt = '%s' % float('%.2g' % self.min_stcpr_stpt) new_stcpr_stpt = new_stcpr_stpt + ' in. w.g.' msg = ('The duct static pressure set point is at the ' 'minimum value configured by the building ' 'operator: {})'.format(new_stcpr_stpt)) dx_msg = 22.1 else: msg = ('Duct static pressure set point was detected to be ' 'too high but auto-correction is not enabled.') dx_msg = 23.1 else: msg = ('No re-tuning opportunity was detected during the high duct ' 'static pressure diagnostic.') dx_msg = 20.0 self.dx_table.update({DUCT_STC_RCX2 + DX: dx_msg}) dx_result.log(msg, logging.INFO) return dx_result
def econ_alg(self, dx_result, oat, mat, oad, cur_time): """ Check diagnostic prerequisites and manage data arrays. :param dx_result: :param oat: :param mat: :param oad: :param cur_time: :return: """ if oad > self.oad_temperature_threshold: if self.steady_state is None: self.steady_state = cur_time elif cur_time - self.steady_state >= self.econ_time_check: self.oat_values.append(oat) self.mat_values.append(mat) self.timestamp.append(cur_time) else: self.steady_state = None elapsed_time = self.timestamp[-1] - self.timestamp[0] if self.timestamp else td(minutes=0) if elapsed_time >= self.data_window: if len(self.oat_values) > self.no_required_data: mat_oat_diff_list = [abs(x - y) for x, y in zip(self.oat_values, self.mat_values)] open_damper_check = mean(mat_oat_diff_list) table_key = create_table_key(self.analysis, self.timestamp[-1]) diagnostic_msg = {} for sensitivity, threshold in self.oat_mat_check.items(): if open_damper_check > threshold: msg = "{} - {}: OAT and MAT are inconsistent when OAD is near 100%".format(ECON1, sensitivity) result = 0.1 else: msg = "{} - {}: OAT and MAT are consistent when OAD is near 100%".format(ECON1, sensitivity) result = 0.0 diagnostic_msg.update({sensitivity: result}) dx_result.log(msg) dx_table = {ECON1 + DX: diagnostic_msg} dx_result.insert_table_row(table_key, dx_table) self.clear_data() return dx_result
def publish_to_historian(self, to_publish_list): for item in to_publish_list: if self._gather_timing_data: turnaround_time = add_timing_data_to_header(item["headers"], self.core.agent_uuid or self.core.identity, "published") self._turnaround_times.append(turnaround_time) if len(self._turnaround_times) > 10000: # Test is now over. Button it up and shutdown. mean = math_utils.mean(self._turnaround_times) stdev = math_utils.stdev(self._turnaround_times) _log.info("Mean time from collection to publish: " + str(mean)) _log.info("Std dev time from collection to publish: " + str(stdev)) self._turnaround_times = [] #_log.debug("publishing {}".format(item)) _log.debug("recieved {} items to publish" .format(len(to_publish_list))) self.report_all_handled()
def calculate_average_power(self, current_power, current_time): """ Calculate the average power. :param current_power: :param current_time: :return: """ if self.simulation_running: self.check_schedule(current_time) if self.bldg_power: current_average_window = self.bldg_power[-1][0] - self.bldg_power[0][0] + td(seconds=15) else: current_average_window = td(minutes=0) if current_average_window >= self.average_building_power_window and current_power > 0: self.bldg_power.append((current_time, current_power)) self.bldg_power.pop(0) elif current_power > 0: self.bldg_power.append((current_time, current_power)) smoothing_constant = 2.0/(len(self.bldg_power) + 1.0)*2.0 if self.bldg_power else 1.0 smoothing_constant = smoothing_constant if smoothing_constant <= 1.0 else 1.0 power_sort = list(self.bldg_power) power_sort.sort(reverse=True) average_power = 0 for n in xrange(len(self.bldg_power)): average_power += power_sort[n][1] * smoothing_constant * (1.0 - smoothing_constant) ** n norm_list = [float(i[1]) for i in self.bldg_power] normal_average_power = mean(norm_list) if norm_list else 0.0 _log.debug("Reported time: {} - instantaneous power: {}".format(current_time, current_power)) _log.debug( "{} minute average power: {} - exponential power: {}".format(current_average_window, normal_average_power, average_power)) return average_power, normal_average_power, current_average_window
def insufficient_oa(self, dx_result, table_key): """ If the detected problems(s) are consistent generate a fault message(s). :param dx_result: :param cur_time: :param table_key: :return: """ oaf = [(m - r) / (o - r) for o, r, m in zip(self.oat_values, self.rat_values, self.mat_values)] avg_oaf = mean(oaf) * 100.0 diagnostic_msg = {} if avg_oaf < 0 or avg_oaf > 125.0: msg = ("{}: Inconclusive result, the OAF calculation led to an " "unexpected value: {}".format(ECON5, avg_oaf)) # color_code = "GREY" dx_table = {ECON5 + DX: self.invalid_oaf_dict} dx_result.log(msg) dx_result.insert_table_row(table_key, dx_table) self.clear_data() return dx_result avg_oaf = max(0.0, min(100.0, avg_oaf)) for sensitivity, threshold in self.ventilation_oaf_threshold.items(): if self.desired_oaf - avg_oaf > threshold: msg = "{}: Insufficient OA is being provided for ventilation - sensitivity: {}".format(ECON5, sensitivity) result = 43.1 else: msg = "{}: The calculated OAF was within acceptable limits - sensitivity: {}".format(ECON5, sensitivity) result = 40.0 dx_result.log(msg) diagnostic_msg.update({sensitivity: result}) dx_table = {ECON5 + DX: diagnostic_msg} dx_result.insert_table_row(table_key, dx_table) self.clear_data() return dx_result
def setpoint_control_check(set_point_array, point_array, setpoint_deviation_threshold, dx_name, dx_offset, dx_result): """ Verify that point if tracking with set point - identify potential control or sensor problems. :param set_point_array: :param point_array: :param allowable_deviation: :param dx_name: :param dx_offset: :param dx_result: :return: """ avg_set_point = None diagnostic_msg = {} for key, threshold in setpoint_deviation_threshold.items(): if set_point_array: avg_set_point = sum(set_point_array)/len(set_point_array) zipper = (set_point_array, point_array) set_point_tracking = [abs(x - y) for x, y in zip(*zipper)] set_point_tracking = mean(set_point_tracking)/avg_set_point*100. if set_point_tracking > threshold: # color_code = 'red' msg = '{} - {}: point deviating significantly from set point.'.format(key, dx_name) result = 1.1 + dx_offset else: # color_code = 'green' msg = " {} - No problem detected for {} set".format(key, dx_name) result = 0.0 + dx_offset else: # color_code = 'grey' msg = "{} - {} set point data is not available.".format(key, dx_name) result = 2.2 + dx_offset dx_result.log(msg) diagnostic_msg.update({key: result}) dx_table = {dx_name + DX: diagnostic_msg} return avg_set_point, dx_table, dx_result
def run(self, cur_time, points): device_dict = {} dx_result = Results() fan_status_data = [] supply_fan_off = False low_dx_cond = False high_dx_cond = False for key, value in points.items(): point_device = [_name.lower() for _name in key.split('&')] if point_device[0] not in device_dict: device_dict[point_device[0]] = [(point_device[1], value)] else: device_dict[point_device[0]].append((point_device[1], value)) if self.fan_status_name in device_dict: fan_status = device_dict[self.fan_status_name] fan_status = [point[1] for point in fan_status] fan_status = [status for status in fan_status if status is not None] if fan_status_data: fan_status_data.append(min(fan_status)) if not int(fan_status_data[0]): supply_fan_off = True self.warm_up_flag = True if self.fansp_name in device_dict: fan_speed = device_dict[self.fansp_name] fan_speed = mean([point[1] for point in fan_speed]) if self.fan_status_name is None: if not int(fan_speed): supply_fan_off = True self.warm_up_flag = True fan_status_data.append(bool(int(fan_speed))) if fan_speed > self.high_sf_threshold: low_dx_cond = True elif fan_speed < self.low_sf_threshold: high_dx_cond = True stc_pr_data = [] stcpr_sp_data = [] zn_dmpr_data = [] satemp_data = [] rht_data = [] sat_stpt_data = [] validate = {} sched_val = {} def validate_builder(value_tuple, point_name): value_list = [] for item in value_tuple: tag = item[0] + '/' + point_name validate.update({tag: item[1]}) value_list.append(item[1]) return value_list for key, value in device_dict.items(): data_name = key if value is None: continue if data_name == self.duct_stp_stpt_name: stcpr_sp_data = validate_builder(value, data_name) sched_val.update(validate) elif data_name == self.sat_stpt_name: sat_stpt_data = validate_builder(value, data_name) sched_val.update(validate) elif data_name == self.duct_stp_name: sched_val.update(validate) stc_pr_data = validate_builder(value, data_name) sched_val.update(validate) elif data_name == self.sa_temp_name: satemp_data = validate_builder(value, data_name) sched_val.update(validate) elif data_name == self.zone_reheat_name: rht_data = validate_builder(value, data_name) elif data_name == self.zone_damper_name: zn_dmpr_data = validate_builder(value, data_name) missing_data = [] if not satemp_data: missing_data.append(self.sa_temp_name) if not rht_data: missing_data.append(self.zone_reheat_name) if not sat_stpt_data: dx_result.log('Supply-air temperature set point data is ' 'missing. This will limit the effectiveness of ' 'the supply-air temperature diagnostics.') if not stc_pr_data: missing_data.append(self.duct_stp_name) if not stcpr_sp_data: dx_result.log('Duct static pressure set point data is ' 'missing. This will limit the effectiveness of ' 'the duct static pressure diagnostics.') if not zn_dmpr_data: missing_data.append(self.zone_damper_name) if not fan_status: missing_data.append(self.fan_status_name) if missing_data: raise Exception('Missing required data: {}'.format(missing_data)) return dx_result dx_result = ( self.sched_occ_dx.sched_rcx_alg(cur_time, stc_pr_data, stcpr_sp_data, sat_stpt_data, fan_status, dx_result, sched_val)) if supply_fan_off: dx_result.log('Supply fan is off. Data will not be used for ' 'retuning diagnostics.') return dx_result if self.warm_up_flag: self.warm_up_flag = False self.warm_up_start = cur_time return dx_result time_check = td(minutes=self.warm_up_time) if (self.warm_up_start is not None and (cur_time - self.warm_up_start) < time_check): dx_result.log('Unit is in warm-up. Data will not be analyzed.') return dx_result dx_result = ( self.static_dx.duct_static(cur_time, stcpr_sp_data, stc_pr_data, zn_dmpr_data, low_dx_cond, high_dx_cond, dx_result, validate)) dx_result = ( self.sat_dx.sat_rcx(cur_time, satemp_data, sat_stpt_data, rht_data, zn_dmpr_data, dx_result, validate)) return dx_result
def unocc_fan_operation(self, dx_result): """ AIRCx to determine if AHU is operating excessively in unoccupied mode. :param dx_result: :return: """ avg_duct_stcpr = 0 percent_on = 0 fan_status_on = [(fan[0].hour, fan[1]) for fan in self.fan_status_array if int(fan[1]) == 1] fanstat = [(fan[0].hour, fan[1]) for fan in self.fan_status_array] hourly_counter = [] thresholds = zip(self.unocc_time_thr.items(), self.unocc_stcpr_thr.items()) diagnostic_msg = {} for counter in range(24): fan_on_count = [fan_status_time[1] for fan_status_time in fan_status_on if fan_status_time[0] == counter] fan_count = [fan_status_time[1] for fan_status_time in fanstat if fan_status_time[0] == counter] if len(fan_count): hourly_counter.append(fan_on_count.count(1)/len(fan_count)*100) else: hourly_counter.append(0) if self.schedule_time_array: if self.fan_status_array: percent_on = (len(fan_status_on)/len(self.fan_status_array)) * 100.0 if self.stcpr_array: avg_duct_stcpr = mean(self.stcpr_array) for (key, unocc_time_thr), (key2, unocc_stcpr_thr) in thresholds: if percent_on > unocc_time_thr: msg = "{} - Supply fan is on during unoccupied times".format(key) result = 63.1 else: if avg_duct_stcpr < unocc_stcpr_thr: msg = "{} - No problems detected for schedule diagnostic.".format(key) result = 60.0 else: msg = ("{} - Fan status show the fan is off but the duct static " "pressure is high, check the functionality of the " "pressure sensor.".format(key)) result = 64.2 diagnostic_msg.update({key: result}) dx_result.log(msg) else: msg = "ALL - No problems detected for schedule diagnostic." dx_result.log(msg) diagnostic_msg = {"low": 60.0, "normal": 60.0, "high": 60.0} if 64.2 not in diagnostic_msg.values(): for _hour in range(24): diagnostic_msg = {} utc_offset = self.timestamp_array[0].isoformat()[-6:] push_time = self.timestamp_array[0].date() push_time = datetime.combine(push_time, datetime.min.time()) push_time = push_time.replace(hour=_hour) for key, unocc_time_thr in self.unocc_time_thr.items(): diagnostic_msg.update({key: 60.0}) if hourly_counter[_hour] > unocc_time_thr: diagnostic_msg.update({key: 63.1}) dx_table = {SCHED_RCX + DX: diagnostic_msg} table_key = create_table_key(self.analysis, push_time) + utc_offset dx_result.insert_table_row(table_key, dx_table) else: push_time = self.timestamp_array[0].date() table_key = create_table_key(self.analysis, push_time) dx_result.insert_table_row(table_key, {SCHED_RCX + DX: diagnostic_msg}) return dx_result
def sat_rcx(self, current_time, sat_data, sat_stpt_data, zone_rht_data, zone_dmpr_data, dx_result, validate): """Manages supply-air diagnostic data sets. Args: current_time (datetime): current timestamp for trend data. sat_data (lst of floats): supply-air temperature measurement for AHU. sat_stpt_data (List[floats]): supply-air temperature set point data for AHU. zone_rht_data (List[floats]): reheat command for terminal boxes served by AHU. zone_dmpr_data (List[floats]): damper command for terminal boxes served by AHU. dx_result (Object): Object for interacting with platform and devices. Returns: Results object (dx_result) to Application. """ if check_date(current_time, self.timestamp_arr): self.reinitialize() return dx_result file_key = create_table_key(VALIDATE_FILE_TOKEN, current_time) tot_rht = sum(1 if val > self.rht_on_thr else 0 for val in zone_rht_data) count_rht = len(zone_rht_data) tot_dmpr = sum(1 if val > self.high_dmpr_thr else 0 for val in zone_dmpr_data) count_damper = len(zone_dmpr_data) data = validation_builder(validate, SA_VALIDATE, DATA) run_status = check_run_status(self.timestamp_arr, current_time, self.no_req_data) if run_status is None: dx_result.log('Current analysis data set has insufficient data ' 'to produce a valid diagnostic result.') self.reinitialize() return dx_result if run_status: self.table_key = create_table_key(self.analysis, self.timestamp_arr[-1]) avg_sat_stpt, dx_table = setpoint_control_check(self.sat_stpt_arr, self.satemp_arr, self.stpt_allowable_dev, SA_TEMP_RCX, DX, SAT_NAME, self.token_offset) self.dx_table.update(dx_table) dx_result = self.low_sat(dx_result, avg_sat_stpt) dx_result = self.high_sat(dx_result, avg_sat_stpt) dx_result.insert_table_row(self.table_key, self.dx_table) self.data.update({SA_VALIDATE + DATA + ST: 1}) dx_result.insert_table_row(self.file_key, self.data) self.reinitialize() self.satemp_arr.append(mean(sat_data)) self.rht_arr.append(mean(zone_rht_data)) self.sat_stpt_arr.append(mean(sat_stpt_data)) self.percent_rht.append(tot_rht/count_rht) self.percent_dmpr.append(tot_dmpr/count_damper) self.timestamp_arr.append(current_time) if self.data: self.data.update({SA_VALIDATE + DATA + ST: 0}) dx_result.insert_table_row(self.file_key, self.data) self.data = data self.file_key = file_key return dx_result
def excess_oa(self, dx_result, table_key): """ If the detected problems(s) are consistent generate a fault message(s). :param dx_result: :param table_key: :return: """ oaf = [(m - r) / (o - r) for o, r, m in zip(self.oat_values, self.rat_values, self.mat_values)] avg_oaf = mean(oaf) * 100.0 avg_damper = mean(self.oad_values) desired_oaf = self.desired_oaf / 100.0 diagnostic_msg = {} energy_impact = {} if avg_oaf < 0 or avg_oaf > 125.0: msg = ("{}: Inconclusive result, unexpected OAF value: {}".format(ECON4, avg_oaf)) # color_code = "GREY" dx_table = {ECON4 + DX: self.invalid_oaf_dict} dx_result.log(msg) dx_result.insert_table_row(table_key, dx_table) self.clear_data() return dx_result avg_oaf = max(0.0, min(100.0, avg_oaf)) thresholds = zip(self.excess_damper_threshold.items(), self.excess_oaf_threshold.items()) for (key, damper_thr), (key2, oaf_thr) in thresholds: if avg_damper > damper_thr: msg = "{}: The OAD should be at the minimum but is significantly higher.".format(ECON4) # color_code = "RED" result = 32.1 if avg_oaf - self.desired_oaf > oaf_thr: msg = ("{}: The OAD should be at the minimum for ventilation " "but is significantly above that value. Excess outdoor air is " "being provided; This could significantly increase " "heating and cooling costs".format(ECON4)) energy = self.energy_impact_calculation(desired_oaf) result = 34.1 elif avg_oaf - self.desired_oaf > oaf_thr: msg = ("{}: Excess outdoor air is being provided, this could " "increase heating and cooling energy consumption.".format(ECON4)) # color_code = "RED" energy = self.energy_impact_calculation(desired_oaf) result = 33.1 else: # color_code = "GREEN" msg = ("{}: The calculated OAF is within configured limits.".format(ECON4)) result = 30.0 energy = 0.0 dx_result.log(msg) energy_impact.update({key: energy}) diagnostic_msg.update({key: result}) dx_table = { ECON4 + DX: diagnostic_msg, ECON4 + EI: energy_impact } dx_result.insert_table_row(table_key, dx_table) self.clear_data() return dx_result