GithubHelp home page GithubHelp logo

jedie / inverter-connect Goto Github PK

View Code? Open in Web Editor NEW
23.0 7.0 6.0 514 KB

Get information from Deye Microinverter

Home Page: https://pypi.org/project/inverter-connect/

Python 100.00%
cli deye inverter python solar-energy homeassistant raspberry-pi solar-system

inverter-connect's Introduction

inverter-connect

New maintainer wanted! I will no longer use Deye devices. Partly because of the #RelaisGate.

tests codecov inverter-connect @ PyPi Python Versions License GPL-3.0-or-later

Get information from Deye Microinverter

The whole thing is just a learning exercise for now. We will see.

quickstart

overview

  • clone the sources
  • Bootstrap and create default user settings by just call ./cli.py edit-settings
  • Change the settings for your needs
  • ...use the commands... ;)
  • Setup systemd service to publish the inventer values to a Home Assistant instance via MQTT

Currently just clone the project and just start the cli (that will create a virtualenv and installs every dependencies)

Note: Please enable https://www.piwheels.org/ if you are on a Raspberry Pi !

e.g.:

~$ git clone https://github.com/jedie/inverter-connect.git
~$ cd inverter-connect
~/inverter-connect$ ./cli.py --help

The output of ./cli.py --help looks like:

Usage: ./cli.py [OPTIONS] COMMAND [ARGS]...

╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ --help      Show this message and exit.                                                          │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────╮
│ debug-settings        Display (anonymized) MQTT server username and password                     │
│ edit-settings         Edit the settings file. On first call: Create the default one.             │
│ inverter-version      Print all version information of the inverter                              │
│ print-at-commands     Print one or more AT command values from Inverter.                         │
│ print-values          Print all known register values from Inverter, e.g.:                       │
│ publish-loop          Publish current data via MQTT for Home Assistant (endless loop)            │
│ read-register         Read register(s) from the inverter                                         │
│ set-time              Set current date time in the inverter device.                              │
│ systemd-debug         Print Systemd service template + context + rendered file content.          │
│ systemd-remove        Write Systemd service file, enable it and (re-)start the service. (May     │
│                       need sudo)                                                                 │
│ systemd-setup         Write Systemd service file, enable it and (re-)start the service. (May     │
│                       need sudo)                                                                 │
│ systemd-status        Display status of systemd service. (May need sudo)                         │
│ systemd-stop          Stops the systemd service. (May need sudo)                                 │
│ test-mqtt-connection  Test connection to MQTT Server                                             │
│ version               Print version and exit                                                     │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

Setup systemd services for Home Assistant

Update your settings via: ./cli.py edit-settings and insert MQTT credentials in section [mqtt]

Check also the config section [systemd] and [systemd.template_context] but normally they must not be changed ;)

To verify your settings, call: ./cli.py debug-settings

To see the systemd service file content, just call: ./cli.py systemd-debug

Note: Some of the systemd commands, needs sudo because a normal user can't change systemd services! You will see permission errors with a hint to call the cli with sudo ;)

If everything looks okay, setup and start the systemd service with: sudo ./cli.py systemd-setup

Check the services with: sudo ./cli.py systemd-status

most important commands

publish-loop

Help from ./cli.py print-values --help Looks like:

Usage: ./cli.py publish-loop [OPTIONS]

 Publish current data via MQTT for Home Assistant (endless loop)
 The "Daily Production" count will be cleared in the night, by set the current date time via
 AT-command.

╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ *  --ip             TEXT                                  IP address of your inverter [required] │
│ *  --port           INTEGER                               Port of inverter services              │
│                                                           [default: 48899]                       │
│                                                           [required]                             │
│ *  --inverter       [deye_2mppt|deye_4mppt|deye_sg04lp3]  Prefix of yaml config files in         │
│                                                           inverter/definitions/                  │
│                                                           [default: deye_2mppt]                  │
│                                                           [required]                             │
│    --verbosity  -v  INTEGER RANGE [0<=x<=3]               Verbosity level; Accepts integer value │
│                                                           e.g.: "--verbose 2" or can be count    │
│                                                           e.g.: "-vv"                            │
│                                                           [default: 0; 0<=x<=3]                  │
│    --help                                                 Show this message and exit.            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

print-values

Help from ./cli.py print-values --help Looks like:

Usage: ./cli.py print-values [OPTIONS]

 Print all known register values from Inverter, e.g.:
 .../inverter-connect$ ./cli.py print-values

╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ *  --ip             TEXT                                  IP address of your inverter [required] │
│ *  --port           INTEGER                               Port of inverter services              │
│                                                           [default: 48899]                       │
│                                                           [required]                             │
│ *  --inverter       [deye_2mppt|deye_4mppt|deye_sg04lp3]  Prefix of yaml config files in         │
│                                                           inverter/definitions/                  │
│                                                           [default: deye_2mppt]                  │
│                                                           [required]                             │
│    --verbosity  -v  INTEGER RANGE [0<=x<=3]               Verbosity level; Accepts integer value │
│                                                           e.g.: "--verbose 2" or can be count    │
│                                                           e.g.: "-vv"                            │
│                                                           [default: 0; 0<=x<=3]                  │
│    --compact    -c                                        Only show the values concerning power  │
│                                                           generation                             │
│    --help                                                 Show this message and exit.            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

Example output of print-values call:

print-values


print-at-commands

Help from ./cli.py print-at-commands --help Looks like:

Usage: ./cli.py print-at-commands [OPTIONS] [COMMANDS]...

 Print one or more AT command values from Inverter.
 Use all known AT commands, if no one is given, e.g.:
 .../inverter-connect$ ./cli.py print-at-commands
 Or specify one or more AT-commands, e.g.:
 .../inverter-connect$ ./cli.py print-at-commands WEBVER .../inverter-connect$ ./cli.py
 print-at-commands WEBVER WEBU
 e.g.: Set NTP server, enable NTP and check the values:
 .../inverter-connect$ ./cli.py print-at-commands NTPSER=192.168.1.1 NTPEN=on NTPSER NTPEN
 wait a while and request the current date time:
 .../inverter-connect$ ./cli.py print-at-commands NTPTM
 (Note: The prefix "AT+" will be added to every command)

╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ *  --ip             TEXT                     IP address of your inverter [required]              │
│ *  --port           INTEGER                  Port of inverter services [default: 48899]          │
│                                              [required]                                          │
│    --verbosity  -v  INTEGER RANGE [0<=x<=3]  Verbosity level; Accepts integer value e.g.:        │
│                                              "--verbose 2" or can be count e.g.: "-vv"           │
│                                              [default: 0; 0<=x<=3]                               │
│    --help                                    Show this message and exit.                         │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

Example output of print-at-commands call:

print-at-commands


read-register

Help from ./cli.py read-register --help Looks like:

Usage: ./cli.py read-register [OPTIONS] REGISTER LENGTH

 Read register(s) from the inverter
 e.g.: read 3 registers starting from 0x16:
 .../inverter-connect$ ./cli.py read-register 0x16 3
 e.g.: read the first 32 registers:
 .../inverter-connect$ ./cli.py read-register 0 32
 The start address can be pass as decimal number or as hex string, e.g.: 0x123

╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ *  --ip             TEXT                     IP address of your inverter [required]              │
│ *  --port           INTEGER                  Port of inverter services [default: 48899]          │
│                                              [required]                                          │
│    --verbosity  -v  INTEGER RANGE [0<=x<=3]  Verbosity level; Accepts integer value e.g.:        │
│                                              "--verbose 2" or can be count e.g.: "-vv"           │
│                                              [default: 0; 0<=x<=3]                               │
│    --help                                    Show this message and exit.                         │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

