Skip to content

feat: Add MIT-5000-8T (MIT_8CH) inverter support + RF capture mode - #2962

Open
Geoffn-Hub wants to merge 11 commits into
tbnobody:masterfrom
Geoffn-Hub:feature/capture-mode
Open

feat: Add MIT-5000-8T (MIT_8CH) inverter support + RF capture mode#2962
Geoffn-Hub wants to merge 11 commits into
tbnobody:masterfrom
Geoffn-Hub:feature/capture-mode

Conversation

@Geoffn-Hub

@Geoffn-Hub Geoffn-Hub commented Feb 1, 2026

Copy link
Copy Markdown

Summary

This PR adds native support for the Hoymiles MIT-5000-8T 3-phase microinverter and includes an RF capture mode tool used to reverse-engineer the protocol.

Closes #2742

MIT-5000-8T Inverter Support

Key Findings

  • The MIT-5000-8T (serial prefix 0x1520) reports 4 independent MPPT channels over RF, despite the "8T" name — each MPPT tracker has 2 panel inputs sharing a single tracker
  • The inverter follows the HMT 3-phase pattern for AC fields (line-neutral voltages, line-line voltages, per-phase currents, power factor)
  • DC layout: 12 bytes per channel (UDC/IDC/PDC/YT[4]/YD)
  • Total payload: 92 bytes across 6 RF frames (01-05 + 0x86), reassembling to 88 data + 2 CRC16

Protocol Behaviour Differences from HMS/HMT

Testing with a live MIT-5000-8T revealed several protocol differences that required OpenDTU changes:

  1. SystemConfigPara response is 38 bytes (vs 48 for HMS/HMT). The hardcoded SYSTEM_CONFIG_PARA_SIZE check was rejecting valid responses. Fix: made expected byte count configurable via setExpectedByteCount(), with MIT_8CH setting 38 in its constructor.

  2. The MIT sends only 2 fragments per response burst, then waits for individual retransmit requests for each remaining fragment. The HMS/HMT sends all fragments in a single burst. Each retransmit cycle requires ~3 requests before the MIT responds. With 6 total fragments and 4 needing individual recovery at ~3 retransmits each, the default MAX_RETRANSMIT_COUNT of 5 was insufficient. Increased to 20 for reliable reception.

  3. Retransmit RX window: The SingleDataCommand (RequestFrame) timeout was increased from 100ms to 250ms to accommodate the MIT's slower response timing.

Result: MIT RealTimeRunData now achieves ~100% success rate, with each poll cycle taking ~4-5 seconds due to the retransmit dance.

Validated Byte Assignments

All byte assignments were validated by cross-referencing passive RF captures from the MIT-5000-8T against DTU-Pro Modbus TCP data read via Home Assistant sensors:

Field RF Value HA Value Match
MPPT1 YT 80.368 kWh ~80.37 kWh
MPPT2 YT 65.066 kWh ~65.07 kWh
MPPT3 YT 155.147 kWh ~155.15 kWh
MPPT4 YT 151.566 kWh ~151.57 kWh
Frequency 50.00 Hz 50.00 Hz
PAC 225.4 W ~230 W
Temperature 12.0 °C 12.0 °C
Power Factor 1.000 1.000

Complete Decoded Capture (230W production, 2026-02-01)

Raw frames:

0x01: 01 00 01 01 51 00 67 01 5D 00 01 39 F0 00 25 01 62
0x02: 02 00 66 01 70 00 00 FE 2A 00 23 02 D3 00 7E 03 91
0x03: 03 00 02 5E 0B 00 58 02 B5 00 6F 03 03 00 02 50 0E
0x04: 04 00 64 09 0D 09 1C 09 1F 0F AB 0F D2 0F C1 13 88
0x05: 05 00 00 08 CE 00 00 00 E1 00 22 00 21 00 1E 03 E8
0x86: 86 00 78 00 03 00 00 00 00 00 1A 63 40

Unresolved Fields

  • Offset 68: Always 0 in captures — likely reserved
  • Offset 70: Value matches PAC exactly — possibly apparent power (S in VA). Needs capture under reactive load to confirm.

Files Changed (MIT Support)

  • lib/Hoymiles/src/inverters/MIT_8CH.cpp — Inverter implementation with validated byte assignments
  • lib/Hoymiles/src/inverters/MIT_8CH.h — Header
  • lib/Hoymiles/src/Hoymiles.cpp — Register MIT_8CH for serial prefix 0x1520
  • lib/Hoymiles/src/parser/SystemConfigParaParser.cpp — Configurable expected byte count
  • lib/Hoymiles/src/parser/SystemConfigParaParser.hsetExpectedByteCount() method
  • lib/Hoymiles/src/commands/CommandAbstract.hMAX_RETRANSMIT_COUNT increased to 20
  • lib/Hoymiles/src/commands/SingleDataCommand.cpp — RequestFrame timeout 100ms → 250ms

RF Capture Mode

A diagnostic tool for reverse-engineering new inverter protocols by passively sniffing RF traffic.

How It Works

  1. Channel hopping: CMT2300A radio sweeps all legal channels (863-870 MHz EU) with 50ms dwell time
  2. Frame logging: All valid CRC8 frames are logged to the serial console (WebSocket) with source/destination serial, RSSI, frequency, and hex dump
  3. Non-destructive: Operates alongside normal inverter polling infrastructure

API

# Enable
curl -X POST "http://admin:<pass>@<dtu-ip>/api/dtu/capture" \
  --data-urlencode 'data={"capture_mode":true}'

# Status
curl -s "http://admin:<pass>@<dtu-ip>/api/dtu/capture"
# {"capture_mode":true}

Files Changed (Capture Mode)

  • lib/Hoymiles/src/HoymilesRadio_CMT.cpp — Capture mode: channel hopping, frame logging, CRC error reporting
  • lib/Hoymiles/src/HoymilesRadio_CMT.h — Capture mode state and configuration
  • src/WebApi_dtu.cpp — REST API endpoints (/api/dtu/capture GET/POST)
  • include/WebApi_dtu.h — Handler declarations
  • docs/CaptureMode.md — Full documentation

Testing

Tested on:

  • Hardware: OpenDTU Fusion v2 board (ESP32-S3)
  • Inverter: MIT-5000-8T (SN: 1520a025566b) with 8 panels across 4 MPPT inputs
  • Conditions: Low power (~230W, winter) and medium power (~1.8kW) captures
  • Validation: Cross-referenced all fields against DTU-Pro Modbus TCP data via Home Assistant
  • Live polling: MIT RealTimeRunData achieving ~100% success rate with retransmit tuning, SystemConfigPara parsing correctly at 38 bytes, all 4 MPPT channels + 3-phase AC data updating in web UI

@tbnobody

tbnobody commented Feb 1, 2026

Copy link
Copy Markdown
Owner

Thank you!
In #2742 you wrote, that the main loop is too slow to handle the received packages (reading it from the CMT module). I just had a look at your code and didn't find any changes related to this. Is there still any improvement required to get this inverter working?

@Geoffn-Hub

Copy link
Copy Markdown
Author

