Skip to content

Commit 7700737

Browse files
committed
Fix floating-point rounding errors in register encoding
Changed all single-register value encoding from int() to round() to prevent floating-point precision issues. Issue: Using int() for float division can lose precision: - 48.3V / 0.1 = 483.0 but float arithmetic gives 482.99999... - int(482.99999...) = 482 ❌ (loses 0.1V) - round(483.0) = 483 ✅ (correct) This fixes slight discrepancies between emulator display and what Home Assistant integration reads via Modbus. Changed in all single-register conversions: - PV voltages/currents - AC voltages/currents/frequency - Battery voltage/current/SOC/temperature - Temperatures (inverter, IPM, boost) - Grid/load power (single-register versions) - Backup voltage/current/power/frequency - Self-consumption percentage 32-bit register pairs unchanged (already use int() on pre-scaled values)
1 parent 8eac51e commit 7700737

1 file changed

Lines changed: 31 additions & 31 deletions

File tree

emulator/simulator.py

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -496,17 +496,17 @@ def _map_register_to_value(self, reg_name: str, reg_def: Dict[str, Any]) -> int:
496496

497497
# PV values
498498
elif reg_name == 'pv1_voltage':
499-
return int(self.values['voltages']['pv1'] / scale)
499+
return round(self.values['voltages']['pv1'] / scale)
500500
elif reg_name == 'pv2_voltage':
501-
return int(self.values['voltages']['pv2'] / scale)
501+
return round(self.values['voltages']['pv2'] / scale)
502502
elif reg_name == 'pv3_voltage' and self.model.has_pv3:
503-
return int(self.values['voltages']['pv3'] / scale)
503+
return round(self.values['voltages']['pv3'] / scale)
504504
elif reg_name == 'pv1_current':
505-
return int(self.values['currents']['pv1'] / scale)
505+
return round(self.values['currents']['pv1'] / scale)
506506
elif reg_name == 'pv2_current':
507-
return int(self.values['currents']['pv2'] / scale)
507+
return round(self.values['currents']['pv2'] / scale)
508508
elif reg_name == 'pv3_current' and self.model.has_pv3:
509-
return int(self.values['currents']['pv3'] / scale)
509+
return round(self.values['currents']['pv3'] / scale)
510510

511511
# PV power (32-bit pairs)
512512
elif 'pv1_power_high' in reg_name:
@@ -544,11 +544,11 @@ def _map_register_to_value(self, reg_name: str, reg_def: Dict[str, Any]) -> int:
544544

545545
# AC values
546546
elif reg_name == 'ac_voltage':
547-
return int(self.values['voltages']['ac'] / scale)
547+
return round(self.values['voltages']['ac'] / scale)
548548
elif reg_name == 'ac_current':
549-
return int(self.values['currents']['ac'] / scale)
549+
return round(self.values['currents']['ac'] / scale)
550550
elif reg_name == 'ac_frequency':
551-
return int(50.0 / scale) # 50 Hz
551+
return round(50.0 / scale) # 50 Hz
552552
elif 'ac_power_high' in reg_name:
553553
combined_scale = reg_def.get('combined_scale', 0.1)
554554
power_raw = int(self.values['ac_power'] / combined_scale)
@@ -561,14 +561,14 @@ def _map_register_to_value(self, reg_name: str, reg_def: Dict[str, Any]) -> int:
561561
# Three-phase AC
562562
elif reg_name in ['ac_voltage_r', 'ac_voltage_s', 'ac_voltage_t']:
563563
phase = reg_name.split('_')[-1]
564-
return int(self.values['voltages'][f'ac_{phase}'] / scale)
564+
return round(self.values['voltages'][f'ac_{phase}'] / scale)
565565
elif reg_name in ['ac_current_r', 'ac_current_s', 'ac_current_t']:
566566
phase = reg_name.split('_')[-1]
567-
return int(self.values['currents'][f'ac_{phase}'] / scale)
567+
return round(self.values['currents'][f'ac_{phase}'] / scale)
568568
elif reg_name in ['ac_power_r', 'ac_power_s', 'ac_power_t']:
569569
phase = reg_name.split('_')[-1]
570570
power = self.values['ac_power'] / 3 # Distribute across phases
571-
return int(power / scale)
571+
return round(power / scale)
572572
# Three-phase AC power (32-bit pairs)
573573
elif reg_name in ['ac_power_r_high', 'ac_power_s_high', 'ac_power_t_high']:
574574
combined_scale = reg_def.get('combined_scale', 0.1)
@@ -582,33 +582,33 @@ def _map_register_to_value(self, reg_name: str, reg_def: Dict[str, Any]) -> int:
582582
return power_raw & 0xFFFF
583583
elif reg_name in ['ac_voltage_rs', 'ac_voltage_st', 'ac_voltage_tr']:
584584
phases = reg_name.split('_')[-1]
585-
return int(self.values['voltages'][f'ac_{phases}'] / scale)
585+
return round(self.values['voltages'][f'ac_{phases}'] / scale)
586586

