88from custom_components .solisconnect .const import CONN_TYPE_TCP , DOMAIN
99
1010
11+ def _encode_serial_registers (serial : str ) -> list [int ]:
12+ """Inverse of helpers.extract_serial_number: pack an ASCII serial into 16-bit words."""
13+ padded = serial if len (serial ) % 2 == 0 else serial + "\x00 "
14+ return [(ord (padded [i ]) << 8 ) | ord (padded [i + 1 ]) for i in range (0 , len (padded ), 2 )]
15+
16+
1117@pytest .fixture (autouse = True )
1218def auto_enable_custom_integrations (enable_custom_integrations ):
1319 yield
@@ -81,6 +87,8 @@ async def test_flow_user_hmi_version_overrides_has_v2(hass: HomeAssistant):
8187 async def fake_read_input_register (register , count ):
8288 if register == 33002 :
8389 return [0x4AFF ] # below the V2 threshold
90+ if register == 33004 :
91+ return None # no serial data available; must not corrupt the typed serial
8492 return [1 ]
8593
8694 with (
@@ -113,6 +121,7 @@ async def fake_read_input_register(register, count):
113121
114122 assert result ["type" ] == data_entry_flow .FlowResultType .CREATE_ENTRY
115123 assert result ["data" ]["has_v2" ] is False
124+ assert result ["data" ]["inverter_serial" ] == "SN124" # no serial detected; typed value survives
116125
117126
118127@pytest .mark .asyncio
@@ -122,6 +131,8 @@ async def test_flow_user_hmi_read_failure_keeps_user_choice(hass: HomeAssistant)
122131 async def fake_read_input_register (register , count ):
123132 if register == 33002 :
124133 raise TimeoutError ("no response" )
134+ if register == 33004 :
135+ return None
125136 return [1 ]
126137
127138 with (
@@ -154,6 +165,92 @@ async def fake_read_input_register(register, count):
154165
155166 assert result ["type" ] == data_entry_flow .FlowResultType .CREATE_ENTRY
156167 assert result ["data" ]["has_v2" ] is False
168+ assert result ["data" ]["inverter_serial" ] == "SN125"
169+
170+
171+ @pytest .mark .asyncio
172+ async def test_flow_user_device_serial_overrides_typed_serial (hass : HomeAssistant ):
173+ """A serial read from device registers 33004-33019 overrides a mistyped form value."""
174+ device_serial_registers = _encode_serial_registers ("1234567890123456" )
175+
176+ async def fake_read_input_register (register , count ):
177+ if register == 33004 :
178+ return device_serial_registers
179+ return [1 ]
180+
181+ with (
182+ patch ("custom_components.solisconnect.modbus_controller.ModbusController.connect" , return_value = True ),
183+ patch (
184+ "custom_components.solisconnect.modbus_controller.ModbusController.async_read_input_register" ,
185+ side_effect = fake_read_input_register ,
186+ ),
187+ patch ("custom_components.solisconnect.async_setup_entry" , return_value = True ),
188+ ):
189+ result = await hass .config_entries .flow .async_init (DOMAIN , context = {"source" : config_entries .SOURCE_USER })
190+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ], user_input = {"connection_type" : CONN_TYPE_TCP })
191+
192+ config_input = {
193+ "host" : "1.2.3.4" ,
194+ "port" : 502 ,
195+ "slave" : 1 ,
196+ "model" : "S6-EH1P" ,
197+ "connection" : "S2_WL_ST" ,
198+ "has_v2" : True ,
199+ "has_pv" : True ,
200+ "has_ac_coupling" : False ,
201+ "has_parallel" : False ,
202+ "has_battery" : True ,
203+ "has_hv_battery" : False ,
204+ "has_generator" : False ,
205+ "inverter_serial" : "wrongserial" ,
206+ }
207+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ], user_input = config_input )
208+
209+ assert result ["type" ] == data_entry_flow .FlowResultType .CREATE_ENTRY
210+ assert result ["data" ]["inverter_serial" ] == "1234567890123456"
211+
212+
213+ @pytest .mark .asyncio
214+ async def test_flow_user_grid_model_skips_serial_detection (hass : HomeAssistant ):
215+ """Grid/string inverters don't expose the serial-number register range; detection must be skipped."""
216+ registers_read = []
217+
218+ async def fake_read_input_register (register , count ):
219+ registers_read .append (register )
220+ return [1 ]
221+
222+ with (
223+ patch ("custom_components.solisconnect.modbus_controller.ModbusController.connect" , return_value = True ),
224+ patch (
225+ "custom_components.solisconnect.modbus_controller.ModbusController.async_read_input_register" ,
226+ side_effect = fake_read_input_register ,
227+ ),
228+ patch ("custom_components.solisconnect.async_setup_entry" , return_value = True ),
229+ ):
230+ result = await hass .config_entries .flow .async_init (DOMAIN , context = {"source" : config_entries .SOURCE_USER })
231+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ], user_input = {"connection_type" : CONN_TYPE_TCP })
232+
233+ config_input = {
234+ "host" : "1.2.3.4" ,
235+ "port" : 502 ,
236+ "slave" : 1 ,
237+ "model" : "S6-GR1P" ,
238+ "connection" : "S2_WL_ST" ,
239+ "has_v2" : True ,
240+ "has_pv" : True ,
241+ "has_ac_coupling" : False ,
242+ "has_parallel" : False ,
243+ "has_battery" : True ,
244+ "has_hv_battery" : False ,
245+ "has_generator" : False ,
246+ "inverter_serial" : "sn126" ,
247+ }
248+ result = await hass .config_entries .flow .async_configure (result ["flow_id" ], user_input = config_input )
249+
250+ assert result ["type" ] == data_entry_flow .FlowResultType .CREATE_ENTRY
251+ assert result ["data" ]["inverter_serial" ] == "SN126"
252+ assert 33004 not in registers_read # serial detection is hybrid-only; grid/string lacks this register range
253+ assert 33002 in registers_read # HMI-version detection is not gated by inverter type
157254
158255
159256@pytest .mark .asyncio
0 commit comments