nmakel / solaredge_modbus

SolarEdge Modbus data collection library
MIT License
146 stars 36 forks source link

Adding additional parameters #36

Open fredlcore opened 3 years ago

fredlcore commented 3 years ago

I'm not sure if this would work, but here is a list of ModBus parameters that include additional ones (for example related to the battery) which are currently not supported: Power Control Open Protocol for SolarEdge Inverters.pdf (from https://www.photovoltaikforum.com/core/attachment/157903-power-control-open-protocol-for-solaredge-inverters-pdf/)

I would specifically be interested in those from the Global StorEdge Control Block (starting from E004 onwards), if possible in both read and write mode. This could be used to implement a software-controlled charge management either consumption/time-based or with even more complex dependencies on other devices.

Would this be possible to implement?

nmakel commented 3 years ago

Interesting! Thanks for sharing the document. Most of the battery registers are already supported, and I will take a look at the power control registers. Displaying the status should be no problem, while the writable registers I will need to have a better look at to make sure people won't be setting their houses on fire.

fredlcore commented 3 years ago

Maybe this could be done with a command line switch or something like that? If you have a look at my BSB-LAN project which deals with various heating systems, you can see that this also enables lots of features usually not available to the everyday user, and I was in doubt as well whether I should make these accessible, but in the end these devices (must) have security measures in place that prevent hazardous results. Plus, this is an official document which lists these parameters as read/writeable, so external software is allowed to make use of this.

I just tried adding a line in init.py such as

"c_storage_control_mode": (0xe004 + self.offset, 1, registerType.HOLDING, registerDataType.UINT16, int, "Storage Control Mode", "", 1),

and I can read the value without a problem then using read(), but it is not listed when running example.py --json.

fredlcore commented 3 years ago

Thanks for the new version! But did I get it right that parameter e004 is not yet implemented?

nmakel commented 3 years ago

I haven't had time to work through all of the storage and export control document. Feel free to send a pr.

I just tried adding a line in init.py such as

"c_storage_control_mode": (0xe004 + self.offset, 1, registerType.HOLDING, registerDataType.UINT16, int, "Storage Control Mode", "", 1),

That should work, but you will need to set the last variable (batch number) to 3 or 4.

fredlcore commented 3 years ago

Sorry, I didn't mean to push at all, I just read about the added parameters in the new release and wasn't sure if the code was working correctly with my battery. I've never coded in Python, so I'd be a bit reluctant to send a PR, but if it's just adding lines like the one you quoted, I could do that, however, I'm off-site until mid-August, so I cannot really test until then. As for the batch number, how do I know whether it's 3 or 4? And is there a mechanism that decodes the values into human-readable options?

nmakel commented 3 years ago

Sorry, I didn't mean to push at all, I just read about the added parameters in the new release and wasn't sure if the code was working correctly with my battery.

No worries.

As for the batch number, how do I know whether it's 3 or 4? And is there a mechanism that decodes the values into human-readable options?

The batch number is based on a maximum number of register that can be polled sequentially. It can be 3 if it fits in the range of the other registers in batch 3. Otherwise, it will become batch 4. This should only incur a small performance penalty.

Human readable options can be specified in the second to last parameter in a register definition. Usually this field is for a human readable unit (W, kW, %, for example), unless it is a list, in which case integer values can be mapped to human readable strings. Actually translating the register value to this mapped string still requires a bit of work, see examply.py line 51 for example.

fredlcore commented 3 years ago

Sorry, I still don't get the concept behind the batch number: When I look at all the entries, the number seems to increase from 1 to 4 every few entries. How/where is one batch defined/configured? As for human readable options, I think I got it. (At least) for my purpose, numeric values will probably be sufficient, but for the sake of completeness, it may be good to add them nevertheless.

nmakel commented 3 years ago

Sorry, I still don't get the concept behind the batch number: When I look at all the entries, the number seems to increase from 1 to 4 every few entries. How/where is one batch defined/configured?

Modbus registers are read from a device in sequential blocks. These blocks have a maximum size. The maximum size is not large enough to read all the registers we want in one read request. Therefore we split the reads so the total size of the request is less than the maximum, but reads as many registers in one go. This is faster than sending a read request for each register individually. Therefore, the batch number increases if the address to be read plus its size minus the first address read in that batch is greater than the maximum request size.

fredlcore commented 3 years ago

Ah, ok, thanks, I think I got it. Is the maximum request size defined somewhere or do I have to find out by trial and error? And should I add new parameter lines based on their address or add them at the end of each block?

fredlcore commented 3 years ago

I'm sorry, I don't think I get the way the self.registers works. If I add

            "storage_control_mode": (0xe004, 1, registerType.HOLDING, registerDataType.UINT16, int, "Storage Control Mode", "", 3),
            "storage_ac_charge_policy": (0xe005, 1, registerType.HOLDING, registerDataType.UINT16, int, "Storage AC Charge Policy", "", 3),
            "storage_ac_charge_limit": (0xe006, 2, registerType.HOLDING, registerDataType.FLOAT32, float, "Storage AC Charge Limit", "", 3),
            "storage_backup_reserved_setting": (0xe008, 2, registerType.HOLDING, registerDataType.FLOAT32, float, "Storage Backup Reserved Setting", "%", 3),
            "storage_default_mode": (0xe00a, 1, registerType.HOLDING, registerDataType.UINT16, int, "Storage Charge/Discharge Default Mode", "", 3),
            "rc_cmd_timeout": (0xe00b, 2, registerType.HOLDING, registerDataType.UINT32, int, "Remote Control Command Timeout", "s", 3),
            "rc_cmd_mode": (0xe00d, 1, registerType.HOLDING, registerDataType.UINT16, int, "Remote Control Command Mode", "", 3),
            "rc_charge_limit": (0xe00e, 2, registerType.HOLDING, registerDataType.FLOAT32, float, "Remote Control Command Charge Limit", "W", 3),
            "rc_discharge_limit": (0xe010, 2, registerType.HOLDING, registerDataType.FLOAT32, float, "Remote Control Command Discharge Limit", "W", 3),

before

            "rrcr_state": (0xf000, 1, registerType.HOLDING, registerDataType.UINT16, int, "RRCR State", "", 3),

the additional parameters are not shown when running example.py. However, if I comment out the lines from rrcr_state until cosphi, the new parameters show up, but some other values show up completely messed up:

            "rated_energy": -3.4028234663852886e+38,
            "maximum_charge_continuous_power": -3.4028234663852886e+38,
            "maximum_discharge_continuous_power": -3.4028234663852886e+38,
            "maximum_charge_peak_power": -3.4028234663852886e+38,
            "maximum_discharge_peak_power": -3.4028234663852886e+38,
            "average_temperature": -3.4028234663852886e+38,
            "maximum_temperature": 0.0,
            "instantaneous_voltage": -3.4028234663852886e+38,
            "instantaneous_current": -3.4028234663852886e+38,
            "instantaneous_power": 0.0,
            "lifetime_export_energy_counter": 0,
            "lifetime_import_energy_counter": 0,
            "maximum_energy": -3.4028234663852886e+38,
            "available_energy": -3.4028234663852886e+38,
            "soh": -3.4028234663852886e+38,
            "soe": -3.4028234663852886e+38,

I've tried batch 3 and 4, but no difference. Maybe you could try and add the lines above and then I know how to do it?

fredlcore commented 3 years ago

Correction, the invalid values seem to only come up occasionally, it now seems to work, but I still have to comment out the rrcr_state until cosphi lines.

nmakel commented 3 years ago

I hope to have time this weekend to start adding some of the other registers.

fredlcore commented 3 years ago

Thanks! In the meantime, can I somehow use a "fixed" attribute for the data type encoding when writing? For now, I only want to write UINT16 values to a holding register, so I assume that no encoding is necessary if I just pass the value directly?

nmakel commented 3 years ago

Thanks! In the meantime, can I somehow use a "fixed" attribute for the data type encoding when writing? For now, I only want to write UINT16 values to a holding register, so I assume that no encoding is necessary if I just pass the value directly?

You provide the register name to the write function. The encoding is then done depending on the register's data type. So, if for example you were able to write to the power_ac register, which has the following definition:

"power_ac": (0x9c93, 1, registerType.HOLDING, registerDataType.INT16, int, "Power", "W", 2)

The function inverter.write("power_ac", 1234) will have 1234 encoded as an int. I wouldn't recommend passing a string "1234", the modbus library might not be kind to you if you do this.

fredlcore commented 3 years ago

Thanks, passing an int works fine, I'm now wondering how to pass 32-Bit integer - these occupy two ModBus registers, if I'm not mistaken, and the endian-ness seems to be different. Could I still just do it the cheap way by sending the two bytes reversed to inverter.write() or do I have to call it twice with an increased address or completely different?

