Created
April 23, 2025 12:50
-
-
Save mherweg/83d0469e52f89884f8a9a10c36b617c2 to your computer and use it in GitHub Desktop.
python script that reads energy & power from 2 SMA Inverters and sends it to MQTT and pvoutput.org
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# | |
# reads energy & power from 2 SMA Inverters and sends it to MQTT and pvoutput.org | |
# | |
from pymodbus.client import ModbusTcpClient | |
import struct | |
import time | |
import paho.mqtt.client as mqtt | |
# Modbus TCP connection parameters | |
MODBUS_IP = 'xx.xx.xx.xx' # Replace with your Modbus device IP | |
MODBUS_IP2 = 'xx.xx.xx.xx' | |
MODBUS_PORT = 502 | |
MODBUS_ID = 126 | |
# MQTT broker parameters | |
MQTT_BROKER = 'mqtt.example.org' # Replace with your MQTT broker address | |
MQTT_PORT = 1883 | |
MQTT_TOPIC_64BIT = 'mytopic/total_energy' | |
MQTT_TOPIC_16BIT = 'mytopc/power' | |
# MQTT credentials | |
MQTT_USERNAME = '' | |
MQTT_PASSWORD = '' | |
import requests | |
from datetime import datetime | |
# Replace with your actual API key and system ID | |
API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' | |
SYSTEM_ID = 'nnnnn' | |
old_wh = 0 | |
# Example data | |
DATE = datetime.now().strftime('%Y%m%d') # Format: yyyyMMdd | |
TIME = datetime.now().strftime('%H:%M') # Format: HH:mm | |
# PVOutput API endpoint | |
url = 'https://pvoutput.org/service/r2/addstatus.jsp' | |
headers = { | |
'X-Pvoutput-Apikey': API_KEY, | |
'X-Pvoutput-SystemId': SYSTEM_ID | |
} | |
# Modbus register addresses | |
# Assuming 64-bit value is stored in 4 consecutive 16-bit registers starting at address 0 | |
# Assuming 16-bit value is stored in 1 register at address 4 | |
REGISTER_64BIT_START = 40303-1 | |
REGISTER_16BIT = 40200-1 | |
def read_64bit_value(client, address): | |
# Read 4 registers (4 x 16 bits = 64 bits) | |
rr = client.read_holding_registers(address, 4, slave=MODBUS_ID ) | |
if rr.isError(): | |
print("Error reading 64-bit value") | |
return None | |
# Combine 4 registers into 64-bit integer (big endian) | |
registers = rr.registers | |
# Pack registers into bytes | |
bytes_value = struct.pack('>HHHH', *registers) | |
# Unpack as unsigned long long (64-bit) | |
value = struct.unpack('>Q', bytes_value)[0] | |
return value | |
def read_16bit_value(client, address): | |
rr = client.read_holding_registers(address, 1, slave=MODBUS_ID ) | |
if rr.isError(): | |
print("Error reading 16-bit value") | |
return None | |
return rr.registers[0] | |
def main(): | |
old_wh = 0 | |
# Connect to Modbus TCP | |
modbus_client = ModbusTcpClient(MODBUS_IP, port=MODBUS_PORT) | |
if not modbus_client.connect(): | |
print("Failed to connect to Modbus server", MODBUS_IP ) | |
return | |
modbus_client2 = ModbusTcpClient(MODBUS_IP2, port=MODBUS_PORT) | |
if not modbus_client2.connect(): | |
print("Failed to connect to Modbus server", MODBUS_IP ) | |
return | |
# Connect to MQTT broker | |
mqtt_client = mqtt.Client() | |
mqtt_client.username_pw_set(username=MQTT_USERNAME, password=MQTT_PASSWORD) | |
mqtt_client.connect(MQTT_BROKER, MQTT_PORT) | |
mqtt_client.loop_start() | |
try: | |
#while True: | |
val64 = read_64bit_value(modbus_client, REGISTER_64BIT_START) | |
val16 = read_16bit_value(modbus_client, REGISTER_16BIT) | |
scale =read_16bit_value(modbus_client, REGISTER_16BIT+1) | |
val64b = read_64bit_value(modbus_client2, REGISTER_64BIT_START) | |
val16b = read_16bit_value(modbus_client2, REGISTER_16BIT) | |
scaleb = read_16bit_value(modbus_client2, REGISTER_16BIT+1) | |
validation = 0 | |
if val64 is not None and val64b is not None: | |
validation += 1 | |
wh=(val64+val64b) #Wh | |
sum1 = wh/1000 | |
#wh_diff = wh - old_wh | |
#old_wh = wh | |
mqtt_client.publish(MQTT_TOPIC_64BIT, str(sum1)) | |
print(f"Published total production: {sum1}") | |
if val16 is not None and val16b is not None: | |
validation += 1 | |
if val16 != 32768 and val16b != 32768: | |
sum2 = val16 * (scale*10) + val16b * (scaleb*10) | |
mqtt_client.publish(MQTT_TOPIC_16BIT, str(sum2)) | |
print(f"Published active power: {sum2}") | |
if validation == 2: | |
payload = { | |
'd': datetime.now().strftime('%Y%m%d'), # Format: yyyyMMdd | |
't': datetime.now().strftime('%H:%M'), | |
'v1': wh, | |
'v2': sum2, | |
'c1': 1, | |
# 'v5': TEMPERATURE, # Optional | |
# 'v6': VOLTAGE # Optional | |
} | |
response = requests.post(url, headers=headers, data=payload) | |
if response.status_code == 200: | |
print('Data uploaded successfully.') | |
else: | |
print(f'Failed to upload data. Status code: {response.status_code}') | |
print(response.text) | |
# time.sleep(300) # 5 minuten | |
except KeyboardInterrupt: | |
print("Stopping...") | |
finally: | |
mqtt_client.loop_stop() | |
modbus_client.close() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment