Compare commits

..

6 Commits

Author SHA1 Message Date
9bad35fcd5 SQL table for the stored data 2026-02-26 17:11:33 +01:00
b421086235 QoL 2026-02-26 17:11:18 +01:00
8ef830e1ad Added pysnmp to requirements 2026-02-26 17:11:04 +01:00
3dd849e515 Collected information about needed OIDs 2026-02-26 17:10:58 +01:00
09a63770d7 Added MIBs for IDRAC 2026-02-26 17:10:26 +01:00
fe51d046f6 Added more code 2026-02-26 17:09:56 +01:00
10 changed files with 28987 additions and 42 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
cont/code/__pycache__
cont/code/venv

20
MIBs.md
View File

@@ -26,6 +26,23 @@ input voltage for PSU(s)
Divide by 2 to get power usage
### To get per PSU power in watts
Multiply Amperage by Voltage:
.1.3.6.1.4.1.674.10892.5.4.600.30.1.6 (amperageProbeReading)
1.1 is PSU1 and 1.2 is PSU2
Amperage is in hundreds of miliamps. So if amperageProbeReading read 2, it means that PSUx is drawing 0.2 Amps
.1.3.6.1.4.1.674.10892.5.4.600.20.1.6 (voltageProbeReading)
1.31 is PSU1 and 1.32 is PSU2
Here the voltage i given without deicmal point (240000) so decimal point needs to be moved (240.000) or just convert to int (240)
## other
@@ -34,3 +51,6 @@ Divide by 2 to get power usage
Get the CPU name(s)
.1.3.6.1.4.1.674.10892.5.2.5.0 (systemPowerUpTime)
Uptime in seconds

13500
cont/code/MIBs/iDRAC-SMIv1.mib Normal file

File diff suppressed because it is too large Load Diff

15282
cont/code/MIBs/iDRAC-SMIv2.mib Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ from datetime import datetime, timezone, timedelta
from typing import Optional, Annotated
from sqlalchemy.dialects.mysql import MEDIUMINT, TINYINT, TEXT, TIMESTAMP, FLOAT
from sqlalchemy.ext.declarative import declarative_base
from dataclasses import dataclass
# -----------
@@ -41,21 +42,23 @@ class snmpValueToHost():
lastV: Annotated[str, "Last value\nNone when initializing"] = None,
remote: Annotated[str, "Needs to be a valid IP address"] = None,
nextV: Annotated[str, "Value to insert next\nNone when initializing"] = None,
lasttime: Annotated[float, "Last time since sending data\nSince Epoch"] = time.time(),
lasttime: Annotated[float, "Last time since sending data\nSince Epoch, so time.time()"] = None,
lastStatus: Annotated[bool, "True if value newest value was inserted\nFalse if data is pending to be sent"] = False
):
self.__lastValue = lastV # Save last sent value
self.__remote = remote # Remote IP address of the SNMP client
self.__nextValue = nextV # Next value to send. Maybe will be used
self.__lastSentStatus = lastStatus # If previously sent new data, set to True.
self.__lastSentTime = lasttime # The time since the Epoch of the last sent data*
self.__lastSentTime = lasttime if lasttime else time.time() # The time since the Epoch of the last sent data*
def ___str__():
def ___str__(self):
return self.__lastValue
def __changeLastSentValue(slef):
def __changeLastSentValue(self, newValue: int) -> bool:
self.__nextValue == newValue
self.__lastValue = self.__nextValue
self.__nextValue == None
return True
def __updateLastSentTime(self):
@@ -71,5 +74,23 @@ class snmpValueToHost():
return True
def updateStats(self, nextV: Annotated[int, "Value that will be set as the new one"]):
__changeLastSentValue(nextV)
__updateLastSentTime()
...
# Dataclasses--------------------------------------------
@dataclass
class snmpPyData():
"""Dataclass for snmp data"""
hostname: str
powerDrawPSU1: int
powerDrawPSU2: int
voltagePSU1: int
voltagePSU2: int
inletTemp: float
exhaustTemp: float
uptime: int

View File