I'm going to submit a dedicated PR for the changes to the receive loop - I figured you might want to have the parsing logic for the MIT inverter packaged seperately from code that alters the core code of the project in more fundamental ways

@Geoffn-Hub

Copy link
Copy Markdown
Author

FWIW - I did take a look at the AhoyDTU implementation which is in the form of a state machine, but don't think it works in this case either

Miraz added 3 commits February 2, 2026 10:54
Add passive RF capture mode to the CMT2300A radio for protocol
reverse-engineering. When enabled via the web API, the radio hops
across all legal EU channels (50ms dwell) and streams decoded packets
over a WebSocket endpoint.

Key changes:
- CMT radio starts in RX mode on init for passive listening
- Channel hopping across legal frequency range in capture mode
- FIFO drain loop processes all queued packets per iteration
- WebSocket endpoint for real-time packet streaming
- Web API endpoints to enable/disable capture mode
- Documentation in docs/CaptureMode.md

Also fixes FIFO burst reception: the hardware FIFO drain now runs
unconditionally and uses break (not continue) on buffer full, ensuring
back-to-back MIT response fragments are not lost.

Increases MAX_RETRANSMIT_COUNT to 20 for MIT inverters which respond
with 6 rapid fragments per poll.
Add initial support for the Hoymiles MIT-5000-8T microinverter.
The MIT-5000-8T presents as a 4-channel device at the RF level
(each MPPT has dual panel inputs aggregated internally).