Example output of read-register call:

read-register


start development

For development, we have a separate CLI, just call it:

~/inverter-connect$ ./dev-cli.py --help

The output of ./dev-cli.py --help looks like:

Usage: ./dev-cli.py [OPTIONS] COMMAND [ARGS]...

╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ --help      Show this message and exit.                                                          │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────╮
│ check-code-style            Check code style by calling darker + flake8                          │
│ coverage                    Run and show coverage.                                               │
│ create-default-settings     Create a default user settings file. (Used by CI pipeline ;)         │
│ fix-code-style              Fix code style of all inverter source code files via darker          │
│ install                     Run pip-sync and install 'inverter' via pip as editable.             │
│ mypy                        Run Mypy (configured in pyproject.toml)                              │
│ publish                     Build and upload this project to PyPi                                │
│ safety                      Run safety check against current requirements files                  │
│ test                        Run unittests                                                        │
│ tox                         Run tox                                                              │
│ update                      Update "requirements*.txt" dependencies files                        │
│ update-test-snapshot-files  Update all test snapshot files (by remove and recreate all snapshot  │
│                             files)                                                               │
│ version                     Print version and exit                                               │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

credits

Others before me have done good work. In particular, I have learned a lot from the following projects:

The included definitions yaml files are from:

https://github.com/StephanJoubert/home_assistant_solarman/tree/main/custom_components/solarman/inverter_definitions

various links

inverter-connect's People

Contributors

jedie avatar kwaaak avatar nandbert2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

inverter-connect's Issues

Mac error

Hi,

thanks for the awesome project!

when running the cli.py --help on a Mac it installed all dependencies as expected and then gives this error message:

NotADirectoryError: Directory does not exists: "/etc/systemd/system"

Thanks for any help.

Total Production for modules in wrong order

Hej,

I run a SUN1300-2000G3-EU-230 with only two solar pannels attached.
According to SOLARMAN the pannels are "1" and "2" (at least those are the only modules showing values in SOLARMAN).

This is also in line with what your program shows in terms of Voltage, Current and Power for module 1 and 2.
What is shown wrong is the "Total Production 3" - The value shown should be for "Total Production 2" and most likely the registers for "Total Production 2" and "Total Production 3" are wrong.

Since I only have two modules, I can't confirm my assumption about the "Total Production 3/4" registers.

Attached is a diff with the suggested changes.
deye_4mppt.yaml.diff.txt

Without patch:
image

With patch applied:
image

Cheers,
Bjoern

set-time command sets wrong register values

On my Deye Sun 600 inverter the set-time command sets wrong register values.
After calling this command today (30.04.2023 14:20:22) output of the software looks like this:

┏━━━━┳━━━━━┳━━━━━┳━━━━┳━━━┓
┃ Counter ┃ Address ┃ Address ┃ Value ┃ Value ┃
┃               ┃  (hex)     ┃   (dec)    ┃ (hex)  ┃ (dec) ┃
┡━━━━━╇━━━━━╇━--━━━╇━-━━╇━━━━┩
│       1          │  0x16        │      22         │  06        │     6      │
│       2         │  0x17         │      23         │  17        │    23     │
│       3         │  0x18        │      24          │  04       │     4      │
│       4         │  0x19        │      25          │  1e        │    30     │
│       5         │  0x1a        │      26          │  0e        │    14     │
│       6         │  0x1b       │      27           │  14        │    20     │
└────┴-────┴─-─----─┴──-─┴ ───┘

First it seems that the addressing is wrongly shown as the values are written in 16 bit registers instead of 8 bit, so address 0x16 would contain 0x0617 and 0x17 contains 0x041e etc. after this call.

Second it is not clear to me where the 0x06 in register 0x16 is coming from. In my understanding register 0x16 should contain YY/MM, 0x17 should contain DD/HH and 0x18 should contain MM/SS. After the call 06/YY, MM/DD, HH/MM is written there instead. So seems like shifted for one byte by 0x06 in register 0x16.

For me it should look like this:

━━━━┳━━━━━┳━━━━━┳━━━━┳━━━━┳━━━┓
┃ Counter ┃ Address ┃ Address ┃ Value ┃ Value ┃Value
┃               ┃  (hex)     ┃   (dec)    ┃ (hex)  ┃ (dec) ┃(parsed)
┡━━━━━╇━━━━━╇━--━━━╇━-━━╇━-━━╇━━━━┩
│       1          │  0x16        │      22         │  1704        │     5892      │ 23/04
│       2         │  0x17         │      23         │  1e0e        │    7694     │ 30/40
│       3         │  0x18        │      24          │  1416       │     5142      │ 20/22
└────┴-────┴─-─----─┴──-─ --┴ ──-─┴───┘

Errors after ./cli.py edit-settings

Hello i downloaded the Package and the i get the following errors, may you can help me, Thanks

root@raspimessen /opt/fhem/bin/inverter-connect # ./cli.py edit-settings
Create virtual env here: /opt/fhem/bin/inverter-connect/.venv-app
The virtual environment was not created successfully because ensurepip is not
available. On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.

apt-get install python3-venv

You may need to use sudo with that command. After installing the python3-venv
package, recreate your virtual environment.

Failing command: ['/opt/fhem/bin/inverter-connect/.venv-app/bin/python3', '-Im', 'ensurepip', '--upgrade', '--default-pip']

root@raspimessen /opt/fhem/bin/inverter-connect # apt-get install python3-venv
Paketlisten werden gelesen… Fertig
Abhängigkeitsbaum wird aufgebaut… Fertig
Statusinformationen werden eingelesen… Fertig
python3-venv ist schon die neueste Version (3.9.2-3).
Das folgende Paket wurde automatisch installiert und wird nicht mehr benötigt:
libjs-node-uuid
Verwenden Sie »apt autoremove«, um es zu entfernen.
0 aktualisiert, 0 neu installiert, 0 zu entfernen und 0 nicht aktualisiert.
root@raspimessen /opt/fhem/bin/inverter-connect # ./cli.py edit-settings

  • /opt/fhem/bin/inverter-connect/.venv-app/bin/python -m pip install -U pip-tools

/opt/fhem/bin/inverter-connect/.venv-app/bin/python: No module named pip
Traceback (most recent call last):
File "/opt/fhem/bin/inverter-connect/./cli.py", line 115, in
main(sys.argv)
File "/opt/fhem/bin/inverter-connect/./cli.py", line 97, in main
verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip-tools')
File "/opt/fhem/bin/inverter-connect/./cli.py", line 81, in verbose_check_call
return subprocess.check_call(popen_args)
File "/usr/lib/python3.9/subprocess.py", line 373, in check_call
raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '(PosixPath('/opt/fhem/bin/inverter-connect/.venv-app/bin/python'), '-m', 'pip', 'install', '-U', 'pip-tools')' returned non-zero exit status 1.

support for Deye hybrid high voltage inverters

Hi,
I am trying to set up my new Deye SUN-12K-SG01HP3-EU-AM2, however I get parse errors when using flag print-values on "deye_2mppt".
I assume it needs a new definition file for this high voltage inverter in /inverters.
In below project I found a working definition file.
My question now is can anyone point me to how to translate below format to the yaml format that is required for print-values to work?

https://github.com/kbialek/deye-inverter-mqtt/blob/main/src/deye_sensors_deye_sg01hp3.py

print-at-commands shows wrong results after the first AT command failed

Hi,

Today the first query of an AT command failed. As a result, the wrong values were displayed for the subsequent AT commands. For the current command the result of the previously executed command was displayed - as this example shows:

# ./cli.py print-at-commands inverter 

+ /home/carsten/inverter-connect/.venv-app/bin/inverter_app print-at-commands inverter

inverter v0.4.0
INFO:inverter.connection:Connect to inverter:48899...
InverterInfo(ip='192.168.xxx.xxx', mac='xxxxxxxxxxxx', serial=xxxxxxxxxx)

        * AT+VER       : WARNING:inverter.connection:Get no response from inverter: timed out - retry...
retry...-1
        * AT+BVER      : HF4.13.23-YZ (2023-03-01 15:00 2M)
        * AT+HWVER     : Non-standard version, 0xC1272
        * AT+WEBVER    : U06,6
        * AT+YZVER     : V1.0.24
        * AT+PING      : MW3_16U_5406_1.56
        * AT+TIME      : -3
        * AT+NDBGS     : 5,60,120
        * AT+WIFI      : 0
        * AT+WMODE     : UP
        * AT+WEBU      : STA
[...]

Expected result:

# ./cli.py print-at-commands inverter 

+ /home/carsten/inverter-connect/.venv-app/bin/inverter_app print-at-commands inverter

inverter v0.4.0
INFO:inverter.connection:Connect to inverter:48899...
InverterInfo(ip='192.168.xxx.xxx', mac='xxxxxxxxxxxx', serial=xxxxxxxxxx)

        * AT+VER       : WARNING:inverter.connection:Get no response from inverter: timed out - retry...
retry...-1
        * AT+BVER      : Non-standard version, 0xC1272
        * AT+HWVER     : U06,6
        * AT+WEBVER    : V1.0.24
        * AT+YZVER     : MW3_16U_5406_1.56
        * AT+PING      : -3
        * AT+TIME      : 5,60,120
        * AT+NDBGS     : 0
        * AT+WIFI      : UP
        * AT+WMODE     : STA
[...]

I use a Deye SUN300G3 EU 230 inverter.

That do you think about this behaviour?

Regards,
Carsten

.\cli.py --help not working

Hi I try to to run i with the raspberry pi. It always produces an error. Please see the output of the terminal. I updated to the newest version, but the result is the same. Do you have any idea?
Thanks and BR
Alfons

pi@raspberrypi:~/inverter-connect $ python3.9 ./cli.py --help

+ /home/pi/inverter-connect/.venv/bin/pip-sync /home/pi/inverter-connect/requirements.dev.txt

Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple, https://www.piwheels.org/simple
Collecting arrow==1.2.3 (from -r /tmp/tmprb_8bxwc (line 1))
  Using cached arrow-1.2.3-py3-none-any.whl (66 kB)
Collecting astor==0.8.1 (from -r /tmp/tmprb_8bxwc (line 4))
  Using cached https://www.piwheels.org/simple/astor/astor-0.8.1-py2.py3-none-any.whl (27 kB)
Collecting autopep8==2.0.2 (from -r /tmp/tmprb_8bxwc (line 7))
  Using cached autopep8-2.0.2-py2.py3-none-any.whl (45 kB)
Collecting binaryornot==0.4.4 (from -r /tmp/tmprb_8bxwc (line 10))
  Using cached https://www.piwheels.org/simple/binaryornot/binaryornot-0.4.4-py2.py3-none-any.whl (9.0 kB)
Collecting black==23.3.0 (from -r /tmp/tmprb_8bxwc (line 13))
  Using cached black-23.3.0-py3-none-any.whl (180 kB)
Collecting bleach==6.0.0 (from -r /tmp/tmprb_8bxwc (line 39))
  Using cached bleach-6.0.0-py3-none-any.whl (162 kB)
Collecting bx-py-utils==80 (from -r /tmp/tmprb_8bxwc (line 42))
  Using cached https://www.piwheels.org/simple/bx-py-utils/bx_py_utils-80-py3-none-any.whl (45 kB)
Collecting cachetools==5.3.0 (from -r /tmp/tmprb_8bxwc (line 45))
  Using cached cachetools-5.3.0-py3-none-any.whl (9.3 kB)
Collecting certifi==2022.12.7 (from -r /tmp/tmprb_8bxwc (line 48))
  Using cached certifi-2022.12.7-py3-none-any.whl (155 kB)
Collecting cffi==1.15.1 (from -r /tmp/tmprb_8bxwc (line 51))
  Using cached cffi-1.15.1.tar.gz (508 kB)
  Preparing metadata (setup.py) ... done
Collecting chardet==5.1.0 (from -r /tmp/tmprb_8bxwc (line 116))
  Using cached chardet-5.1.0-py3-none-any.whl (199 kB)
Collecting charset-normalizer==3.1.0 (from -r /tmp/tmprb_8bxwc (line 119))
  Using cached charset_normalizer-3.1.0-py3-none-any.whl (46 kB)
Collecting codespell==2.2.4 (from -r /tmp/tmprb_8bxwc (line 195))
  Using cached codespell-2.2.4-py3-none-any.whl (234 kB)
Collecting colorama==0.4.6 (from -r /tmp/tmprb_8bxwc (line 198))
  Using cached https://www.piwheels.org/simple/colorama/colorama-0.4.6-py2.py3-none-any.whl (25 kB)
Collecting cookiecutter==2.1.1 (from -r /tmp/tmprb_8bxwc (line 201))
  Using cached cookiecutter-2.1.1-py2.py3-none-any.whl (36 kB)
