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

Beta (2.8.x) #385

Open
wants to merge 18 commits into
base: Beta-(2.8.x)
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ The purpose of Grott is to read, parse and forward the *raw metrics as they are
### New in Version 2.8
* Added first SPA support (2.8.1)
* Added first MIN support (2.8.2)
* Merged with master (2.8.3)

### New in Version 2.7
* Added first beta of **grottserver** to act as destination for inverter/datalogger data (remove need to cummunicate with internet).
- grottserver is able to sent read/write register commands to inverter and datalogger.
- see https://github.com/johanmeijer/grott/wiki/Grottserver and discussions https://github.com/johanmeijer/grott/discussions/98 for more information:
### New in Version 2.7.8
* Added first beta of grottserver to act as destination for inverter/datalogger data (remove need to cummunicate with internet).
- grottserver version 0.0.5 is able to sent read/write register commands to inverter and datalogger.
- see discussions (#98) for more information: https://github.com/johanmeijer/grott/discussions/98
* Support for SDM630/Raillog connected (see issue #88)
* Support for SDM630/Inverter (modbus) connected 3 phases support
* Export to CSV file (see issue #79, pull request #91).
Expand All @@ -40,12 +41,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
9 changes: 6 additions & 3 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 Down
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