@@ -103,43 +103,82 @@ async def export_register_dump(call: ServiceCall) -> None:
103103 )
104104
105105
106- def _read_registers_chunked (client , start : int , count : int , slave_id : int , chunk_size : int = 50 ) -> Dict [int , int ]:
106+ def _read_registers_chunked (client , start : int , count : int , slave_id : int , chunk_size : int = 50 ) -> Dict [int , Dict [ str , Any ] ]:
107107 """
108108 Read registers in chunks to avoid timeouts.
109-
110- Returns dict mapping register address to value (only non-zero values).
109+
110+ Returns dict mapping register address to: {
111+ 'value': int or None,
112+ 'status': 'success'|'error'|'exception',
113+ 'error': str (error description if status != 'success')
114+ }
111115 """
112116 register_data = {}
113-
117+
114118 for chunk_start in range (0 , count , chunk_size ):
115119 chunk_count = min (chunk_size , count - chunk_start )
116120 chunk_address = start + chunk_start
117-
121+
118122 try :
119123 response = client .read_input_registers (
120124 address = chunk_address ,
121125 count = chunk_count ,
122126 device_id = slave_id
123127 )
124-
128+
125129 if not response .isError ():
130+ # Store ALL values, including zeros
126131 for i , value in enumerate (response .registers ):
127- if value > 0 : # Only store non-zero values
128- register_data [chunk_address + i ] = value
129- _LOGGER .debug (f"Read chunk { chunk_address } -{ chunk_address + chunk_count - 1 } : { len ([v for v in response .registers if v > 0 ])} non-zero" )
132+ register_data [chunk_address + i ] = {
133+ 'value' : value ,
134+ 'status' : 'success' ,
135+ 'error' : None
136+ }
137+ _LOGGER .debug (f"Read chunk { chunk_address } -{ chunk_address + chunk_count - 1 } : { chunk_count } registers" )
130138 else :
131- _LOGGER .debug (f"Chunk { chunk_address } -{ chunk_address + chunk_count - 1 } returned error" )
132-
139+ # Store error for each register in the chunk
140+ error_msg = str (response )
141+ # Try to extract specific error type
142+ if hasattr (response , 'exception_code' ):
143+ error_code = response .exception_code
144+ error_names = {
145+ 1 : "Illegal Function" ,
146+ 2 : "Illegal Data Address" ,
147+ 3 : "Illegal Data Value" ,
148+ 4 : "Slave Device Failure" ,
149+ 5 : "Acknowledge" ,
150+ 6 : "Slave Device Busy" ,
151+ 10 : "Gateway Path Unavailable" ,
152+ 11 : "Gateway Target Failed to Respond"
153+ }
154+ error_msg = error_names .get (error_code , f"Error Code { error_code } " )
155+
156+ for i in range (chunk_count ):
157+ register_data [chunk_address + i ] = {
158+ 'value' : None ,
159+ 'status' : 'error' ,
160+ 'error' : error_msg
161+ }
162+ _LOGGER .debug (f"Chunk { chunk_address } -{ chunk_address + chunk_count - 1 } returned error: { error_msg } " )
163+
133164 except Exception as e :
165+ # Store exception for each register in the chunk
166+ error_msg = f"Exception: { type (e ).__name__ } : { str (e )} "
167+ for i in range (chunk_count ):
168+ register_data [chunk_address + i ] = {
169+ 'value' : None ,
170+ 'status' : 'exception' ,
171+ 'error' : error_msg
172+ }
134173 _LOGGER .debug (f"Chunk { chunk_address } exception: { e } " )
135-
174+
136175 return register_data
137176
138177
139- def _detect_inverter_model (register_data : Dict [int , int ]) -> Dict [str , str ]:
178+ def _detect_inverter_model (register_data : Dict [int , Dict [ str , Any ] ]) -> Dict [str , str ]:
140179 """
141180 Analyze register responses to detect inverter model.
142-
181+
143182 Returns dict with: model, confidence, profile_key, register_map, reasoning
144183 """
145184 detection = {
@@ -149,16 +188,16 @@ def _detect_inverter_model(register_data: Dict[int, int]) -> Dict[str, str]:
149188 "register_map" : "UNKNOWN" ,
150189 "reasoning" : [],
151190 }
152-
153- # Helper to check if register exists
191+
192+ # Helper to check if register exists with valid data
154193 def has_reg (addr ):
155- return addr in register_data
194+ return addr in register_data and register_data [ addr ][ 'status' ] == 'success' and register_data [ addr ][ 'value' ] is not None and register_data [ addr ][ 'value' ] > 0
156195
157- # Check register ranges
158- has_0_124 = any (0 <= r <= 124 for r in register_data .keys ())
159- has_1000_1124 = any (1000 <= r <= 1124 for r in register_data .keys ())
160- has_3000_3124 = any (3000 <= r <= 3124 for r in register_data .keys ())
161- has_3125_3249 = any (3125 <= r <= 3249 for r in register_data .keys ())
196+ # Check register ranges (only successful reads with non-zero values)
197+ has_0_124 = any (0 <= r <= 124 and register_data [ r ][ 'status' ] == 'success' and register_data [ r ][ 'value' ] > 0 for r in register_data .keys ())
198+ has_1000_1124 = any (1000 <= r <= 1124 and register_data [ r ][ 'status' ] == 'success' and register_data [ r ][ 'value' ] > 0 for r in register_data .keys ())
199+ has_3000_3124 = any (3000 <= r <= 3124 and register_data [ r ][ 'status' ] == 'success' and register_data [ r ][ 'value' ] > 0 for r in register_data .keys ())
200+ has_3125_3249 = any (3125 <= r <= 3249 and register_data [ r ][ 'status' ] == 'success' and register_data [ r ][ 'value' ] > 0 for r in register_data .keys ())
162201
163202 # Key register checks
164203 has_pv1_at_3 = has_reg (3 ) # PV1 voltage in 0-124 range
@@ -301,20 +340,22 @@ def _export_registers_to_csv(hass, host: str, port: int, slave_id: int) -> dict:
301340 # Scan ALL ranges
302341 all_register_data = {}
303342 range_responses = {}
304-
343+
305344 for range_config in UNIVERSAL_SCAN_RANGES :
306345 range_name = range_config ["name" ]
307346 start = range_config ["start" ]
308347 count = range_config ["count" ]
309-
348+
310349 _LOGGER .info (f"Scanning { range_name } ..." )
311-
350+
312351 registers = _read_registers_chunked (client , start , count , slave_id , chunk_size = 50 )
313-
352+
314353 if registers :
315354 all_register_data .update (registers )
316- range_responses [range_name ] = len (registers )
317- _LOGGER .info (f"{ range_name } : { len (registers )} non-zero registers" )
355+ # Count successful non-zero reads for range summary
356+ successful_count = sum (1 for r in registers .values () if r ['status' ] == 'success' and r ['value' ] > 0 )
357+ range_responses [range_name ] = successful_count
358+ _LOGGER .info (f"{ range_name } : { successful_count } non-zero registers out of { len (registers )} attempted" )
318359 else :
319360 range_responses [range_name ] = 0
320361 _LOGGER .info (f"{ range_name } : No response" )
@@ -369,51 +410,82 @@ def _export_registers_to_csv(hass, host: str, port: int, slave_id: int) -> dict:
369410 "×0.1" ,
370411 "×0.01" ,
371412 "Signed" ,
372- "32-bit Combined (with next reg)"
413+ "32-bit Combined (with next reg)" ,
414+ "Status/Comment"
373415 ])
374416
375417 # Write all registers sorted by address
376418 total = 0
377- non_zero = len ( all_register_data )
378-
419+ non_zero = 0
420+
379421 # Group by ranges for organized output
380422 for range_config in UNIVERSAL_SCAN_RANGES :
381423 range_name = range_config ["name" ]
382424 start = range_config ["start" ]
383425 end = start + range_config ["count" ]
384-
426+
427+ # Get all registers in this range
385428 range_registers = {k : v for k , v in all_register_data .items () if start <= k < end }
386-
429+
387430 if range_registers :
388431 writer .writerow ([])
389432 writer .writerow ([f"--- { range_name } ---" ])
390-
391- for reg_addr in sorted (range_registers .keys ()):
392- value = range_registers [reg_addr ]
393- total += 1
394-
395- # Calculate interpretations
396- scaled_01 = value * 0.1
397- scaled_001 = value * 0.01
398- signed = value - 65536 if value > 32767 else value
399-
400- # Try to combine with next register for 32-bit values
401- combined_32bit = ""
402- if reg_addr + 1 in all_register_data :
403- next_val = all_register_data [reg_addr + 1 ]
404- combined = (value << 16 ) | next_val
405- if 0 < combined < 10000000 :
406- combined_32bit = f"{ combined } (×0.1={ combined * 0.1 :.1f} )"
407-
408- writer .writerow ([
409- reg_addr ,
410- f"0x{ reg_addr :04X} " ,
411- value ,
412- f"{ scaled_01 :.1f} " ,
413- f"{ scaled_001 :.2f} " ,
414- signed ,
415- combined_32bit
416- ])
433+
434+ # Write ALL registers in sequential order
435+ for reg_addr in range (start , end ):
436+ if reg_addr in range_registers :
437+ reg_info = range_registers [reg_addr ]
438+ value = reg_info ['value' ]
439+ status = reg_info ['status' ]
440+ error = reg_info ['error' ]
441+
442+ total += 1
443+
444+ # Build status/comment field
445+ if status == 'success' :
446+ if value == 0 :
447+ status_comment = "Read OK (zero value)"
448+ else :
449+ status_comment = "Read OK"
450+ non_zero += 1
451+ elif status == 'error' :
452+ status_comment = f"Modbus Error: { error } "
453+ value = "" # Clear value field for errors
454+ else : # exception
455+ status_comment = error
456+ value = "" # Clear value field for exceptions
457+
458+ # Calculate interpretations only for successful reads
459+ if status == 'success' and value is not None :
460+ scaled_01 = value * 0.1
461+ scaled_001 = value * 0.01
462+ signed = value - 65536 if value > 32767 else value
463+
464+ # Try to combine with next register for 32-bit values
465+ combined_32bit = ""
466+ if reg_addr + 1 in all_register_data :
467+ next_info = all_register_data [reg_addr + 1 ]
468+ if next_info ['status' ] == 'success' and next_info ['value' ] is not None :
469+ next_val = next_info ['value' ]
470+ combined = (value << 16 ) | next_val
471+ if 0 < combined < 10000000 :
472+ combined_32bit = f"{ combined } (×0.1={ combined * 0.1 :.1f} )"
473+ else :
474+ scaled_01 = ""
475+ scaled_001 = ""
476+ signed = ""
477+ combined_32bit = ""
478+
479+ writer .writerow ([
480+ reg_addr ,
481+ f"0x{ reg_addr :04X} " ,
482+ value ,
483+ f"{ scaled_01 :.1f} " if scaled_01 != "" else "" ,
484+ f"{ scaled_001 :.2f} " if scaled_001 != "" else "" ,
485+ signed ,
486+ combined_32bit ,
487+ status_comment
488+ ])
417489
418490 result ["success" ] = True
419491 result ["filename" ] = filename
0 commit comments