WouterTuinstra / Homeassistant-Growatt-Local-Modbus

Provides Home Assistant sensors for Growatt inverters that you can connect directly using modbus protocol
Apache License 2.0
25 stars 13 forks source link

Supporting MID 15KTL3-X #4

Open mikaabra opened 1 year ago

mikaabra commented 1 year ago

Hi,

I have a Growatt MID 15KTL3-X. It seems to not currently be supported by this project (when trying to add it, it says "device didn't respond"). I've found the modbus registers for it and have it working by using the following config in HA:

modbus:
  - name: "growatt-tcp"
    type: tcp
    host: 192.168.200.230
    port: 502
    sensors:
      - name: "growatt-tcp power"
        unit_of_measurement: W
        device_class: power
        slave: 1
        address: 1
        input_type: input
        scan_interval: 15
        data_type: int32
        count: 2
        scale: 0.1

This maps to the modbus input registers as found in the PDF at https://www.photovoltaicsolar.in/Growatt_Manual/MAX%20Series%20Modbus%20RTU%20Protocol.pdf

First group
--
0. | Inverter Status | Inverter run state | 0:waiting, 1:normal, 3:fault
1. | Ppv H | Input power (high) | 0.1W
2. | Ppv L | Input power (low) | 0.1W

This is similar but not the same to the ones already in this project, which seems to be float instead of int32.

    GrowattDeviceRegisters(
        name=ATTR_INPUT_POWER, register=1, value_type=float, length=2
    ),

When I use a modbus client directly to the device I get the following information which matches above pdf:

$ docker run --rm oitc/modbus-client:latest -s 192.168.200.230 -p 502 -t 4 -r 0 -l 4 -i 1
           HEX16  UINT16  INT16               BIT       HEX32  \
register                                                        
30000     0x0001       1      1  0000000000000001  0x00010000   
30001     0x0002       2      2  0000000000000010  0x00020001   
30002     0x5502   21762  21762  0101010100000010  0x55020002   
30003     0x18D0    6352   6352  0001100011010000  0x18D05502   

Now, I can go through and manually adding all of these registers in my HA configuration.yaml and solve my own problem, but I'd rather solve it for everybody by contributing to this project.

Do you believe my inverter uses a different protocol version other than the 1.20 and 3.15 already supported so my inverter would need to add a new protocol version? So it'd basically need a new file to inverter_120.py and inverter_315.py?

If it's needed, I'd like to do the work to add inverter.py file for my inverter but I would need help getting started. I can run this modbus-client (or anything else) to help identifying the device to help start the work.

WouterTuinstra commented 1 year ago

Hi,

I suspect that the used modbus framer for a direct connection to the device is different then how it’s currently configured.

I tested it with a serial to network conversion. However because you get the message that device didn’t respond. I think the cause is the framer.

This would be the item to look at: https://github.com/WouterTuinstra/Homeassistant-Growatt-Local-Modbus/blob/0ba0f232038d7f38ee37d1e4981ce04698150368/custom_components/growatt_local/API/growatt.py#L169

mikaabra commented 1 year ago

I use "Modbus over TCP" and not "modbus RTU over TCP" (the gateway I use converts from RTU to standard modbus as well as serial<->tcp), and looking at that code it seems to default to "Modbus RTU over TCP"?

I added:

from pymodbus.framer.socket_framer import ModbusSocketFramer

and then changed the framer to:

        if network_type.lower() == "tcp":
            self.client = AsyncModbusTcpClient(
                host,
                port if port else Defaults.TcpPort,
                framer=ModbusSocketFramer,
                timeout=timeout,
                retries=retries,
            )

Then everything works. So to support both of these modes one way would be that the current mode should be renamed to "TCP Modbus RTU" and there needs to be a new mode added called "TCP Modbus" that instead uses the socket framer?

PS. There are several entities that don't work properly, have wrong values etc, so the "everything works" might be an exaggeration. But approx half of the entities are getting data that are somewhat sane. I'll try to correlate what works and doesn't in the code and see if I can see something in common.

WouterTuinstra commented 1 year ago

I have added the option to select the Framer protocol used in the config flow.

Most likely when items don't align you will have to use the other protocol version. My expectation is that you need to use Protocol version 1.20

mikaabra commented 1 year ago

Thanks, I've tried it out but I now get "unknown error occured" when I first try to connect (when I enter the modbus server IP, port, modbus ID and modbus tcp/rtu settings).

This is what I found that seemed relevant in the log

