initial commit
This commit is contained in:
104
src/main.py
Normal file
104
src/main.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import socket
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import datetime
|
||||
import goodwe
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
# -----------------------------
|
||||
# CONFIGURATION
|
||||
# -----------------------------
|
||||
MQTT_BROKER = os.getenv("MQTT_BROKER", "localhost")
|
||||
MQTT_PORT = int(os.getenv("MQTT_PORT", 1883))
|
||||
MQTT_TOPIC = os.getenv("MQTT_TOPIC", "goodwe/runtime")
|
||||
DISCOVERY_INTERVAL = int(os.getenv("DISCOVERY_INTERVAL", 60)) # seconds
|
||||
BROADCAST_SUBNET = os.getenv("BROADCAST_SUBNET", "192.168.1.255") # directed broadcast
|
||||
|
||||
UDP_PORT = 48899
|
||||
UDP_MSG = b"WIFIKIT-214028-READ"
|
||||
|
||||
# -----------------------------
|
||||
# LOGGING SETUP
|
||||
# -----------------------------
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s"
|
||||
)
|
||||
logger = logging.getLogger("goodwe-daemon")
|
||||
|
||||
# -----------------------------
|
||||
# MQTT CLIENT SETUP
|
||||
# -----------------------------
|
||||
mqtt_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
|
||||
mqtt_client.connect(MQTT_BROKER, MQTT_PORT)
|
||||
mqtt_client.loop_start()
|
||||
|
||||
# -----------------------------
|
||||
# HELPER FUNCTIONS
|
||||
# -----------------------------
|
||||
async def discover_inverters(timeout=3):
|
||||
"""Discover inverters using UDP broadcast (non-root)."""
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
sock.settimeout(timeout)
|
||||
inverters = []
|
||||
try:
|
||||
sock.sendto(UDP_MSG, (BROADCAST_SUBNET, UDP_PORT))
|
||||
while True:
|
||||
data, addr = sock.recvfrom(1024)
|
||||
inverters.append(addr[0])
|
||||
except socket.timeout:
|
||||
pass
|
||||
finally:
|
||||
sock.close()
|
||||
return list(set(inverters))
|
||||
|
||||
async def read_runtime(ip):
|
||||
"""Connect to a GoodWe inverter and read runtime data."""
|
||||
try:
|
||||
inverter = await goodwe.connect(ip)
|
||||
runtime = await inverter.read_runtime_data()
|
||||
print(runtime)
|
||||
return runtime
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read {ip}: {e}")
|
||||
return None
|
||||
|
||||
def json_serializer(obj):
|
||||
if isinstance(obj, datetime.datetime):
|
||||
return obj.isoformat()
|
||||
raise TypeError(f"Type {type(obj)} not serializable")
|
||||
|
||||
async def publish_runtime(ip):
|
||||
runtime = await read_runtime(ip)
|
||||
if runtime:
|
||||
payload = {"ip": ip, "data": runtime}
|
||||
mqtt_client.publish(MQTT_TOPIC, json.dumps(payload, default=json_serializer))
|
||||
logger.info(f"Published runtime for {ip}")
|
||||
|
||||
# -----------------------------
|
||||
# MAIN LOOP
|
||||
# -----------------------------
|
||||
async def main_loop():
|
||||
while True:
|
||||
logger.info("Discovering inverters...")
|
||||
inverters = await discover_inverters()
|
||||
if not inverters:
|
||||
logger.warning("No inverters found")
|
||||
for ip in inverters:
|
||||
await publish_runtime(ip)
|
||||
await asyncio.sleep(DISCOVERY_INTERVAL)
|
||||
|
||||
# -----------------------------
|
||||
# ENTRY POINT
|
||||
# -----------------------------
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main_loop())
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Daemon stopped")
|
||||
mqtt_client.loop_stop()
|
||||
mqtt_client.disconnect()
|
||||
Reference in New Issue
Block a user