Bonsoir à tous,
Je suis sur le point de signer un devis d’installation de 12 panneaux photovoltaïques avec un onduleur SUN-6K-SG03LP1-EU.
Je voudrais pouvoir interroger régulièrement l’onduleur de manière à prendre des décisions de consommation du courant produit par les panneaux.
Etant néophyte dans le monde du solaire, je voudrais savoir si le plugin Solarman est bien celui qui me permettra accéder aux données de production du SUN-6K-SG03LP1-EU sans avoir à décoder un flux compliqué de données !
Merci à tous de vos conseils.
Salut, d’après la doc certains onduleurs deye sont compatibles mais tu comprendras bien que je ne peux pas avoir tous les types d’onduleurs chez moi pour te le garantir pour le tien.
Merci pour ta réponse.
Je pense que je vais me lancer !
Merci pour le travail, mon Sun-6K me donne des valeurs cohérentes avec le LSW3. Je jette une bouteille à la mer : je n’ai pas de pince CT (Tor) car l’arrivée est trop loin. J’ai un Pi4 qui rapatrie via un ESP8266 les valeurs Power (W), tension (V), courant (A). J’aimerais les injecter dans le Deye avec un dongle RS485 pour la gestion du zéro injection. Aurais-tu une idée de comment procéder pour y parvenir ?
Comment veux tu faire du 0 injection si tu ne connais pas le sens ni la valeur du courant qui entre dans ton installation ?
je connais le sens et la valeur du courant avec plusieurs PZEM004 à l’entrée du domicile et en avale , j’integre ça ds un virtuel j ai une valeur signé en W une valeur absolue en courant et en tension
import serial
import crcmod
import time
from collections import defaultdict
# Port série
PORT = '/dev/ttyACM0'
TIMEOUT = 1 # Restauré de v3
DELAY_BETWEEN_TESTS = 0.5 # Délai entre les tests pour éviter les conflits RS485
DELAY_BETWEEN_RETRIES = 0.1 # Délai supplémentaire entre retries
DEFAULT_RESPONSE_DELAY = 0.01 # Délai par défaut (v3)
RETRY_RESPONSE_DELAYS = [0.02, 0.03] # Délais pour les retries
# Paramètres (focalisé sur standard Deye 6K SG01)
BAUDRATES = [9600] # Standard Deye
PARITIES = ['N'] # 8N1 uniquement
STOPBITS = [1] # Standard
SLAVE_IDS = [1] # ID esclave courant
# Registres basés sur deye_hybrid_map.yaml et Deye Modbus PDF
REGISTRE_DESCRIPTIONS = {
3: "Numéro de série (entier 16 bits)",
13: "Valeur interne (entier 16 bits)",
59: "Statut onduleur (0=Veille, 1=Normal, 2=Alarme, 3=Faute)",
63: "Énergie réseau (kWh, diviser par 10, signé)",
70: "Énergie totale (kWh, diviser par 10)",
71: "Énergie totale (kWh, diviser par 10)",
72: "Énergie totale (kWh, diviser par 10)",
76: "Énergie autre (kWh, diviser par 10)",
77: "Énergie autre (kWh, diviser par 10)",
78: "Énergie autre (kWh, diviser par 10)",
79: "Fréquence réseau (Hz, diviser par 100)",
81: "Énergie autre (kWh, diviser par 10)",
84: "Énergie autre (kWh, diviser par 10)",
85: "Énergie autre (kWh, diviser par 10)",
90: "Température interne (°C, diviser par 100)",
91: "Température interne (°C, diviser par 100)",
96: "Énergie autre (kWh, diviser par 10)",
101: "Valeur interne (entier 16 bits)",
109: "Tension (V, diviser par 10)",
110: "Courant (A, diviser par 100)",
111: "Tension (V, diviser par 10)",
150: "Tension réseau (V, diviser par 10)",
160: "Courant PV (A, diviser par 100)",
161: "Courant PV (A, diviser par 100)",
165: "Courant (A, diviser par 100)",
166: "Puissance (W)",
167: "Puissance (W)",
168: "Puissance (W)",
169: "Puissance (W)",
170: "Puissance PV (W)",
171: "Puissance PV (W)",
173: "Puissance (W)",
174: "Puissance (W)",
176: "Puissance (W)",
177: "Puissance (W)",
178: "Puissance (W)",
182: "Température batterie (°C, diviser par 10)",
183: "Tension batterie (V, diviser par 100)",
184: "SOC batterie (%)",
186: "Puissance batterie (W)",
187: "Puissance batterie (W)",
189: "Statut batterie (0=Inactif, 1=Charge, 2=Décharge)",
190: "Puissance (W)",
191: "Courant (A, diviser par 100, signé)",
192: "Fréquence réseau (Hz, diviser par 100)",
194: "Statut onduleur (0=Veille, 1=Sur réseau)",
244: "Valeur interne (entier 16 bits)",
248: "Mode (0=Désactivé, 1=Activé)",
250: "Plage horaire 1 (minutes depuis minuit)",
251: "Plage horaire 2 (minutes depuis minuit)",
252: "Plage horaire 3 (minutes depuis minuit)",
253: "Plage horaire 4 (minutes depuis minuit)",
254: "Plage horaire 5 (minutes depuis minuit)",
255: "Plage horaire 6 (minutes depuis minuit)",
256: "Puissance max (W)",
257: "Puissance max (W)",
258: "Puissance max (W)",
259: "Puissance max (W)",
260: "Puissance max (W)",
261: "Puissance max (W)",
268: "Valeur interne (entier 16 bits)",
269: "Valeur interne (entier 16 bits)",
270: "Valeur interne (entier 16 bits)",
271: "Valeur interne (entier 16 bits)",
272: "Valeur interne (entier 16 bits)",
273: "Valeur interne (entier 16 bits)",
274: "Valeur interne (entier 16 bits)",
275: "Valeur interne (entier 16 bits)",
276: "Valeur interne (entier 16 bits)",
277: "Valeur interne (entier 16 bits)"
}
REGISTRES = list(REGISTRE_DESCRIPTIONS.keys())
NUM_REGISTRES = [1] # Lire 1 registre à la fois pour simplicité
# CRC Modbus RTU
crc16 = crcmod.mkCrcFun(0x18005, rev=True, initCrc=0xFFFF, xorOut=0x0000)
results = defaultdict(list) # Clé: (baud, parity, stopbits, slave_id, reg, delay) -> réponse hex
failed = []
def build_command(slave_id, func_code=3, start_reg=0, num_regs=1):
"""Construit une commande Modbus RTU pour lire des holding registers"""
command = bytes([slave_id, func_code, (start_reg >> 8) & 0xFF, start_reg & 0xFF,
(num_regs >> 8) & 0xFF, num_regs & 0xFF])
crc = crc16(command)
return command + bytes([crc & 0xFF, (crc >> 8) & 0xFF])
def decode_response(reg, response):
"""Convertit la réponse hexadécimale en valeur lisible selon le registre"""
if len(response) < 5:
return "Réponse invalide"
try:
value = int(response[6:10], 16) # Extraire les 2 octets de données
if reg in [63, 70, 71, 72, 76, 77, 78, 81, 84, 85, 96]: # Énergie (kWh, diviser par 10)
return f"{value / 10:.1f} kWh"
elif reg in [79, 192]: # Fréquence réseau (Hz, diviser par 100)
return f"{value / 100:.2f} Hz"
elif reg in [90, 91]: # Température interne (°C, diviser par 100)
return f"{value / 100:.2f} °C"
elif reg in [150]: # Tension réseau (V, diviser par 10)
return f"{value / 10:.1f} V"
elif reg in [160, 161, 165, 191]: # Courant (A, diviser par 100, signé pour 191)
if reg == 191 and value > 0x7FFF:
value = value - 0x10000
return f"{value / 100:.2f} A"
elif reg in [166, 167, 168, 169, 170, 171, 173, 174, 176, 177, 178, 186, 187, 190]: # Puissance (W)
return f"{value} W"
elif reg == 182: # Température batterie (°C, diviser par 10)
return f"{value / 10:.1f} °C"
elif reg == 183: # Tension batterie (V, diviser par 100)
return f"{value / 100:.2f} V"
elif reg == 184: # SOC batterie (%)
return f"{value}%"
elif reg in [250, 251, 252, 253, 254, 255]: # Plages horaires (minutes depuis minuit)
hours = value // 60
minutes = value % 60
return f"{hours:02d}:{minutes:02d}"
elif reg in [256, 257, 258, 259, 260, 261]: # Puissance max (W)
return f"{value} W"
elif reg == 59: # Statut onduleur
return {0: "Veille", 1: "Normal", 2: "Alarme", 3: "Faute"}.get(value, f"Inconnu ({value})")
elif reg == 189: # Statut batterie
return {0: "Inactif", 1: "Charge", 2: "Décharge"}.get(value, f"Inconnu ({value})")
elif reg == 194: # Statut onduleur (autre)
return {0: "Veille", 1: "Sur réseau"}.get(value, f"Inconnu ({value})")
elif reg == 248: # Mode
return {0: "Désactivé", 1: "Activé"}.get(value, f"Inconnu ({value})")
else: # Valeur entière brute
return str(value)
except Exception as e:
return f"Erreur décodage: {e}"
def test_combination(ser, baud, parity, stopbits, slave_id, reg, num_regs, delay):
"""Teste une combinaison spécifique avec un délai donné"""
key = (baud, parity, stopbits, slave_id, reg, num_regs, delay)
try:
ser.close()
ser.port = PORT
ser.baudrate = baud
ser.parity = parity
ser.stopbits = stopbits
ser.bytesize = 8
ser.timeout = TIMEOUT
ser.open()
command = build_command(slave_id, start_reg=reg, num_regs=num_regs)
ser.write(command)
time.sleep(delay)
response = ser.read(100)
if len(response) > 0 and response[0] == slave_id and response[1] == 3:
decoded_value = decode_response(reg, response.hex())
results[key].append((response.hex(), decoded_value))
return True
else:
failed.append((key, "Réponse vide ou invalide"))
return False
except Exception as e:
failed.append((key, str(e)))
return False
finally:
if ser.is_open:
ser.close()
time.sleep(DELAY_BETWEEN_TESTS)
# Script principal
ser = serial.Serial()
print("Exécution du script: modbus_test_v8.py")
print("Début des tests pour Deye 6K SG01 monophasé avec Waveshare USB to RS485...")
total_tests = len(BAUDRATES) * len(PARITIES) * len(STOPBITS) * len(SLAVE_IDS) * len(REGISTRES) * len(NUM_REGISTRES)
print(f"Nombre total de combinaisons à tester: {total_tests} (plus retries si échec à 0.01s)")
for baud in BAUDRATES:
for parity in PARITIES:
for stopbits in STOPBITS:
for slave_id in SLAVE_IDS:
for reg in REGISTRES:
for num_regs in NUM_REGISTRES:
# Test initial avec délai par défaut (0.01s)
success = test_combination(ser, baud, parity, stopbits, slave_id, reg, num_regs, DEFAULT_RESPONSE_DELAY)
status = "✓" if success else "✗"
description = REGISTRE_DESCRIPTIONS.get(reg, "Inconnu")
decoded_value = results.get((baud, parity, stopbits, slave_id, reg, num_regs, DEFAULT_RESPONSE_DELAY), [(None, None)])[0][1] if success else ""
print(f"Test {baud}-{parity}-{stopbits}-{slave_id}-{reg}-{num_regs} (Délai: {DEFAULT_RESPONSE_DELAY}s, {description}): {status} {decoded_value}")
# Si échec, retenter avec délais alternatifs
if not success:
for retry_delay in RETRY_RESPONSE_DELAYS:
time.sleep(DELAY_BETWEEN_RETRIES) # Délai supplémentaire avant retry
success = test_combination(ser, baud, parity, stopbits, slave_id, reg, num_regs, retry_delay)
status = "✓" if success else "✗"
decoded_value = results.get((baud, parity, stopbits, slave_id, reg, num_regs, retry_delay), [(None, None)])[0][1] if success else ""
print(f"Retry {baud}-{parity}-{stopbits}-{slave_id}-{reg}-{num_regs} (Délai: {retry_delay}s, {description}): {status} {decoded_value}")
if success:
break # Arrêter si un retry réussit
# Résumé
print("\n=== RÉSUMÉ ===")
success_count = len(results)
fail_count = len(failed)
print(f"Tests réussis: {success_count}")
print(f"Tests échoués: {fail_count}")
print("\nCombinaisons qui marchent (réponses obtenues):")
for key, resp in results.items():
baud, parity, stopbits, slave_id, reg, num_regs, delay = key
description = REGISTRE_DESCRIPTIONS.get(reg, "Inconnu")
hex_response, decoded_value = resp[0]
print(f" Baud: {baud}, Parité: {parity}, Stop bits: {stopbits}, Slave ID: {slave_id}, Registre: {reg} (Nb: {num_regs}, Délai: {delay}s, {description}) -> Réponse: {hex_response} ({decoded_value})")
print("\nCombinaisons qui ne marchent pas (exemples):")
for fail, reason in failed[:10]:
baud, parity, stopbits, slave_id, reg, num_regs, delay = fail
description = REGISTRE_DESCRIPTIONS.get(reg, "Inconnu")
print(f" Baud: {baud}, Parité: {parity}, Stop bits: {stopbits}, Slave ID: {slave_id}, Registre: {reg} (Nb: {num_regs}, Délai: {delay}s, {description}) -> Erreur: {reason}")
if len(failed) > 10:
print(f" ... et {len(failed) - 10} autres")
print("\nNotes pour Deye 6K SG01 monophasé avec Waveshare USB to RS485:")
print("- Paramètres standard: 9600 8N1, Slave ID 1 (activez mode slave via app Deye/Solarman ou Modbus SN 01).")
print("- Câblage: RJ45 fil 2 (A+) à broche 7 Deye, fil 1 (B-) à broche 8, fil 3 (GND) à broche 3/6 ou blindage.")
print("- Testez inversion A/B si échec (fil 1 sur A+, fil 2 sur B-).")
print("- GND recommandé pour stabilité (>1m). Résistance 120Ω onboard Waveshare activable si bus long.")
print("- Registres basés sur deye_hybrid_map.yaml et Deye Modbus PDF (ex. 160=Courant PV, 184=SOC batterie, 194=Statut).")
print("- Délai par défaut de 0.01s (comme v3), retries à 0.02s/0.03s avec délai supplémentaire de 0.1s entre retries.")
print("- Vérifiez mode esclave activé, alimentation inverter, et état (PV actif, batterie connectée).")
print("- Si aucun succès, contactez support Deye pour PDF Modbus ou confirmez pinout RJ45.")
print("- Exécutez dans venv avec pyserial et crcmod.")
C’est difficilement lisible, il faudrait que tu mettes le code entre les balises texte preformaté.
Tu pourrais expliquer un peu plus. Ton installation ? Qui fait quoi? Ce que tu veux faire exactement ? …?
Est ce que tu as un registre qui pourrait brider la valeur max que tes pv produisent?
Un lien qui explique comment partager du code correctement sur la communauté :
Registre Modbus pour limiter la production PV maximale sur un onduleur DeyeD’après l’analyse du répertoire Modbus du firmware Deye sur GitHub (DeyeFirmware/Modbus at main · Mights001/DeyeFirmware · GitHub) et des documents de protocole Modbus associés aux onduleurs Deye/Sunsynk (comme la version V118 du protocole RTU), un registre clé qui peut « brider » (limiter) la valeur maximale de production PV est le registre 43 (adresse Modbus 43, en holding register).
Autres registres liés à la limitation PV :Registre 340 : Maximum Sell Power (Puissance de vente maximale au réseau). Limite la puissance PV exportée vers le grid (en W), mais n’impacte pas directement la production totale si l’excédent va à la batterie/charge.
Registre 45-48 : Time-of-Use (TOU) Power Limits – Limites PV par créneaux horaires (pour une limitation dynamique).
Registre 80 : Switch On/Off – Peut indirectement limiter à 0 en arrêtant l’onduleur.
ma question est : es ce la puissance signé ou le courrant signé ou les 2 qui doivent étre écrit dans le ou les registres du Deye pour pouvoir faire du zero injection
Ma réponse : je ne sais pas. Est ce que ces registres sont en R/W (lecture / écriture) ou en lecture seule. La réponse à cette question conditionne tout. En général ils sont en lecture seule.
Si tu n’as pas de ct comme tu l’as écrit dans ton 1er post alors la seule façon de ne pas faire d’export vers le réseau c’est d’analyser en permanence ce que tu « consommes » du réseau. Si tu exportes alors il fait limiter d’autant ta production, si tu importes il faut remonter d’autant la limitation jusqu’au max. Si ta consommation est assez aléatoire cela ne va pas être de tout repos pour ton onduleur. L’idéal serait la ct.