- New MIT_8CH class extending HMT_Abstract
- 4-channel AC/DC data parsing from validated RF captures
- Auto-detection by serial number prefix in Hoymiles.cpp
MIT-5000-8T sends SystemConfigPara across 3 fragments (vs 2 for HM/HMT),
requiring a larger buffer. Increase expected size to 48 bytes and add
validation for the MIT response format.
@Geoffn-Hub
Geoffn-Hub force-pushed the feature/capture-mode branch from 2744569 to 016c9af Compare February 2, 2026 10:54
if (memcmp(&f.fragment[5], &dtuId.b[1], 4) == 0) {
// The CMT RF module does not filter foreign packages by itself.
// Has to be done manually here.
if (memcmp(&f.fragment[5], &dtuId.b[1], 4) == 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here we could consider to run the Capture mode in promiscuous mode if we simply log received data regardless of the DTU ID.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea — capture mode already logs all valid CRC-checked frames regardless of source/destination serial, so it's effectively promiscuous already. The DTU serial filtering happens later in the normal processing path (the dumpFragment/command matching), which capture mode bypasses by logging before that stage. We could make this more explicit by skipping the _inverters lookup entirely when in capture mode, but functionally it already captures everything on the current channel.

Miraz added 8 commits February 2, 2026 13:00
The MIT-5000-8T sends response fragments at ~835ms intervals instead
of the ~50ms typical for HMS/HMT inverters. The default 500ms RX
timeout causes premature retransmit requests after receiving only 1-2
of 6 fragments, resulting in 13-15x retransmit ratios and ~55% success.

Discovery: capture mode showed all 6 MIT fragments arriving correctly
at ~835ms spacing. The DTU-Pro (which works) likely uses longer timeouts.

Changes:
- RealTimeRunDataCommand: 6000ms timeout for 0x1520 (MIT) serials
- AlarmDataCommand: 12000ms timeout for 0x1520 serials
- SingleDataCommand: 2000ms retransmit timeout for 0x1520 serials
- Clean up debug logging from HoymilesRadio_CMT

Note: OTA updates via /api/firmware/update appear to silently fail
(device reports old git hash). Flash via USB/serial to test.
The CMT2300A radio automatically exits RX mode (goes to STBY) after
receiving a packet. The read() function was reading the FIFO and
clearing interrupts, but never putting the radio back into RX mode.
This caused subsequent fragments in a burst to be missed — the radio
was in STBY when they arrived.

This explains why only 2 of 6 MIT-5000-8T fragments were received:
frag 1 arrives, radio exits RX, re-enters RX only after the drain
loop completes, missing frags 2-3. Then catches frag 4, misses 5-6.

The HMS-4CH (Shed) worked because its fragments arrive at ~50ms
intervals which is fast enough that the drain loop + next poll
caught them. The MIT's ~835ms (capture mode) / ~50ms (normal mode)
timing hit a window where the radio was in STBY.

Fix: after reading each packet, immediately re-enter RX mode by
clearing the FIFO and calling GoRx(). This keeps the radio
continuously listening during multi-fragment bursts.
…ch read

The previous fix (GoRx after ReadFifo) improved reception from 2/6 to
3/6 fragments, but the GoRx state transition takes too long — fragments
arriving during the STBY→RFS→RX transition are still missed.

Better approach: set the RX_AUTO_EXIT_DIS bit (0x20) in MODE_CTL when
entering RX mode. This tells the CMT2300A to stay in RX after receiving
a packet, eliminating the state transition gap entirely. The radio
remains continuously listening throughout the entire fragment burst.
The previous commit set RX_AUTO_EXIT_DIS in startListening() before
calling GoRx(), but GoRx() writes the bare GO_RX command (0x08) to
MODE_CTL, overwriting the 0x20 bit we just set.

Fix: OR the RX_AUTO_EXIT_DIS bit into the GoRx command itself within
CMT2300A_AutoSwitchStatus(), so both bits are written atomically.
Also fix the TX/RX status check comparisons to mask out the new bit.
…instead

RX_AUTO_EXIT_DIS caused zero packets to be received — the radio likely
needs the RX→STBY transition to properly latch FIFO data.

New approach: after reading each packet, write GO_RX directly to
MODE_CTL with a single register write (no polling, no FIFO clear).
This is the fastest possible RX re-entry — just one SPI transaction
instead of the full startListening() sequence.
Instead of draining only what's immediately available and returning,
keep polling for 80ms after the last received packet. This catches
burst fragments arriving at ~50ms intervals — the radio re-enters RX
after each read (fast GoRx write), and the polling loop checks for
the next fragment before the loop() cycle completes.

Previously, the drain loop would exit after reading 1 packet, set
_packetReceived=false, and not check again until the next loop()
iteration — by which time the radio had been in STBY and missed the
next fragment.
The bare GoRx write (single register write to MODE_CTL) corrupted
FIFO reads — 19 'Frame kaputt' CRC failures per cycle. The radio
needs the full GoStby → EnableReadFifo → ClearRxFifo → GoRx sequence
to properly reset its internal state between packets.

Combined with the 80ms polling drain loop, this should catch burst
fragments: read packet → full RX re-entry → poll for next packet
within 80ms window.
Every approach to re-enter RX after reading a packet has failed:
- Bare GoRx write: corrupts FIFO reads (Frame kaputt flood)
- Full startListening (GoStby+EnableReadFifo+ClearRxFifo+GoRx): also
  corrupts because ClearRxFifo destroys in-flight packet data
- RX_AUTO_EXIT_DIS bit: radio receives nothing at all

The CMT2300A's single-packet FIFO design means it can only hold one
packet at a time. After receiving a packet, the radio must exit RX,
have the FIFO read, then re-enter RX for the next packet. This
inherently creates a gap where packets are missed.

Falling back to the extended timeout approach: the radio naturally
receives 2-3 of 6 fragments per burst, and retransmit requests
(with 6000ms/2000ms timeouts for MIT) eventually collect all fragments.
This gives ~75-83% first-try success rate.
// processed per loop() call, and only when no new packet was arriving.
// Processing the entire buffer each iteration reduces latency and prevents
// the software buffer from growing unboundedly during bursts.
while (!_rxBuffer.empty()) {

@stefan123t stefan123t Feb 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Geoffn-Hub if you have received anything from the Radio, you are trying to flush everything immediately out of the Ring-Buffer again until it is empty.
Is this eventually occupying the loop method for too long to fetch the next received bits from the Radio into the buffer ?

I think you had some code to break off the loop early already in Step 1 if something was received, i.e. somewhat before Step 2.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair concern, but in practice the ring buffer stays very small — typical burst is 5-6 fragments, and processing each one (CRC check + dumpFragment) is microsecond-level work. The CMT2300A's single-packet FIFO means we only ever have 1 packet waiting in hardware at a time; the ~50-835ms gap between fragments (depending on inverter type) gives plenty of time for both drain and process.

The original code only processed one packet per loop() call and skipped processing entirely while _packetReceived was true — meaning the software buffer grew unboundedly during bursts. This change fixes that by processing everything each iteration.

That said, if you'd prefer a hybrid approach (process N packets per iteration, or yield back to Step 1 periodically), that's easy to add. In testing with the MIT-5000-8T (6 fragments at ~835ms intervals) and HMS-4CH (5 fragments at ~50ms), the buffer never exceeded ~3-4 entries.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to think ahead of us here 😉 as I have even longer Command Sequences in mind, which we still may need to handle:

@Geoffn-Hub Geoffn-Hub left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point about longer sequences — worth thinking ahead. A few observations from digging deeper:

The ring buffer drain is fast (microsecond-level per fragment), so 15-16 packets aren't a problem from a buffering perspective.

The bigger factor for MIT is per-packet frequency hopping. Looking at the CMT2300A datasheet, this is actually a designed hardware feature — "fast manual frequency hopping" via FH_CHANNEL/FH_OFFSET registers, with a dedicated app note (AN197) covering RX-side hopping including AFC reconfiguration.

OpenDTU already sets FH_OFFSET=100 (250 kHz steps) during init — which matches exactly what we observed on the MIT. The current RX hop implementation in this PR uses the heavyweight stopListening→setChannel→startListening cycle. We can optimise this to a single FH_CHANNEL register write, which is what the hardware FH feature is designed for.

We also need to add CMT2300A_SetAfcOvfTh() per AN197 for proper RX-side hopping — without it the AFC circuit may not be properly calibrated after channel switches.

For HMS/HMT, longer sequences should work fine on a single channel — the per-packet hopping appears to be MIT-specific behaviour (HMS/HMT consistently deliver all fragments on the same frequency). But having the FH infrastructure in place would also benefit the frequency-drift issue (#2137) down the road.

@stefan123t

stefan123t commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

Adding @lumapu @DanielR92 to the party 🎉 as they have done the initial research and CMT implementation for HMS.
Maybe they can answer or add something to the CMT2300A frequency hopping ?

Found the documentation you mentioned under 2. AFC Parameter Setting for RX Frequency Hopping:

Details

When using manual frequency hopping in RX, the frequency hopping operation will affect the setting of an
important parameter AFC_OVF_TH<7:0> of AFC (Automatic Frequency Control). If the setting is incorrect, it may
result in failure to receive at the current frequency.
Under normal circumstances, this parameter is automatically
calculated by RFPDK based on the frequency points input by the user, and the user does not need to configure
it specially. However, when users use fast frequency hopping or manually configure frequency points, the
setting of frequency points is separated from RFPDK, so RFPDK cannot help users configure this parameter.
CMOSTEK provides another tool for customers to automatically calculate this parameter, which is the
"CMT2300A-CMT2219B Frequency Hopping Calculation Table". The usage method is as follows:

  1. The user fills in 4 parameters respectively.
    Xtal Frequency (MHz) - Crystal frequency, the default is 26MHz. If necessary, modify RX Xtal Tolerance (ppm)
    according to the actual frequency - The maximum error of the crystal used by the receiver
    TX Xtal Tolerance (ppm) - Crystal error used by the transmitter RX Frequency Range (MHz) - Frequency
    hopping range of the receiver
    Note that the crystal errors related to TX and RX refer to the crystal errors of the respective communication
    parties. For TRX products, one party can be assumed to be RX and the other to be TX.
  2. Then, in one column of the Index, fill in each frequency point of frequency hopping that needs to be used
    in order, with a maximum of 100 entries. If the user does not use that many frequency points, the
    remaining ones can be left blank.
  3. After filling in all the frequency hopping points, the table will calculate the values (in decimal) of
    AFC_OVF_TH<7:0> corresponding to each frequency point. At the same time, the value of Initial
    AFC_OVF_TH<7:0> will be displayed in the left table.
  • If all values of AFC_OVF_TH<7:0> are equivalent to Initial AFC_OVF_TH<7:0>, it indicates that there
    are no frequency points requiring special handling. In this case, the user only needs to write the value of
    Initial AFC_OVF_TH<7:0> into the 0x27 CUS_FSK4 register once during the chip initialization process,
    and there will be no need to make changes afterward.
  • If in a certain frequency point, the value of AFC_OVF_TH<7:0> is different from that of Initial
    AFC_OVF_TH<7:0>
    , then this value will be marked in red. The user needs to configure the value of Initial
    AFC_OVF_ TH<7:0> during the initialization process. Before hopping to this frequency point (in the STBY
    state), update the corresponding value of AFC_OVF_TH<7:0> to the 0x27 CUS_FSK4 register. Before
    jumping to the next frequency point, if the two values are the same, it is necessary to restore the register
    content to the value of Initial AFC_OVF_TH<7:0>.

In short, users need to ensure that before jumping to each frequency point, the value of the 0x27 CUS_FSK
register must be the same as the corresponding AFC_OVF_TH<7:0> value in the table

and 3. The Overall Process of Rapid Manual Frequency Hopping

The following summarizes the overall process of fast manual frequency hopping:

  1. Use the CMT2300A-CMT2219B Frequency Hopping Calculation Table to obtain the Initial AFC_OVF_TH<7:0>, and write this value into the 0x27 CUSFSK4 register during the chip initialization process.
    If only TX frequency hopping is used, this step can be ignored.
  2. Send go_stby to make the chip return to the STBY state.
  3. Set FH CHANNEL<7:0>.
    If it is necessary to reset FH_OFFSET<7:0>, handle it in this step as well.
    [...]
show fine print

Refer to the operating frequency band division in Table 3 to limit the frequency hopping range.
It should be kept in mind that the chip does not support cross-band frequency hopping operations, that is, the value of the FREQ_DIVX_CODE<2:0> register cannot be changed.
Since the base frequency is determined by RFPDK, the parameter table generated by default by RFPDK includes the value of the FREQ_DIVX_CODE<2:0> register corresponding to this frequency band, which is configured during initialization, so users do not need to configure it every time they frequency hop.
However, it should be noted that FREQ_VCO_BANK<2:0> will change within the same frequency band.
When users set the frequency hopping points in this step, corresponding to the range marked in Table 3, if necessary, they should update the value of FREQ_VCO_BANK<2:0>.

  1. If performing RX frequency hopping operation, according to the method introduced in Chapter 2, use the EXCEL tool sheet provided by CMOSTEK to ensure that the value of the 0x27 CUS_FSK4 register is equal to the value of AFC_OVF_TH<7:0> corresponding to the current frequency point to be hopped to.
    If it is a TX frequency hopping operation, this step can be ignored.
  2. Send go_tx to enter TX for transmission, or go_rx to enter RX for reception.

If the user performs a frequency hopping operation on TX and then switches to RX state for receiving, they
must set FH_CHANNEL<7:0> back to 0 before reception to return to the RX basic frequency point set by
RFPDK. Conversely, after performing the frequency hopping operation of RX, FH_CHANNEL<7:0> must also
be set back to 0
to using the TX basic frequency point for transmission, setting by RFPDK.

The advantage of manual frequency hopping is that the frequency point can be changed by setting only 1 to 2
registers. Without this mechanism, it would be necessary to write to the frequency region to change the
frequency, which would significantly increase the content of the registers that need to be written, lengthen the
time, and might fail to meet application requirements.

@Geoffn-Hub

Copy link
Copy Markdown
Author

Happy for help. :-)

Other than the buffer overflow issue - this is the main problem that was preventing us from getting a full capture from the MIT inverter, and I don't have it fully beaten yet - I have to go through several retransmit request cycles for complete updates to succeed.

image

@Geoffn-Hub

Copy link
Copy Markdown
Author

To save hunting back through the comments...this is what we are seeing from the captures

CAPTURE 863.50 MHz | frag 01 (src=a025566b)
CAPTURE 863.75 MHz | frag 02
CAPTURE 864.00 MHz | frag 03
...
CAPTURE 863.50 MHz | frag 04
CAPTURE 863.75 MHz | frag 05

The MIT IS hopping across 3 frequencies: 863.50, 863.75, 864.00 MHz — 250 kHz steps. The pattern per fragment ID:
•⁠ ⁠Frag 1 → 863.50 (-1)
•⁠ ⁠Frag 2 → 863.75 (base)
•⁠ ⁠Frag 3 → 864.00 (+1)
•⁠ ⁠Frag 4 → 863.50 (-1) ← repeats
•⁠ ⁠Frag 5 → 863.75 (base)

@stefan123t

Copy link
Copy Markdown
Contributor

@Geoffn-Hub any news from your adventures in MIT Frequency Hopping ?
I have been on holidays so did not check back regularly.

@Dirk-Ahoy

Copy link
Copy Markdown

@stefan123t - es könnte sein das HM das auch in die neuere Chargen der HM1600 eingebaut hat S/N 1164A02...

siehe Discord Opendtu Channel, wir haben geloggt - da werden nur ein paar Frames empfangen - kannst dich ja mal im Discord melden dann sende ich dir die Logs. Ist aber alles nur Vermutung im Moment. An der Firmware kann es nicht liegen, wir haben da auf dem WR ein Downgrade auf 1.1.12 veranlasst - die sollten funktioniern - fragt sich ob die im Funkmodul was an der Firmware oder Timings geschraubt haben.

@Zurrmaxe

Copy link
Copy Markdown

Hallo zusammen
Ich habe mir jetzt auch ein paar MIT-5000 zugelegt und bin auf der Suche nach eine Möglichkeit, erstmal nur die wichtigtsten Daten (z.B. die aktuelle Leistung) für weitere eine Verarbeitungen auslesen zu können.
Dann könnte ich auch mitlesen und testen, wie der Datenfluss und die Kommandos eventuell funktionieren.

Gibt es von eueren Versuchen schon eine BETA-Version ?

lg Alf

@stefan123t

Copy link
Copy Markdown
Contributor

@Zurrmaxe Du bist im PR von Geoffn-Hub da sind alle Änderungen drin. Einfach clonen, ggf den Branch wechseln und den Code bauen...

@Zurrmaxe

Copy link
Copy Markdown

@stefan123t
Ich bin leider nur mit der Arduino IDE unterwegs und hatte mich bisher noch nicht mit anderen Systemen wie Platformio pp. beschäftigt.
Werde es aber heute abend mal versuchen. Anleitungen gibts bestimmt hier irgendwo im Netz.

LG
Alf

@Zurrmaxe

Zurrmaxe commented Apr 3, 2026

Copy link
Copy Markdown

@stefan123t

Ich habe einen nagelneuen MIT5000 diese Woche angeschlossen.
Brunch von Geoffn-Hub auf ESP32 gespielt und zusammen mit 2 x HMT2250 getestet.
Die HMT2250 werden ausgelesen, MIT5000 jedoch garnichts.

Nach Ostern gehts weiter...
Alf

@rauschgiftengel

Copy link
Copy Markdown

@Zurrmaxe Du bist im PR von Geoffn-Hub da sind alle Änderungen drin. Einfach clonen, ggf den Branch wechseln und den Code bauen...

Hallo Stefan,

ich hab das ebenfalls getestet, ich bekomme auch Werte vom MIT5000 auf der openDTU - allerdings funktioniert dann die DTU S Pro nicht mehr - auch wenn ich der OpenDTU die selbe ID gebe - ist das normal ?
Ich meine irgendwo gelesen zu haben dass jemand die Werte der OpenDTU mit der DTU S Pro verglichen hat - in dem Fall müssten ja beide parallel funktioniert haben.

@Zurrmaxe

mit diesen Settings funktioniert es bei mir (branch capture_mode):
image

@Zurrmaxe

Zurrmaxe commented Jun 30, 2026

Copy link
Copy Markdown

At the moment, my successful radio reception rate is only 38% as well. Maybe I've also been messing with the source code a bit too much.

But I've got the DTUs under control now. The first one is running with 3 × HMS 1800 at 864.25 MHz, and the second DTU is on 866.50 MHz. That way they no longer interfere with each other.

In general, the code already works very well. We just need to find the right values/settings for the timing.

This timing is very poor with the MIT5000 and needs to be adjusted.
Fehler

It should be reduced, as it only causes unnecessary traffic. ButI just don't know where.

Now i hve only theses errors

[19:31:07.943] I (7854671) hoymiles: Fetch inverter: 138290780488 ( => HMT2250T)
[19:31:08.446] I (7854672) hoymiles: Request SystemConfigPara
[19:31:08.446] I (7854673) hoymiles: Queue size - NRF: 0 CMT: 8
[19:31:08.446] I (7855175) hoymiles: RX Period End
[19:31:12.942] E (7855176) hoymiles: ERROR in SystemConfigPara: Received fragment size: 16, min expected size: 38
[19:31:12.942] W (7855176) hoymiles: Packet handling error
.....
[19:31:12.942] I (7859673) hoymiles: Fetch inverter: 138291500326 (=>HMT2250T)
[19:31:13.525] I (7859674) hoymiles: Request SystemConfigPara
[19:31:13.525] I (7859675) hoymiles: Queue size - NRF: 0 CMT: 7
[19:31:13.525] I (7860253) hoymiles: RX Period End
[19:31:13.685] E (7860254) hoymiles: ERROR in SystemConfigPara: Received fragment size: 16, min expected size: 48
[19:31:13.685] W (7860254) hoymiles: Packet handling error
....
[19:31:17.944] I (7864675) hoymiles: Fetch inverter: 1520a023df4e (=>MIT5000)
[19:31:19.937] I (7864676) hoymiles: Request SystemConfigPara
[19:31:19.938] I (7864676) hoymiles: Queue size - NRF: 0 CMT: 5
[19:31:19.938] I (7866666) hoymiles: RX Period End
[19:31:20.115] E (7866667) hoymiles: ERROR in SystemConfigPara: Received fragment size: 16, min expected size: 48
[19:31:20.116] W (7866667) hoymiles: Packet handling error

but other error says:

[19:33:03.007] I (7969735) hoymiles: Fetch inverter: 1520a023df4e (=>MIT5000)
[19:33:07.485] I (7969736) hoymiles: Request SystemConfigPara
[19:33:07.485] I (7969736) hoymiles: Queue size - NRF: 0 CMT: 8
[19:33:07.485] I (7974211) hoymiles: RX Period End
[19:33:07.658] E (7974212) hoymiles: ERROR in SystemConfigPara: Received fragment size: 16, min expected size: 38
[19:33:07.658] W (7974212) hoymiles: Packet handling error
Why are there different values? expected size: 38 ? then expected size: 48 ?

Alf

@OlafWalther1969

OlafWalther1969 commented Jul 23, 2026

Copy link
Copy Markdown

Das mit dem Schwachlichtverhaltenverhalten mit der Firmware V01.01.12-01.01.08 kann ich bestätigen.
Trotz 8 PV Module und 84V pro MPPT hat der Wechselrichter frühestens 1 Stunde nach den HMS und HMT Wechselrichtern mit der Stromproduktion begonnen.

Es gibt eine neue Firmware von Hoymiles V01.04.00-01.04.00.
Ich hatte gestern den Support Deutschland angerufen.
Die haben über den Hoymiles Server sofort die neue Firmware eingespielt.

Die neue Firmware wird nicht in der DTU Pro S angezeigt.
Dafür müsst ihr den Support anrufen.

Das neue Firmware merkt man sofort, die PV Leistung steigt um 5% und morgens beginnt der MIT-5000-8T fast schon früher wie die HMS und HMT Serie.

Zu der Startspannung kann ich aber noch nichts genaues sagen.

Grüße

@Zurrmaxe

Zurrmaxe commented Jul 23, 2026

Copy link
Copy Markdown

@OlafWalther1969
Besten Dank für die Info über eine neue Hoymiles-Firmware.
Halte uns bitte auf dem Laufenden, wie die Firmware ist und was besser geworden ist.
Hast du mal für mich den Link der Ahoy-Version ? Will mal sehen, wie die es machen.

Ich betreibe meinen MIT5000 mit jeweils drei "älteren" 395W-Platten.
Diese haben ein max. Spannung von 38 Volt und können somit im Regelbereich der MPPTs gut betrieben werden.
Wichtig ist nur, dass die Summe der Leerlaufspannungen der drei Platten immer (auch im Frühling) unter den maximalen MPPT -Spannungen (140 Volt) bleiben.
Somit konnte man ich mit einem MIT5000 meine zwei HMT2250T ersetzen.
lg Alf

@OlafWalther1969

OlafWalther1969 commented Jul 24, 2026

Copy link
Copy Markdown

@Zurrmaxe
an den Eingängen von dem MIT-5000-8 sind 8 Trina angeschlossen, mit über 80V pro Eingang am Wechselrichter.
Mit der alten Firmware hat der MIT über 1 Stunde später mit der Produktion begonnen, als die HMT und HMS WR.

Das hat sich mit der neuen Firmware V01.04.00-01.04.00 vollkommen geändert.

@smoe

smoe commented Jul 27, 2026

Copy link
Copy Markdown

I have wired-up an 5000-MIT-8T today as a quick test if it is basically functional after having 3 30V modules at a single lane not making it blink yesterday. Today it first remained dead, but having those three modules at three different lanes then had it blink red, so it appears to fire up also without all lanes connected. I then added a fourth module, which I think to have sped up the blinking frequency. Connecting it to the grid then had the device blink green.

I had a look at @Geoffn-Hub's two PRs and liked them.

Is there a way for me to help bringing this forward? It should be reasonably possible for me to rebase, compile and test these PRs, but there is only little incentive to just do it for myself.

@smoe

smoe commented Jul 27, 2026

Copy link
Copy Markdown

@Geoffn-Hub I followed up on my offer and prepared three handoff branches, moving both PR histories from their old common base (f7e36933) onto current tbnobody/master (b6f0353b, v26.3.30):

The combined branch intentionally places #2963 first and then applies all 11 commits from #2962. #2962 already duplicates three parts of #2963—the FIFO drain, processing all buffered packets, and breaking when the buffer is full—but lacks #2963’s PKT_OK-only available() fix. I reconciled that overlap while preserving the complete #2962 series.

As a cross-check, the combined tree differs from the rebased #2962 tree only by that missing #2963 behavior.

The only conflict between #2962 and current master was the web API route registration. Current master’s handler casts were retained for the new capture endpoints.

All three branches pass a complete PlatformIO generic_esp32 firmware build. I have not hardware-tested the rebased branches yet, but I can now flash the combined branch on my MIT-5000-8T and report the results.

Credit where it is due: OpenAI Codex did the heavy lifting here—history inspection, both rebases, conflict and overlap resolution, and build verification—under my direction and review.

I have not knowingly changed either of your PR branches; these are review and handoff pointers.

@smoe

smoe commented Jul 27, 2026

Copy link
Copy Markdown

CMT burst processing reads and removes different queue elements

Codex found a queue-ordering bug in the CMT burst-drain implementation.

When _packetReceived is set, HoymilesRadio_CMT::loop() drains the CMT hardware FIFO. For every received packet it constructs a complete fragment_t, including its payload, length, channel and RSSI:

fragment_t f;
memset(f.fragment, 0xcc, MAX_RF_PAYLOAD_SIZE);
f.len = std::min<uint8_t>(
    _radio->getDynamicPayloadSize(),
    MAX_RF_PAYLOAD_SIZE);
f.channel = _radio->getChannel();
f.rssi = _radio->getRssiDBm();
_radio->read(f.fragment, f.len);
_rxBuffer.push(f);

The important insertion is:

_rxBuffer.push(f);

_rxBuffer is a [std::queue](https://en.cppreference.com/w/cpp/container/queue)<fragment_t>. According to the [std::queue documentation](https://en.cppreference.com/w/cpp/container/queue), it is FIFO: push() inserts at the end, front() accesses the first element, back() accesses the last element, and pop() removes the first element.

The processing loop instead accessed back() and subsequently called pop():

fragment_t f = _rxBuffer.back();
// process f
_rxBuffer.pop();

Consequently, with queued fragments [A, B, C]:

  1. back() processes C, while pop() removes A.
  2. back() processes C again, while pop() removes B.
  3. C is processed for a third time and then removed.

A and B are never processed. This directly defeats the purpose of draining multi-packet bursts and can prevent multi-fragment responses from being reassembled.

The minimal correction is to access the same oldest element that pop() removes:

-        fragment_t f = _rxBuffer.back();
+        fragment_t f = _rxBuffer.front();

This is about to be committed on supporting_5000_MIT_8T as:

41e4c2dd fix(cmt): process queued RX fragments in FIFO order

The complete generic_esp32 firmware build passes with this change.

@smoe

smoe commented Jul 27, 2026

Copy link
Copy Markdown

SystemConfig buffer capacity became an unintended response requirement

Codex found a regression caused by using one size for two different purposes in SystemConfigParaParser.

Before the MIT changes, the SystemConfig buffer was 16 bytes and getExpectedByteCount() returned that size. PR #2962 correctly enlarged the buffer to 48 bytes so it could hold the longer MIT response:

-#define SYSTEM_CONFIG_PARA_SIZE 16
+#define SYSTEM_CONFIG_PARA_SIZE 48

It also introduced a configurable expected byte count, but initialized it from that enlarged buffer size:

uint8_t _expectedByteCount = SYSTEM_CONFIG_PARA_SIZE;

The command does not compare this value for equality. It treats it as a lower bound:

const uint8_t fragmentsSize =
    getTotalFragmentSize(fragment, max_fragment_id);

if (fragmentsSize < expectedSize) {
    return false;
}

Consequently, every inverter other than MIT inherited a minimum response requirement of 48 bytes. Valid legacy HM/HMS/HMT responses satisfying the previous 16-byte minimum could therefore be rejected.

These are separate concepts:

  • Buffer capacity: the maximum amount of response data that can safely be stored.
  • Minimum response size: the smallest reassembled response considered complete enough to parse.

The patch names and configures them separately:

-#define SYSTEM_CONFIG_PARA_SIZE 48
+#define SYSTEM_CONFIG_PARA_BUFFER_SIZE 48
+#define SYSTEM_CONFIG_PARA_DEFAULT_MIN_RESPONSE_SIZE 16
-uint8_t _expectedByteCount = SYSTEM_CONFIG_PARA_SIZE;
+uint8_t _minimumResponseSize =
+    SYSTEM_CONFIG_PARA_DEFAULT_MIN_RESPONSE_SIZE;

The API and validation code are renamed to describe the actual lower-bound semantics:

-uint8_t getExpectedByteCount() const;
-void setExpectedByteCount(uint8_t count);
+uint8_t getMinimumResponseSize() const;
+void setMinimumResponseSize(uint8_t size);

MIT retains its model-specific requirement:

SystemConfigPara()->setMinimumResponseSize(38);

The resulting behavior is:

Inverter Buffer capacity Minimum accepted response
HM/HMS/HMT 48 bytes 16 bytes
MIT-5000-8T 48 bytes 38 bytes

Thus, the larger MIT response still fits safely, while existing inverter families retain the acceptance threshold used before the buffer expansion.

The fix is committed on supporting_5000_MIT_8T as:

a813cc14 fix(hoymiles): separate SystemConfig buffer and response sizes

The complete generic_esp32 firmware build passes with this change.

@tbnobody

Copy link
Copy Markdown
Owner

@smoe thank you for the hint regarding the rx buffer. Havn't testet yet but it also affects the the nrf Part and could make things better! Would you mind to genervte a seperate PR just to fix this issue in cmt and nrf code? (I can also create a fix and mention your Name. As you prefere)

@smoe

smoe commented Jul 28, 2026

Copy link
Copy Markdown

@smoe thank you for the hint regarding the rx buffer. Havn't testet yet but it also affects the the nrf Part and could make things better! Would you mind to genervte a seperate PR just to fix this issue in cmt and nrf code? (I can also create a fix and mention your Name. As you prefere)

@tbnobody, great to hear that this was putatively helpful beyond the MIT work. Please proceed directly. My literally only own contribution was to ask Codex "Is there anything that raises you attention?" and to then review what Codex came up with. Not sure if I should be credited for that. But I have a Codex-driven OpenClaw instance with a seperate GitHub account @smoe-bot, who I guess would be much appreciating to be mentioned.

@smoe

smoe commented Jul 28, 2026

Copy link
Copy Markdown

This one in my reading may be the major blocker for merging this MIT work.

MIT recovery settings unintentionally affect every inverter

The MIT workaround changes two recovery settings for all inverter models:

  1. The global fragment retransmission limit was increased from 5 to 20.
  2. The non-MIT RequestFrameCommand response timeout was increased from the upstream 100 ms to 250 ms.

RequestFrameCommand is used whenever OpenDTU requests a missing response fragment. Consequently, a partial response from an existing HM/HMS/HMT inverter could occupy the shared radio for approximately:

upstream:       5 × 100 ms = 0.5 s
current branch: 20 × 250 ms = 5.0 s

This can delay every other inverter and command in the queue by roughly ten times as long.

The patch introduces separate, centrally defined retransmission limits:

#define MAX_DEFAULT_RETRANSMIT_COUNT 5
#define MAX_MIT_RETRANSMIT_COUNT 20

It selects the MIT limit only for serial prefix 0x1520 and restores the established 100 ms missing-fragment timeout for every other inverter:

                    Maximum requests    Response timeout
HM/HMS/HMT          5                   100 ms
MIT-5000-8T         20                  2000 ms

The separate MAX_RESEND_COUNT setting, used when no fragments arrive at all, remains unchanged at 4.

diff --git a/lib/Hoymiles/src/commands/CommandAbstract.h b/lib/Hoymiles/src/commands/CommandAbstract.h
@@
 #define RF_LEN 32
 #define MAX_RESEND_COUNT 4 // Used if all packages are missing
-#define MAX_RETRANSMIT_COUNT 20 // MIT-5000-8T: 2 frags/burst, ~3-4 retransmits per remaining frag, need margin
+#define MAX_DEFAULT_RETRANSMIT_COUNT 5 // Used to send the retransmit package
+#define MAX_MIT_RETRANSMIT_COUNT 20 // Used to send the retransmit package to MIT inverters

diff --git a/lib/Hoymiles/src/commands/CommandAbstract.cpp b/lib/Hoymiles/src/commands/CommandAbstract.cpp
@@
 uint8_t CommandAbstract::getMaxRetransmitCount() const
 {
-    return MAX_RETRANSMIT_COUNT;
+    // Keep the experimental MIT recovery allowance local to that model.
+    constexpr uint16_t mitSerialPrefix = 0x1520;
+    const uint16_t serialPrefix = (_inv->serial() >> 32) & 0xffff;
+    if (serialPrefix == mitSerialPrefix) {
+        return MAX_MIT_RETRANSMIT_COUNT;
+    }
+
+    return MAX_DEFAULT_RETRANSMIT_COUNT;
 }

diff --git a/lib/Hoymiles/src/commands/SingleDataCommand.cpp b/lib/Hoymiles/src/commands/SingleDataCommand.cpp
@@
-    // MIT-5000-8T responds to retransmit requests at ~835ms intervals
+    // Keep the experimental MIT recovery timeout local to that model.
     const uint16_t prefix = (inv->serial() >> 32) & 0xffff;
     if (prefix == 0x1520) {
         setTimeout(2000);
     } else {
-        setTimeout(250);
+        setTimeout(100);
     }
 }

This isolation fix does not establish that 20 requests or 2000 ms are the correct MIT values. Those values are preserved to avoid changing MIT behaviour in the same patch, but they should be measured again after correcting the receive-queue bug.

The generic ESP32 firmware build succeeds with this patch.

Commit: 2c8b9b00 fix(hoymiles): scope recovery settings to MIT

@smoe

smoe commented Jul 28, 2026

Copy link
Copy Markdown

Here is now a final "Priority 1" observation by Codex. This is not perfectly fixable until we truly know how the channel-changes work. Because of the queue-bug above I also did not want to induce the scheme from what was reported before. Codex asked me for an SDR. Not today. :-)

@upstream, I prepared these patches as #3161 for your convenience.

Capture mode can make OpenDTU send a command on the wrong frequency

The CMT2300A is the radio chip that OpenDTU uses to communicate with HMS, HMT and MIT inverters.

A radio frequency is the frequency on which the radio sends or receives a message. The source code also calls each available frequency a “channel”.

This branch adds an optional function called capture mode. When capture mode is switched on, OpenDTU regularly changes the CMT2300A frequency and listens for messages from nearby inverters. It stays on each frequency for 50 milliseconds before trying the next one.

For normal communication, OpenDTU uses the value shown as CMT2300A Frequency in the DTU settings. In the source code, this value is stored in _inverterTargetFrequency. I will call it the communication frequency below.

The following sequence was possible:

  1. Capture mode selected a frequency and listened for inverter messages there.
  2. OpenDTU then found a normal command waiting to be sent.
  3. The old send code did not first return the radio to the communication frequency.
  4. OpenDTU therefore sent the command on the frequency that capture mode had selected.
  5. After sending the command, OpenDTU returned to the communication frequency and waited for the inverter’s answer.
  6. The inverter had probably not heard the command because it was listening on the communication frequency. It could therefore not answer.

The complete command was sent. It was not stopped while it was being sent. The problem was that it could be sent on the wrong frequency.

A later attempt could succeed because the radio had returned to the communication frequency by then. This could make the problem appear as an occasional lost command or a slow answer.

This problem is not limited to MIT inverters. HMS and HMT inverters use the same CMT2300A send code. Capture mode could therefore also disturb communication with these existing inverter models. The problem can only occur while capture mode is switched on.

Capture mode can make OpenDTU send a command on the wrong frequency

The small correction is to select the correct frequency immediately before sending a command:

 if (cmd.getDataPayload()[0] == 0x56) {
     cmtSwitchDtuFreq(getInvBootFrequency());
+} else {
+    cmtSwitchDtuFreq(_inverterTargetFrequency);
 }

OpenDTU already treats one command differently.
Command 0x56 tells an inverter to change its communication frequency.
OpenDTU already sends command 0x56 on the start frequency stored for the selected geographic region. It then returns to the communication frequency selected in the DTU settings. This established OpenDTU behaviour predates the MIT changes and is left unchanged by this patch.

All other commands are now sent on the CMT2300A Frequency selected in the DTU settings.

Capture mode can still change frequencies while OpenDTU is only listening. It can also still miss messages because it listens on only one frequency at a time. This patch does not try to solve those limitations. It only prevents the frequency selected by capture mode from being used accidentally for a normal command.

@smoe

smoe commented Jul 28, 2026

Copy link
Copy Markdown

Upon request by @ms49434, key attributes have been abstracted away into the InverterAbstract class. This simplified the code and ensures more of a consistency - see 5c1df91 .

@Zurrmaxe

Copy link
Copy Markdown

Upon request by @ms49434, key attributes have been abstracted away into the InverterAbstract class. This simplified the code and ensures more of a consistency - see 5c1df91 .

Hi smoe
I try to compile these new files with VS PlatformIO, but have this error:

lib/Hoymiles/src/commands/CommandAbstract.cpp:132:12: error: 'MAX_RESEND_COUNT' was not declared in this scope
return MAX_RESEND_COUNT;
^~~~~~~~~~~~~~~~
lib/Hoymiles/src/commands/CommandAbstract.cpp:132:12: note: suggested alternative: 'MAX_RF_FRAGMENT_COUNT'
return MAX_RESEND_COUNT;
^~~~~~~~~~~~~~~~
MAX_RF_FRAGMENT_COUNT

Alf

@ms49434

ms49434 commented Jul 29, 2026

Copy link
Copy Markdown

Upon request by @ms49434, key attributes have been abstracted away into the InverterAbstract class. This simplified the code and ensures more of a consistency - see 5c1df91 .

Hi smoe I try to compile these new files with VS PlatformIO, but have this error:

lib/Hoymiles/src/commands/CommandAbstract.cpp:132:12: error: 'MAX_RESEND_COUNT' was not declared in this scope return MAX_RESEND_COUNT; ^~~~~~~~~~~~~~~~ lib/Hoymiles/src/commands/CommandAbstract.cpp:132:12: note: suggested alternative: 'MAX_RF_FRAGMENT_COUNT' return MAX_RESEND_COUNT; ^~~~~~~~~~~~~~~~ MAX_RF_FRAGMENT_COUNT

Alf

Please doublecheck if MAX_RESEND_COUNT is defined in CommandAbstract.h

line 10: #define MAX_RESEND_COUNT 4 // Used if all packages are missing

also, apply PR 3161: #3161, not this one

@schwatter

schwatter commented Jul 30, 2026

Copy link
Copy Markdown

Ok, yesterday the DTU Pro S arrived. I installed it quickly in the afternoon. At first, I noticed it needed an update because the MIT5000 wouldn't come online. After the update, it started receiving inverter data.

I sent an email to [service@hoymiles.com] asking them to update my inverter to firmware version V01.04.00-01.04.00. They updated it this morning, and the whole process took about half an hour.

Now I'm really surprised: since 11:30 this morning until now (13:43), I've had 100% successful reception.

I'm still using Geoffn-Hub's capture-mode branch with #define MAX_RETRANSMIT_COUNT 80, but that was just for tinkering. I'll switch back to the default value of 20 tomorrow.

MIT5000-OpenDTU

@Zurrmaxe

Copy link
Copy Markdown

First thanks to @ms49434.

Here my old results from last month:

In general, the code already works very well. We just need to find the right values/settings for the timing.

This timing is very poor with the MIT5000 and needs to be adjusted. Fehler

Alf

I build the new the firmware this morning and have this results:
Mit5000 new

Unfortunately, my levels have worsened.

Alf

@smoe

smoe commented Jul 30, 2026

Copy link
Copy Markdown

Unfortunately, my levels have worsened.

Just for the sake of curiosity and since I cannot try myself - can you influence your success rate by getting the OpenDTU closer to the inverter (or move thad giant chunk of lead out of the way or unsheath the OpenDTU from its aluminium hat)?

@schwatter

Copy link
Copy Markdown

Did you both read my comment? I would say the initial firmware is worse or the OpenDTU part won't fit for the firmware. I upgraded from V01.01.12-01.01.08 to V01.04.00-01.04.00. And now 100℅. Before i get also 33℅/33℅/33℅.

@Zurrmaxe

Zurrmaxe commented Jul 31, 2026

Copy link
Copy Markdown

Hi everyone 😄

The solution to our problem is quite simple: Firmware V01.04.00-01.04.00 ❗ ❗

Today in the morning i get the new firmware on my MIT-5000.

Here the results:
heute

DTU = ESP32S3 with the last modified code 5c1df91

Alf

@smoe

smoe commented Jul 31, 2026

Copy link
Copy Markdown

I just wish we would not need the vendor's DTU at all.

@schwatter

schwatter commented Jul 31, 2026

Copy link
Copy Markdown

@Zurrmaxe
I told you :) Btw i tested right now again with #define MAX_RETRANSMIT_COUNT 20 again.
So it is too low.

Empfang Erfolgreich | 13 | 65 %
Empfang Fehler: Nichts empfangen | 0 | 0 %
Empfang Fehler: Teilweise empfangen | 7 | 35 %
Empfang Fehler: Beschädigt empfangen | 0 | 0 %

So #define MAX_RETRANSMIT_COUNT 40 is ok.

Empfang Erfolgreich | 10 | 100 %
Empfang Fehler: Nichts empfangen | 0 | 0 %
Empfang Fehler: Teilweise empfangen | 0 | 0 %
Empfang Fehler: Beschädigt empfangen | 0 | 0 %

@smoe

smoe commented Jul 31, 2026

Copy link
Copy Markdown

@Zurrmaxe I told you :) Btw i tested right now again with #define MAX_RETRANSMIT_COUNT 20 again. So it is too low.

