Initial commit: LAN Checker
Network health monitoring script with MQTT reporting for Home Assistant. - Ping, HTTP, and SNMP checkers - MQTT Discovery for automatic entity creation - Configurable check intervals Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
9
checkers/__init__.py
Normal file
9
checkers/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .ping import PingChecker
|
||||
from .http import HttpChecker
|
||||
from .snmp import SnmpChecker
|
||||
|
||||
CHECKERS = {
|
||||
"ping": PingChecker,
|
||||
"http": HttpChecker,
|
||||
"snmp": SnmpChecker,
|
||||
}
|
||||
21
checkers/base.py
Normal file
21
checkers/base.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckResult:
|
||||
success: bool
|
||||
message: str
|
||||
response_time: float | None = None
|
||||
details: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class BaseChecker(ABC):
|
||||
def __init__(self, name: str, config: dict):
|
||||
self.name = name
|
||||
self.config = config
|
||||
|
||||
@abstractmethod
|
||||
def check(self) -> CheckResult:
|
||||
pass
|
||||
53
checkers/http.py
Normal file
53
checkers/http.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
from .base import BaseChecker, CheckResult
|
||||
|
||||
|
||||
class HttpChecker(BaseChecker):
|
||||
def check(self) -> CheckResult:
|
||||
url = self.config["url"]
|
||||
method = self.config.get("method", "GET").upper()
|
||||
timeout = self.config.get("timeout", 10)
|
||||
expected_status = self.config.get("expected_status", 200)
|
||||
verify_ssl = self.config.get("verify_ssl", True)
|
||||
headers = self.config.get("headers", {})
|
||||
|
||||
start = time.time()
|
||||
try:
|
||||
response = requests.request(
|
||||
method=method,
|
||||
url=url,
|
||||
timeout=timeout,
|
||||
verify=verify_ssl,
|
||||
headers=headers
|
||||
)
|
||||
response_time = (time.time() - start) * 1000 # ms
|
||||
|
||||
if response.status_code == expected_status:
|
||||
return CheckResult(
|
||||
success=True,
|
||||
message=f"HTTP {response.status_code}",
|
||||
response_time=response_time,
|
||||
details={"status_code": response.status_code}
|
||||
)
|
||||
else:
|
||||
return CheckResult(
|
||||
success=False,
|
||||
message=f"Unexpected status: {response.status_code} (expected {expected_status})",
|
||||
response_time=response_time,
|
||||
details={"status_code": response.status_code}
|
||||
)
|
||||
except requests.Timeout:
|
||||
return CheckResult(
|
||||
success=False,
|
||||
message="HTTP timeout",
|
||||
response_time=None
|
||||
)
|
||||
except requests.RequestException as e:
|
||||
return CheckResult(
|
||||
success=False,
|
||||
message=f"HTTP error: {e}",
|
||||
response_time=None
|
||||
)
|
||||
53
checkers/ping.py
Normal file
53
checkers/ping.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import subprocess
|
||||
import time
|
||||
import platform
|
||||
|
||||
from .base import BaseChecker, CheckResult
|
||||
|
||||
|
||||
class PingChecker(BaseChecker):
|
||||
def check(self) -> CheckResult:
|
||||
host = self.config["host"]
|
||||
count = self.config.get("count", 1)
|
||||
timeout = self.config.get("timeout", 5)
|
||||
|
||||
# Adapt ping command for OS
|
||||
if platform.system().lower() == "windows":
|
||||
cmd = ["ping", "-n", str(count), "-w", str(timeout * 1000), host]
|
||||
else:
|
||||
cmd = ["ping", "-c", str(count), "-W", str(timeout), host]
|
||||
|
||||
start = time.time()
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout + 5
|
||||
)
|
||||
response_time = (time.time() - start) * 1000 # ms
|
||||
|
||||
if result.returncode == 0:
|
||||
return CheckResult(
|
||||
success=True,
|
||||
message="Host is reachable",
|
||||
response_time=response_time
|
||||
)
|
||||
else:
|
||||
return CheckResult(
|
||||
success=False,
|
||||
message="Host is unreachable",
|
||||
response_time=None
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
return CheckResult(
|
||||
success=False,
|
||||
message="Ping timeout",
|
||||
response_time=None
|
||||
)
|
||||
except Exception as e:
|
||||
return CheckResult(
|
||||
success=False,
|
||||
message=f"Ping error: {e}",
|
||||
response_time=None
|
||||
)
|
||||
62
checkers/snmp.py
Normal file
62
checkers/snmp.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import time
|
||||
|
||||
from pysnmp.hlapi import (
|
||||
SnmpEngine,
|
||||
CommunityData,
|
||||
UdpTransportTarget,
|
||||
ContextData,
|
||||
ObjectType,
|
||||
ObjectIdentity,
|
||||
getCmd,
|
||||
)
|
||||
|
||||
from .base import BaseChecker, CheckResult
|
||||
|
||||
|
||||
class SnmpChecker(BaseChecker):
|
||||
def check(self) -> CheckResult:
|
||||
host = self.config["host"]
|
||||
port = self.config.get("port", 161)
|
||||
community = self.config.get("community", "public")
|
||||
oid = self.config.get("oid", "1.3.6.1.2.1.1.1.0") # sysDescr
|
||||
timeout_val = self.config.get("timeout", 5)
|
||||
|
||||
start = time.time()
|
||||
try:
|
||||
iterator = getCmd(
|
||||
SnmpEngine(),
|
||||
CommunityData(community),
|
||||
UdpTransportTarget((host, port), timeout=timeout_val, retries=1),
|
||||
ContextData(),
|
||||
ObjectType(ObjectIdentity(oid))
|
||||
)
|
||||
|
||||
error_indication, error_status, error_index, var_binds = next(iterator)
|
||||
response_time = (time.time() - start) * 1000 # ms
|
||||
|
||||
if error_indication:
|
||||
return CheckResult(
|
||||
success=False,
|
||||
message=f"SNMP error: {error_indication}",
|
||||
response_time=None
|
||||
)
|
||||
elif error_status:
|
||||
return CheckResult(
|
||||
success=False,
|
||||
message=f"SNMP error: {error_status.prettyPrint()}",
|
||||
response_time=None
|
||||
)
|
||||
else:
|
||||
values = {str(oid): str(val) for oid, val in var_binds}
|
||||
return CheckResult(
|
||||
success=True,
|
||||
message="SNMP response OK",
|
||||
response_time=response_time,
|
||||
details=values
|
||||
)
|
||||
except Exception as e:
|
||||
return CheckResult(
|
||||
success=False,
|
||||
message=f"SNMP error: {e}",
|
||||
response_time=None
|
||||
)
|
||||
Reference in New Issue
Block a user