Log Details (WARNING)
This error originated from a custom integration.
Logger: custom_components.growatt_local.API.growatt
Source: custom_components/growatt_local/API/growatt.py:369
Integration: Growatt Local ([documentation](https://github.com/WouterTuinstra/homeassistant-growatt-local-modbus), [issues](https://github.com/WouterTuinstra/Homeassistant-Growatt-Local-Modbus/issues))
First occurred: 15:37:38 (7 occurrences)
Last logged: 15:50:14

Inverter Modbus version not default supported.
Log Details (ERROR)
This error originated from a custom integration.
Logger: aiohttp.server
Source: custom_components/growatt_local/API/growatt.py:74
Integration: Growatt Local ([documentation](https://github.com/WouterTuinstra/homeassistant-growatt-local-modbus), [issues](https://github.com/WouterTuinstra/Homeassistant-Growatt-Local-Modbus/issues))
First occurred: 15:37:38 (9 occurrences)
Last logged: 15:50:14

Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/aiohttp/web_protocol.py", line 433, in _handle_request
    resp = await request_handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/aiohttp/web_app.py", line 504, in _handle
    resp = await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/aiohttp/web_middlewares.py", line 117, in impl
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/security_filter.py", line 85, in security_filter_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/forwarded.py", line 100, in forwarded_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/request_context.py", line 28, in request_context_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/ban.py", line 80, in ban_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/auth.py", line 236, in auth_middleware
    return await handler(request)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/view.py", line 148, in handle
    result = await handler(request, **request.match_info)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/config/config_entries.py", line 181, in post
    return await super().post(request, flow_id)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/http/data_validator.py", line 72, in wrapper
    result = await method(view, request, data, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/data_entry_flow.py", line 110, in post
    result = await self._flow_mgr.async_configure(flow_id, data)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 297, in async_configure
    result = await self._async_handle_step(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/data_entry_flow.py", line 393, in _async_handle_step
    result: FlowResult = await getattr(flow, method)(user_input)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/growatt_local/config_flow.py", line 383, in async_step_network
    await server.close()
  File "/config/custom_components/growatt_local/API/growatt.py", line 74, in close
    await self.client.close()
TypeError: object NoneType can't be used in 'await' expression
mikaabra commented 1 year ago

Never mind, on a hunch I restarted the entire machine (and not just HA itself) and then it worked to connect. Will try with 1.20 now.

mikaabra commented 1 year ago

Most of the sensors look fine now, the only one that looks wrong is "reactive wattage".

Sensors
AC frequency
49.99 Hz
Input 1 Amperage
9.0 A
Input 1 energy today
13.3 kWh
Input 1 total energy
3,178.2 kWh
Input 1 voltage
595.3 V
Input 1 Wattage
5,357.7 W
Input 2 Amperage
9.1 A
Input 2 energy today
13.8 kWh
Input 2 voltage
600.4 V
Input 2 Wattage
5,463.6 W
Intelligent Power Management temperature
63.1 °C
Internal wattage
10,821.3 W
Output 1 Amperage
14.8 A
Output 1 voltage
235.7 V
Output 1 Wattage
3,488.3 W
Output 2 Amperage
14.8 A
Output 2 voltage
236.6 V
Output 2 Wattage
3,501.6 W
Output 3 Amperage
14.9 A
Output 3 voltage
236.6 V
Output 3 Wattage
3,525.3 W
Output power
10,700.3 W
Reactive wattage
247,372,185.6 W
Real power output percentage
0%
Running hours
1197:38:02
Status
Normal
Temperature
74.3 °C
Total energy input
6,404.4 kWh
Total energy produced
6,615.6 kWh
WouterTuinstra commented 1 year ago

Allright nice to hear that it works, the modbus version warning is not really relevant. Although if you don't mind to share I would like to know what device information has been extracted. This is reported in the complete Home Assistant logs somewhere as a informational message.

The reactive wattage contained a copying error when I added the new protocol. I have updated the register value.

mikaabra commented 1 year ago

Can confirm now that reactive wattage looks much more reasonable. The only thing that shows up in the main overview is:

"Firmware: DL1.0ZBAA"

This is correct. I tried to turn on debug logging and reload the integration but the only thing I see are below. I removed the integration, restarted the machine (I got the same error again when trying to re-add), then added again, but nothing showed up in the log that seemed informative. If you can give better tip on how to get this information I'd gladly try.

2023-07-16 17:08:01.841 DEBUG (MainThread) [custom_components.growatt_local] Finished fetching growatt_local data in 0.343 seconds (success: True)
2023-07-16 17:08:03.934 DEBUG (MainThread) [custom_components.growatt_local.API.utils] split sequence keys: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 35, 36, 37, 38, 39, 40, 41, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 91, 92, 93, 94, 101, 104, 105, 234, 235]
2023-07-16 17:08:03.935 DEBUG (MainThread) [custom_components.growatt_local.API.utils] split sequence indexes based on key seperation: {11, 39}
2023-07-16 17:08:03.935 DEBUG (MainThread) [custom_components.growatt_local.API.utils] split sequence indexes based on maximum length: {36, 39}
2023-07-16 17:08:03.935 DEBUG (MainThread) [custom_components.growatt_local.API.utils] split sequence commom indexes: {39}
2023-07-16 17:08:03.935 DEBUG (MainThread) [custom_components.growatt_local.API.utils] determined key seqences ['start: 35, end: 106', 'start: 234, end: 236', 'start: 0, end: 11']
2023-07-16 17:08:03.945 DEBUG (MainThread) [custom_components.growatt_local] Finished fetching growatt_local data in 0.011 seconds (success: True)
2023-07-16 17:08:13.515 DEBUG (MainThread) [custom_components.growatt_local] Finished fetching growatt_local data in 0.318 seconds (success: True)
WouterTuinstra commented 1 year ago

The device information is only added to the logs when creating the device in HA