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:

CopyEdit
0x0000–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):

RegisterDescriptionNotes
8Battery Voltage (Set A)Needs ÷100 scaling (e.g. 1280 = 12.8V)
9Battery CurrentMay be signed Int16
10Battery PowerOften derived from V × I
12Battery Voltage (Set B)Alternate reading
14PV VoltageNeeds scaling
15Load CurrentOften accurate in live testing
17Load PowerUsually matches measured wattage
24–25Battery Energy (In/Out)Possibly cumulative Wh counters
26–27PV Energy Today / TotalScaling varies by firmware
50Operating Mode0 = Idle, 1 = Charging, etc.
52Fault CodeNon-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.

📍 Slate House Farm   🌡️ 14.3°C   💧 91%   💨 5.4 mph   🌧️ 0 mm   📋 Current Conditions: 🌧️