Je pense avoir trouvé une solution mais pour la valider, il faudrait qu'elle soit testée par d'autres utilisateurs d'Eedomus et de systèmes Hoymiles (micro-onduleurs + DTU-PRO-S).
Pour résumer mon installation est la suivante :
9 panneaux photovoltaïques, raccordés 2 à 2 sur des micro-onduleurs Hoymile, sauf pour le 9ème qui est tout seul.
Les 5 micro-onduleurs sont connectés en WiFi sur un DTU-PRO-S Hoymiles, qui collecte les données et les transmet à un cloud. On y accède avec l'application S-Miles Installer ou S-Miles Enduser (PS : pour ceux qui y accèdent via S-Mile Enduser, qui est assez limitée, on peut installer S-Miles Installer, puis demander à Hoymiles de faire le transfert).
Etape 1
La première chose à faire est de se connecter sur le réseau WiFi du DTU-PRO-S, d’aller sur la page information, pour accéder à la configuration du port RS485, d'activer le contrôle à distance en attribuant une adresse pour le port "slave" (au choix entre 200 et 290, j'ai choisi 240), puis on se déconnecte de ce réseau WiFi. Le DTU-PRO-S enverra les données depuis le port 502 (fixe) vers le port "slave" (240) choisi.
Etape 2
Ensuite, on récupère l'adresse ip du DTU-PRO-S (chez moi : 192.168.1.50) et dans la configuration de la box internet ou du router général, on fixe cette adresse ip pour qu'elle ne change plus.
Etape 3
On teste la transmission des données en interrogeant le DTU-PRO-S via le protocole modbus.
Pour cela, avec un PC sur linux Ubuntu, on exécute dans le terminal :
- Code : Tout sélectionner
mbpoll -m tcp -a 240 -r 4096 -c 80 -t 4 -0 -1 192.168.1.50
Avec Windows, l'équivalent de l'instruction à exécuter dans la console doit être :
- Code : Tout sélectionner
modpoll -m tcp -a 240 -r 4096 -c 80 -t 4:int -0 -1 192.168.1.50
Dans cette ligne de commande je récupère la valeur des 80 premiers ports à partir du port 4096. Attention, il ne faut pas tenter de récupérer un trop grand nombre de port, si non le DTU-PRO-S plante. Si on veut interroger les ports suivants, il suffit de décaler l'adresse de départ et choisir 4156 par exemple au lieu de 4096.
et on récupère une réponse du type :
- Code : Tout sélectionner
~$ mbpoll -m tcp -a 240 -r 4096 -c 80 -t 4 -0 -1 192.168.1.50
mbpoll 1.0-0 - ModBus(R) Master Simulator
Copyright (c) 2015-2023 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.
Protocol configuration: ModBus TCP
Slave configuration...: address = [240]
start reference = 4096, count = 80
Communication.........: 192.168.1.50, port 502, t/o 1.00 s, poll rate 1000 ms
Data type.............: 16-bit register, output (holding) register table
-- Polling slave 240...
[4096]: 3092
[4097]: 4256
[4098]: 7455
[4099]: 58369 (-7167)
[4100]: 419
[4101]: 115
[4102]: 2362
[4103]: 5000
[4104]: 484
[4105]: 20
[4106]: 13
[4107]: 28088
[4108]: 96
[4109]: 3
[4110]: 0
[4111]: 0
[4112]: 263
[4113]: 0
[4114]: 0
[4115]: 0
[4116]: 3092
[4117]: 4256
[4118]: 7455
[4119]: 58370 (-7166)
[4120]: 416
[4121]: 120
[4122]: 2362
[4123]: 5000
[4124]: 503
[4125]: 20
[4126]: 13
[4127]: 40932 (-24604)
[4128]: 96
[4129]: 3
[4130]: 0
[4131]: 0
[4132]: 263
[4133]: 0
[4134]: 0
[4135]: 0
[4136]: 3092
[4137]: 4256
[4138]: 7456
[4139]: 16897
[4140]: 413
[4141]: 142
[4142]: 2350
[4143]: 5000
[4144]: 589
[4145]: 23
[4146]: 13
[4147]: 51513 (-14023)
[4148]: 99
[4149]: 3
[4150]: 0
[4151]: 0
[4152]: 263
[4153]: 0
[4154]: 0
[4155]: 0
[4156]: 3092
[4157]: 4256
[4158]: 7456
[4159]: 16898
[4160]: 410
[4161]: 146
[4162]: 2350
[4163]: 5000
[4164]: 599
[4165]: 23
[4166]: 13
[4167]: 35502 (-30034)
[4168]: 99
[4169]: 3
[4170]: 0
[4171]: 0
[4172]: 263
[4173]: 0
[4174]: 0
[4175]: 0
On remarque qu'il y a une suite de valeur qui se répète et celles-ci sont du type :
3092 / 4256 / 7456 / 16898
Le 3ème chiffre et le 4ème chiffre correspondent aux références de chaque micro-onduleurs, c'est pourquoi ils changent, quand on passe d'un micro-onduleur à un autre.
Il y a autant de séquence que d'entrées sur les micro-onduleurs (5 micro-onduleurs avec 2 entrées donnent 10 séquences).
Etape 4
Il arrive, par moment, que lors de l'extraction des données, le DTU-PRO-S se plante et envoie des séquences incomplètes qui se superposent aux autres, ce qui devient incompréhensible.
Pour éviter ce problème, le script (dtu_pv.php) ci dessous (qui est à charger dans Eedomus) va interroger le DTU-PRO-S pour récupérer les valeurs des ports (ou registres) en y allant progressivement. Le principe est de trouver les séquences successives et de récupérer la puissance instantanée de chaque panneau, qui se trouve dans le huitième registre après celui contenant la valeur 3092 (ex : registre 4104 = 484, registre 4124 = 503 ...) Cette valeur doit être divisée par 10, pour obtenir la vraie valeur instantanée en W délivrée par un panneau.
Dans le script :
- je m'arrête au registre 4485
- les codes à rechercher (PV1 à PV10) sont à adapter en fonction des codes de vos onduleurs
- Code : Tout sélectionner
<?php
// ============================================================
// DTU-PRO-S -> eedomus : puissance instantanée totale (W)
// Appel : http://localhost/script/?exec=dtu_pv.php
// ============================================================
// ---------- CONFIGURATION ----------
$DTU_IP = '192.168.1.50';
$DTU_PORT = 502;
$SLAVE_ID = 240;
$START_GLOBAL = 4096;
$END_GLOBAL = 4485; // inclus (au-delà de 4485 le DTU refuse la lecture)
$CHUNK_SIZE = 100;
$SUB_SIZE = 10; // taille des sous-blocs si un bloc est refusé
$TIMEOUT = 5; // secondes
$RETRIES = 3; // tentatives en cas de délai / connexion
$DIVISEUR = 10; // puissance en 0,1 W -> 10 ; mettre 1 si déjà en W
$OFFSET = 5; // valeur = 5e registre après celui contenant le code
// Début de suite commun, le 3e nombre varie selon l'entrée PV
$P1 = 3092;
$P2 = 4256;
$PV_CODES = array(
'PV1' => 58369, 'PV2' => 58370,
'PV3' => 16897, 'PV4' => 16898,
'PV5' => 36353, 'PV6' => 36354,
'PV7' => 52225, 'PV8' => 52226,
'PV9' => 19457, 'PV10' => 19458
);
// ---------- MODBUS TCP ----------
function sdk_read_exact($fp, $len) {
$buf = '';
$tries = 0;
while (strlen($buf) < $len && $tries < 50) {
$d = stream_get_contents($fp, $len - strlen($buf));
if ($d === false) return false;
$buf .= $d;
$tries++;
}
if (strlen($buf) < $len) return false;
return $buf;
}
// err : 0 = OK, 2 = écriture, 3 = en-tête (délai), 4 = données (délai),
// 100+n = exception Modbus n (102 = adresse illégale)
function sdk_modbus_req($fp, $tid, $unit, $addr, $qty) {
$res = array('err' => 2, 'regs' => array());
$req = pack('nnnCCnn', $tid, 0, 6, $unit, 3, $addr, $qty);
if (fwrite($fp, $req) === false) return $res;
$head = sdk_read_exact($fp, 9);
if ($head === false) {
$res['err'] = 3;
return $res;
}
$h = unpack('ntid/nproto/nlen/Cunit/Cfunc/Cbytes', $head);
if ($h['func'] != 3) {
$res['err'] = 100 + $h['bytes'];
return $res;
}
$data = sdk_read_exact($fp, $h['bytes']);
if ($data === false) {
$res['err'] = 4;
return $res;
}
$u = unpack('n*', $data);
$out = array();
foreach ($u as $v) {
$out[] = $v;
}
$res['err'] = 0;
$res['regs'] = $out;
return $res;
}
// ---------- LECTURE DE LA PLAGE ----------
$map = array();
$erreur = 0;
$adresse_err = 0;
$tid = 1;
$fp = false;
// File de blocs à lire : array(adresse, nombre, déjà_découpé)
$q = array();
$a = $START_GLOBAL;
while ($a <= $END_GLOBAL) {
$reste = $END_GLOBAL - $a;
$reste = $reste + 1;
$c = min($CHUNK_SIZE, $reste);
$q[] = array($a, $c, 0);
$a = $a + $c;
}
$i = 0;
while ($i < count($q)) {
$addr = $q[$i][0];
$cnt = $q[$i][1];
$sub = $q[$i][2];
$i++;
$ok = false;
$exc = false;
for ($t = 0; $t < $RETRIES; $t++) {
if (!$fp) {
$fp = @fsockopen($DTU_IP, $DTU_PORT, $errno, $errstr, $TIMEOUT);
if ($fp) {
stream_set_timeout($fp, $TIMEOUT);
} else {
$erreur = 1;
usleep(500000);
continue;
}
}
$r = sdk_modbus_req($fp, $tid, $SLAVE_ID, $addr, $cnt);
$tid++;
if ($r['err'] == 0) {
$ok = true;
break;
}
if ($r['err'] >= 100) {
$exc = true;
$erreur = $r['err'];
break;
}
$erreur = $r['err'];
fclose($fp);
$fp = false;
usleep(500000);
}
if ($ok) {
foreach ($r['regs'] as $k => $v) {
$pos = $addr + $k;
$map[$pos] = $v;
}
} elseif ($exc) {
if ($sub == 0 && $cnt > $SUB_SIZE) {
$e = $addr + $cnt;
for ($s = $addr; $s < $e; $s += $SUB_SIZE) {
$diff = $e - $s;
$c = min($SUB_SIZE, $diff);
$q[] = array($s, $c, 1);
}
}
} else {
$adresse_err = $addr;
break;
}
usleep(50000);
}
if ($fp) {
fclose($fp);
}
// Reconstitution du tableau dans l'ordre (-1 = illisible)
$regs = array();
for ($a = $START_GLOBAL; $a <= $END_GLOBAL; $a++) {
if (isset($map[$a])) {
$regs[] = $map[$a];
} else {
$regs[] = -1;
}
}
// ---------- RECHERCHE DES SUITES (1re occurrence seulement) ----------
$n = count($regs);
$vals = array();
$total = 0;
$trouves = 0;
foreach ($PV_CODES as $name => $code) {
$found = null;
for ($j = 0; $j + 3 + $OFFSET < $n; $j++) {
$j3 = $j + 3;
if ($regs[$j] == $P1 && $regs[$j+1] == $P2 && $regs[$j3] == $code) {
$j8 = $j3 + $OFFSET;
$found = $regs[$j8];
$trouves++;
break; // les doublons sont ignorés
}
}
if ($found === null || $found < 0) {
$vals[$name] = 0;
} else {
$vals[$name] = $found / $DIVISEUR;
}
$total += $vals[$name];
}
// ---------- SORTIE XML POUR LE CAPTEUR HTTP ----------
$xml = '<?xml version="1.0" encoding="UTF-8"?><root>';
foreach ($vals as $name => $v) {
$xml .= "<$name>" . $v . "</$name>";
}
$xml .= '<PUISSANCE_TOTALE>' . $total . '</PUISSANCE_TOTALE>';
$xml .= '<SUITES_TROUVEES>' . $trouves . '</SUITES_TROUVEES>';
$xml .= '<ERREUR>' . $erreur . '</ERREUR>';
$xml .= '<ADRESSE_ERREUR>' . $adresse_err . '</ADRESSE_ERREUR>';
$xml .= '</root>';
sdk_header('text/xml');
echo $xml;
?>
L'exécution du script dans un navigateur donne :
- Code : Tout sélectionner
<root>
<PV1>165.2</PV1>
<PV2>141.9</PV2>
<PV3>180.3</PV3>
<PV4>185.7</PV4>
<PV5>127.7</PV5>
<PV6>100.1</PV6>
<PV7>0</PV7>
<PV8>148.6</PV8>
<PV9>136.2</PV9>
<PV10>143.5</PV10>
<PUISSANCE_TOTALE>1329.2</PUISSANCE_TOTALE>
<SUITES_TROUVEES>10</SUITES_TROUVEES>
<ERREUR>102</ERREUR>
<ADRESSE_ERREUR>0</ADRESSE_ERREUR>
</root>
Le PV7 donne 0 car il correspond à l'entrée du 5ème onduleur non connectée à un panneau.
Etape 5
Ensuite, il suffit de créer autant de capteur HTTP que de panneau et un capteur supplémentaire pour y mettre la puissance totale.
L'URL de la requête sera pour tous les capteurs la même :
- Code : Tout sélectionner
http://localhost/script/?exec=dtu_pv.php
Pour chaque panneau le chemin XPATH sera du type :
- Code : Tout sélectionner
/root/PV1
ou
- Code : Tout sélectionner
/root/PUISSANCE_TOTALE
Voila, si certains d'entre vous peuvent me faire un retour, cela me permettrait de valider la solution...