nmakel commented 3 years ago

Thanks, passing an int works fine,

Really, how? I just noticed the necessary function to encode data to a register (_encode_value) was missing.

Could I still just do it the cheap way by sending the two bytes reversed to inverter.write() or do I have to call it twice with an increased address or completely different?

Have a look at the _write function, and check out _encode_value. You'll see that your input is encoded according to the register type as defined in the register definition.

fredlcore commented 3 years ago

Yes, the function is (was?) missing, I just removed the encode_value function and passed the data directly like this: return self._write_holding_register(address, data) This works for int16 values, but not other data types. But I just saw that you added that function an hour ago, thanks!

fredlcore commented 3 years ago

And did you have a chance to figure out why adding parameters results in such a strange behaviour that some existing parameters have to be removed for new ones to work? And what bugs me more is the fact that non-INT16 values seem to be quite a bit off:

    "rc_cmd_timeout": 235929600,
    "rc_charge_limit": 2.00424861907959,
    "rc_discharge_limit": 2.00424861907959,

rc_cmd_timeout for example should be 3600 (the default value), and the charge and discharge limit also make no sense...

fredlcore commented 3 years ago

I think it's a matter of endianness: 3600 is 0x00000E10, the above value of 235929600 is 0x0E100000. However, I have not installed the most recent version, so in case this is also covered by your changes, please ignore!

herbi3 commented 2 years ago

I am interested in also changing the options on the inverter also (i.e when the feed in tariff for solar goes negative (getting charged for exporting) I would like to automate the option to change the inverter to zero export.

I am already this to read the data from the inverters, but it would be great to get some assistance on being able to "push" the option changes too. I don't have the experience, or the knowledge apart from basic python changes. do you or anyone have an example in python of pushing changes back to the inverter?

herbi3 commented 2 years ago

there is the repo https://github.com/binsentsu/home-assistant-solaredge-modbus where it is able to change the settings on the inverter by writing the changes to the inverter, but it is for homeassistant - is there something similar that could be implemented easily?

nmakel commented 2 years ago

@herbi3, have you tried using the write() function on the active_power_limit register?

herbi3 commented 1 year ago

thanks @nmakel, I got the active power limit to work, however i am trying to change the export control mode, limit and site limit (I have x2 inverters, trying to get it to still generate but only to match the load demand from the SE meter.)

I tried adding

"export_control_mode": (0xe000, 1, registerType.HOLDING, registerDataType.UINT16, int, "Export Control Mode", "", 1), export_control_limit_mode": (0xe001, 1, registerType.HOLDING, registerDataType.UINT16, int, "Export Control Limit Mode", "", 1), export_control_site_limit": (0xe002, 2, registerType.HOLDING, registerDataType.FLOAT32, int, "Export Control Site Limit", "W", 2),

ultimately I don't know what I'm doing, but I am giving it a good crack.

nmakel commented 1 year ago

I tried adding

"export_control_mode": (0xe000, 1, registerType.HOLDING, registerDataType.UINT16, int, "Export Control Mode", "", 1), export_control_limit_mode": (0xe001, 1, registerType.HOLDING, registerDataType.UINT16, int, "Export Control Limit Mode", "", 1), export_control_site_limit": (0xe002, 2, registerType.HOLDING, registerDataType.FLOAT32, int, "Export Control Site Limit", "W", 2),

ultimately I don't know what I'm doing, but I am giving it a good crack.

Trying this yourself is very much appreciated. I had a quick look at the documentation and it looks like it's a bit more convoluted than just writing to export_control_site_limit. There's a number of other registers that need to be set to the correct value for this to work.

I will have a look at defining these registers in the next day or so. After that it should be as simple as calling write in the correct order to a number of registers to do what you need.

nmakel commented 1 year ago

Ok @herbi3. Attached is a modified __init__.py which has the required registers and supporting functionality added.

Let me state the following before you begin: I have not tested this. I do not know what unintended consequences can result from messing with these values. Only do this if you have access to the inverter in order to revert any changes you make.

According to the documentation you'll need to:

init.txt

herbi3 commented 1 year ago

thank you @nmakel I really appreciate this and all the work you've put into this!

I tried the changing of settings and commit, however it I get this error from enum

Traceback (most recent call last): File "./solaredge-emoncms.py.bak.20221006", line 29, in <module> master = solaredge_modbus.Inverter(host="10.0.2.100", port=1502) File "/home/administrator/.local/lib/python3.8/site-packages/solaredge_modbus/__init__.py", line 493, in __init__ "reactive_power_config": (0xf104, 2, registerType.HOLDING, registerDataType.INT32, int, "Reactive Power Config", REACTIVE_POWER_CONFIG_MAP, 4), File "/usr/lib/python3.8/enum.py", line 384, in __getattr__ raise AttributeError(name) from None AttributeError: INT32

I saw that INT32 is missing from registerDataType, but im not cleaver enough to figure out what value to put into the register, I tried INT32 = 4 at first and got ValueError: not enough values to unpack (expected 8, got 7) when trying to write the data

herbi3 commented 1 year ago

Interesting - I didn't have to enable the advanced power control or change the RRCR mode (Although it is explicitly stated in the Solaredge documentation)

I wrote the changes for export_control_mode & export_control_limit_mode - once that was enabled, it started to try and read from export_control_site_limit which gave not a number NaN - After writing the value, it was then able to read the value during the inverter read all function.

nmakel commented 1 year ago

thank you @nmakel I really appreciate this and all the work you've put into this!

I tried the changing of settings and commit, however it I get this error from enum

Traceback (most recent call last): File "./solaredge-emoncms.py.bak.20221006", line 29, in <module> master = solaredge_modbus.Inverter(host="10.0.2.100", port=1502) File "/home/administrator/.local/lib/python3.8/site-packages/solaredge_modbus/__init__.py", line 493, in __init__ "reactive_power_config": (0xf104, 2, registerType.HOLDING, registerDataType.INT32, int, "Reactive Power Config", REACTIVE_POWER_CONFIG_MAP, 4), File "/usr/lib/python3.8/enum.py", line 384, in __getattr__ raise AttributeError(name) from None AttributeError: INT32

I saw that INT32 is missing from registerDataType, but im not cleaver enough to figure out what value to put into the register, I tried INT32 = 4 at first and got ValueError: not enough values to unpack (expected 8, got 7) when trying to write the data

Try 8.

nmakel commented 1 year ago

Interesting - I didn't have to enable the advanced power control or change the RRCR mode (Although it is explicitly stated in the Solaredge documentation)

I wrote the changes for export_control_mode & export_control_limit_mode - once that was enabled, it started to try and read from export_control_site_limit which gave not a number NaN - After writing the value, it was then able to read the value during the inverter read all function.

Sounds good. Does it work?

herbi3 commented 1 year ago

Interesting - I didn't have to enable the advanced power control or change the RRCR mode (Although it is explicitly stated in the Solaredge documentation)

I wrote the changes for export_control_mode & export_control_limit_mode - once that was enabled, it started to try and read from export_control_site_limit which gave not a number NaN - After writing the value, it was then able to read the value during the inverter read all function. Sounds good. Does it work?

this particular part works just as intended, thank you!

nmakel commented 1 year ago

this particular part works just as intended, thank you!

Great. Please let me know if there are any other issues with these changes. For example, does reading all of the registers work and return sensible values?

If all is well I can merge the changes so others can make use of it too.

herbi3 commented 1 year ago

I saw that INT32 is missing from registerDataType, but im not cleaver enough to figure out what value to put into the register, I tried INT32 = 4 at first and got ValueError: not enough values to unpack (expected 8, got 7) when trying to write the data Try 8.

When using 8 as the registerDataType I get UNSPEC_NOTIMPLEMENTED[dtype.name]: KeyError: 'INT32'

so I put in "INT32": 0x00000000, OR "INT32": 0x8000, the value returned for reactive_power_config goes from 4 to 262144

If I switch back to INT32 = 4 then reactive_power_config returns back to 4

advanced_power_control_enable returns 17096 when INT32 = 4 - when INT32 = 8 then it returns 65536

nonetheless, being able to change the maximum export to 0 when FiT is negative pricing is working

nmakel commented 1 year ago

My bad, I see what's happening. The int32 you added to registerDataType is fine, keep it at 8. Make sure it is second to last in the list.

The real issue is in the register definitions. Try adding , 1 after 0xf100 and 0xf101. In the sequence for those those two registers I forgot to put the length. Compared to the other registers the definition is a value short, hence the unpack error.

herbi3 commented 1 year ago

this particular part works just as intended, thank you!

Great. Please let me know if there are any other issues with these changes. For example, does reading all of the registers work and return sensible values?

If all is well I can merge the changes so others can make use of it too.

yes, reading all of the registers return sensible values, export_control_site_limit returns NaN if export_control_mode is not enabled and export_control_site_limit is not set. it'll throw an ignorable error, but for "cleanliness" others may wish to comment export_control_site_limit out if there's no intention of using it for them to prevent showing any errors during readall

herbi3 commented 1 year ago

My bad, I see what's happening. The int32 you added to registerDataType is fine, keep it at 8. Make sure it is second to last in the list.

The real issue is in the register definitions. Try adding , 1 after 0xf100 and 0xf101. In the sequence for those those two registers I forgot to put the length. Compared to the other registers the definition is a value short, hence the unpack error.

with that, I get:

Traceback (most recent call last): File "./solaredge-emoncms.py", line 32, in <module> mastervalues = master.read_all() File "/home/administrator/.local/lib/python3.8/site-packages/solaredge_modbus/__init__.py", line 412, in read_all register_batch = {k: v for k, v in registers.items() if (v[7] == batch)} File "/home/administrator/.local/lib/python3.8/site-packages/solaredge_modbus/__init__.py", line 412, in <dictcomp> register_batch = {k: v for k, v in registers.items() if (v[7] == batch)} IndexError: tuple index out of range

nmakel commented 1 year ago

Teaches me not to debug on a phone... I've attached a new __init__.py. Please give this one a go.

The values for reactive_power_config and reactive_power_response_time don't actually seem to be in the place the documentation says they are. I got things to look right according to how my inverter is configured, I'm curious whether they look right on your end. For example, is reactive_power_response_time set to 200ms in your setup?

Thanks for helping debug this one.

init.txt

herbi3 commented 1 year ago

great stuff!! yes, my inverters are both reporting reactive_power_response_time as 200ms and now advanced_power_control_enable is giving a sensible reading

Screen Shot 2022-10-12 at 06 55 08

nmakel commented 1 year ago

Cheers, thanks for letting me know.

herbi3 commented 1 year ago

just noting here, it looks like TCP Modbus is now available for all set app-enabled inverters over wifi (secured networks) or ethernet in the latest September 2022 setapp update. Screen Shot 2022-10-13 at 9 06 26 am

herbi3 commented 1 year ago

it seems writing the data comes out different as to what the setapp says.

when setting export_control_site_limit to e.g 30000 via modbus, the setapp shows as "not configured"

when I set it to 100000 within setapp, I get the response export_control_site_limit 8608746496

When I write export_control_site_limit as 8608746496 setapp shows it as 100000 also (testing this as a previous value, then ran .write and it showed as 100000 within setapp.)

I wonder if that's the same as why I haven't yet been successful in sending 1 to the commit_power_control_settings

I kept investigating earlier today because when export site limit is set to 0 (production to match load only) via modbus, quite often the reading on both inverters for all power_ac etc drop to 0, but the solaredge app is still reporting correct values.

Then setting to 8608746496 (100000w shown in app) the weirdness went away

¯_(ツ)_/¯

nmakel commented 1 year ago

¯(ツ)

Odd. This means the register definitions aren't quite right, yet. In the image you posted things looked right (when reading the registers, at least). Do you recall what SetApp thought was happening when the registers showed reactive_power_config 4, export_control_site_limit 30000?

herbi3 commented 1 year ago

export_control_site_limit showed in SetApp at “not configured” yet when reading via modbus, it showed “30000” which is what it was set to via modbus. Otherwise everything else seemed normal within the app. When setting this value to “0” via modbus, is when power_ac and most of the other power readings stayed at 0 or showed very low values. - change the limit back to 30000 and the weirdness goes away. Setting this value to 20000, showed as 2W limit within SetApp.

Changing the active_power_limit manually via modbus showed correct values in both reading via modbus and via the app too

if I change self.wordorder = Endian.Big > self.wordorder = Endian.Little then export_control_site_limit reads 100000 after it was set to 8608746496 via the .write command.

however, this then messes up reactive_power_response_time and a couple other registers report funny values as I guess they require self.wordorder = Endian.Big

herbi3 commented 1 year ago

@nmakel is there much luck with the export_control_site_limit between writing and reading? I have the modbus proxy infont of my inverters now, and can open access to yourself for testing remotely if that is possible

herbi3 commented 1 year ago

@fredlcore are you able to kindly share what you did for the endian-ness of specific registers?

If I read the registers as self.wordorder = Endian.Little I get the correct value for some items (i.e export_control_site_limit) but then scale registers naturally return incorrect values.

I am thinking of creating another class, essentially a duplicate of the inverter section, but with self.wordorder = Endian.Little instead, and only having the required registers within that new class where self.wordorder = Endian.Little is needed.

herbi3 commented 1 year ago

@nmakel @fredlcore

I ended up adding after class Inverter(SolarEdge): - I commented out the registers within class Inverter(SolarEdge): with the difference being self.wordorder = Endian.Little instead

class StorageInverter(SolarEdge):

    def __init__(self, *args, **kwargs):
        self.model = "StorageInverter"
#        self.wordorder = Endian.Big
        self.wordorder = Endian.Little

        super().__init__(*args, **kwargs)

        self.registers = {
            # name, address, length, register, type, target type, description, unit, batch
            "export_control_mode": (0xf700, 1, registerType.HOLDING, registerDataType.UINT16, int, "Export Control Mode", "", 1),
            "export_control_limit_mode": (0xf701, 1, registerType.HOLDING, registerDataType.UINT16, int, "Export Control Limit Mode", "", 1),
            "export_control_site_limit": (0xf702, 2, registerType.HOLDING, registerDataType.FLOAT32, float, "Export Control Site Limit", "W", 1),

            "storage_control_mode": (0xe004, 1, registerType.HOLDING, registerDataType.UINT16, int, "Storage Control Mode", "", 2),
            "storage_ac_charge_policy": (0xe005, 1, registerType.HOLDING, registerDataType.UINT16, int, "Storage AC Charge Policy", "", 2),
            "storage_ac_charge_limit": (0xe006, 2, registerType.HOLDING, registerDataType.FLOAT32, float, "Storage AC Charge Limit", "", 2),
            "storage_backup_reserved_setting": (0xe008, 2, registerType.HOLDING, registerDataType.FLOAT32, float, "Storage Backup Reserved Setting", "%", 2),
            "storage_default_mode": (0xe00a, 1, registerType.HOLDING, registerDataType.UINT16, int, "Storage Charge/Discharge Default Mode", "", 2),
            "rc_cmd_timeout": (0xe00B, 2, registerType.HOLDING, registerDataType.UINT32, int, "Remote Control Command Timeout", "s", 2),
            "rc_cmd_mode": (0xe00d, 1, registerType.HOLDING, registerDataType.UINT16, int, "Remote Control Command Mode", "", 2),
            "rc_charge_limit": (0xe00e, 2, registerType.HOLDING, registerDataType.FLOAT32, float, "Remote Control Command Charge Limit", "W", 2),
            "rc_discharge_limit": (0xe010, 2, registerType.HOLDING, registerDataType.FLOAT32, float, "Remote Control Command Discharge Limit", "W", 2)
        }

then in my script I called

storage = solaredge_modbus.StorageInverter(parent=master)
storage.read_all()

now I can read and write normal values into the registers.

Screen Shot 2023-02-02 at 09 30 45

herbi3 commented 1 year ago

@nmakel I have included my changes here

https://github.com/herbi3/soalredge-modbus-emoncms

S-Woodford commented 1 year ago

Guys, please forgive me jumping in here, especially given that what I'm trying to do appears (to me) to be much simpler than what you (collectively) have already achieved. Basically, I am trying to minimise overnight battery charging based on the Solcast for the following day using a Python script. I think I have most of the basic code written, but I can't seem to write to the Inverter to change battery charging mode.

In the thread above, it states: According to the documentation you'll need to:

I've read and re-read the code and the exchanges between nmakel, herbi3 and fredlcore (who pointed me towards this thread), but I'm at a loss to understand what I'm doing wrong (or not doing). Are you able to tell me exactly what code I need to add to my script to be able to set the inverter battery charging mode, please? Also, do you happen to know whether the maximum battery charge rate can be set? I see mention of max discharge rate, but not charge rate. Any and all input very gratefully received. Thanks in advance...

herbi3 commented 1 year ago

@S-Woodford - I found just sending the commands below did the trick. I don't think (after test) that RRCR has any impact, despite the solaredge documentation.

I use my own version from what @nmakel has done here, you would need to change it to my one (init.py this is in my version and in a pull request to this, otherwise you won't be able to use the below)

To charge all batteries connected to the inverter: master = solaredge_modbus.Inverter(host=LEADER_HOST, port=LEADER_PORT, retries=10, timeout=10, unit=1) storage = solaredge_modbus.StorageInverter(parent=master) storage.write("storage_control_mode", 4) #Set to REMOTE (option 4) storage.write("rc_cmd_mode", 3) #Set the mode, in this case 3 is Charge from PV+AC

you can also send: storage.write("rc_cmd_timeout", 300) #300s after rc_cmd_mode is set, return to default storage mode

this is just a basic example. if you want to charge from AC & PV, you'll need to set the AC charge policy and AC charge limit (if applicable to you)

S-Woodford commented 1 year ago

@herbi3

Thanks very much for your reply, and for the additional testing that you did on my behalf - much appreciated.

Unfortunately, I still can’t write to the inverter. Using your init.py (it’s in the same folder as my test script - do I need to do anything else to ‘activate’ it?) and the lines that you supplied below, I can get the code to return data for the ‘master’, but I get the following error message when attempting to connect to ’storage’: "Exception has occurred: AttributeError module 'solaredge_modbus' has no attribute 'StorageInverter' File "/Users/spenceandvik/Documents/Spence's Folder/SolarEdge_Predictive_Charging-main/batt_test2.py", line 27, in get_device_data data = solaredge_modbus.StorageInverter(parent=master).read_all() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/spenceandvik/Documents/Spence's Folder/SolarEdge_Predictive_Charging-main/batt_test2.py", line 82, in values = get_device_data(0, "storage") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: module 'solaredge_modbus' has no attribute ‘StorageInverter' “

At the moment, I have a very simple test script to try to write the storage_control_mode and the rc_cmd_mode values, but I get the error above using your method, and the error below using @nmakel’s method: “Exception has occurred: KeyError 'storage_control_mode' File "/Users/spenceandvik/Documents/Spence's Folder/SolarEdge_Predictive_Charging-main/opt_charge_2.py", line 110, in inverter.write("storage_control_mode", 4) KeyError: ‘storage_control_mode’ "

I wonder if it’s a firmware version, or whether I just don’t understand how to make Python do what I want?

Any further advice/input very gratefully received…

On 20 Apr 2023, at 06:15, herbi3 @.***> wrote:

@S-Woodford https://github.com/S-Woodford - I found just sending the commands below did the trick. I don't think (after test) that RRCR has any impact, despite the solaredge documentation.

I use my own version from what @nmakel https://github.com/nmakel has done here, you would need to change it to my one (init.py this is in my version and in a pull request to this, otherwise you won't be able to use the below)

To charge all batteries connected to the inverter: master = solaredge_modbus.Inverter(host=LEADER_HOST, port=LEADER_PORT, retries=10, timeout=10, unit=1) storage = solaredge_modbus.StorageInverter(parent=master) storage.write("storage_control_mode", 4) #Set to REMOTE (option 4) storage.write("rc_cmd_mode", 3) #Set the mode, in this case 3 is Charge from PV+AC

you can also send: storage.write("rc_cmd_timeout", 300) #300s after rc_cmd_mode is set, return to default storage mode

this is just a basic example. if you want to charge from AC & PV, you'll need to set the AC charge policy and AC charge limit (if applicable to you)

— Reply to this email directly, view it on GitHub https://github.com/nmakel/solaredge_modbus/issues/36#issuecomment-1515726823, or unsubscribe https://github.com/notifications/unsubscribe-auth/A6LRBTAU57MSF3O4GWOLIRTXCDBAHANCNFSM5A5J6GCQ. You are receiving this because you were mentioned.

[ { @.": "http://schema.org", @.": "EmailMessage", "potentialAction": { @.": "ViewAction", "target": "https://github.com/nmakel/solaredge_modbus/issues/36#issuecomment-1515726823", "url": "https://github.com/nmakel/solaredge_modbus/issues/36#issuecomment-1515726823", "name": "View Issue" }, "description": "View this Issue on GitHub", "publisher": { @.": "Organization", "name": "GitHub", "url": "https://github.com" } } ]

herbi3 commented 1 year ago

Gidday @S-Woodford

There’s away to import it locally, but i dont know how to do that.

I made the changes to this file in Ubuntu

~/.local/lib/python3.8/site-packages/solaredge_modbus/__init__.py

Backup the original __init__.py to something like __init__.py.bak

Then overwrite the entire file __init__.py with my one above. Then the StorageInverter and writing registers should work.