Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

PR dealing with Issue #359 (Beta) #400

Open
wants to merge 29 commits into
base: Beta-(2.8.x)
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
23c4049
Rebased Issue-359 on top of Beta 2.8 #1
johanmeijer Feb 6, 2023
a3a9580
Update README.md
johanmeijer Feb 6, 2023
2388fff
Update grott.ini
phavekes Feb 6, 2023
19cf647
Corrected a bug in the generation of configuration.
egguy Feb 14, 2023
2a4aff8
More debug message + QoS 1
egguy Feb 14, 2023
48dde6a
Bump to RC4
egguy Feb 14, 2023
7a460c6
Updated unit of time for the totalwork time measure
egguy Feb 15, 2023
291674e
Add retain flag to MQTT
egguy Feb 19, 2023
a0b0c64
Add RC6 tag
egguy Feb 19, 2023
af9b737
Split sensors.yaml into mqtt.yaml and template.yaml to match new HA c…
catch56 Feb 8, 2023
f04d78f
- added flags discussed in issue #359
KevinEeckman Jul 3, 2023
10da787
- added flag management for mqtt and influxdb
KevinEeckman Jul 3, 2023
6c0ae48
- minor fix to conf. printing
KevinEeckman Jul 3, 2023
4f861ed
- Added messages consistency check
KevinEeckman Jul 7, 2023
e0039a7
- print out full message before discarding it, if required
KevinEeckman Jul 8, 2023
1fd3cc2
Merge remote-tracking branch 'upstream/Beta-(2.8.x)' into issue359
KevinEeckman Sep 10, 2023
cf5ee9a
Update README.md
johanmeijer May 17, 2024
1146fb9
Update README.md
johanmeijer May 17, 2024
352c74d
Add files via upload
johanmeijer Jun 18, 2024
8880e20
Changed SPF layout
johanmeijer Jun 18, 2024
f52ece0
Changed SPF layout
johanmeijer Jun 18, 2024
823f79b
Add MOD layout to examples
johanmeijer Jul 22, 2024
36845fe
2.8.3 (build 20240722)
johanmeijer Jul 22, 2024
cfff02a
Update README.md
johanmeijer Aug 2, 2024
73f93a6
Update README.md
johanmeijer Aug 2, 2024
4b6a4ec
Update README.md
johanmeijer Aug 2, 2024
09a9934
Update README.md
johanmeijer Aug 2, 2024
3fa7ef9
Copy MOD inverter layout in base folder
johanmeijer Sep 3, 2024
569895f
Merge branch 'johanmeijer:Master' into issue359
KevinEeckman Sep 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@

## The Growatt Inverter Monitor
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?business=RQFS46F9JTESQ&item_name=Grott+&currency_code=EUR)

### !!!! This is Beta version (2.8.x)
### Be aware no changes (only hyper fixes) will be commited to 2.8.x anymore. A new version (for now 2.9.x) is being worked on.
#### Before using grott please read disclaimer: https://github.com/johanmeijer/grott/wiki/@disclaimer,-statement-of-use-and-limitations
### From 17-05-2024 this version (2.8.3) is the new master

Growatt inverters can send performance and status metrics (log data) to the Growatt company servers. The inverters rely on either a ShineWIFI module or a ShineLAN box to relay the data to Growatt. The metrics stored on the Growatt servers then can be viewed on the Growatt website or using the ShinePhone mobile app.

The purpose of Grott is to read, parse and forward the *raw metrics as they are sent* to Growatt servers. This means other applications can consume the raw Growatt metrics without relying on the Growatt API and servers and without delay.

### Please see: https://github.com/johanmeijer/grott/wiki/@Statement-of-use-and-limitations before using Grott.
Grottserver (under development) is emulating the Growatt server so you do not need a connection with the growatt servers anymore. Grottserver provides also an API interface to read and write Inverter and Datalogger registers.
For more information see: https://github.com/johanmeijer/grott/wiki/Grottserver.

### Before using Grott please see: https://github.com/johanmeijer/grott/wiki/@Statement-of-use-and-limitations
### First time users please start with: https://github.com/johanmeijer/grott/wiki/@-First-time-installation