@@ -1,11 +1,10 @@
import influxdb_client, sqlalchemy, random, os
# from sqlmodel import Field, Session, SQLModel, create_engine, select
from sqlalchemy import create_engine, exc # , Column, Integer, String, Numeric
from sqlalchemy.ext.declarative import declarative_base
# from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from typing import Annotated, Final
from classes import *
from typing import Annotated, Final
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS, WriteOptions
@@ -68,12 +67,12 @@ def tempsnsrIntoSQLDB(
try:
data = sensorsqldata(tableInp)
sqlData = sensorsqldata(tableInp)
# print("2")
if inpayload:
# print("3")
session.add(data(
session.add(sqlData(
data = insertData,
))
# print("4")
@@ -88,7 +87,7 @@ def tempsnsrIntoSQLDB(
def tempsnsrIntoFluxQLDB(
inpayload: Annotated[SensorPayload, "Payload"]
inpayload: Annotated[snmpPyData, "Payload"]
) -> int:
"""

View File

@@ -1,32 +1,61 @@
# import pysnmp, sqlalchemy, ipaddress, os, influxdb_client, re, time
# # SQL
# from sqlalchemy import create_engine, exc # , Column, Integer, String, Numeric
# from sqlalchemy.ext.declarative import declarative_base
# from sqlalchemy.orm import sessionmaker
# # InfluxDB
# from influxdb_client import InfluxDBClient, Point, WritePrecision
# from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS, WriteOptions
import pysnmp, ipaddress, os, re, time
import ipaddress, os, re, time, funct, classes
#SNMP
from easysnmp import Session
# from easysnmp import Session
# QoL
from typing import Annotated
from typing import Annotated, Final
# Program ENV---------------------------------------
HOST_LIST: Final[list] = os.getenv("HOST_LIST")
try:
RESOLV_ADDR: Final[str] = ipaddress(os.getenv("RESOLV_ADDR", "1.1.1.1"))
except ValueError:
raise Exception("Malformed DNS IP address.\nExiting...")
SNMPUSER: Final[str] = os.getenv("SNMPUSER", None)
SNMPPRIVKEY: Final[str] = os.getenv("SNMPPRIVKEY", None)
SNMPAUTHKEY: Final[str] = os.getenv("SNMPAUTHKEY", None)
SNMPAUTHPROTO: Final[str] = os.getenv("SNMPAUTHPROTO", "SHA")
SNMPPRIVPROTO: Final[str] = os.getenv("SNMPPRIVPROTO", "SHA")
SNMPORT: Final[int] = os.getenv("SNMPORT", 161)
# Flightchecks-------------------------------------------
# Helper functions
def check_host_string(input_text: Annotated[str, "Text to check"]):
pattern = re.compile(r"^[A-Za-z].*$", re.IGNORECASE)
return pattern.match(input_text)
if SNMPAUTHPROTO and SNMPPRIVPROTO == "SHA":
USMDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmHMACSHAAuthProtocol,
)
elif SNMPAUTHPROTO and SNMPPRIVPROTO == "AES128":
USMDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmAesCfb128Protocol,
privProtocol=usmAesCfb128Protocol,
)
elif SNMPAUTHPROTO == "SHA" and SNMPPRIVPROTO == "AES128":
USMDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol,
)
elif SNMPAUTHPROTO == "AES128" and SNMPPRIVPROTO == "SHA":
USMDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmAesCfb128Protocol,
privProtocol=usmHMACSHAAuthProtocol,
)
else:
raise Exception(f"No PrivAuth option like {SNMPPRIVPROTO}")
# Helper functions
def wattageCalc(amperageInp: float, voltageInp: float) -> float:
return amperageInp * voltageInp
# Check if some neccesary ENVs are passed
if not USEINFLUX and not USESQL:
@@ -37,25 +66,67 @@ if not HOST_LIST:
raise Exception("No hosts passed\nExiting...")
# for host in HOST_LIST check if valid IP and create a list with strings
HOST_LIST_STR = []
for host in HOST_LIST:
try:
if not check_host_string(host):
ip = str(ipaddress.IPv4Address(host))
HOST_LIST_STR.append(ip)
except ValueError:
raise Exception("Malformed IP address.\nExiting...")
raise Exception(f" IP {ip} is invalid.\nExiting...")
# Checking if DNS IP is a valid one
# Done under "Program ENV" on line ~18
# Code-------------------------------------------
import asyncio
from pysnmp.hlapi.v3arch.asyncio import *
async def poolRemote(remoteIP: str):
snmpEngine = SnmpEngine()
iterator = get_cmd(
snmpEngine,
UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmHMACSHAAuthProtocol,
),
await UdpTransportTarget.create((remoteIP, SNMPORT)),
ContextData(),
ObjectType(ObjectIdentity("SNMPv2-MIB", "sysDescr", 0)),
)
errorIndication, errorStatus, errorIndex, varBinds = await iterator
if errorIndication:
print(errorIndication)
elif errorStatus:
print(
"{} at {}".format(
errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or "?",
)
)
else:
for varBind in varBinds:
print(" = ".join([x.prettyPrint() for x in varBind]))
snmpEngine.close_dispatcher()
# tasks = [poolRemote(ip) for ip in HOST_LIST]
# results = await asyncio.gather(*tasks)
# Create connections for MySQL and InfluxDB
# functions for inserting data into two DBs

8
cont/conf/contLogs.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
while true; do
docker container logs snmpython-dev -f
# Added to have time to ctrl+C
sleep 0.5
done

View File

@@ -6,7 +6,6 @@ pydantic_core==2.27.2
annotated-types==0.7.0
typing==3.7.4.3
pysnmp==7.1.22
# requests==2.32.3
influxdb-client==1.49.0
# influxdb3-python==0.18.0

43
tables.sql Normal file
View File

@@ -0,0 +1,43 @@
-- Set correct timezone
SET GLOBAL time_zone = 'Europe/Stockholm';
SET NAMES utf8;
SET time_zone = '+01:00';
-- SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
-- DELIMITER ;;
-- DROP EVENT IF EXISTS `Prune_old_entries_staging_sensr`;;
-- CREATE EVENT `Prune_old_entries_staging_sensr`
-- ON SCHEDULE EVERY 1 DAY STARTS '2024-07-17 22:00:00'
-- ON COMPLETION
-- NOT PRESERVE
-- DISABLE ON SLAVE DO
-- DELETE FROM staging_sensr
-- WHERE time_stamp < NOW() - INTERVAL 2 MONTH;;
-- DELIMITER ;
SET NAMES utf8mb4;
-- New universal table type
CREATE TABLE newUniversalSensorTable (
id MEDIUMINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
time_stamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
hostname TEXT(30),
powerDrawPSU1 TINYINT(100),
powerDrawPSU2 TINYINT(100) NULL,
voltagePSU1 TINYINT(100),
voltagePSU2 TINYINT(100) NULL,
inletTemp FLOAT,
exhaustTemp FLOAT,
uptime INT NULL,
pressure FLOAT NULL,
altitude FLOAT NULL
);