44
55import logging
66import re
7+ import time
78from typing import Any
89
910from pysnmp .hlapi .v3arch .asyncio import (
2526from pysnmp .proto .rfc1902 import OctetString
2627
2728from .const import (
29+ DEFAULT_ERROR_LOG_INTERVAL ,
2830 DEVICE_STATUS ,
31+ OID_COVER_DESCRIPTION ,
2932 OID_COVER_STATUS ,
33+ OID_DEVICE_DESCRIPTION ,
3034 OID_DEVICE_ERRORS ,
3135 OID_DEVICE_STATE ,
3236 OID_DISPLAY_BUFFER ,
4145 OID_MARKER_SUPPLIES_TYPE ,
4246 OID_MEMORY_SIZE ,
4347 OID_PAGE_COUNT ,
48+ OID_PRINTER_ERRORS ,
49+ OID_PRINTER_STATUS ,
4450 OID_SERIAL_NUMBER ,
4551 OID_SYSTEM_CONTACT ,
4652 OID_SYSTEM_DESCRIPTION ,
@@ -93,12 +99,73 @@ def __init__(
9399 self ._transport = None # Will be created async
94100 self ._auth_data = self ._get_auth_data ()
95101
102+ # Connection state tracking for better error logging
103+ self ._connection_state = "unknown" # unknown, online, offline
104+ self ._last_error_log_time = 0
105+ self ._consecutive_failures = 0
106+
96107 def _create_engine (self ):
97108 """Create SNMP engine (blocking operation)."""
98109 if self ._engine is None :
99110 self ._engine = SnmpEngine ()
100111 return self ._engine
101112
113+ def _handle_snmp_error (self , error_message : str ) -> None :
114+ """Handle SNMP errors with intelligent logging to reduce spam."""
115+ current_time = time .time ()
116+ self ._consecutive_failures += 1
117+
118+ # Determine if we should log this error
119+ should_log_error = False
120+ log_level = logging .ERROR
121+
122+ if self ._connection_state == "unknown" or self ._connection_state == "online" :
123+ # First error or was previously online - log as error
124+ should_log_error = True
125+ self ._connection_state = "offline"
126+ self ._last_error_log_time = current_time
127+ elif self ._connection_state == "offline" :
128+ # Already offline - check if enough time has passed for another log
129+ time_since_last_log = current_time - self ._last_error_log_time
130+ if time_since_last_log >= DEFAULT_ERROR_LOG_INTERVAL :
131+ should_log_error = True
132+ log_level = logging .WARNING # Reduce severity for ongoing issues
133+ self ._last_error_log_time = current_time
134+
135+ if should_log_error :
136+ if self ._consecutive_failures == 1 :
137+ _LOGGER .error (
138+ "Printer %s: %s (switching to cached data if available)" ,
139+ self .host ,
140+ error_message ,
141+ )
142+ else :
143+ _LOGGER .warning (
144+ "Printer %s still offline: %s (attempt %d, using cached data)" ,
145+ self .host ,
146+ error_message ,
147+ self ._consecutive_failures ,
148+ )
149+ else :
150+ # Still log at debug level for troubleshooting
151+ _LOGGER .debug (
152+ "Printer %s offline: %s (suppressed, attempt %d)" ,
153+ self .host ,
154+ error_message ,
155+ self ._consecutive_failures ,
156+ )
157+
158+ def _mark_connection_success (self ) -> None :
159+ """Mark a successful connection to reset error tracking."""
160+ if self ._connection_state == "offline" :
161+ _LOGGER .info (
162+ "Printer %s is back online after %d failed attempts" ,
163+ self .host ,
164+ self ._consecutive_failures ,
165+ )
166+ self ._connection_state = "online"
167+ self ._consecutive_failures = 0
168+
102169 async def _ensure_transport (self ):
103170 """Ensure transport and engine are created (async operation)."""
104171 if self ._engine is None :
@@ -160,17 +227,16 @@ async def _get_oid(self, oid: str) -> Any:
160227 )
161228
162229 if errorIndication :
163- _LOGGER . debug ( "SNMP error: %s" , errorIndication )
230+ self . _handle_snmp_error ( f "SNMP error: { errorIndication } " )
164231 return None
165232 elif errorStatus :
166- _LOGGER .debug (
167- "SNMP error: %s at %s" ,
168- errorStatus .prettyPrint (),
169- errorIndex and varBinds [int (errorIndex ) - 1 ][0 ] or "?" ,
233+ self ._handle_snmp_error (
234+ f"SNMP error: { errorStatus .prettyPrint ()} at { errorIndex and varBinds [int (errorIndex ) - 1 ][0 ] or '?' } "
170235 )
171236 return None
172237
173238 for varBind in varBinds :
239+ self ._mark_connection_success ()
174240 return varBind [1 ].prettyPrint ()
175241
176242 return None
@@ -191,16 +257,18 @@ async def _walk_oid(self, oid: str) -> dict[str, str]:
191257 lexicographicMode = False ,
192258 ):
193259 if errorIndication :
194- _LOGGER . error ( "SNMP walk error: %s" , errorIndication )
260+ self . _handle_snmp_error ( f "SNMP walk error: { errorIndication } " )
195261 break
196262 elif errorStatus :
197- _LOGGER .error (
198- "SNMP walk error: %s at %s" ,
199- errorStatus .prettyPrint (),
200- errorIndex and varBinds [int (errorIndex ) - 1 ][0 ] or "?" ,
263+ self ._handle_snmp_error (
264+ f"SNMP walk error: { errorStatus .prettyPrint ()} at { errorIndex and varBinds [int (errorIndex ) - 1 ][0 ] or '?' } "
201265 )
202266 break
203267 else :
268+ # Mark success if we get any data
269+ if not results and varBinds :
270+ self ._mark_connection_success ()
271+
204272 for varBind in varBinds :
205273 # Extract the index from the OID (last part after the base OID)
206274 full_oid = str (varBind [0 ])
0 commit comments