### New in Version 2.8
* Added first SPA support (2.8.1)
* Added first MIN support (2.8.2)
* For all changes see Version_history file (https://github.com/johanmeijer/grott/blob/2.8.3/Version_history.txt)

### New in Version 2.7
* Added first beta of **grottserver** to act as destination for inverter/datalogger data (remove need to cummunicate with internet).
Expand Down Expand Up @@ -41,12 +45,7 @@ The purpose of Grott is to read, parse and forward the *raw metrics as they are
* Added option to add inverter serial to MQTT topic (thanks to @ebosveld)
- Add mqttinverterintopic = True to MQTT section of grott.ini or use qmqttinverterintopic = "True" environmental (e.g. docker).

### planned in Version 2.7.x (not commited yet)
* Auto detect for SPF, SPH, TL3 inverters
* Improved / configurable PVOutput support
* MQTT Retain message support
* Enhanced record layout for SPH
* tbd
### For all changes see: https://github.com/johanmeijer/grott/blob/Master-(2.7.8)/Version_history.txt

### Two modes of metric data retrieval
Grott can intercept the inverter metrics in two distinct modes:
Expand Down
208 changes: 208 additions & 0 deletions T06NNNNXMOD.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Version_history.txt
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,8 @@ Complement bugfix prepared by KBraham (issue / pull request #41)
2.8.3
- Removed space at the end of epvtotal and epv1today keywords in min layout (#481)
- Changed bms_batterycurr in MIN layout to numx (issue #362)
- Add gpvuplimit environmental for limiting pvoutpy writes in docker container (#346), be ware need new docker image 2.8.3 (will be created in next gen)
- Add gpvuplimit environmental for limiting pvoutpy writes in docker container (#346), be ware need new docker image 2.8.3 (will be created in next gen)
- add invfanspeed to SPF layout
2.8.3 (20240722)
- add T06NNNNXMOD MOd type inverter layout to examples directory
- change default growatt IP address to server.growatt.com URL, hopefully this will prevent growatt from changing IP address and the need for changing IP address in grott.ini
11 changes: 6 additions & 5 deletions examples/Extensions/grottext.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ def grottext(conf,data,jsonmsg) :

print("\t - " + "Grott extension module entered ")
###
### uncomment this print statements if you want to see the information that is availble.
### uncomment this print statements if you want to see the information that is available.
###

#print(jsonmsg)
#print(data)
#print(dir(conf))
#print(conf.extvar)

url = "http://" + conf.extvar["ip"] + ":" + str(conf.extvar["port"])

if "url" in conf.extvar:
url = conf.extvar["url"]
else:
url = f"http://{conf.extvar['ip']}:{conf.extvar['port']}"

try:
r = requests.post(url, json = jsonmsg)

Expand All @@ -33,5 +36,3 @@ def grottext(conf,data,jsonmsg) :

#print(r.text)
return resultcode


106 changes: 88 additions & 18 deletions examples/Home Assistent/grott_ha.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,25 @@

from grottconf import Conf

__version__ = "0.0.7-rc2"
__version__ = "0.0.7-rc6"

"""A pluging for grott
This plugin allow to have autodiscovery of the device in HA

Should be able to support multiples inverters

Version 0.0.7
- Corrected a bug when creating the configuration
- Add QoS 1 to reduce the possibility of lost message.
- Updated Total work time unit.
- Add support for setting the retain flag

Config:
- ha_mqtt_host (required): The host of the MQTT broker user by HA (often the IP of HA)
- ha_mqtt_port (required): The port (the default is oftent 1883)
- ha_mqtt_user (optional): The user use to connect to the broker (you can use your user)
- ha_mqtt_password (optional): The password to connect to the mqtt broket (you can use your password)
- ha_mqtt_retain (optional): Set the retain flag for the data message (default: False)

Return codes:
- 0: Everything is OK
Expand Down Expand Up @@ -241,7 +248,7 @@
"totworktime": {
"name": "Working time",
"device_class": "duration",
"unit_of_measurement": "hours",
"unit_of_measurement": "h",
"value_template": "{{ value_json.totworktime| float / 7200 | round(2) }}",
},
"pvtemperature": {
Expand Down Expand Up @@ -491,6 +498,12 @@
},
}

MQTT_HOST_CONF_KEY = "ha_mqtt_host"
MQTT_PORT_CONF_KEY = "ha_mqtt_port"
MQTT_USERNAME_CONF_KEY = "ha_mqtt_user"
MQTT_PASSWORD_CONF_KEY = "ha_mqtt_password"
MQTT_RETAIN_CONF_KEY = "ha_mqtt_retain"


def make_payload(conf: Conf, device: str, name: str, key: str, unit: str = None):
# Default configuration payload
Expand All @@ -514,14 +527,13 @@ def make_payload(conf: Conf, device: str, name: str, key: str, unit: str = None)
layout = conf.recorddict[conf.layout]
if "value_template" not in payload and key in layout:
# From grottdata:207, default type is num, also process numx
if layout[key].get("type", "num") in ("num", "numx") and layout[key].get(
"divide", "1"
):
if layout[key].get("type", "num") in ("num", "numx"):
divider = layout[key].get("divide", "1")
payload[
"value_template"
] = "{{{{ value_json.{key} | float / {divide} }}}}".format(
key=key,
divide=layout[key].get("divide"),
divide=divider,
)

if "value_template" not in payload:
Expand All @@ -545,29 +557,29 @@ def set_configured(cls, serial: str):

def process_conf(conf: Conf):
required_params = [
"ha_mqtt_host",
"ha_mqtt_port",
MQTT_HOST_CONF_KEY,
MQTT_PORT_CONF_KEY,
]
if not all([param in conf.extvar for param in required_params]):
print("Missing configuration for ha_mqtt")
raise AttributeError

if "ha_mqtt_user" in conf.extvar:
if MQTT_USERNAME_CONF_KEY in conf.extvar:
auth = {
"username": conf.extvar["ha_mqtt_user"],
"password": conf.extvar["ha_mqtt_password"],
"username": conf.extvar[MQTT_USERNAME_CONF_KEY],
"password": conf.extvar[MQTT_PASSWORD_CONF_KEY],
}
else:
auth = None

# Need to convert the port if passed as a string
port = conf.extvar["ha_mqtt_port"]
port = conf.extvar[MQTT_PORT_CONF_KEY]
if isinstance(port, str):
port = int(port)
return {
"client_id": MqttStateHandler.client_name,
"auth": auth,
"hostname": conf.extvar["ha_mqtt_host"],
"hostname": conf.extvar[MQTT_HOST_CONF_KEY],
"port": port,
}

Expand All @@ -586,8 +598,8 @@ def grottext(conf: Conf, data: str, jsonmsg: str):
"""Allow to push to HA MQTT bus, with auto discovery"""

required_params = [
"ha_mqtt_host",
"ha_mqtt_port",
MQTT_HOST_CONF_KEY,
MQTT_PORT_CONF_KEY,
]
if not all([param in conf.extvar for param in required_params]):
print("Missing configuration for ha_mqtt")
Expand Down Expand Up @@ -615,7 +627,9 @@ def grottext(conf: Conf, data: str, jsonmsg: str):
conf, "layout", None
):
configs_payloads = []
print(f"\tGrott HA {__version__} - creating {device_serial} config in HA")
print(
f"\tGrott HA {__version__} - creating {device_serial} config in HA, {len(values.keys())} to push"
)
for key in values.keys():
# Generate a configuration payload
payload = make_payload(conf, device_serial, key, key)
Expand All @@ -634,6 +648,7 @@ def grottext(conf: Conf, data: str, jsonmsg: str):
"topic": topic,
"payload": json.dumps(payload),
"retain": True,
"qos": 1,
}
)
except Exception as e:
Expand All @@ -657,28 +672,83 @@ def grottext(conf: Conf, data: str, jsonmsg: str):
"topic": topic,
"payload": json.dumps(payload),
"retain": True,
"qos": 1,
}
)
except Exception as e:
print(
f"\t - [grott HA] {__version__} Exception while creating new sensor last push: {e}"
)
return 4
print(f"\tPushing {len(configs_payloads)} configurations payload to HA")
publish_multiple(conf, configs_payloads)
print(f"\tConfigurations pushed")
# Now it's configured, no need to come back
MqttStateHandler.set_configured(device_serial)

if not MqttStateHandler.is_configured(device_serial):
print(f"\t[Grott HA] {__version__} Can't configure device: {device_serial}")
return 7

# Push the vales to the topics
# Push the values to the topic
retain = conf.extvar.get(MQTT_RETAIN_CONF_KEY, False)
try:
publish_single(
conf, state_topic.format(device=device_serial), json.dumps(values)
conf,
state_topic.format(device=device_serial),
json.dumps(values),
retain=retain,
)
except Exception as e:
print("[HA ext] - Exception while publishing - {}".format(e))
# Reset connection state in case of problem
return 2
return 0


def test_generate_payload():
"Test that an auto generated payload for MQTT configuration"

class TestConf:
recorddict = {
"test": {
"pvpowerout": {"value": 122, "length": 4, "type": "num", "divide": 10}
}
}
layout = "test"

payload = make_payload(TestConf(), "NCO7410", "pvpowerout", "pvpowerout")
print(payload)
# The default divider for pvpowerout is 10
assert payload["value_template"] == "{{ value_json.pvpowerout | float / 10 }}"
assert payload["name"] == "NCO7410 PV Output (Actual)"
assert payload["unique_id"] == "grott_NCO7410_pvpowerout"
assert payload["state_class"] == "measurement"
assert payload["device_class"] == "power"
assert payload["unit_of_measurement"] == "W"


def test_generate_payload_without_divider():
"Test that an auto generated payload for MQTT configuration"

class TestConf:
recorddict = {
"test": {
"pvpowerout": {
"value": 122,
"length": 4,
"type": "num",
}
}
}
layout = "test"

payload = make_payload(TestConf(), "NCO7410", "pvpowerout", "pvpowerout")
print(payload)
# The default divider for pvpowerout is 10
assert payload["value_template"] == "{{ value_json.pvpowerout | float / 1 }}"
assert payload["name"] == "NCO7410 PV Output (Actual)"
assert payload["unique_id"] == "grott_NCO7410_pvpowerout"
assert payload["state_class"] == "measurement"
assert payload["device_class"] == "power"
assert payload["unit_of_measurement"] == "W"
Loading