Looks much like it, indeed. I do not have an exact idea what the basis of that counting of packages is. But I could imagine some scenario with those channel changes and queue problems in which a package was not counted as missed or partly missed. Ideally, the MAX_RETRANSMIT_COUNT should be .. what .. 0 ? So having the need for such a high number of again-transmitted-requests is an indication of something being wrong.

Please kindly once try that 5c1df91 branch with those 20.

@Zurrmaxe

Zurrmaxe commented Jul 31, 2026

Copy link
Copy Markdown

I just wish we would not need the vendor's DTU at all.

We only need the vendors's DTU for the Firmware update. 😒

I used this settings:
#define RF_LEN 32
#define MAX_RESEND_COUNT 4 // Used if all packages are missing
#define MAX_DEFAULT_RETRANSMIT_COUNT 5 // Used to send the retransmit package
#define MAX_MIT_RETRANSMIT_COUNT 20

Alf

@smoe

smoe commented Jul 31, 2026

Copy link
Copy Markdown

I just wish we would not need the vendor's DTU at all.

We only need the vendors's DTU for the Firmware update. 😒

There is admittedly an immediate reflex to reverse-engineer a firmware for the Hoymiles devices. Just like we are doing it for various Wifi routers - I am just not ultimately confident that I want any of those inverters to run on OpenWRT .-) . Preferably though we watch out for Open Source projects like

