Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SAJ H1 hybrid inverters #18097

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

h3llrais3r
Copy link

@h3llrais3r h3llrais3r commented Jan 6, 2025

This PR contains the initial setup for a SAJ H1 inverter and is still work in progress for some additional meter sensors.

H1 inverter is quite similar as the H2 inverter, so I based myself the template for the H2 (https://github.com/evcc-io/evcc/blob/master/templates/definition/meter/saj-h2.yaml)

Documentation about H1:
https://img.saj-electric.com/file/H1-3-6K-S2-15%C2%A0user%20manual%20EN-V0.0-2023041004191770.pdf
https://github.com/sgsancho/saj_h1_s2_modbus_esp32/blob/main/documentacion/SAJ_Modbus_Communication_Protocol_2020.pdf

Included meters:

  • pv
    • power
    • energy
  • battery
    • power
    • energy
    • soc
    • batterymode
  • grid
    • power (smart meter grid power)
    • currents -> N/A

About the grid meter:
- I found a register which gives the total grid power, so adding the "power" shouldn't be a problem (individual powers for every phase are not available according to my knowledge)
- For the currents, I only find 1 register which seems to contain the sum of all currents across all phases, not for every single phase. Is it possible to add this single current or not? I only see "currents", not "current" as an option?
Grid has been added (power and energy).
Be aware that the grid meter requires the smart meter device (in my case dtsu666) to be installed together with the inverter.

I'm working on the battery control (I already found the similar registers as for the H2) but I would like to understand first how this works. Is there someone who can provide some info about the battery control of the H2 inverter?
As far as I understood the "backup mode" is used by default instead of the "self-use" mode?
@UDicke, it would be great if you could provide me some info as it seems that you did all the testing for the H2 inverter in #15988

@andig andig marked this pull request as draft January 6, 2025 21:54
@andig
Copy link
Member

andig commented Jan 6, 2025

For the currents, I only find 1 register which seems to contain the sum of all currents across all phases, not for every single phase. Is it possible to add this single current or not? I only see "currents", not "current" as an option?

No, but total power is fine.

@andig andig added the devices Specific device support label Jan 6, 2025
@h3llrais3r
Copy link
Author

I think I have to omit the "grid" part, as it's not consistent.
I have a 1-phase H1 inverter, added on my 3-phase home installation, so the "grid" sensors are not correct.
I only have correct values from the additional smart meter that can be connected to the inverter.
Since not everyone has this, I think it's better to omit the "grid" part.

@premultiply
Copy link
Member

I only have correct values from the additional smart meter that can be connected to the inverter.

This is exactly what is needed for grid meter.

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 7, 2025

I only have correct values from the additional smart meter that can be connected to the inverter.

This is exactly what is needed for grid meter.

Yes, I know, but this is only possible if you have the converter in combination with the meter.
Since this is not the default setup, I won't add the meter sensors to the template.
They can always be added as custom sensor when needed, right?
Or is there a way to provide 2 alternatives for the grid? 1 for normal 1-phase setup and 1 for the setup with the connected smart meter? (I can only test the latter as I have the smart meter setup).
So basically, can I add 2 "grid" meters for the inverter? F.e. "grid-1phase" and "grid-3phase"?

@andig
Copy link
Member

andig commented Jan 7, 2025

1p cannot be a grid meter, so 3p should be the case to add here imho.

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 7, 2025

Ok, I'm going to leave out the grid, as it's too different (I just remember that with an older firmware, the meter readout was even in another register).
Grid sensor will have to be added manually for this inverter, unless someone can confirm that the following registers are working as well...

meters:
  - name: saj.h1.grid
    type: custom
    power:
      source: modbus
      id: 1
      uri: <ip>:502
      register:
        address: 0x40A7 # TotalGridPowerWatt (giving wrong values for me, only smart meter gives correct values)
        type: holding
        decode: int16
      timeout: 3s
  - name: saj.h1.grid.meter
    type: custom
    power:
      source: modbus
      id: 1
      uri: <ip>:502
      register:
        address: 0x40A1 # SmartMeterGridPowerWatt (undocumented)
        type: holding
        decode: int16
      timeout: 3s

@h3llrais3r
Copy link
Author

Small question: is there a way to also test the "battery control" options with custom meters, without creating the template?

@andig
Copy link
Member

andig commented Jan 7, 2025

Sure. See

evcc meter --help

Ok, I'm going to leave out the grid, as it's too different

What's "too different" about just using another register?

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 7, 2025

evcc meter --help

Cool, thanks!

I mean, the specific register works in my case with a specific firmware version (and with the smart meter).
I know that in an older firmware version, it was another register that provided the correct grid power.
That's what I mean with "too different", there is not a single register that can be used in all cases to provide the correct grid power. It all depends on the type of installation. (in my case: a single phase inverter installed on a 3-phase system with a smart meter)
So I would rather leave it out and let it up to the user to choose another meter for the grid.

@h3llrais3r
Copy link
Author

Ok, maybe I'm going to add it anyway, but it's the data coming from the smart meter in this case.
This gives the right data, see below:
image

@h3llrais3r
Copy link
Author

Last part is the battery control, but for that I need some more time/testing and clarification about the modes, especially about the "hold" mode... I suppose this tries to keep the battery with a specific soc?
I see that it uses the backup mode of the inverter to keep the battery at a specific SOC, correct?
Does it always try to keep it at the maxsoc value or it it determined dynamically with the SOC of the battery at the time the mode is engaged?

image

@UDicke
Copy link

UDicke commented Jan 7, 2025

The "hold" just sets the backup-soc to the maxsoc value set in ui or evcc.yaml. This disables use of the battery under the set value for maxsoc and will not initiate charging,
This does only work in backup-mode, because the register for backup-soc is used for this.

@UDicke
Copy link

UDicke commented Jan 7, 2025

grafik
it seems that the h1 model has a difference to h2 regarding the backup mode. Just look at the docs you provided.
this could meen, that the battery will load to a specific value when in backup mode.
Documentation about H1:
https://img.saj-electric.com/file/H1-3-6K-S2-15%C2%A0user%20manual%20EN-V0.0-2023041004191770.pdf
https://github.com/sgsancho/saj_h1_s2_modbus_esp32/blob/main/documentacion/SAJ_Modbus_Communication_Protocol_2020.pdf

in this case I would switch to "self-use mode" and propose to use register "to be find out" for "hold"
The doc is not obvios which register migt be the one to use. I guess you have to find out which one is to use...
grafik

@premultiply
Copy link
Member

"hold" means disable discharge of the battery.
If that is not possible battery should be disabled completly.

Whenever possible do not use any SoC or time related controls for battery control.

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 8, 2025

Hmm, still some more investigation to be done then...

But just to be sure with the H2:

  • when you put it in backup mode and the backup soc on maxsoc (95), your battery is not charged automatically? Not even if your current battery soc is lower then the maxsoc? Because according to the documentation, it should only charge when battery soc is below the backup soc...
    image
  • if you say it's not charged automatically from grid, would it start charging if you have solar then? In my case it started charging from grid in backup mode with backup soc on 95%.

in this case I would switch to "self-use mode" and propose to use register "to be find out" for "hold"

I'm not sure there is a register in self-use mode that disables the discharge... the only one I can think of is the register that determines the total discharge power?

BATSOC_H and BATSOC_L are just the boundaries of the battery: currently set to 20 and 100 (were the default settings when the inverter got installed)

image

Besides that, the inverter also has a passive mode... but I think this completely deactivates everthing (solar & battery).
I see that the H2 also has this mode (according to the modbus register documentation)

image

@UDicke
Copy link

UDicke commented Jan 8, 2025

In Backup Mode the Battery is charged from solar up to parameter 13894 "maxload" which is 100%, but not from grid. The Batterie is discharched to the value in 13902 backup soc or to 13892 "lowlimit soc" depending which register has the higher value.

I did know about the existense of passiv mode, but ignored this according to doc from saj.

The total discharge soc 13892 has Shops discharging also in self-use mode.
Here is a screenshot from home assistant...

image

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 8, 2025

I'm having similar registers for H1:

  • 3250H #BatDisDepth (80%) -> I think this one is replaced by the 2 below where you can define lower and upper boundaries
  • 3273H #BatSetSOC_H (100%)
  • 3274H #BatSetSOC_L (20%)
  • 3271H #BackModSOCRetain (80%) -> should correspond to the BatSocLimitKeep, currently on 80 (default) but could be set to 95 as in your case.

H2 registers:

  • 13892 = 3644h #BatOnGridDisDepth
  • 13894 = 3646h #BatcharDepth
  • 13905 = 3651h #BatSocLimitkeep

But apparently the behavior of the backup mode is different then for the H1, as it always charges until the backup SOC, even from grid (while for the H2 it's only with solar according to your findings).
I really need to find a way to block the charging from the grid...

I'm just brainstorming:
What if we set the backup SOC to the current battery SOC instead of the maxsoc?
That would mean that system would not charge anymore from grid, but still allows being charged with solar (as it starts behaving as 'self-use'?). But since all solar would go to the EV, the battery SOC would probably remain constant?

In the meantime, I've also checked with my installer and they say that if you would put the inverter in passive mode, the battery stops charging and discharging. I suppose we could use this as well, as this is also considered as "hold", as it cannot be charged and discharged when in this state. This should be ok, as the goal is to put everything in the EV, right?

EDIT: I've just tested the passive mode and it seems to block the discharging and charging... I can't test the solar charging for now as there is no more PV production for the moment.

EDTI: I've just tested the backup mode by setting it to the backup SOC to the current battery SOC (35%) and it also now seems to lock the battery. It doesn't import from grid, as the backup SOC = current battery SOC. So if EVCC is able to set the current battery SOC to the backup SOC when the 'hold' batterymode is engaged, it should probably work as well.
Is this something that we can do? Using the current SOC when setting the backup mode?
Something like this:
image

@andig
Copy link
Member

andig commented Jan 11, 2025

But apparently the behavior of the backup mode is different then for the H1, as it always charges until the backup SOC, even from grid (while for the H2 it's only with solar according to your findings).

Sounds like you can use limitsoc instead of batterymode? See fox-ess for an example!

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 11, 2025

What's the behavior of this limitsoc and what's the difference with the batterymode?
Because I still need the charge functionality for the batterymode by using the 'time-of-use' inverter app mode.

What about my suggestion of writing the value of the current soc as value for the backup soc retain for the hold batterymode?

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 12, 2025

I did some digging in the code, and I think I start to understand the usage of the limitsoc
image

It's the register to which a soc value is written, based on the logic in the image above, right? And it's being written with the value from this logic, when the battery mode is changed, right?

However, since I need to change the appMode of the inverter itself also to be able to "charge from grid", I probably need to write some custom logic for the batterymode instead, like for the H2 inverter?
So I'm coming back to my previous question if I can use the .soc value in the template?
image

@h3llrais3r
Copy link
Author

Ok... I tried to test this via a custom meters and it seems to work. (tested via evcc meter -b <mode>)
image

However, I would also try to test this with the template... just to be sure that the template is fine.
As far as I understood, this is only possible when running it from the devcontainer, right?

1 more question for the template: is it possible to skip a step, depending on the value of {{ .defaultmode }}?
The step in the red block is actually only required when the defaultmode is set to 3 (backup)
image
If it's not possible, no big deal to always write it, as the value is only used when the app mode is set to 3 (backup)

@UDicke
Copy link

UDicke commented Jan 15, 2025

I am interested in your complete custom configuration. Would you please provide this?

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 16, 2025

Sure, will commit it already, so you can have a look.
But I still need to properly test it, as I don't know when those "batterymodes" are being set?
Is there some documentation when these modes are enabled in the flow?
Or are they all manual triggers?

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 20, 2025

@andig , @premultiply, I finally managed to setup my dev container to run it from source with the newly created template.
However, when trying to use the battery meter, I'm getting the following error:
[main ] FATAL 2025/01/20 19:16:51 cannot create meter 'battery': cannot create meter type 'template': cannot create meter type 'custom': battery mode: strconv.ParseInt: parsing "<no value>": invalid syntax

Any idea what could be wrong with my template?

EDIT: after some investigation, it comes back again to the same question if it's possible to use the current soc value to write it to the backup soc.
image
When trying to do it like above, the code throws the error.
When I'm using a static value (f.e. 20), the template compiles.
So is there a way to write the current {{ .soc }} value to another register?

PS: it would be great if you could also reply on my questions related the batterymode in my previous comment: #18097 (comment)

Be aware that the power is coming from the additional smart meter that should be installed together with the inverter.
Without the smart meter, the readings won't be correct or 0.
@andig
Copy link
Member

andig commented Jan 22, 2025

So is there a way to write the current {{ .soc }} value to another register?

Template is evaluated at startup. {{ .soc }} does not give you the soc at runtime! To do that at runtime you can use the go plugin to copy from one input to one output. Check the other templates, there are examples of doing so.

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 22, 2025

So is there a way to write the current {{ .soc }} value to another register?

Template is evaluated at startup. {{ .soc }} does not give you the soc at runtime! To do that at runtime you can use the go plugin to copy from one input to one output. Check the other templates, there are examples of doing so.

I assume you mean something like this?
image

So I need to read the soc register in the in and write its value to the out register?

@andig
Copy link
Member

andig commented Jan 22, 2025

Exactly!

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 22, 2025

I suppose the script value should reflect the name value for in and out?
So I can use holdsoc or something similar for that?

EDIT: I think this should be it: https://github.com/evcc-io/evcc/pull/18097/files#diff-4abfe070b75d043f244499a32940f0b439c9d526d0c72a62ee02a259e94f58feR139-R163

The only way to test this is probably via the evcc meter -b option, right?

@h3llrais3r
Copy link
Author

I'm still struggling to get this properly tested.
When running it as a custom meter, it works, but when running it via the template in the dev container, I'm getting modbus errors...
I've defined a default timeout of 5s to all modbus sources... I've done this because I sometimes get timeout error when reading an address when I'm not setting a timeout of at least 3s. Should I define also a timeout for writing a register? (I did it in the template, but not sure if this could also cause some problem)

Can someone do a review on my template to see if I did something wrong?

@andig
Copy link
Member

andig commented Jan 23, 2025

What errors?

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 23, 2025

I'll have retest to get the exact modbus errors, as I've already closed my command window and they are gone... :(
The reading parts were fine, but from the moment I try to write, the answer of the write was not correct (I think) and it started to fail all over the place then.

But I'm testing this over the AIO3 module of SAJ, which seems to be known as quite unstable....
This might have something to do with it as well, because when I'm reading/writing the modbus registers manually, it mostly works.
So I'm a bit afraid that it will be very hard for me to test, due to this...

Anyway, will retry and post the errors here. (probably this evening).

@UDicke
Copy link

UDicke commented Jan 23, 2025

But I testing this over the AIO3 module of SAJ, which seems to be known as quite unstable....

@h3llrais3r, one hint for you: the SAJ / AIO3 connection does not like to much connections, I believe there is a limit of 2 concurrently.
I use therefore https://github.com/TCzerny/ha-modbusproxy
first connection for a wallbox, second for SAJ / AIO3 and third is still available.
may be you did know that already or this is not the problem...

@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 23, 2025

But I testing this over the AIO3 module of SAJ, which seems to be known as quite unstable....

@h3llrais3r, one hint for you: the SAJ / AIO3 connection does not like to much connections, I believe there is a limit of 2 concurrently. I use therefore https://github.com/TCzerny/ha-modbusproxy first connection for a wallbox, second for SAJ / AIO3 and third is still available. may be you did know that already or this is not the problem...

Thanks for the tip. I'm going to have a look at it.
But I have some timeouts from time to time with only 1 connection as well (with only my ha integration that reads out the data from the inverter).
It might be that I'm now getting the errors on the moment that my ha integration is also reading out some values (which it does every 10s). So a modbus proxy might be an idea, but I'll need to have a look at it.

I'm currently testing the saj h1 template with custom sensors, which seems to work fine at first glance. I'm using my home assistant sensors for reading/writing to the registers instead of doing the modbus calls directly from the template.
My home assistant integration is also using the AIO3 module, but for those I'm using modbus over mqqt (I'm sending mqtt modbus messages, which are processed by the AIO3 module, which converts it to modbus).
So I assume that this is also counting as a connection. I have the impression that if I send a few requests quickly after each other (like it's done in the template to set a battery mode), there is a higher change of failure/connection issues.
The connection via modbus over mqtt is a lot more stable than the direct modbus connection over tcp.

- use 'self-use' inverter mode for 'normal' battery mode
- use 'passive' inverter mode for 'hold' battery mode
- use 'time-of-use' inverter mode for 'charge' battery mode
@h3llrais3r
Copy link
Author

h3llrais3r commented Jan 30, 2025

What errors?

I took some time to test it again with the modbus over tcp, and getting the following error when making too much calls too fast:
read failed: modbus: exception '2' (illegal data address), function '3'

I still feel the issue is related to the modbus over tcp via the AIO3 module.
I've read on the ha forum that other people are also facing similar issues with it (not being very stable) and the solution would be using a dedicated device (like the elfin ee10a, see https://community.home-assistant.io/t/saj-solar-inverter/111932/277).

So I know the registers are correct but the readout can be unstable when using it directly over the AIO3 module.
image

In the same home assistant community forum, they are also saying that some registers are not even writable via the AIO3 module. (see https://community.home-assistant.io/t/saj-solar-inverter/111932/205)
This could be right, as I was unable to test the writing (also getting modbus exceptions).

So any suggestion on how to proceed with this?
Maybe we can just use the template for the readouts and skip the battery control (which needs to write to the registers)?

Or can we just keep it (but not really tested for now)?
I know the registers for battery control are also correct, as I'm currently using ha sensors for doing the writing of the same registers. (which work flawlessly, as it uses a far more stable connection via modbus over mqqt)

@github-actions github-actions bot added the stale Outdated and ready to close label Feb 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
devices Specific device support stale Outdated and ready to close
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants