Skip to content

Claude/growatt inverter emulator 01 co sswio n uh f3 kh2j hu edzg - #26

Merged
0xAHA merged 25 commits into
devfrom
claude/growatt-inverter-emulator-01CoSswioNUhF3Kh2jHuEDZG
Nov 15, 2025
Merged

Claude/growatt inverter emulator 01 co sswio n uh f3 kh2j hu edzg#26
0xAHA merged 25 commits into
devfrom
claude/growatt-inverter-emulator-01CoSswioNUhF3Kh2jHuEDZG

Conversation

@0xAHA

@0xAHA 0xAHA commented Nov 15, 2025

Copy link
Copy Markdown
Owner

No description provided.

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
0xAHA merged commit eb04b2c into dev Nov 15, 2025
4 checks passed
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants