@@ -95,3 +95,81 @@ def get_interface_index(self) -> str:
9595 f"(Get-NetAdapter '{ self ._interface ().name } ').InterfaceIndex" , expected_return_codes = {0 }
9696 )
9797 return result .stdout .strip ()
98+
99+ def get_phy_info (self , adapter_interface_description : str = None ) -> Dict :
100+ """
101+ Get PHY information and check auto-negotiation bits using WMI.
102+
103+ :param adapter_interface_description: Optional specific adapter description to match
104+ :return: Dictionary containing PHY information and auto-negotiation status
105+ :raises: UtilsException if PHY info command execution failed
106+ """
107+ cmd = (
108+ 'gwmi -namespace "root\\ WMI" IntlLan_GetPhyInfo -property InstanceName,PhyInfo | '
109+ 'Format-Table InstanceName, @{n="PhyInfo";e={($_ | select -expand PhyInfo) -join ","}} -auto'
110+ )
111+ result = self ._connection .execute_powershell (cmd , custom_exception = UtilsException )
112+ phy_data = {}
113+ phy_info_found = auto_neg_bits_detected = False
114+
115+ # Only process if we have output
116+ if result .stdout :
117+ lines = [
118+ line .strip ()
119+ for line in result .stdout .splitlines ()
120+ if line .strip () and not any (x in line for x in ['InstanceName' , 'PhyInfo' , '----' ])
121+ ]
122+
123+ # Normalize adapter name (strip "for ..." and trailing #N)
124+ base_name = None
125+ if adapter_interface_description :
126+ base_name = adapter_interface_description .split (' for ' )[0 ].strip ()
127+ base_name = re .sub (r'\s+#\d+$' , '' , base_name ).strip ().lower ()
128+
129+ matching_line = None
130+ if base_name :
131+ for line in lines :
132+ # Extract full InstanceName part (everything before two or more spaces)
133+ instance_match = re .match (r'^(.*?)\s{2,}' , line )
134+ if not instance_match :
135+ continue
136+ instance_name = instance_match .group (1 ).strip ()
137+ normalized_name = re .sub (r'\s+#\d+$' , '' , instance_name , flags = re .IGNORECASE ).strip ().lower ()
138+
139+ # Match exact normalized name
140+ if normalized_name == base_name :
141+ matching_line = line
142+ break
143+
144+ # Fallback if no exact match, use first line
145+ if not matching_line and lines :
146+ matching_line = lines [0 ]
147+
148+ if matching_line and ',' in matching_line :
149+ # Extract the comma-separated PHY values (rightmost part)
150+ phy_info_str = matching_line .split (None , 1 )[- 1 ]
151+ if ',' in phy_info_str :
152+ phy_values = phy_info_str .split (',' )
153+ phy_data = {'raw_values' : phy_values }
154+
155+ # Extract third value (auto-negotiation bits live here)
156+ if len (phy_values ) >= 3 :
157+ try :
158+ third_value = int (phy_values [2 ].strip ())
159+ auto_neg_bits = {f'bit_{ bit } ' : bool (third_value & (1 << bit )) for bit in [0 , 1 ]}
160+ phy_data .update ({
161+ 'third_value_decimal' : third_value ,
162+ 'third_value_binary' : bin (third_value )[2 :],
163+ 'auto_neg_bits' : auto_neg_bits
164+ })
165+ phy_info_found = True
166+ auto_neg_bits_detected = any (auto_neg_bits .values ())
167+ except ValueError as e :
168+ phy_data ['parse_error' ] = str (e )
169+
170+ return {
171+ 'phy_info_found' : phy_info_found ,
172+ 'auto_neg_bits_detected' : auto_neg_bits_detected ,
173+ 'phy_data' : phy_data ,
174+ 'raw_output' : result .stdout or ''
175+ }
0 commit comments