Skip to content

Sungrow PV Inverter and ESS Implementation - #3935

Merged
sfeilmeier merged 18 commits into
OpenEMS:developfrom
opernikus-common:feature/Sungrow
Sep 19, 2026
Merged

sfeilmeier merged 18 commits into
OpenEMS:developfrom
opernikus-common:feature/Sungrow

Conversation

@tsicking

Copy link
Copy Markdown
Contributor

Updated version of Sungrow implementation. Replaces #2528
This version is used on a handful of productive systems run by opernikus.

@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.97054% with 136 lines in your changes missing coverage. Please review.

Additional details and impacted files
@@              Coverage Diff              @@
##             develop    #3935      +/-   ##
=============================================
- Coverage      58.34%   55.46%   -2.88%     
- Complexity     19469    19533      +64     
=============================================
  Files           3306     3861     +555     
  Lines         161114   178070   +16956     
  Branches       11953    14818    +2865     
=============================================
+ Hits           93993    98743    +4750     
- Misses         63425    75440   +12015     
- Partials        3696     3887     +191     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sfeilmeier sfeilmeier 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.

See my remarks. Same applies to other similar files.

Who can work on these topics?

String ess_id() default "ess0";

@AttributeDefinition(name = "ESS target filter", description = "This is auto-generated by 'ESS-ID'.")
String ess_target() default "(enabled=true)";

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.

Please replace with ReferenceConfigurationPlugin

}

private void mapChannelValues() {
this.ess.getTotalDcPowerChannel().onSetNextValue(value -> {

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.

Why do you not map those Registers directly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So the alternative would be to make this a ModbusComponent, read the registers here and leave them out in the ESS implementation? This would mean that you send more modbus tasks per second but you can remove some channels from the ESS, right?
I have no strong opinion which is better, but this was the first thing that came into my mind. I just had a look at the GoodWe implementations and saw that the single string chargers have their own modbus protocol, while the two string chargers don't.
Now, as I write, one more options comes into my mind. If the charger is referenced in the ESS and not the other way round, one could do something like the following in the modbus protocol:

m(new UnsignedDoubleWordElement(5016)).build() //
								.onUpdateCallback(value -> this.charger._setActualPower(value)), //

Is that what you have in mind?

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.

Ok, I did not realize this is actually copying from ess component. Then it's ok. Only flaw is, that you would have to remove the listener on deactivate, otherwise the garbage collector will not be able to cleanup.

Comment thread io.openems.edge.sungrow/src/io/openems/edge/sungrow/ess/EssSungrow.java Outdated
*
* @param value the next value
*/
public default void _setGridMode(GridMode value) {

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.

No, it was replaced with ChannelUtils.setValue() by purpose. Please reset to develop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay, I see. I was wondering why this method disappeared. For what reason do you use ChannelUtils.setValue()?
The same then for ManagedSymmetricEss._setAllowedChargePower() I guess? But why is ManagedSymmetricEss._setAllowedDischargePower() still there?

@Reference
private ConfigurationAdmin cm;

@Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY)

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.

If you use GenerateTargetsFromReferences, you have to set the target property here - see documentation: https://github.com/OpenEMS/openems/blob/develop/io.openems.core.referencetarget/readme.adoc

this.config = config;
super.activate(context, config.id(), config.alias(), config.enabled());

if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", config.ess_id())) {

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.

Also this is obsolete then

}

private void mapChannelValues() {
this.ess.getTotalDcPowerChannel().onSetNextValue(value -> {

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.

Ok, I did not realize this is actually copying from ess component. Then it's ok. Only flaw is, that you would have to remove the listener on deactivate, otherwise the garbage collector will not be able to cleanup.


@ObjectClassDefinition(//
name = "ESS Sungrow", //
description = "Implements the Sungrow SH10RT Hybrid Ess")

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.

Do you know how different the protocols of Sungrow devices are? Should we probably rename the Factory-ID to something like Ess.Sungrow.Hybrid.* to have a namespace for future implementations?

"Modbus", config.modbus_id())) {
return;
}
this.getActivePowerChannel().onSetNextValue(value -> { //

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.

ElectricityMeter .calculatePhasesFromActivePower() does exactly this

@@ -0,0 +1,25 @@
package io.openems.edge.sungrow.dccharger;

import org.junit.Test;

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.

JUnit4 is deprecated. Please use Juniper

new ComponentTest(new SungrowDcChargerImpl()) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("ess", new EssSungrowImpl()) //
.activate(MyConfig.create() //

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 can only recommend having one test with strict-mode for Modbus devices.

@sfeilmeier
sfeilmeier merged commit 9b0353a into OpenEMS:develop Sep 19, 2026
2 checks passed
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