@schwatter

schwatter commented Jul 31, 2026

Copy link
Copy Markdown

@Zurrmaxe I told you :) Btw i tested right now again with #define MAX_RETRANSMIT_COUNT 20 again. So it is too low.

Looks much like it, indeed. I do not have an exact idea what the basis of that counting of packages is. But I could imagine some scenario with those channel changes and queue problems in which a package was not counted as missed or partly missed. Ideally, the MAX_RETRANSMIT_COUNT should be .. what .. 0 ? So having the need for such a high number of again-transmitted-requests is an indication of something being wrong.

Please kindly once try that 5c1df91 branch with those 20.

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
But i think it is the "supporting_5000_MIT_8T" branch? I will build and test.

edit:
I build it. But the same with #define MAX_MIT_RETRANSMIT_COUNT 20

Gesendete Anfragen | 16 |  
Empfang Erfolgreich | 10 | 63 %
Empfang Fehler: Nichts empfangen | 0 | 0 %
Empfang Fehler: Teilweise empfangen | 5 | 31 %
Empfang Fehler: Beschädigt empfangen | 0 | 0 %
Gesendete Fragment Wiederanforderungen |  211

So i will go higher again to 40

edit2:
Maybe a problem with the refreshrate? I set it to 13sec in OpenDTU webinterface @ settings. Could be to fast.

