Skip to content

SMA NaN check for values at night - #3675

Merged
benderl merged 4 commits into
openWB:masterfrom
seaspotter:fix_sma_nan
Aug 14, 2026
Merged

SMA NaN check for values at night#3675
benderl merged 4 commits into
openWB:masterfrom
seaspotter:fix_sma_nan

Conversation

@seaspotter

Copy link
Copy Markdown
Collaborator

Fix for #3674

Copilot AI left a comment

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.

Pull request overview

Fixes SMA Sunny Boy night-standby behavior where the inverter reports sentinel “NaN” values (e.g., -0x80000000) that previously triggered the peak filter and put the module into an error state (Issue #3674).

Changes:

  • Extend SMA “NaN” sentinel constants (INT16/INT32/UINT32/UINT64).
  • Treat sentinel “NaN” power readings as 0 at night (and reset related values) to avoid peak filter errors.
  • Broaden the “NaN” detection for energy counters to include additional sentinel representations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/modules/devices/sma/sma_sunny_boy/inverter.py
Comment thread packages/modules/devices/sma/sma_sunny_boy/inverter.py Outdated
@seaspotter
seaspotter requested review from ndrsnhs and removed request for ndrsnhs July 29, 2026 07:22
@seaspotter

Copy link
Copy Markdown
Collaborator Author

@ndrsnhs kannst du dir das mal ansehen?

@ndrsnhs

ndrsnhs commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Copilot kritisiert, dass hier eine Überfilterung stattfindet. In der Realität können die NaN Schranken aber nicht erreicht werden.

@benderl

benderl commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Copilot kritisiert, dass hier eine Überfilterung stattfindet. In der Realität können die NaN Schranken aber nicht erreicht werden.

Ich sehe das anders. Bei einem UINT_64 sind die anderen beiden NaN Werte durchaus gültige Zahlen. Das Problem ist, dass der Registertyp nicht mit beachtet wird.

@seaspotter

Copy link
Copy Markdown
Collaborator Author

Vorschlag ich setz je nach Inverter typ auf einen nan Wert und mach den Vergleich anschließend, so könnte man das umgehen?

Also so:

` def update(self) -> None:
unit = self.component_config.configuration.modbus_id

    if self.component_config.configuration.version == SmaInverterVersion.default:
        power_total = self.tcp_client.read_holding_registers(30775, ModbusDataType.INT_32, unit=unit) * -1
        power_nan = -self.SMA_INT32_NAN

        energy = self.tcp_client.read_holding_registers(30529, ModbusDataType.UINT_32, unit=unit)
        energy_nan = self.SMA_UINT32_NAN

        dc_power = (self.tcp_client.read_holding_registers(30773, ModbusDataType.INT_32, unit=unit) +
                    self.tcp_client.read_holding_registers(30961, ModbusDataType.INT_32, unit=unit)) * -1

        currents = self.tcp_client.read_holding_registers(30977, [ModbusDataType.INT_32] * 3, unit=unit)
        if all(c == self.SMA_INT32_NAN for c in currents):
            currents = None
        else:
            currents = [current / -1000 if current != self.SMA_INT32_NAN else 0 for current in currents]

    elif self.component_config.configuration.version == SmaInverterVersion.core2:
        power_total = self.tcp_client.read_holding_registers(40084, ModbusDataType.INT_16, unit=unit) * -10
        power_nan = -self.SMA_INT16_NAN * 10

        energy = self.tcp_client.read_holding_registers(40094, ModbusDataType.UINT_32, unit=unit) * 100
        energy_nan = self.SMA_UINT32_NAN * 100

        dc_power = self.tcp_client.read_holding_registers(40101, ModbusDataType.UINT_32, unit=unit) * -100

        currents = self.tcp_client.read_holding_registers(30977, [ModbusDataType.INT_32] * 3, unit=unit)
        if all(c == self.SMA_INT32_NAN for c in currents):
            currents = None
        else:
            currents = [current / -1000 if current != self.SMA_INT32_NAN else 0 for current in currents]

    elif self.component_config.configuration.version == SmaInverterVersion.datamanager:
        power_total = self.tcp_client.read_holding_registers(30775, ModbusDataType.INT_32, unit=unit) * -1
        power_nan = -self.SMA_INT32_NAN

        energy = self.tcp_client.read_holding_registers(30513, ModbusDataType.UINT_64, unit=unit)
        energy_nan = self.SMA_UINT64_NAN

        # Aus Kompatibilitätsgründen wird dc_power auf den Wert der AC-Wirkleistung gesetzt.
        dc_power = power_total

        # Der Data-Manager/Cluster-Controller bietet keine Modbus-Register mit Phasenströmen an.
        # Daher die Phasenströme berechnen (es wird davon ausgegangen, dass eine symmetrische
        # Erzeugung erfolgt).
        currents = [(power_total / 3 / 230)] * 3

    else:
        raise ValueError("Unbekannte Version " + str(self.component_config.configuration.version))

    # WR geht nachts in Standby und gibt einen NaN-Wert für die Leistung aus.
    if power_total == power_nan:
        power_total = 0
        dc_power = 0
        currents = [0, 0, 0]

    if energy == energy_nan:
        raise ValueError(
            f'Wechselrichter lieferte nicht plausiblen Zählerstand: {energy}. '
            'Sobald PV Ertrag vorhanden ist sollte sich dieser Wert ändern, '
            'andernfalls kann ein Defekt vorliegen.'
        )`

@benderl

benderl commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Vom Prinzip passt die Prüfung jetzt. Rein von der Struktur hast Du zwei Varianten umgesetzt.

currents wird direkt in den Systemspezifischen if-Blöcken auf NaN geprüft und dann in das richtige Format gebracht (z.B. / -1000). Für power_total, energyund ggf. dc_power wird in den if-Blöcken direkt das erwartete Format berechnet (z.B. * -1) und später in der Hauptschleife gegen NaN geprüft.

Das macht den Code für mich relativ schwer lesbar und verschleiert ggf. Probleme, wenn bei der Zuweisung von power_nan ein negatives Vorzeichen vergessen wird. Wie wäre es mit einer Hilfsfunktion check_nan(value, nan_value, default_value), welche direkt beim Auslesen der Register verwendet wird? Optional kann außer dem korrigierten Wert noch ein Statusflag als Bool zurückgegeben werden, wenn es Sinn für die weitere Verarbeitung macht.

Ungetesteter Pseudocode:

def check_nan(value, nan_value, default_value):
    if value == nan_value:
        return default_value, True
    return value, False

power_total, power_is_nan = check_nan(self.tcp_client.read_holding_registers(30775, ModbusDataType.INT_32, unit=unit), self.SMA_INT32_NAN, 0)
if not power_is_nan:  # oder weglassen, wenn `default_value` auch mit `-1` multipliziert werden kann
    power_total = power_total * -1

@seaspotter

seaspotter commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Danke für den Vorschlag Lutz, ich habs mal so umgebaut, bei mir läuft das so in meinem Testsystem mit nem SMA String WR. So ist es auch besser lesbar, der Vorschlag von mir war echt auch schlecht lesbar.

Comment thread packages/modules/devices/sma/sma_sunny_boy/inverter.py

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/modules/devices/sma/sma_sunny_boy/inverter.py:96

  • Im core2-Zweig wird energy auch dann skaliert (*= 100), wenn energy_is_nan True ist. Dadurch geht der (laut Kommentar gewünschte) rohe SMA-Sentinel-Wert in der Fehlermeldung verloren und der angezeigte Wert wird künstlich verändert. Skalierung daher nur anwenden, wenn kein NaN erkannt wurde.
            energy, energy_is_nan = self.check_nan(
                self.tcp_client.read_holding_registers(40094, ModbusDataType.UINT_32, unit=unit),
                self.SMA_UINT32_NAN, self.SMA_UINT32_NAN)
            energy *= 100

packages/modules/devices/sma/sma_sunny_boy/inverter.py:40

  • Die Rückgabetyp-Annotation ist als String geschrieben ("tuple[int, bool]"). Im Codebase werden die Built-in-Generics ohne Quotes verwendet (z.B. packages/modules/common/utils/peak_filter.py:24). Ohne Quotes ist die Annotation konsistenter und besser für Type-Checker/Refactoring.
    def check_nan(value: int, nan_value: int, default_value: int = 0) -> "tuple[int, bool]":

Added comments explaining the SMA sentinel values and their handling in Modbus.
@benderl
benderl merged commit 4bf0722 into openWB:master Aug 14, 2026
1 check passed
@seaspotter
seaspotter deleted the fix_sma_nan branch August 14, 2026 06: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.

[BUG] Peakfilter für SMA bei keiner Produktion nachts

4 participants