587587
# Battery
588588
elif reg_name == 'battery_voltage' and self.model.has_battery:
589-
return int(self.values['voltages']['battery'] / scale)
589+
return round(self.values['voltages']['battery'] / scale)
590590
elif reg_name == 'battery_current' and self.model.has_battery:
591591
current = self.values['currents']['battery']
592592
if is_signed:
593-
return self._to_signed_16bit(int(current / scale))
594-
return int(abs(current) / scale)
593+
return self._to_signed_16bit(round(current / scale))
594+
return round(abs(current) / scale)
595595
elif reg_name == 'battery_power' and self.model.has_battery:
596596
power = self.values['battery_power']
597597
if is_signed:
598-
return self._to_signed_16bit(int(power / scale))
599-
return int(abs(power) / scale)
598+
return self._to_signed_16bit(round(power / scale))
599+
return round(abs(power) / scale)
600600
elif reg_name == 'battery_soc' and self.model.has_battery:
601-
return int(self.battery_soc)
601+
return round(self.battery_soc)
602602
elif reg_name == 'battery_temp' and self.model.has_battery:
603-
return int(30.0 / scale) # Fixed battery temp
603+
return round(30.0 / scale) # Fixed battery temp
604604

605605
# Temperatures
606606
elif reg_name == 'inverter_temp':
607-
return int(self.values['temperatures']['inverter'] / scale)
607+
return round(self.values['temperatures']['inverter'] / scale)
608608
elif reg_name == 'ipm_temp':
609-
return int(self.values['temperatures']['ipm'] / scale)
609+
return round(self.values['temperatures']['ipm'] / scale)
610610
elif reg_name == 'boost_temp':
611-
return int(self.values['temperatures']['boost'] / scale)
611+
return round(self.values['temperatures']['boost'] / scale)
612612

613613
# Energy (32-bit pairs)
614614
elif 'energy_today_high' in reg_name:
@@ -644,8 +644,8 @@ def _map_register_to_value(self, reg_name: str, reg_def: Dict[str, Any]) -> int:
644644
# Single register
645645
power = self.values['grid_power']['grid']
646646
if is_signed:
647-
return self._to_signed_16bit(int(power / scale))
648-
return int(abs(power) / scale)
647+
return self._to_signed_16bit(round(power / scale))
648+
return round(abs(power) / scale)
649649

650650
elif 'load_power' in reg_name or 'power_to_load' in reg_name:
651651
# Handle 32-bit pairs for power_to_load
@@ -659,7 +659,7 @@ def _map_register_to_value(self, reg_name: str, reg_def: Dict[str, Any]) -> int:
659659
return power_raw & 0xFFFF
660660
else:
661661
# Single register
662-
return int(self.house_load / scale)
662+
return round(self.house_load / scale)
663663

664664
# Battery charge/discharge power (SPH TL3 specific)
665665
elif reg_name == 'discharge_power_high' and self.model.has_battery:
@@ -712,7 +712,7 @@ def _map_register_to_value(self, reg_name: str, reg_def: Dict[str, Any]) -> int:
712712
if self.house_load > 0:
713713
self_consumption = self.house_load - self.values['grid_power']['import']
714714
percentage = (max(0, self_consumption) / self.house_load) * 100
715-
return int(min(100, percentage))
715+
return round(min(100, percentage))
716716
return 0
717717

718718
# Energy to user/grid (SPH TL3 specific)
@@ -829,13 +829,13 @@ def _map_register_to_value(self, reg_name: str, reg_def: Dict[str, Any]) -> int:
829829

830830
# Backup output
831831
elif reg_name == 'backup_voltage':
832-
return int(self.values['voltages'].get('backup', 240.0) / scale)
832+
return round(self.values['voltages'].get('backup', 240.0) / scale)
833833
elif reg_name == 'backup_current':
834-
return int(self.values['currents'].get('backup', 0) / scale)
834+
return round(self.values['currents'].get('backup', 0) / scale)
835835
elif reg_name == 'backup_power':
836-
return int(self.house_load / scale)
836+
return round(self.house_load / scale)
837837
elif reg_name == 'backup_frequency':
838-
return int(50.0 / scale)
838+
return round(50.0 / scale)
839839

840840
# Default
841841
return 0

0 commit comments

Comments
 (0)