/* * This file is part of the weatherdaemon project. * Copyright 2026 Edward V. Emelianov . * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include // set flag FORCE_OFF only within FORCEOFF_PAUSE seconds after power loss #define FORCEOFF_PAUSE 30 #include "weathlib.h" #define SENSOR_NAME "SNMP UPS monitor" // https://mibs.observium.org/mib/XUPS-MIB/ // gcc $(net-snmp-config --cflags --libs) snmp.c -o snmptest enum{ NBATSTAT, NTONBAT, NTREMAIN, NBATCAP, NSOURCE, NONBAT, NAMOUNT }; enum{ BATT_STAT_UNKN = 1, BATT_STAT_NORMAL, BATT_STAT_LOW, BATT_STAT_DEPLETED, BATT_STAT_AMOUNT }; const char* batt_stat[BATT_STAT_AMOUNT]= { "--", [BATT_STAT_UNKN] = "Unknown", [BATT_STAT_NORMAL] = "Normal", [BATT_STAT_LOW] = "Low", [BATT_STAT_DEPLETED] = "Depleted" }; enum{ SOURCE_OTHER = 1, SOURCE_NONE, SOURCE_NORMAL, SOURCE_BYPASS, SOURCE_BATTERY, SOURCE_BOOSTER, SOURCE_REDUCER, SOURCE_AMOUNT }; const char *sources[SOURCE_AMOUNT] = { "--", [SOURCE_OTHER] = "Other", [SOURCE_NONE] = "None", [SOURCE_NORMAL] = "Normal", [SOURCE_BYPASS] = "Bypass", [SOURCE_BATTERY] = "Battery", [SOURCE_BOOSTER] = "Booster", [SOURCE_REDUCER] = "Reducer" }; enum{ OID_BATT_STATUS, // batt status: 1 - unkn, 2 - normal, 3 - low, 4 - depleted OID_BATT_SECONDS_ONBAT, // seconds from ONBAT starts OID_BATT_EST_MINUTES, // estimated minutes of work OID_BATT_CAPACITY, // capacity of battery OID_OUTPUT_SOURCE, // input source: 1 - other, 2 - none, 3 - normal, 4 - bypass, 5 - battery, 6 - booster, 7 - reducer OID_AMOUNT }; static netsnmp_session *snmp_session; static oid anOID[OID_AMOUNT][MAX_OID_LEN]; static size_t anOID_len[OID_AMOUNT]; const char *oids[OID_AMOUNT] = { [OID_BATT_STATUS] = ".1.3.6.1.2.1.33.1.2.1.0", [OID_BATT_SECONDS_ONBAT] = ".1.3.6.1.2.1.33.1.2.2.0", [OID_BATT_EST_MINUTES] = ".1.3.6.1.2.1.33.1.2.3.0", [OID_BATT_CAPACITY] = ".1.3.6.1.2.1.33.1.2.4.0", [OID_OUTPUT_SOURCE] = ".1.3.6.1.2.1.33.1.4.1.0", }; static const val_t values[NAMOUNT] = { [NBATSTAT] = {.sense = VAL_RECOMMENDED, .type = VALT_STRING, .meaning = IS_OTHER, .name = "UPSBTST", .comment = "UPS battery status"}, [NTONBAT] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "UPSTONBT", .comment = "UPS worked on battery time (s)"}, [NTREMAIN] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "UPSTREM", .comment = "UPS estimated time on battery (s)"}, [NBATCAP] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "UPSBATCP", .comment = "UPS battery capacity, percents"}, [NSOURCE] = {.sense = VAL_RECOMMENDED, .type = VALT_STRING, .meaning = IS_OTHER, .name = "UPSSRC", .comment = "UPS power source"}, [NONBAT] = {.sense = VAL_FORCEDSHTDN, .type = VALT_UINT, .meaning = IS_FORCEDSHTDN, .name = "UPSONBAT", .comment = "AC power lost, works on battery"}, }; static void *mainthread(void *s){ double t0 = sl_dtime(); sensordata_t *sensor = (sensordata_t *)s; netsnmp_pdu *pdu, *response; while(sensor->fdes > -1){ //DBG("run"); pdu = snmp_pdu_create(SNMP_MSG_GET); for(int i = 0; i < OID_AMOUNT; ++i) snmp_add_null_var(pdu, anOID[i], anOID_len[i]); int status = snmp_synch_response(snmp_session, pdu, &response); //DBG("status = %d", status); if(status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR){ time_t curt = time(NULL); netsnmp_variable_list *vars = response->variables; // OID_BATT_STATUS pthread_mutex_lock(&sensor->valmutex); int ival = *vars->val.integer; if(ival > 0 && ival < BATT_STAT_AMOUNT){ snprintf(sensor->values[NBATSTAT].value.str, STRT_LEN+1, "%s", batt_stat[ival]); } vars = vars->next_variable; // OID_BATT_SECONDS_ONBAT uint32_t tonbat = (uint32_t) *vars->val.integer; sensor->values[NTONBAT].value.u = tonbat; vars = vars->next_variable; // OID_BATT_EST_MINUTES sensor->values[NTREMAIN].value.u = 60 * (uint32_t) *vars->val.integer; vars = vars->next_variable; // OID_BATT_CAPACITY sensor->values[NBATCAP].value.u = (uint32_t) *vars->val.integer; vars = vars->next_variable; // OID_OUTPUT_SOURCE ival = *vars->val.integer; if(ival > 0 && ival < SOURCE_AMOUNT) snprintf(sensor->values[NSOURCE].value.str, STRT_LEN+1, "%s", sources[ival]); if(ival == SOURCE_BATTERY && tonbat > FORCEOFF_PAUSE){ sensor->values[NONBAT].value.u = 1; }else sensor->values[NONBAT].value.u = 0; for(int i = 0; i < NAMOUNT; ++i) sensor->values[i].time = curt; //DBG("times updated to %zd", curt); pthread_mutex_unlock(&sensor->valmutex); if(sensor->freshdatahandler) sensor->freshdatahandler(sensor); }else DBG("Error in packet"); if(response) snmp_free_pdu(response); //DBG("sleep"); while(sl_dtime() - t0 < sensor->tpoll) usleep(500); t0 = sl_dtime(); } return NULL; } static void snmp_kill(sensordata_t *s){ s->fdes = -1; pthread_join(s->thread, NULL); snmp_close(snmp_session); common_kill(s); } int sensor_init(sensordata_t *s){ FNAME(); if(!s || !s->path[0]) return FALSE; netsnmp_session session; init_snmp("snmpapp"); snmp_sess_init(&session); session.version = SNMP_VERSION_1; session.community = (u_char *)"public"; session.community_len = strlen((const char *)session.community); DBG("PATH: %s", s->path); const char *colon = strchr(s->path, ':'); if(colon) ++colon; // omit "N:" in field "N:host" session.peername = strdup(colon); snmp_session = snmp_open(&session); if(!snmp_session){ snmp_sess_perror("snmp_open", &session); FREE(session.peername); return FALSE; } s->kill = snmp_kill; snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME); s->fdes = 0; s->Nvalues = NAMOUNT; s->values = MALLOC(val_t, NAMOUNT); for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i]; DBG("init OIDs"); for(int i = 0; i < OID_AMOUNT; ++i){ anOID_len[i] = MAX_OID_LEN; if(!read_objid(oids[i], anOID[i], &anOID_len[i])){ snmp_perror(oids[i]); continue; } DBG("Got OID %s", oids[i]); //snmp_add_null_var(snmp_pdu, anOID, anOID_len); } DBG("Start main thread"); if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) || pthread_create(&s->thread, NULL, mainthread, (void*)s)){ s->kill(s); return FALSE; } return TRUE; }