@smoe

smoe commented Jul 31, 2026

Copy link
Copy Markdown

@Zurrmaxe I told you :) Btw i tested right now again with #define MAX_RETRANSMIT_COUNT 20 again. So it is too low.

Looks much like it, indeed. I do not have an exact idea what the basis of that counting of packages is. But I could imagine some scenario with those channel changes and queue problems in which a package was not counted as missed or partly missed. Ideally, the MAX_RETRANSMIT_COUNT should be .. what .. 0 ? So having the need for such a high number of again-transmitted-requests is an indication of something being wrong.
Please kindly once try that 5c1df91 branch with those 20.

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. But i think it is the "supporting_5000_MIT_8T" branch? I will build and test.

No, it is a fork of the repository, like the one you tried before that was from gueffn-hub, this one took gueff's work, rebased it on the current main and added some extras.

edit: I build it. But the same with #define MAX_MIT_RETRANSMIT_COUNT 20

Thank you!

Gesendete Anfragen | 16 |   Empfang Erfolgreich | 10 | 63 % Empfang Fehler: Nichts empfangen | 0 | 0 % Empfang Fehler: Teilweise empfangen | 5 | 31 % Empfang Fehler: Beschädigt empfangen | 0 | 0 % Gesendete Fragment Wiederanforderungen |  211

So i will go higher again to 40

So, for 16 packages the OpenDTU needs to ask 211+16=227 times and is successful only for 6 out of 10 packages is wanted to receive.

edit2: Maybe a problem with the refreshrate? I set it to 13sec in OpenDTU webinterface @ settings. Could be to fast.

Yes, please do.

My temporary explanation is that something is wrong with the channel transition. I presume the OpenDTU asks just so often until that channel iteration by some random chance is working. Well, not having any immediate other ideas, so be it.

It would possibly be interesting to learn what channel was successful in receiving the message and the how-many-th request it was to which was successfully replied.

@schwatter

Copy link
Copy Markdown

Even with 26sec i get:

Empfang Erfolgreich | 19 | 58 %
Empfang Fehler: Nichts empfangen | 0 | 0 %
Empfang Fehler: Teilweise empfangen | 13 | 39 %
Empfang Fehler: Beschädigt empfangen | 0 | 0 %
Gesendete Fragment Wiederanforderungen | 447

So again, i get back to #define MAX_MIT_RETRANSMIT_COUNT 40

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.

[Request] Support of new Hoymiles Inverter-Series MIT-4000/4500/5000-8T