Collecting coverage==7.2.3 (from -r /tmp/tmprb_8bxwc (line 204))
  Using cached coverage-7.2.3.tar.gz (757 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Installing backend dependencies ... done
  Preparing metadata (pyproject.toml) ... error
  error: subprocess-exited-with-error
  
  × Preparing metadata (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [66 lines of output]
      running dist_info
      creating /tmp/pip-modern-metadata-j03uhkpl/coverage.egg-info
      writing /tmp/pip-modern-metadata-j03uhkpl/coverage.egg-info/PKG-INFO
      writing dependency_links to /tmp/pip-modern-metadata-j03uhkpl/coverage.egg-info/dependency_links.txt
      writing entry points to /tmp/pip-modern-metadata-j03uhkpl/coverage.egg-info/entry_points.txt
      writing requirements to /tmp/pip-modern-metadata-j03uhkpl/coverage.egg-info/requires.txt
      writing top-level names to /tmp/pip-modern-metadata-j03uhkpl/coverage.egg-info/top_level.txt
      writing manifest file '/tmp/pip-modern-metadata-j03uhkpl/coverage.egg-info/SOURCES.txt'
      reading manifest file '/tmp/pip-modern-metadata-j03uhkpl/coverage.egg-info/SOURCES.txt'
      reading manifest template 'MANIFEST.in'
      no previously-included directories found matching 'doc/_build'
      no previously-included directories found matching 'doc/_spell'
      no previously-included directories found matching 'tests/eggsrc/build'
      adding license file 'LICENSE.txt'
      writing manifest file '/tmp/pip-modern-metadata-j03uhkpl/coverage.egg-info/SOURCES.txt'
      creating '/tmp/pip-modern-metadata-j03uhkpl/coverage-7.2.3.dist-info'
      Traceback (most recent call last):
        File "/home/pi/inverter-connect/.venv/lib/python3.10/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
          main()
        File "/home/pi/inverter-connect/.venv/lib/python3.10/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
        File "/home/pi/inverter-connect/.venv/lib/python3.10/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 149, in prepare_metadata_for_build_wheel
          return hook(metadata_directory, config_settings)
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/build_meta.py", line 380, in prepare_metadata_for_build_wheel
          self.run_setup()
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/build_meta.py", line 338, in run_setup
          exec(code, locals())
        File "<string>", line 224, in <module>
        File "<string>", line 214, in main
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/__init__.py", line 107, in setup
          return distutils.core.setup(**attrs)
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/_distutils/core.py", line 185, in setup
          return run_commands(dist)
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/_distutils/core.py", line 201, in run_commands
          dist.run_commands()
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 969, in run_commands
          self.run_command(cmd)
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/dist.py", line 1244, in run_command
          super().run_command(command)
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 988, in run_command
          cmd_obj.run()
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/command/dist_info.py", line 104, in run
          bdist_wheel = self.get_finalized_command('bdist_wheel')
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/_distutils/cmd.py", line 304, in get_finalized_command
          cmd_obj = self.distribution.get_command_obj(command, create)
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 860, in get_command_obj
          klass = self.get_command_class(command)
        File "/tmp/pip-build-env-tnozhfrh/overlay/lib/python3.10/site-packages/setuptools/dist.py", line 989, in get_command_class
          self.cmdclass[command] = cmdclass = ep.load()
        File "/usr/local/lib/python3.10/importlib/metadata/__init__.py", line 171, in load
          module = import_module(match.group('module'))
        File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
          return _bootstrap._gcd_import(name[level:], package, level)
        File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
        File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
        File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
        File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
        File "<frozen importlib._bootstrap_external>", line 883, in exec_module
        File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
        File "/tmp/pip-build-env-tnozhfrh/normal/lib/python3.10/site-packages/wheel/bdist_wheel.py", line 28, in <module>
          from .macosx_libfile import calculate_macosx_platform_tag
        File "/tmp/pip-build-env-tnozhfrh/normal/lib/python3.10/site-packages/wheel/macosx_libfile.py", line 43, in <module>
          import ctypes
        File "/usr/local/lib/python3.10/ctypes/__init__.py", line 8, in <module>
          from _ctypes import Union, Structure, Array
      ModuleNotFoundError: No module named '_ctypes'
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.
Traceback (most recent call last):
  File "/home/pi/inverter-connect/.venv/bin/pip-sync", line 8, in <module>
    sys.exit(cli())
  File "/home/pi/inverter-connect/.venv/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/home/pi/inverter-connect/.venv/lib/python3.10/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/home/pi/inverter-connect/.venv/lib/python3.10/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/pi/inverter-connect/.venv/lib/python3.10/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/home/pi/inverter-connect/.venv/lib/python3.10/site-packages/piptools/scripts/sync.py", line 174, in cli
    sync.sync(
  File "/home/pi/inverter-connect/.venv/lib/python3.10/site-packages/piptools/sync.py", line 244, in sync
    run(  # nosec
  File "/usr/local/lib/python3.10/subprocess.py", line 526, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['/home/pi/inverter-connect/.venv/bin/python', '-m', 'pip', 'install', '-r', '/tmp/tmprb_8bxwc', '--extra-index-url', 'https://www.piwheels.org/simple']' returned non-zero exit status 1.
Traceback (most recent call last):
  File "/home/pi/inverter-connect/./cli.py", line 115, in <module>
    main(sys.argv)
  File "/home/pi/inverter-connect/./cli.py", line 101, in main
    verbose_check_call(PIP_SYNC_PATH, str(DEP_LOCK_PATH))
  File "/home/pi/inverter-connect/./cli.py", line 81, in verbose_check_call
    return subprocess.check_call(popen_args)
  File "/usr/local/lib/python3.9/subprocess.py", line 373, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '(PosixPath('/home/pi/inverter-connect/.venv/bin/pip-sync'), '/home/pi/inverter-connect/requirements.dev.txt')' returned non-zero exit status 1.

Calling edit-settings fails if no config file exists

Editing the configuration settings fails if the configuration file does not exist yet.

# ./cli.py --help

+ /home/carsten/inverter-connect/.venv-app/bin/inverter_app --help

Use user settings file: /home/carsten/.config/inverter-connect/inverter-connect.toml...



╭─────────────────────────────────────────── Error ────────────────────────────────────────────╮
│                                                                                              │
│                                                                                              │
│     [yellow]No settings created yet[/yellow]: [green](Hint: call "edit-settings" first!)     │
│                                                                                              │
│                                                                                              │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯

# ./cli.py edit-settings

+ /home/carsten/inverter-connect/.venv-app/bin/inverter_app edit-settings

Use user settings file: /home/carsten/.config/inverter-connect/inverter-connect.toml...



╭─────────────────────────────────────────── Error ────────────────────────────────────────────╮
│                                                                                              │
│                                                                                              │
│     [yellow]No settings created yet[/yellow]: [green](Hint: call "edit-settings" first!)     │
│                                                                                              │
│                                                                                              │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯

# ll ~/.config/inverter-connect/
insgesamt 0

Affected version:

  • current main branch

Workaround:

  • create empty configuration file

    # touch ~/.config/inverter-connect/inverter-connect.toml
    
  • execute ./cli.py edit-settings again

What do you think about this behavior?

Regards,
Carsten

filter wrong values

Sometimes there are some "out-of-range" values, e.g.:

image

We should validate and reject these values.

Discovery in current Home Assistant v2023.6.1 fails

The configuration sent via MQTT all seems to indicate numeric values, but some actually have string values (like 'Disabled'), which leads to errors in home assistant.

The error message looks like this:

ValueError: Sensor sensor.restore_factory_settings has device class 'energy', state class 'measurement' unit '' and suggested precision 'None' thus indicating it has a numeric value; however, it has the non-numeric value: 'Disabled' (<class 'str'>)

image

ERROR: Can not execute `setup.py` since setuptools is not available in the build environment.

When I try to run cli.py I get the error

  Using cached MarkupSafe-2.1.3.tar.gz (19 kB)
  Preparing metadata (setup.py) ... error
  error: subprocess-exited-with-error
  
  × python setup.py egg_info did not run successfully.
  │ exit code: 1
  ╰─> [1 lines of output]
      ERROR: Can not execute `setup.py` since setuptools is not available in the build environment.
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

The hint that the setup tools are missing seems to be misleading. According to the complete output

Requirement already satisfied: setuptools in ./.venv-app/lib/python3.9/site-packages (from pip-tools) (58.1.0)

setuptools are installed.

Here is the complete output:

# ./cli.py --help 
Create virtual env here: /tmp/inverter-connect/.venv-app

+ /tmp/inverter-connect/.venv-app/bin/python -m pip install -U pip

Requirement already satisfied: pip in ./.venv-app/lib/python3.9/site-packages (23.0.1)
Collecting pip
  Using cached pip-23.1.2-py3-none-any.whl (2.1 MB)
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 23.0.1
    Uninstalling pip-23.0.1:
      Successfully uninstalled pip-23.0.1
Successfully installed pip-23.1.2

+ /tmp/inverter-connect/.venv-app/bin/python -m pip install -U pip-tools

Collecting pip-tools
  Downloading pip_tools-6.14.0-py3-none-any.whl (55 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 55.9/55.9 kB 620.0 kB/s eta 0:00:00
Collecting build (from pip-tools)
  Using cached build-0.10.0-py3-none-any.whl (17 kB)
Collecting click>=8 (from pip-tools)
  Using cached click-8.1.3-py3-none-any.whl (96 kB)
Requirement already satisfied: pip>=22.2 in ./.venv-app/lib/python3.9/site-packages (from pip-tools) (23.1.2)
Requirement already satisfied: setuptools in ./.venv-app/lib/python3.9/site-packages (from pip-tools) (58.1.0)
Collecting wheel (from pip-tools)
  Using cached wheel-0.40.0-py3-none-any.whl (64 kB)
Collecting tomli (from pip-tools)
  Using cached tomli-2.0.1-py3-none-any.whl (12 kB)
Collecting packaging>=19.0 (from build->pip-tools)
  Using cached packaging-23.1-py3-none-any.whl (48 kB)
Collecting pyproject_hooks (from build->pip-tools)
  Using cached pyproject_hooks-1.0.0-py3-none-any.whl (9.3 kB)
Installing collected packages: wheel, tomli, packaging, click, pyproject_hooks, build, pip-tools
Successfully installed build-0.10.0 click-8.1.3 packaging-23.1 pip-tools-6.14.0 pyproject_hooks-1.0.0 tomli-2.0.1 wheel-0.40.0

+ /tmp/inverter-connect/.venv-app/bin/pip-sync /tmp/inverter-connect/requirements.txt

Ignoring error when setting locale: unsupported locale setting
Collecting arrow==1.2.3 (from -r /tmp/tmpu2cisu1b (line 1))
  Using cached arrow-1.2.3-py3-none-any.whl (66 kB)
Collecting backoff==2.2.1 (from -r /tmp/tmpu2cisu1b (line 4))
  Using cached backoff-2.2.1-py3-none-any.whl (15 kB)
Collecting binaryornot==0.4.4 (from -r /tmp/tmpu2cisu1b (line 7))
  Using cached binaryornot-0.4.4-py2.py3-none-any.whl (9.0 kB)
Collecting bx-py-utils==83 (from -r /tmp/tmpu2cisu1b (line 10))
  Using cached bx_py_utils-83-py3-none-any.whl (46 kB)
Collecting certifi==2023.5.7 (from -r /tmp/tmpu2cisu1b (line 13))
  Using cached certifi-2023.5.7-py3-none-any.whl (156 kB)
Collecting chardet==5.1.0 (from -r /tmp/tmpu2cisu1b (line 16))
  Using cached chardet-5.1.0-py3-none-any.whl (199 kB)
Collecting charset-normalizer==3.1.0 (from -r /tmp/tmpu2cisu1b (line 19))
  Using cached charset_normalizer-3.1.0-py3-none-any.whl (46 kB)
Collecting cookiecutter==2.1.1 (from -r /tmp/tmpu2cisu1b (line 95))
  Using cached cookiecutter-2.1.1-py2.py3-none-any.whl (36 kB)
Collecting ha-services==0.3.2 (from -r /tmp/tmpu2cisu1b (line 98))
  Using cached ha_services-0.3.2-py3-none-any.whl (53 kB)
Collecting idna==3.4 (from -r /tmp/tmpu2cisu1b (line 101))
  Using cached idna-3.4-py3-none-any.whl (61 kB)
Collecting jinja2==3.1.2 (from -r /tmp/tmpu2cisu1b (line 104))
  Using cached Jinja2-3.1.2-py3-none-any.whl (133 kB)
Collecting jinja2-time==0.2.0 (from -r /tmp/tmpu2cisu1b (line 107))
  Using cached jinja2_time-0.2.0-py2.py3-none-any.whl (6.4 kB)
Collecting manageprojects==0.12.1 (from -r /tmp/tmpu2cisu1b (line 110))
  Using cached manageprojects-0.12.1-py3-none-any.whl (80 kB)
Collecting markdown-it-py==2.2.0 (from -r /tmp/tmpu2cisu1b (line 113))
  Using cached markdown_it_py-2.2.0-py3-none-any.whl (84 kB)
Collecting markupsafe==2.1.3 (from -r /tmp/tmpu2cisu1b (line 116))
  Using cached MarkupSafe-2.1.3.tar.gz (19 kB)
  Preparing metadata (setup.py) ... error
  error: subprocess-exited-with-error
  
  × python setup.py egg_info did not run successfully.
  │ exit code: 1
  ╰─> [1 lines of output]
      ERROR: Can not execute `setup.py` since setuptools is not available in the build environment.
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.
Traceback (most recent call last):
  File "/tmp/inverter-connect/.venv-app/bin/pip-sync", line 8, in <module>
    sys.exit(cli())
  File "/tmp/inverter-connect/.venv-app/lib/python3.9/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/tmp/inverter-connect/.venv-app/lib/python3.9/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/tmp/inverter-connect/.venv-app/lib/python3.9/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/tmp/inverter-connect/.venv-app/lib/python3.9/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/tmp/inverter-connect/.venv-app/lib/python3.9/site-packages/piptools/scripts/sync.py", line 196, in cli
    sync.sync(
  File "/tmp/inverter-connect/.venv-app/lib/python3.9/site-packages/piptools/sync.py", line 244, in sync
    run(  # nosec
  File "/usr/local/lib/python3.9/subprocess.py", line 528, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['/tmp/inverter-connect/.venv-app/bin/python', '-m', 'pip', 'install', '-r', '/tmp/tmpu2cisu1b']' returned non-zero exit status 1.
Traceback (most recent call last):
  File "/tmp/inverter-connect/./cli.py", line 115, in <module>
    main(sys.argv)
  File "/tmp/inverter-connect/./cli.py", line 101, in main
    verbose_check_call(PIP_SYNC_PATH, str(DEP_LOCK_PATH))
  File "/tmp/inverter-connect/./cli.py", line 81, in verbose_check_call
    return subprocess.check_call(popen_args)
  File "/usr/local/lib/python3.9/subprocess.py", line 373, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '(PosixPath('/tmp/inverter-connect/.venv-app/bin/pip-sync'), '/tmp/inverter-connect/requirements.txt')' returned non-zero exit status 1.

Anyone here with the similar experience?

publish-loop sleep time should be configurable

publish-loop currently appears to poll every 10 seconds. The SUN-M80G3-EU-Q0 only offers new values every ~5 Minutes. The delay should be extended or configurable to lessen the load.

Automatic installation with Ansible fails

Hi Jens,

currently I try to automate the installation of inverter-connect with Ansible. This causes this error:

fatal: [rpi4]: FAILED! => changed=true 
  cmd:
  - ./cli.py
  - print-values
  delta: '0:03:27.252071'
  end: '2023-07-24 15:17:17.989186'
  msg: non-zero return code
  rc: 1
  start: '2023-07-24 15:13:50.737115'
  stderr: |-
    Traceback (most recent call last):
      File "/home/spower/inverter-connect/.venv-app/bin/inverter_app", line 5, in <module>
        from inverter.__main__ import main
      File "/home/spower/inverter-connect/inverter/__main__.py", line 7, in <module>
        from inverter.cli import cli_app
      File "/home/spower/inverter-connect/inverter/cli/cli_app.py", line 96, in <module>
        toml_settings = TomlSettings(
      File "/home/spower/inverter-connect/.venv-app/lib/python3.9/site-packages/ha_services/toml_settings/api.py", line 40, in __init__
        self.file_path = self.get_settings_file_path(dir_name, file_name)
      File "/home/spower/inverter-connect/.venv-app/lib/python3.9/site-packages/ha_services/toml_settings/api.py", line 60, in get_settings_file_path
        settings_path.mkdir(parents=False, exist_ok=False)
      File "/usr/lib/python3.9/pathlib.py", line 1312, in mkdir
        self._accessor.mkdir(self, mode)
    PermissionError: [Errno 13] Permission denied: '/home/ansible/inverter-connect'

My installation steps are:

  1. As user ansible: Create a user "spower"
  2. As user spower:
    1. Check out repo to ~spower/inverter-connect/
    2. Copy configuration file to ~spower/.config/inverter-connect/inverter-connect.toml
    3. Run cli.py print-values to create virtual environment and to test the connectivity to the inverter
    4. Permission error occurs, as shown in the error message

The Ansible artifact is:

    - name: Install and configure jedie/inverter
      become: true
      become_user: "{{ solar_user_name }}"
      vars:
        mqtt_server: localhost
        solar_user_name: spower
        solar_user_uid: 2123
        solar_group_name: spower
        solar_group_gid: 2123
        inverter_path: "~spower/inverter-connect/"
      block:
        - name: Clone jedie/inverter repository
          ansible.builtin.git: # noqa: latest[git]
            repo: https://github.com/jedie/inverter-connect/
            dest: "{{ inverter_path }}"
        - name: Create configuration directory ~{{ solar_user_name }}/.config/inverter-connect/
          ansible.builtin.file:
            path: "~{{ solar_user_name }}/.config/inverter-connect/"
            state: directory
            mode: '0755'
        - name: Apply configuration for jedie/inverter
          ansible.builtin.template:
            src: inverter-connect.toml
            dest: "~{{ solar_user_name }}/.config/inverter-connect/inverter-connect.toml"
            owner: "{{ solar_user_name }}"
            group: "{{ solar_group_name }}"
            mode: "0640"
        - name: Test connectivity to inverter
          ansible.builtin.command:
            cmd: "./cli.py print-values"
          register: _output
          changed_when: _output.rc != 0
          args:
            chdir: "{{ inverter_path }}"
          environment:
            NO_COLOR: 1

The error is caused by the special handling of ~ in combination with environment variable SUDO_USER as coded here:
https://github.com/jedie/ha-services/blob/4ad2a1b56d7b521e80dbe4ec3ecebac0440ac2df/ha_services/cli_tools/path_utils.py#L35-L54

From my perspective I access the system with the user "ansible" and use sudo to "spower". It's not intended to store anything in the home directory of the ansible user.

That do you think about this issue?

Regards,
Carsten

pip fails installing dependencies

Python version:

(venv) PS E:\Tools\Solar\inverter-connect> python --version
Python 3.10.11
(venv) PS E:\Tools\Solar\inverter-connect> pip --version
pip 24.0 from E:\Tools\Solar\inverter-connect\venv\lib\site-packages\pip (python 3.10)

Steps to reproduce:

  • Create venv
  • Install dependencies using pip install -r requirements.txt

Current behaviour:
Installation fails with

ERROR: In --require-hashes mode, all requirements must have their versions pinned with ==. These do not:
    colorama from https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl (from click==8.1.7->-r .\requirements.txt (line 25))

Expected behaviour:
Installation of requirements completes successfully.

Notes:
I noticed the colorama package does not exist in the requirements.txt file.

Multiple inverter in settings

Hello,
I have multiple deye_2mppt inverter. Is there a way to configure it in settings for mqtt-loop?
When I try it like this:

[inverter]
# The "name" is the prefix of "inverter/definitions/*.yaml" files!
# 
# Set "ip" of the inverter if it's always the same. (Hint: Pin it in FritzBox settings ;)
# You can leave it empty, but then you must always pass "--ip" to CLI commands.
# Even if it is specified here, you can always override it in the CLI with "--ip".
name = "deye_2mppt"
ip = "inverter_1"
port = 48899

name = "deye_2mppt"
ip = "inverter2"
port = 48899

I got an exception:
tomlkit.exceptions.KeyAlreadyPresent: Key "name" already exists.

FileNotFoundError for service_template.txt

Hi Jens,

I got this error:

$ ./cli.py systemd-debug 

+ /home/spower/inverter-connect/.venv-app/bin/inverter_app systemd-debug

Use user settings file: /home/spower/.config/inverter-connect/inverter-connect.toml.read, ok.
inverter v0.11.1
                                                            (Set log level 0: ERROR)

╭─────────────────────── Traceback (most recent call last) ────────────────────────╮
│ /home/spower/inverter-connect/.venv-app/bin/inverter_app:8 in <module>           │
│                                                                                  │
│   5 from inverter.__main__ import main                                           │
│   6 if __name__ == '__main__':                                                   │
│   7 │   sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])         │
│ ❱ 8 │   sys.exit(main())                                                         │
│   9                                                                              │
│                                                                                  │
│ ╭─────────────────────── locals ───────────────────────╮                         │
│ │ main = <function main at 0x7fba053310>               │                         │
│ │   re = <module 're' from '/usr/lib/python3.9/re.py'> │                         │
│ │  sys = <module 'sys' (built-in)>                     │                         │
│ ╰──────────────────────────────────────────────────────╯                         │
│                                                                                  │
│ /home/spower/inverter-connect/inverter/__main__.py:11 in main                    │
│                                                                                  │
│    8                                                                             │
│    9                                                                             │
│   10 def main():                                                                 │
│ ❱ 11 │   cli_app.main()                                                          │
│   12                                                                             │
│   13                                                                             │
│   14 if __name__ == '__main__':                                                  │
│                                                                                  │
│                             ... 11 frames hidden ...                             │
│                                                                                  │
│ /usr/lib/python3.9/pathlib.py:1241 in open                                       │
│                                                                                  │
│   1238 │   │   Open the file pointed by this path and return a file object, as   │
│   1239 │   │   the built-in open() function does.                                │
│   1240 │   │   """                                                               │
│ ❱ 1241 │   │   return io.open(self, mode, buffering, encoding, errors, newline,  │
│   1242 │   │   │   │   │      opener=self._opener)                               │
│   1243 │                                                                         │
│   1244 │   def read_bytes(self):                                                 │
│                                                                                  │
│ ╭─────────────────────────────────── locals ───────────────────────────────────╮ │
│ │ buffering = -1                                                               │ │
│ │  encoding = 'UTF-8'                                                          │ │
│ │    errors = None                                                             │ │
│ │      mode = 'r'                                                              │ │
│ │   newline = None                                                             │ │
│ │      self = PosixPath('~spower/inverter-connect/.venv-app/lib/python3.9/sit… │ │
│ ╰──────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                  │
│ /usr/lib/python3.9/pathlib.py:1109 in _opener                                    │
│                                                                                  │
│   1106 │                                                                         │
│   1107 │   def _opener(self, name, flags, mode=0o666):                           │
│   1108 │   │   # A stub for the opener argument to built-in open()               │
│ ❱ 1109 │   │   return self._accessor.open(self, flags, mode)                     │
│   1110 │                                                                         │
│   1111 │   def _raw_open(self, flags, mode=0o777):                               │
│   1112 │   │   """                                                               │
│                                                                                  │
│ ╭─────────────────────────────────── locals ───────────────────────────────────╮ │
│ │ flags = 524288                                                               │ │
│ │  mode = 438                                                                  │ │
│ │  name = '~spower/inverter-connect/.venv-app/lib/python3.9/site-packages/ha_… │ │
│ │  self = PosixPath('~spower/inverter-connect/.venv-app/lib/python3.9/site-pa… │ │
│ ╰──────────────────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────╯
FileNotFoundError: [Errno 2] No such file or directory: 
'~spower/inverter-connect/.venv-app/lib/python3.9/site-packages/ha_services/systemd/
service_template.txt'
──────────────────────────── Tue 25 Jul 2023 08:56:05  ─────────────────────────────

as I've specified the template path (and other paths too) in the configuration file like:

$ grep template ~/.config/inverter-connect/inverter-connect.toml 
template_path = "~spower/inverter-connect//.venv-app/lib/python3.9/site-packages/ha_services/systemd/service_template.txt"
[systemd.template_context]

The file itself exists:

$ ll ~spower/inverter-connect/.venv-app/lib/python3.9/site-packages/ha_services/systemd/service_template.txt
-rw-r--r-- 1 spower spower 320 Jul 24 15:15 /home/spower/inverter-connect/.venv-app/lib/python3.9/site-packages/ha_services/systemd/service_template.txt

Please add support for expanding user paths like ~spower.

Thanks,
Carsten

UnboundLocalError

Hi,

I got this error message, as I tried some additional entries for deye 2mppt.

# ./cli.py print-values inverter 

+ /home/carsten/inverter-connect/.venv-app/bin/inverter_app print-values inverter

inverter v0.4.0
INFO:inverter.connection:Connect to inverter:48899...
InverterInfo(ip='192.168.xxx.xxx', mac='xxxxxxxxxxxx', serial=xxxxxxxxxx)

        * PV1 Voltage                    : 33.0 V      (Register: 006D, length: 1)
        * PV2 Voltage                    : 0.0 V       (Register: 006F, length: 1)
        * PV1 Current                    : 0.8 A       (Register: 006E, length: 1)
        * PV2 Current                    : 0.0 A       (Register: 0070, length: 1)
        * Daily Production               : 30.7 kWh    (Register: 003C, length: 1)
        * Daily Production 1             : 30.7 kWh    (Register: 0041, length: 1)
        * Daily Production 2             : 0.0 kWh     (Register: 0042, length: 1)
        * Total Production               : 35.9 kWh    (Register: 003F, length: 2)
WARNING:inverter.api:Parser error with response=ModbusResponse(slave_id=1, modbus_function=3, data_hex='0167') parameter=Parameter(start_register=69, length=1, group='solar', name='Total Production 1', device_class='energy', state_class='total_increasing', unit='kWh', scale=0.1, parser=<function parse_swapped_number at 0x7f83f168fbe0>, offset=None, lookup=None): Wrong len 4: data_hex='0167' - retry...
WARNING:inverter.api:Parser error with response=ModbusResponse(slave_id=1, modbus_function=3, data_hex='0167') parameter=Parameter(start_register=69, length=1, group='solar', name='Total Production 1', device_class='energy', state_class='total_increasing', unit='kWh', scale=0.1, parser=<function parse_swapped_number at 0x7f83f168fbe0>, offset=None, lookup=None): Wrong len 4: data_hex='0167' - retry...
WARNING:inverter.api:Parser error with response=ModbusResponse(slave_id=1, modbus_function=3, data_hex='0167') parameter=Parameter(start_register=69, length=1, group='solar', name='Total Production 1', device_class='energy', state_class='total_increasing', unit='kWh', scale=0.1, parser=<function parse_swapped_number at 0x7f83f168fbe0>, offset=None, lookup=None): Wrong len 4: data_hex='0167' - retry...

Signing off with "AT+Q"...Goodbye ;)

Traceback (most recent call last):
  File "/home/carsten/inverter-connect/.venv-app/bin/inverter_app", line 8, in <module>
    sys.exit(main())
  File "/home/carsten/inverter-connect/inverter/__main__.py", line 11, in main
    cli_app.main()
  File "/home/carsten/inverter-connect/inverter/cli/cli_app.py", line 422, in main
    cli()
  File "/home/carsten/inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/home/carsten/inverter-connect/.venv-app/lib/python3.10/site-packages/rich_click/rich_group.py", line 21, in main
    rv = super().main(*args, standalone_mode=False, **kwargs)
  File "/home/carsten/inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/home/carsten/inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/carsten/inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/carsten/inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/home/carsten/inverter-connect/inverter/cli/cli_app.py", line 102, in print_values
    for value in inverter:
  File "/home/carsten/inverter-connect/inverter/api.py", line 110, in __iter__
    raise Exception from err  # noqa
UnboundLocalError: local variable 'err' referenced before assignment

Please solve this issue,

Thank you,
Carsten

BTW: The definition file is "wrong" as I have a deye 1mppt inverter. I'll strip down it later and file a PR for home_assistant_solarman.

Feedback to systemd

this is my feedback after configuring systemd for inverter-connect. All works fine.

My thoughts are:

  1. please add "systemd-start" to restart the service after "systemd-stop"

  2. please consider splitting "systemd-setup" in "systemd-setup" and "systemd-start", for me it's unexpected, that the a setup step also starts the service - or provide a single setup step w/o starting the service

  3. please adapt the description of "systemd-remove" and "systemd-setup" as both are equal but shouldn't

  4. please update the systemd template to prevent this warning message inverter_connect.service:14: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file ...

  5. please convert the multiline timeout error message to a single line message, as it's better to parse automatically

    Jul 25 10:54:57 rpi4 inverter_connect[10795]: [25/07/23 10:54:57.945479] ERROR    Giving up at_command(...)     _common.py:120
    Jul 25 10:54:57 rpi4 inverter_connect[10795]:                                     after 3 tries
    Jul 25 10:54:57 rpi4 inverter_connect[10795]:                                     (inverter.exceptions.ReadTime
    Jul 25 10:54:57 rpi4 inverter_connect[10795]:                                     out: Get no response from
    Jul 25 10:54:57 rpi4 inverter_connect[10795]:                                     192.xxx.xxx.xxx: timed out)
    

Thank you for your good work!

Carsten

Can't set time

Hello I use a SUN600 with Firmware MW3_16U_5406_1.57
I can't set a time:
Check time by request "AT+NTPTM"...1970-1-1 0:0:1 Thur
Signing off with "AT+Q"...Goodbye ;)

NTP didn't also work.
I have run tcpdump for some weeks. The last NTP query from the inverter is seen on 5 June 2023 but the time in the inverter is still 1970-1-1.

30 │      AT+NTPTM │ 1970-1-1  0:17:27  Thur
31 │     AT+NTPSER │ cn.ntp.org.cn
32 │      AT+NTPRF │ 30
33 │      AT+NTPEN │ on

Did someone know what NTPRF is? Days? Hours? ...

Wrong reads being pushed into MQTT

There are occasional wrong readings being returned from the inverter and being pushed into MQTT, i.e. here pv1current/pv1power/totalpower

Good:

Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv1voltage': 27.5,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv2voltage': 27.0,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv1current': 0.4,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv2current': 0.4,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_dailyproduction': 0.1,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_dailyproduction1': 0.0,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_dailyproduction2': 0.0,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_totalproduction': 0.4,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_totalproduction1': 0.1,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_totalproduction2': 0.1,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_activepowerregulations': 100,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv1power': 11.0,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv2power': 10.8,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_totalpower': 21.8,
Dec 15 13:43:26 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_looprunningtime': 392

Bad:

Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv1voltage': 33.5,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv2voltage': 31.8,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv1current': 51.4,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv2current': 0.3,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_dailyproduction': 0.1,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_dailyproduction1': 0.0,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_dailyproduction2': 0.0,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_totalproduction': 0.4,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_totalproduction1': 0.1,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_totalproduction2': 0.1,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_activepowerregulations': 100,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv1power': 1721.9,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_pv2power': 9.54,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_totalpower': 1731.44,
Dec 15 13:43:36 vzlogger inverter_connect[4766]: <E2><94><82>   'inverter_3811199530_looprunningtime': 402

It's a Deye SUN-M80G3-EU-Q0. I thought those wrong reads for power would be guarded by the definitions in inverter/definitions/deye_2mppt_validations.yaml, but apparently they go through at least to MQTT. Unfortunately I have never observed these values with manual readouts, so I can't tell whether print-values would behave differently.

However, "PV1 Power/PV2 Power/Total Power" appear as "Computed". Can they actually be limited by deye_2mppt_validations.yaml?

These wrong reads happen for pretty much all values

Dec 15 13:46:16 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 100,
Dec 15 13:46:26 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 100,
Dec 15 13:46:36 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 514,
Dec 15 13:46:46 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 100,
Dec 15 13:47:04 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 100,
[...]
Dec 15 13:52:23 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 100,
Dec 15 13:52:34 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 100,
Dec 15 13:52:43 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 1,
Dec 15 13:52:53 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 100,
Dec 15 13:53:03 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 1,
Dec 15 13:53:14 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 100,
Dec 15 13:53:23 vzlogger inverter_connect[4766]: │   'inverter_3811199530_activepowerregulations': 100,

Show last exception only

I tried to query my inverter when it was powered off.

This results in a stack trace with two exceptions, as shown in the following example.

From my user perspective, the second exception ReadTimeout: Get no response from inverter: timed out is enough information. The stack trace and the first exception TimeoutError: timed out is not necessary and should be hidden by default or only shown when debug is enabled.

What do you think about reducing the output in this situation?

# ./cli.py print-at-commands inverter NTPTM NTPSER NTPRF NTPEN

+ inverter-connect/.venv-app/bin/inverter_app print-at-commands inverter NTPTM NTPSER NTPRF NTPEN

inverter v0.4.0
INFO:inverter.connection:Connect to inverter:48899...
Traceback (most recent call last):
  File "inverter-connect/inverter/connection.py", line 229, in recv_command
    data = self.sock.recv(buffer_size)
TimeoutError: timed out

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "inverter-connect/.venv-app/bin/inverter_app", line 8, in <module>
    sys.exit(main())
  File "inverter-connect/inverter/__main__.py", line 11, in main
    cli_app.main()
  File "inverter-connect/inverter/cli/cli_app.py", line 422, in main
    cli()
  File "inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "inverter-connect/.venv-app/lib/python3.10/site-packages/rich_click/rich_group.py", line 21, in main
    rv = super().main(*args, standalone_mode=False, **kwargs)
  File "inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "inverter-connect/.venv-app/lib/python3.10/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "inverter-connect/inverter/cli/cli_app.py", line 209, in print_at_commands
    with InverterSock(config) as inv_sock:
  File "inverter-connect/inverter/connection.py", line 208, in __enter__
    self.inverter_info = self.init_inventer()
  File "inverter-connect/inverter/connection.py", line 274, in init_inventer
    data = self.recv_command(command=self.config.init_cmd)
  File "inverter-connect/inverter/connection.py", line 231, in recv_command
    raise ReadTimeout(f'Get no response from {self.config.host}: {err}')
inverter.exceptions.ReadTimeout: Get no response from inverter: timed out

Regards,
Carsten

Questions for pr #27 'Clear "Daily Production" every night by set time'

Hi @jedie,

I'm not sure if I understand the code right. Every time (in every iteration of the while loop in publish_forever()) you enter the context of DailyProductionReset, and if it's between 1am and 3am, you'll reset the "Daily Production" counter.

Would it be a better solution to track the date of the last reset and check in DailyProductionReset.__enter__() if you did a reset at the same day already? This may reduce the number of resets at night.

I use a smaller inverter a SUN300 and this is offline during night. What's your suggested approch for such a small inverter to reset the "Daily Production" counter?

Regards,
Carsten

PS: This is related to #27

TypeError: get_connected_client() got an unexpected keyword argument 'verbose'

Hi Jens,

during testing the MQTT connection with `` I get this traceback:

spower@rpi4:~/inverter-connect $ ./cli.py test-mqtt-connection 

+ /home/spower/inverter-connect/.venv-app/bin/inverter_app test-mqtt-connection

Use user settings file: /home/spower/.config/inverter-connect/inverter-connect.toml.read, ok.
inverter v0.11.1
                                                            (Set log level 0: ERROR)
╭─────────────────────── Traceback (most recent call last) ────────────────────────╮
│ /home/spower/inverter-connect/.venv-app/bin/inverter_app:8 in <module>           │
│                                                                                  │
│   5 from inverter.__main__ import main                                           │
│   6 if __name__ == '__main__':                                                   │
│   7 │   sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])         │
│ ❱ 8 │   sys.exit(main())                                                         │
│   9                                                                              │
│                                                                                  │
│ ╭─────────────────────── locals ───────────────────────╮                         │
│ │ main = <function main at 0x7fbb273310>               │                         │
│ │   re = <module 're' from '/usr/lib/python3.9/re.py'> │                         │
│ │  sys = <module 'sys' (built-in)>                     │                         │
│ ╰──────────────────────────────────────────────────────╯                         │
│                                                                                  │
│ /home/spower/inverter-connect/inverter/__main__.py:11 in main                    │
│                                                                                  │
│    8                                                                             │
│    9                                                                             │
│   10 def main():                                                                 │
│ ❱ 11 │   cli_app.main()                                                          │
│   12                                                                             │
│   13                                                                             │
│   14 if __name__ == '__main__':                                                  │
│                                                                                  │
│                             ... 6 frames hidden ...                              │
│                                                                                  │
│ /home/spower/inverter-connect/.venv-app/lib/python3.9/site-packages/click/core.p │
│ y:783 in invoke                                                                  │
│                                                                                  │
│    780 │   │                                                                     │
│    781 │   │   with augment_usage_errors(__self):                                │
│    782 │   │   │   with ctx:                                                     │
│ ❱  783 │   │   │   │   return __callback(*args, **kwargs)                        │
│    784 │                                                                         │
│    785 │   def forward(                                                          │
│    786 │   │   __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any  # noqa:  │
│                                                                                  │
│ ╭─────────────────────────────── locals ───────────────────────────────╮         │
│ │ _Context__callback = <function test_mqtt_connection at 0x7fb9f7d1f0> │         │
│ │     _Context__self = <click.core.Context object at 0x7fb9fc76a0>     │         │
│ │               args = ()                                              │         │
│ │                ctx = <click.core.Context object at 0x7fb9fc76a0>     │         │
│ │             kwargs = {'verbosity': 0}                                │         │
│ ╰──────────────────────────────────────────────────────────────────────╯         │
│                                                                                  │
│ /home/spower/inverter-connect/inverter/cli/cli_app.py:553 in                     │
│ test_mqtt_connection                                                             │
│                                                                                  │
│   550 │                                                                          │
│   551 │   setup_logging(verbosity=verbosity)                                     │
│   552 │                                                                          │
│ ❱ 553 │   mqttc = get_connected_client(settings=user_settings.mqtt, verbose=True │
│   554 │   mqttc.loop_start()                                                     │
│   555 │   mqttc.loop_stop()                                                      │
│   556 │   mqttc.disconnect()                                                     │
│                                                                                  │
│ ╭─── locals ────╮                                                                │
│ │ verbosity = 0 │                                                                │
│ ╰───────────────╯                                                                │
╰──────────────────────────────────────────────────────────────────────────────────╯
TypeError: get_connected_client() got an unexpected keyword argument 'verbose'
──────────────────────────── Mon 24 Jul 2023 20:33:10  ─────────────────────────────

It looks like the signature of get_connected_client() differs.

Possibly changing

mqttc = get_connected_client(settings=user_settings.mqtt, verbose=True)
to

mqttc = get_connected_client(settings=user_settings.mqtt, verbosity=1)

solves this issue.

Regards,
Carsten

Python version error on Raspberry Pi

Dear,

Apologies I am new to Github, so please let me know whether I am addressing you properly on this topic.

I have downloaded and used your software with succes and can use in on my MAC now, real nice features!

As I moved to install it on my raspberry Pi however, I am getting an error on the Python version. My current version is 3.11.4 which I think is sufficient.

Below the output showing the problem, I hope you can help:

pi@raspberrypi:~/inverter-connect $ ./cli.py --help
Traceback (most recent call last):
File "./cli.py", line 35, in
assert sys.version_info >= (3, 9), 'Python version is too old!'
AssertionError: Python version is too old!

pi@raspberrypi:~/inverter-connect $ python -V
Python 3.11.4

Some more information on the Raspberry:

model name : ARMv7 Processor rev 4 (v7l)
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.