The Hidden Complexity Behind Wind-Solar Hybrid MPPT Controllers
When it comes to hybrid MPPT controllers that claim to manage both wind and solar inputs—like those sold under brands such as SACLMD, ERNP, vFound, Keanoko, Sulckcys, SolarMr, and MARS—there’s a hidden commonality that most users never realise: they’re all built on the same core system board, the JNGE JN_MPPTWS-Z, and communicate using the Modbus protocol over RS485, often via an ELFIN WiFi module.



Unfortunately, what these controllers gain in hardware commonality, they lose in usability. Despite marketing promises, there is no fully working official app, no clear documentation, and virtually zero after-sales support. Users are left in the dark—literally and figuratively—when trying to understand or manage these devices.
To take back control, we had to go deep. That meant manually discovering the correct Modbus Slave ID, mapping register addresses, and decoding raw values just to understand what the controller was actually doing. This blog documents that journey—so you don’t have to start from scratch.
🔍 Technical Deep Dive: Reverse-Engineering the Hybrid Controller
Once we confirmed the controller was using a JNGE JN_MPPTWS-Z board with Modbus RTU over RS485, our first challenge was simple in theory, but frustrating in practice: getting any data at all.
🧩 Step 1: Finding the Slave ID
The documentation? Nonexistent. The apps? Dead links or abandoned. That meant we had to scan the full Modbus ID range (1–247) to identify the correct Slave ID. After trial and error—and dozens of timeout errors—we discovered our unit responded consistently on ID 6, which appears to be a common default across rebranded models.
🔢 Step 2: Mapping the Registers
With the slave responding, we began a brute-force scan of register addresses, starting from 0x0000
up to 0x012F
, using Function Codes 0x03 (Holding Registers) and 0x04 (Input Registers).
Some highlights:
- Address 8–10: Likely Battery Voltage (A), Current, and Power.
- Address 14–17: PV Voltage, Load Current, and Load Power.
- Address 24–27: Battery metrics (possibly energy in/out).
- Address 50/52: Operating state and fault code.
Each value had to be interpreted as UInt16, Int32, or Float32, and sometimes required pairing two consecutive registers. Worse, some returned nonsensical values until we applied proper byte-swap logic.
🧪 Step 3: Testing the Signals
To validate what we were seeing, we cross-referenced:
- Battery voltage readings against a multimeter
- PV power against known solar irradiance at the time
- Load power vs actual draw from connected appliances
This process allowed us to confidently assign meaning to at least 20+ key registers, enough to build a dashboard and begin monitoring performance in real time.
⚙️ Hardware Interface
Most of these controllers include an ELFIN EW11A or similar module for WiFi-based RS485 communication. It acts as a transparent serial bridge, meaning we could poll the controller from a Node.js server or Python script via TCP on port 8899 with no additional configuration needed.
📊 Register Map & Decoding Logic: Making Sense of the Data
Once the Slave ID was confirmed and raw register data started flowing, the next challenge was decoding the values into something meaningful. The lack of official documentation meant we had to build a custom register map—piece by piece—using real-time testing, logic deduction, and plain old patience.
🗺️ Building the Register Map
Using both Function Code 0x03 (Holding Registers) and 0x04 (Input Registers), we scanned addresses in ranges like:
CopyEdit0x0000–0x001F
0x0020–0x003F
0x0100–0x103C
Through trial and correlation, we began matching addresses to real-world values. Below is a partial mapping with example interpretations (assuming register reads return 16-bit unsigned integers unless otherwise noted):
Register | Description | Notes |
---|---|---|
8 | Battery Voltage (Set A) | Needs ÷100 scaling (e.g. 1280 = 12.8V) |
9 | Battery Current | May be signed Int16 |
10 | Battery Power | Often derived from V × I |
12 | Battery Voltage (Set B) | Alternate reading |
14 | PV Voltage | Needs scaling |
15 | Load Current | Often accurate in live testing |
17 | Load Power | Usually matches measured wattage |
24–25 | Battery Energy (In/Out) | Possibly cumulative Wh counters |
26–27 | PV Energy Today / Total | Scaling varies by firmware |
50 | Operating Mode | 0 = Idle, 1 = Charging, etc. |
52 | Fault Code | Non-zero indicates errors |
Some registers (like power or energy counters) returned 32-bit values, which required combining two 16-bit registers:
jsCopyEdit// Combine two 16-bit registers into a 32-bit unsigned int
const combineUInt32 = (high, low) => (high << 16) | low;
In other cases, floating point decoding was necessary. If the value seemed way off or non-scalar (e.g., 1.4e-38), we assumed Float32 (IEEE 754) encoding:
jsCopyEditconst decodeFloat32 = (high, low) => {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint16(0, high);
view.setUint16(2, low);
return view.getFloat32(0);
};
⚠️ Watch for byte order! Some values had to be byte-swapped or word-swapped depending on firmware quirks. This was discovered by cross-validating values with real voltages/currents from a multimeter or clamp meter.
Here’s a complete Node.js script example that:
- Connects to the controller via TCP (using the Elfin EW11A or similar)
- Uses Modbus RTU over TCP via
modbus-serial
- Reads selected registers (battery, PV, load)
- Decodes values with scaling and float support
- Logs the interpreted results

💾 Why Exporting to JSON Makes Life Easier
Once you’ve decoded the Modbus values from your hybrid MPPT controller, exporting them to JSON is a simple yet powerful step.
Here’s why:
- ✅ Universal Format – JSON is supported by virtually every programming language, frontend framework, and logging tool.
- 📈 Feeds Dashboards – You can load JSON directly into a web dashboard using JavaScript (e.g., with Chart.js or D3).
- 🧪 Easier Testing – You can snapshot your readings and use them offline for testing, debugging, or comparison.
- 🗃️ Historical Logging – Appending each JSON output to a file or database gives you a time-series history of your system’s performance.

🧭 Final Thoughts: From Chaos to Clarity
What began as a frustrating black box of mismatched apps and mystery registers turned into a fully readable and usable data stream—thanks to some Modbus persistence and a bit of reverse engineering.
By exporting the controller’s live data into JSON, we’ve unlocked a practical, flexible format that makes real-time monitoring, historical logging, and dashboard display entirely achievable.
Whether you’re running a small off-grid setup or just want peace of mind about how your wind/solar hybrid system is performing, that humble modbus-data.json
file is the key. It can be loaded into a simple HTML dashboard, refreshed every 60 seconds, and displayed in a clear, user-friendly interface without needing any commercial software.
With a little scripting and some open standards, even the most closed-box controllers can be made transparent.
