Claude/growatt inverter emulator 01 co sswio n uh f3 kh2j hu edzg - #26
Merged
0xAHA merged 25 commits intoNov 15, 2025
Merged
Conversation
Implements a comprehensive emulator for testing Growatt integrations without physical hardware. Features: - Supports all 10 inverter models (MIC, MIN, TL-XH, MID, SPH, SPH-TL3, MOD series) - Realistic simulation of solar generation with day/night cycles - Battery charging/discharging simulation for hybrid models - Grid import/export calculations - Live terminal UI with real-time updates using rich library - Interactive controls for adjusting parameters: * Solar irradiance (0-1000 W/m²) * Cloud cover (0-100%) * House load consumption * Time acceleration (0.1-100x speed) * Battery charge/discharge override Components: - emulator/models.py: Inverter model definitions using existing register maps - emulator/simulator.py: Core simulation engine with realistic calculations * PV generation based on time, irradiance, and cloud cover * Battery state management with SOC tracking * Voltage/current/power calculations for all phases * Temperature simulation based on load * Energy totals with daily reset at midnight - emulator/modbus_server.py: Modbus TCP server (pymodbus 3.x) - emulator/display.py: Rich-based terminal UI with live dashboard - emulator/controls.py: Keyboard input handler with cross-platform support - growatt_emulator.py: Main script with model selection menu Technical Details: - Serves all input and holding registers per model's register map - Properly handles 32-bit register pairs for power/energy values - Supports signed registers for battery current/power - Calculates derived values (current from power/voltage) - Updates values every 2 seconds - Uses dynamic data blocks for real-time value generation Usage: python3 growatt_emulator.py # Interactive selection python3 growatt_emulator.py --list-models # Show available models python3 growatt_emulator.py --model sph_3000_6000 --port 5020 Dependencies: pymodbus>=3.0.0, rich>=13.0.0 This enables comprehensive testing of the Growatt integration across all inverter models without requiring physical hardware.
- Detect platform (Windows vs Unix) before importing modules - Use msvcrt on Windows (built-in, no installation needed) - Use termios/tty/select on Unix/Linux/macOS - Separate input loop implementations for each platform - Graceful fallback if keyboard input unavailable Fixes issue where termios import would fail on Windows since it's a Unix-only module not available on Windows.
Import INVERTER_PROFILES from emulator.models instead of directly from custom_components package. The models.py already uses importlib to load the module without triggering Home Assistant imports. This allows the emulator to run on any PC without requiring Home Assistant to be installed.
Use hasattr() to check if os.geteuid() exists before calling it. On Windows, this function doesn't exist and would cause AttributeError. - Unix/Linux: Check for root privileges if port < 1024 - Windows: Show informational message only Fixes AttributeError on Windows when starting emulator.
pv_power is a dict with keys 'pv1', 'pv2', 'pv3', 'total', but was being added directly to battery_power (float). Changed to use pv_power['total'] for AC power calculation. Fixes: TypeError: unsupported operand type(s) for +: 'dict' and 'float'
The zero_mode parameter doesn't exist in pymodbus 3.x. Addressing is already handled by the data blocks themselves. Fixes: TypeError: ModbusDeviceContext.__init__() got an unexpected keyword argument 'zero_mode'
Changed 'slaves' parameter to 'devices' to match pymodbus 3.x API. Fixes: TypeError: ModbusServerContext.__init__() got an unexpected keyword argument 'slaves'
Changes to reduce vertical space and blank rows: - Reduce header from 3 lines to 2, put everything on one line - Reduce controls from 5 lines to 2, single-line layout - Remove table padding (0,0) and collapse_padding=True - Reduce column widths throughout - Add panel padding (0,1) to all panels for consistency - Shorten panel titles (PV, AC, Grid, Temp, etc.) - Remove empty row from grid panel - Compact battery panel (shorter SOC bar, combined Ch/Disch) - Shorten energy metrics labels and remove 'kWh' suffix (in title) - Reduce decimal places in energy/temp displays Display now fits much better in smaller terminal windows while still showing all essential information.
- Fix table object repr showing in PV panel (use Group instead of str()) - Fix temperature panel padding to match other panels - Reduce refresh rate from 2Hz to 1Hz to minimize flickering - Increase update interval from 0.5s to 1.0s - Make temperature column widths more compact The PV panel was showing '<rich.table.table object at 0x...>' because str(table) was being used instead of rendering the table properly with rich.console.Group.
Display improvements: - Reduce header/controls from 2 to 1 line each - Remove all panel padding (0,0 instead of 0,1) - Compact irradiance text (remove spaces) - Reduce refresh rate from 1Hz to 0.5Hz - Increase update interval from 1s to 5s Keyboard input fixes: - Pass display reference to ControlHandler - Pause live display when prompting for input - Resume display after input completes - Increase success message delay to 2s for visibility - Add pause/resume methods to EmulatorDisplay This fixes the issue where keyboard input values weren't being applied because the display kept refreshing and overwriting the input prompts. Now the display pauses during input and resumes after, allowing values to be changed properly.
- Remove unused generate_battery_panel() and generate_temperature_panel() methods - Reduce column widths in all panels (PV, AC, Grid, Energy) - Shorten column headers and labels (e.g., "Voltage" → "V", "Current" → "A") - Reduce panel width requirements to fit better in standard terminal windows
Major UI overhaul: - Replace Rich panels with ASCII-based layout using simple characters (=, -) - Show modbus register addresses next to each value - Display entity names from inverter profiles - Load register maps directly from profile definitions - Handle single-phase and 3-phase layouts appropriately - Maintain color scheme for readability - Add uptime tracking - Show comprehensive battery information with SOC bar - Display solar irradiance and cloud cover - Similar look and feel to Linux 'top' command Benefits: - More informative: shows register addresses for debugging/development - Better for terminal: no fancy borders that may misalign - Entity names match Home Assistant integration exactly - Easier to correlate emulator values with actual Modbus registers
Display improvements: - Normalize column alignment across all sections (30 chars for entity names) - Fix register width formatting for consistency ([1234-5678] format) - Handle multiple register naming conventions (ac_voltage_r vs grid_voltage_r) - Map SPH TL3 and other model registers correctly - Show actual register addresses for grid export/import and load energy Control improvements: - Remove pause/resume - display now continues updating during input - Use console.print overlay for prompts instead of clearing screen - Simplify input prompts - single line with current value - Battery control menu more compact The display now stays live while you enter values, and registers are properly mapped for all inverter models including SPH TL3.
Control improvements: - Restore pause/resume for input - display now properly stops during user input - All prompts use simple print() with clear current values - Display resumes automatically after input complete Refresh rate: - Update display every 1 second instead of 5 seconds - Uptime now updates in real-time every second - More responsive to value changes These changes ensure input prompts are visible and the display feels more responsive with live uptime tracking.
The simulator was missing mappings for SPH TL3's extended registers in the 1000+ range: Battery charge/discharge power: - discharge_power (1009-1010): Shows discharge power when battery discharging - charge_power (1011-1012): Shows charge power when battery charging Power flow: - power_to_user (1015-1016): PV power going to user (PV - battery charge) - power_to_load (1021-1022): Power consumed by loads (32-bit version) - power_to_grid (1029-1030): Power exported to grid (32-bit version) - self_consumption_power (1037-1038): Self-consumed power (load - grid import) - self_consumption_percentage (1039): % of load from own generation Energy totals: - energy_to_user_today/total (1044-1047) - energy_to_grid_today/total (1048-1051) - discharge_energy_today/total (1052-1055) - charge_energy_today/total (1056-1059) - load_energy_today/total (1060-1063) Other: - system_work_mode (1000): Returns 1 for normal operation - battery_type (1041): Returns 1 for Li-ion This fixes Home Assistant integration showing wrong/missing values for SPH TL3 models.
- Added missing handlers for three-phase AC power registers (ac_power_r_high/low, etc.) - Added async_getValues override for pymodbus 3.x compatibility - Implemented register storage update approach - Added comprehensive debug logging - Identified pymodbus 3.x addressing quirk (off-by-one issue being investigated) Currently investigating off-by-one offset in register addressing that causes values to be shifted. The emulator correctly calculates values but they appear at wrong register addresses when read via Modbus TCP.
Findings: - Added three-phase AC power register handlers (ac_power_r_high/low, etc.) - Implemented both sync and async getValues methods - Discovered that pymodbus 3.x doesn't call overridden getValues/async_getValues - Data appears to come from internal storage with addressing offset - Test shows registers shifted by +1 (register N returns value for N+1) The simulator correctly calculates all values, but Mod bus serving has compatibility issue with pymodbus 3.x that needs further investigation into the library's internals.
BREAKING CHANGE: Complete rewrite of Modbus data block implementation Changes: - Replaced ModbusSequentialDataBlock with ModbusSparseDataBlock base - Implemented GrowattDataBlock with proper pymodbus 3.x API - Fixed addressing offset: pymodbus 3.x adds +1 to addresses internally - Values now fetched directly from simulator on each read (no storage) - Removed update_from_simulator() calls - no longer needed Root cause identified: - pymodbus 3.x completely changed data block architecture - Old getValues() and async_getValues() methods were silently ignored - Server was reading internal storage directly with +1 offset - New implementation uses getValues() which IS called in 3.x Results: ✅ Battery charge power: 5000W (was 327680048W corrupted) ✅ AC voltage/current: 225V @ 19A (was swapped: 18V @ 0A) ✅ Battery voltage/SOC: 48V @ 50% (was 5V @ 0%) ✅ All 32-bit register pairs now correctly combined Fixes #register-offset-issue
- Commented out verbose register logging in modbus_server.py - Changed logging level back to WARNING in growatt_emulator.py - Keeps code cleaner for production use
Features: - Press 'P' to pause/unpause the simulator - When paused, values freeze (no updates to power, voltages, etc.) - Pause indicator shows in header: ⏸ PAUSED (red, blinking) - Display continues to update but shows frozen values - Useful for verifying integration reads exact same values This allows testing with Home Assistant integration which polls every 30 seconds - pause the simulator to confirm the integration is reading the correct static values. Changes: - Added simulator.paused flag and toggle_pause() method - Update() skips when paused - Added [P]ause to controls section - Shows prominent pause indicator in status line
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)
…er support Merged changes from origin/dev: - Updated MOD profile with corrected battery power registers (31126-31127) - Updated SPH TL3 profile with AC register aliases for compatibility - Updated integration logic (growatt_modbus.py, device_profiles.py, manifest.json) Key MOD profile changes: - Fixed battery power registers: moved from 3178-3181 (broken) to 31126-31127 (working) - Renamed battery energy registers to match SPH TL3 naming convention: - battery_discharge_today_* → discharge_energy_today_* - battery_charge_today_* → charge_energy_today_* - Added 'alias' fields to AC registers for generic compatibility Emulator changes: - Added battery_power_high/low handler for MOD series (register 31126-31127) - Supports signed 32-bit values (positive=charging, negative=discharging) - Properly handles two's complement for negative values This ensures the emulator correctly simulates the MOD series with the updated, working battery power registers confirmed by Growatt.
The dev branch version extended the 3000 register range from 111 to 251 registers and added 31000 range reading. This causes 'extra data' errors and communication timeouts with MIN profile inverters. Reverted growatt_modbus.py back to previous version that works with MIN profiles while keeping the updated MOD and SPH TL3 profile definitions. Fixes: 'No response received after 3 retries' and 'extra data' errors
0xAHA
added a commit
that referenced
this pull request
Mar 22, 2026
…CoSswioNUhF3Kh2jHuEDZG Claude/growatt inverter emulator 01 co sswio n uh f3 kh2j hu edzg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.