/* This function allows the subdriver to "claim" a device: return 1 if the device is supported by this subdriver, else 0. */ int blazer_claim(void) { /* To tell whether the UPS is supported or not, we'll check both status (Q1/QS/D) and vendor (I/FW?) - provided that we were not told not to do it with the ups.conf flag 'novendor'. */ item_t *item = find_nut_info("input.voltage", 0, 0); /* Don't know what happened */ if (!item) return 0; /* No reply/Unable to get value */ if (qx_process(item, NULL)) return 0; /* Unable to process value */ if (ups_infoval_set(item) != 1) return 0; if (testvar("novendor")) return 1; /* Vendor */ item = find_nut_info("ups.firmware", 0, 0); /* Don't know what happened */ if (!item) { dstate_delinfo("input.voltage"); return 0; } /* No reply/Unable to get value */ if (qx_process(item, NULL)) { dstate_delinfo("input.voltage"); return 0; } /* Unable to process value */ if (ups_infoval_set(item) != 1) { dstate_delinfo("input.voltage"); return 0; } return 1; }
/* write the status_buf into the info array */ void alarm_commit(void) { if (strlen(alarm_buf) > 0) { dstate_setinfo("ups.alarm", "%s", alarm_buf); alarm_active = 1; } else { dstate_delinfo("ups.alarm"); alarm_active = 0; } }
static int setvar(const char *varname, const char *val) { dummy_info_t *item; upsdebugx(2, "entering setvar(%s, %s)", varname, val); /* FIXME: the below is only valid if (mode == MODE_DUMMY) * if (mode == MODE_REPEATER) => forward * if (mode == MODE_META) => ? */ if (!strncmp(varname, "ups.status", 10)) { status_init(); /* FIXME: split and check values (support multiple values), à la usbhid-ups */ status_set(val); status_commit(); return STAT_SET_HANDLED; } /* Check variable validity */ if (!is_valid_data(varname)) { upsdebugx(2, "setvar: invalid variable name (%s)", varname); return STAT_SET_UNKNOWN; } /* Check value validity */ if (!is_valid_value(varname, val)) { upsdebugx(2, "setvar: invalid value (%s) for variable (%s)", val, varname); return STAT_SET_UNKNOWN; } /* If value is empty, remove the variable (FIXME: do we need * a magic word?) */ if (strlen(val) == 0) { dstate_delinfo(varname); } else { dstate_setinfo(varname, "%s", val); if ( (item = find_info(varname)) != NULL) { dstate_setflags(item->info_type, item->info_flags); /* Set max length for strings, if needed */ if (item->info_flags & ST_FLAG_STRING) dstate_setaux(item->info_type, item->info_len); } } return STAT_SET_HANDLED; }
/* This function allows the subdriver to "claim" a device: return 1 if the device is supported by this subdriver, else 0. */ static int voltronic_qs_hex_claim(void) { /* We need at least M and QS to run this subdriver */ /* UPS Protocol */ item_t *item = find_nut_info("ups.firmware.aux", 0, 0); /* Don't know what happened */ if (!item) return 0; /* No reply/Unable to get value */ if (qx_process(item, NULL)) return 0; /* Unable to process value/Protocol not supported */ if (ups_infoval_set(item) != 1) return 0; item = find_nut_info("input.voltage", 0, 0); /* Don't know what happened */ if (!item) { dstate_delinfo("ups.firmware.aux"); return 0; } /* No reply/Unable to get value */ if (qx_process(item, NULL)) { dstate_delinfo("ups.firmware.aux"); return 0; } /* Unable to process value */ if (ups_infoval_set(item) != 1) { dstate_delinfo("ups.firmware.aux"); return 0; } return 1; }
/* Convert the local status information to NUT format and set NUT status. */ static void netxml_status_set(void) { if (STATUS_BIT(VRANGE)) { dstate_setinfo("input.transfer.reason", "input voltage out of range"); } else if (STATUS_BIT(FRANGE)) { dstate_setinfo("input.transfer.reason", "input frequency out of range"); } else { dstate_delinfo("input.transfer.reason"); } if (STATUS_BIT(ONLINE)) { status_set("OL"); /* on line */ } else { status_set("OB"); /* on battery */ } if (STATUS_BIT(DISCHRG)) { status_set("DISCHRG"); /* discharging */ } if (STATUS_BIT(CHRG)) { status_set("CHRG"); /* charging */ } if (STATUS_BIT(LOWBATT)) { status_set("LB"); /* low battery */ } if (STATUS_BIT(OVERLOAD)) { status_set("OVER"); /* overload */ } if (STATUS_BIT(REPLACEBATT)) { status_set("RB"); /* replace batt */ } if (STATUS_BIT(TRIM)) { status_set("TRIM"); /* SmartTrim */ } if (STATUS_BIT(BOOST)) { status_set("BOOST"); /* SmartBoost */ } if (STATUS_BIT(BYPASSAUTO) || STATUS_BIT(BYPASSMAN)) { status_set("BYPASS"); /* on bypass */ } if (STATUS_BIT(OFF)) { status_set("OFF"); /* ups is off */ } if (STATUS_BIT(SHUTDOWNIMM)) { status_set("FSD"); /* shutdown imminent */ } }
/* Change a variable name in a table */ static void change_name(simple_t *sp, const char *oldname, const char *newname) { while(sp->code) { if (sp->desc && !strcmp(sp->desc, oldname)) { sp->desc = strdup(newname); if (dstate_getinfo(oldname)) { dstate_setinfo(newname, "%s", dstate_getinfo(oldname)); } dstate_delinfo(oldname); upsdebugx(1, "Changing name: %s => %s", oldname, newname); break; } sp++; } }
static int parse_args(int numargs, char **arg) { if (numargs < 1) { return 0; } if (!strcasecmp(arg[0], "PONG")) { upsdebugx(3, "Got PONG from UPS"); return 1; } if (!strcasecmp(arg[0], "DUMPDONE")) { upsdebugx(3, "UPS: dump is done"); dumpdone = 1; return 1; } if (!strcasecmp(arg[0], "DATASTALE")) { dstate_datastale(); return 1; } if (!strcasecmp(arg[0], "DATAOK")) { dstate_dataok(); return 1; } if (numargs < 2) { return 0; } /* DELINFO <var> */ if (!strcasecmp(arg[0], "DELINFO")) { dstate_delinfo(arg[1]); return 1; } if (numargs < 3) { return 0; } /* SETINFO <varname> <value> */ if (!strcasecmp(arg[0], "SETINFO")) { if (!strncasecmp(arg[1], "driver.", 7) || !strcasecmp(arg[1], "battery.charge.low") || !strcasecmp(arg[1], "battery.runtime.low") || !strncasecmp(arg[1], "ups.delay.", 10) || !strncasecmp(arg[1], "ups.timer.", 10)) { /* don't pass on upstream driver settings */ return 1; } if (!strcasecmp(arg[1], "ups.status")) { snprintf(ups.status, sizeof(ups.status), "%s", arg[2]); online = strstr(ups.status, "OL") ? 1 : 0; if (ups.timer.shutdown > 0) { dstate_setinfo("ups.status", "FSD %s", ups.status); return 1; } } if (!strcasecmp(arg[1], "battery.charge")) { battery.charge.act = strtod(arg[2], NULL); dstate_setinfo("battery.charge.low", "%g", battery.charge.low); dstate_setflags("battery.charge.low", ST_FLAG_RW | ST_FLAG_STRING); dstate_setaux("battery.charge.low", 3); } if (!strcasecmp(arg[1], "battery.runtime")) { battery.runtime.act = strtod(arg[2], NULL); dstate_setinfo("battery.runtime.low", "%g", battery.runtime.low); dstate_setflags("battery.runtime.low", ST_FLAG_RW | ST_FLAG_STRING); dstate_setaux("battery.runtime.low", 4); } dstate_setinfo(arg[1], "%s", arg[2]); return 1; } return 0; }
void upsdrv_updateinfo(void) { time_t now; int ok; float load; if (status & UPSC_STAT_NOTINIT) { upsdrv_initinfo(); } if (status & UPSC_STAT_NOTINIT) { return; } status = 0; ok = upsc_getparams("UPDS", simple); time(&now); if (ok && now - last_full > full_update_timer) { last_full = now; ok = upsc_getparams("UPDV", nominal); if (ok && can_upbs) ok = upsc_getparams("UPBS", battery); } if (!ok) { dstate_datastale(); last_full = 0; return; } if (!inited_phaseinfo) { if (dstate_getinfo("input.L3-L1.voltage") || dstate_getinfo("input.L3-N.voltage")) { num_inphases = 3; change_name(simple, "input.current", "input.L1.current"); change_name(simple, "input.realpower", "input.L1.realpower"); change_name(simple, "input.power", "input.L1.power"); change_name(simple, "input.voltage", "input.L1-N.voltage"); } if (dstate_getinfo("output.L3-L1.voltage") || dstate_getinfo("output.L3-N.voltage")) { const char *s; num_outphases = 3; if ((s=dstate_getinfo("ups.model")) && (!strncmp(s, "UPS9075", 7) || !strncmp(s, "UPS9100", 7) || !strncmp(s, "UPS9150", 7) || !strncmp(s, "UPS9200", 7) || !strncmp(s, "UPS9250", 7) || !strncmp(s, "UPS9300", 7) || !strncmp(s, "UPS9400", 7) || !strncmp(s, "UPS9500", 7) || !strncmp(s, "UPS9600", 7)) ) { /* Insert kludges for Fiskars UPS9000 here */ upslogx(LOG_INFO, "Fiskars UPS9000 detected, protocol kludges activated"); batt_volt_nom = 384; dstate_setinfo("battery.voltage.nominal", "%.0f", batt_volt_nom); } else { outpwr_factor *= 3; } change_name(simple, "output.current", "output.L1.current"); change_name(simple, "output.current.peak", "output.L1.current.peak"); change_name(simple, "output.realpower", "output.L1.realpower"); change_name(simple, "output.power", "output.L1.power"); change_name(simple, "output.voltage", "output.L1-N.voltage"); } dstate_setinfo("input.phases", "%d", num_inphases); dstate_setinfo("output.phases", "%d", num_outphases); inited_phaseinfo=1; } load = calc_upsload(); if (load >= 0) { upsdebugx(2, "ups.load: %.1f", load*100); dstate_setinfo("ups.load", "%.1f", load*100); } else { upsdebugx(2, "ups.load: No value"); } /* TODO/FIXME: Set UPS date/time on startup and daily if needed */ if (can_updt) { char dtbuf[UPSC_BUFLEN]; if (upsc_getvalue("UPDT", "0", "ACDT", NULL, dtbuf)) { dstate_setinfo("ups.date", "%s", dtbuf); } } if (can_uptm) { char tmbuf[UPSC_BUFLEN]; if (upsc_getvalue("UPTM", "0", "ACTM", NULL, tmbuf)) { dstate_setinfo("ups.time", "%s", tmbuf); } } if (batt_charge < 0) { if (batt_current < 0) { /* Reset battery current history if discharging */ numbatthist = lastbatthist = 0; } batt_charge = batt_charge_pct(); } if (batt_charge >= 0) { dstate_setinfo("battery.charge", "%.1f", batt_charge); } else { dstate_delinfo("battery.charge"); } /* 9999 == unknown value */ if (batt_runtime >= 0 && batt_runtime < 9999) { dstate_setinfo("battery.runtime", "%.0f", batt_runtime*60); } else if (load > 0 && batt_disch_curr_max != 0) { float est_battcurr = load * abs(batt_disch_curr_max); /* Peukert equation */ float runtime = (batt_cap_nom*3600)/pow(est_battcurr, 1.35); upsdebugx(2, "Calculated runtime: %.0f seconds", runtime); if (batt_runtime_max > 0 && runtime > batt_runtime_max*60) { runtime = batt_runtime_max*60; } dstate_setinfo("battery.runtime", "%.0f", runtime); } else if (batt_runtime_max > 0) { /* Show max possible runtime as reported by UPS */ dstate_setinfo("battery.runtime", "%.0f", batt_runtime_max*60); } else { dstate_delinfo("battery.runtime"); } /* Some UPSen only provides this when on battery, so reset between * each iteration to make sure we use the right value */ batt_charge = -1; batt_runtime = -1; if (!(status & UPSC_STAT_ONBATT)) status |= UPSC_STAT_ONLINE; upsc_setstatus(status); dstate_dataok(); ser_comm_good(); }
void upsdrv_updateinfo(void) { CFPropertyListRef power_dictionary; CFStringRef power_source_state; CFNumberRef battery_voltage, battery_runtime; CFNumberRef current_capacity; CFBooleanRef is_charging; double max_capacity_value = 100.0, current_capacity_value; upsdebugx(1, "upsdrv_updateinfo()"); power_dictionary = copy_power_dictionary( g_power_key ); assert(power_dictionary); /* TODO: call dstate_datastale()? */ status_init(); /* Retrieve OL/OB state */ power_source_state = CFDictionaryGetValue(power_dictionary, CFSTR(kIOPSPowerSourceStateKey)); assert(power_source_state); CFRetain(power_source_state); upsdebugx(3, "Power Source State:"); if(nut_debug_level >= 3) CFShow(power_source_state); if(!CFStringCompare(power_source_state, CFSTR(kIOPSACPowerValue), 0)) { status_set("OL"); } else { status_set("OB"); } CFRelease(power_source_state); /* Retrieve CHRG state */ is_charging = CFDictionaryGetValue(power_dictionary, CFSTR(kIOPSIsChargingKey)); if(is_charging) { Boolean is_charging_value; is_charging_value = CFBooleanGetValue(is_charging); if(is_charging_value) { status_set("CHRG"); } } status_commit(); /* Retrieve battery voltage */ battery_voltage = CFDictionaryGetValue(power_dictionary, CFSTR(kIOPSVoltageKey)); if(battery_voltage) { int battery_voltage_value; CFNumberGetValue(battery_voltage, kCFNumberIntType, &battery_voltage_value); upsdebugx(2, "battery_voltage = %d mV", battery_voltage_value); dstate_setinfo("battery.voltage", "%.3f", battery_voltage_value/1000.0); } /* Retrieve battery runtime */ battery_runtime = CFDictionaryGetValue(power_dictionary, CFSTR(kIOPSTimeToEmptyKey)); if(battery_runtime) { double battery_runtime_value; CFNumberGetValue(battery_runtime, kCFNumberDoubleType, &battery_runtime_value); upsdebugx(2, "battery_runtime = %.f minutes", battery_runtime_value); if(battery_runtime_value > 0) { dstate_setinfo("battery.runtime", "%d", (int)(battery_runtime_value*60)); } else { dstate_delinfo("battery.runtime"); } } else { dstate_delinfo("battery.runtime"); } /* Retrieve current capacity */ current_capacity = CFDictionaryGetValue(power_dictionary, CFSTR(kIOPSCurrentCapacityKey)); if(current_capacity) { CFNumberGetValue(current_capacity, kCFNumberDoubleType, ¤t_capacity_value); upsdebugx(2, "Current Capacity = %.f/%.f units", current_capacity_value, max_capacity_value); if(max_capacity_value > 0) { dstate_setinfo("battery.charge", "%.f", 100.0 * current_capacity_value / max_capacity_value); } } /* TODO: it should be possible to set poll_interval (and maxage in the * server) to an absurdly large value, and use notify(3) to get * updates. */ /* * poll_interval = 2; */ dstate_dataok(); CFRelease(power_dictionary); }
void upsdrv_updateinfo(void) { static int CommTry = COMM_TRIES; /* Comm loss counter */ char buffer[256]; /* Main response buffer */ char buffer2[32]; /* Conversion buffer */ char s; int RetValue; int timevalue; /* Start with EG/ON information */ ser_flush_in(upsfd,"",0); /*just in case*/ ser_send (upsfd,"%c%s", GET_ALL, COMMAND_END); if (strncmp(UpsFamily, FAMILY_EG, FAMILY_SIZE) == 0) { RetValue = OneacGetResponse (buffer,sizeof(buffer),GETALL_EG_RESP_SIZE); } else { RetValue = OneacGetResponse (buffer, sizeof(buffer), GETALL_RESP_SIZE); } if ((RetValue != 0) && (CommTry == 0)) { ser_comm_fail("Oneac UPS Comm failure continues on port %s", device_path); } else if (RetValue != 0) { if (--CommTry == 0) { ser_comm_fail("Oneac UPS Comm failure on port %s",device_path); dstate_datastale(); } upsdebugx(2,"Oneac: Update serial comm retry value: %d", CommTry); return; } else { CommTry = COMM_TRIES; /* Reset serial retries */ s = buffer[12]; status_init(); alarm_init(); /*take care of the UPS status information*/ if (s == '@') { status_set("OL"); } else { if (s & 0x01) /* On Battery */ { status_set("OB"); } else { status_set("OL"); } if (s & 0x02) /* Low Battery */ status_set("LB"); if (s & 0x04) /* General fault */ { dstate_setinfo("ups.test.result","UPS Internal Failure"); } else { dstate_setinfo("ups.test.result","Normal"); } if (s & 0x08) /* Replace Battery */ status_set("RB"); /* if (s & 0x10) */ /* High Line */ if (s & 0x20) /* Unit is hot */ alarm_set("OVERHEAT"); } /*take care of the reason why the UPS last transferred to battery*/ switch (buffer[13]) { case XFER_BLACKOUT : dstate_setinfo("input.transfer.reason", "Blackout"); break; case XFER_LOW_VOLT : dstate_setinfo("input.transfer.reason", "Low Input Voltage"); break; case XFER_HI_VOLT : dstate_setinfo("input.transfer.reason", "High Input Voltage"); break; case NO_VALUE_YET : dstate_setinfo("input.transfer.reason", "No transfer yet."); break; default : upslogx(LOG_INFO,"Oneac: Unknown reason for UPS battery" " transfer [%c]", buffer[13]); } /* now update info for only the non-EG families of UPS*/ if (strncmp(UpsFamily, FAMILY_EG, FAMILY_SIZE) != 0) { dstate_setinfo("ups.load", "0%.2s",buffer+31); /* Output ON or OFF? */ if(buffer[27] == NO_VALUE_YET) status_set("OFF"); /*battery charge*/ if(buffer[10] == YES) dstate_setinfo("battery.charge", "0%.2s",buffer+33); else dstate_setinfo("battery.charge", "100"); EliminateLeadingZeroes (buffer+35, 3, buffer2, sizeof(buffer2)); dstate_setinfo("input.voltage", "%s",buffer2); EliminateLeadingZeroes (buffer+38, 3, buffer2, sizeof(buffer2)); dstate_setinfo("input.voltage.minimum", "%s",buffer2); EliminateLeadingZeroes (buffer+41, 3, buffer2, sizeof(buffer2)); dstate_setinfo("input.voltage.maximum", "%s",buffer2); EliminateLeadingZeroes (buffer+44, 3, buffer2, sizeof(buffer2)); dstate_setinfo("output.voltage", "%s",buffer2); if (buffer[15] == NO_VALUE_YET) { dstate_delinfo("ups.timer.shutdown"); } else { /* A shutdown is underway! */ status_set("FSD"); if(buffer[15] != HIGH_COUNT) { EliminateLeadingZeroes (buffer+15, 3, buffer2, sizeof(buffer2)); dstate_setinfo("ups.timer.shutdown", "%s", buffer2); } else { dstate_setinfo("ups.timer.shutdown", "999"); } } if (buffer[47] == YES) status_set("BOOST"); } /* Now update info for only the OZ/OB families of UPS */ if ((strncmp(UpsFamily, FAMILY_OZ, FAMILY_SIZE) == 0) || (strncmp(UpsFamily, FAMILY_OB, FAMILY_SIZE) == 0)) { ser_flush_in(upsfd,"",0); /*just in case*/ ser_send (upsfd,"%c%s",GETX_ALL_1,COMMAND_END); RetValue = OneacGetResponse (buffer, sizeof(buffer), GETX_ALL1_RESP_SIZE); if(RetValue) { if (--CommTry == 0) { ser_comm_fail("Oneac (OZ) UPS Comm failure on port %s", device_path); dstate_datastale(); } upsdebugx(2,"Oneac: " "Update (OZ) serial comm retry value: %d", CommTry); } else { CommTry = COMM_TRIES; /* Reset count */ EliminateLeadingZeroes (buffer+57, 5, buffer2, sizeof(buffer2)); dstate_setinfo("ups.realpower", "%s",buffer2); dstate_setinfo("input.frequency", "%.2s.%c", buffer+42,buffer[44]); dstate_setinfo("output.frequency", "%.2s.%c", buffer+76, buffer[78]); EliminateLeadingZeroes (buffer+29, 3, buffer2, sizeof(buffer2)); dstate_setinfo("battery.voltage", "%s.%c",buffer2, buffer[32]); dstate_setinfo("ups.temperature", "%.2s",buffer+13); dstate_setinfo("ups.load", "%.3s",buffer+73); strncpy(buffer2, buffer+19, 4); buffer2[4]='\0'; timevalue = atoi(buffer2) * 60; /* Change mins to secs */ dstate_setinfo("battery.runtime", "%d",timevalue); /* Now some individual requests... */ /* Battery replace date */ ser_send (upsfd,"%c%s",GETX_BATT_REPLACED,COMMAND_END); if(!OneacGetResponse (buffer, sizeof(buffer), GETX_DATE_RESP_SIZE)) dstate_setinfo("battery.date", "%.6s (yymmdd)", buffer); /* Low and high output trip points */ ser_send (upsfd,"%c%s",GETX_LOW_OUT_ALLOW,COMMAND_END); if(!OneacGetResponse (buffer, sizeof(buffer), GETX_ALLOW_RESP_SIZE)) { EliminateLeadingZeroes (buffer, 3, buffer2,sizeof(buffer2)); dstate_setinfo("input.transfer.low", "%s", buffer2); } ser_send (upsfd,"%c%s",GETX_HI_OUT_ALLOW,COMMAND_END); if(!OneacGetResponse (buffer, sizeof(buffer), GETX_ALLOW_RESP_SIZE)) dstate_setinfo("input.transfer.high", "%s", buffer); /* Restart delay */ ser_send (upsfd,"%c%s",GETX_RESTART_DLY,COMMAND_END); if(!OneacGetResponse (buffer, sizeof(buffer), GETX_RSTRT_RESP_SIZE)) { EliminateLeadingZeroes (buffer, 4, buffer2, sizeof(buffer2)); dstate_setinfo("ups.delay.start", "%s", buffer2); } /* Buzzer state */ ser_send (upsfd,"%s%s",GETX_BUZZER_WHAT,COMMAND_END); if(!OneacGetResponse (buffer, sizeof(buffer), 1)) { switch (buffer[0]) { case BUZZER_ENABLED : dstate_setinfo("ups.beeper.status", "enabled"); break; case BUZZER_DISABLED : dstate_setinfo("ups.beeper.status", "disabled"); break; case BUZZER_MUTED : dstate_setinfo("ups.beeper.status", "muted"); break; default : dstate_setinfo("ups.beeper.status", "enabled"); } } /* Auto start setting */ ser_send (upsfd,"%s%s",GETX_AUTO_START,COMMAND_END); if(!OneacGetResponse (buffer, sizeof(buffer), 1)) { if (buffer[0] == '0') dstate_setinfo("ups.start.auto", "yes"); else dstate_setinfo("ups.start.auto", "no"); } /* Low Batt at time */ ser_send (upsfd,"%c%s",GETX_LOW_BATT_TIME,COMMAND_END); if(!OneacGetResponse (buffer, sizeof(buffer), 2)) { strncpy(buffer2, buffer, 2); buffer2[2]='\0'; timevalue = atoi(buffer2) * 60; /* Mins to secs */ dstate_setinfo("battery.runtime.low", "%d",timevalue); } /* Shutdown timer */ ser_send (upsfd,"%c%s",GETX_SHUTDOWN,COMMAND_END); if(!OneacGetResponse (buffer, sizeof(buffer), GETX_SHUTDOWN_RESP_SIZE)) { /* ON would have handled NO_VALUE_YET and setting FSD * above so only deal with counter value here. */ if (buffer[0] != NO_VALUE_YET) { EliminateLeadingZeroes (buffer, 5, buffer2, sizeof(buffer2)); dstate_setinfo("ups.timer.shutdown", "%s", buffer2); } } /* Restart timer */ ser_send (upsfd,"%s%s",GETX_RESTART_COUNT,COMMAND_END); if(!OneacGetResponse (buffer, sizeof(buffer), GETX_RSTRT_RESP_SIZE)) { if (atoi(buffer) == 0) { dstate_delinfo("ups.timer.start"); } else { EliminateLeadingZeroes (buffer, 4, buffer2, sizeof(buffer2)); dstate_setinfo("ups.timer.start", "%s", buffer2); } } } } alarm_commit(); status_commit(); /* If the comm retry counter is zero then datastale has been set. * We don't want to set dataok or ser_comm_good if that is the case. */ if (CommTry != 0) { dstate_dataok(); ser_comm_good(); } } }