Files
small_tel/Daemons/weatherdaemon_multimeteo/plugins/snmp.c

217 lines
7.5 KiB
C

/*
* This file is part of the weatherdaemon project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <string.h>
#include <stdlib.h>
// 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;
}