MyModBus beta et chaudiere Okofen : probleme d'ecriture

Difficile de savoir si les 12 bytes de différence ne sont que dans l’entête TCP/IP, ce qui ne devrait pas changer grand chose. Et pourtant…

En analysant toute la séquence de l’échange entre les 2 machines dans les 2 setups, j’ai l’impression que ces 12 octets correspondent à des timestamps qui sont ajoutés dans l’échange. Peut être parce que c’est de l’asynchrone, du coup.

Si l’asynchrone était le problème, les lectures ne fonctionneraient pas non plus.

Pas faux.
Je ne connais pas la mécanique interne chez Okofen, mais j’attends un retour de leur service technique. Peut être qu’on aura des pistes…

Si tu as un accès ssh à ta machine jeedom, tu peux faire un essai d’écriture sans utiliser le mode asynchrone de pymodbus. Si tu veux tenter le coup, je peux te faire un exemple.

Hello,

Si tu as un accès ssh à ta machine jeedom, tu peux faire un essai d’écriture sans utiliser le mode asynchrone de pymodbus. Si tu veux tenter le coup, je peux te faire un exemple.

Ca me dit bien d’essayer, si tu peux me montrer la voie :slight_smile:

Par ailleurs, j’ai eu un retour du service technique d’Okofen.
Ils me confirment qu’avec les automates classiques et les logiciels classiques, ils ne rencontrent pas le problème de non écriture des valeurs. C’est donc bien lié a un truc dans le plugin. Et ils se déclarent incompétents pour ce qui est de la programmation Python…

OK, donc en premier, est-ce que tu as un accès ssh ?
Si ce n’est pas le cas, il me faut des détail sur ce que tu as comme macine et comment ut as installé debian (et Jeedom).

edit: il y a un chapitre dans la doc sur comment activer un accès ssh sur un RPI
https://doc.jeedom.com/fr_FR/installation/rpi

J’ai un accès SSH (avec les identifiants jeedom par défaut), mon jeedom principal sur lequel est installé le plugin tourne sur une box Atlas.
Je suis relativement à l’aise avec un accès terminal.

Bien, donc il s’agirait de te connecter et de lancer les commandes qui permettent de prendre l’environnement pyenv en compte:

export PYENV_ROOT="/var/www/html/plugins/mymodbus/ressources/_pyenv"
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

Puis de vérifier déjà 2 choses :

python3 --version
cd ~www-data/html/plugins/mymodbus/ressources/mymodbusd/
python3 --version

Normalement tu vas obtenir ça :

root@Jeedom-dev:~# python3 --version
Python 3.9.2
root@Jeedom-dev:~# cd ~www-data/html/plugins/mymodbus/ressources/mymodbusd/
root@Jeedom-dev:/var/www/html/plugins/mymodbus/ressources/mymodbusd# python3 --version
Python 3.9.16
root@Jeedom-dev:/var/www/html/plugins/mymodbus/ressources/mymodbusd#

Dans ce répertoire, tu vois que la version de python3 est différente, c’est parce que ce répertoire est configuré pour appeler l’environnement pyenv installé avec le plugin. Mais c’est un détail.

Tu peux lancer un interpréteur python3 :

python3

Pour quitter cet interpréteur, tu peux faire la combinaison CTRL+D ou lancer la commande exit(), tu retourneras dans le terminal shell.

Lecture

Maintenant, pour tester la lecture (dans un premier temps) (tu peux copier plusieurs lignes et les coller dans le terminal):

from pymodbus.client import ModbusTcpClient
from pymodbus.framer.socket_framer import ModbusSocketFramer
from pymodbus.payload import (BinaryPayloadDecoder, BinaryPayloadBuilder)

client = ModbusTcpClient(host='192.168.0.230', port=502, framer=ModbusSocketFramer)
client.connect()
reponse = client.read_holding_registers(address=54, count=1, slave=0)
decoder = BinaryPayloadDecoder.fromRegisters(reponse.registers, '>', '>')
decoder.decode_16bit_int()

La dernière ligne devrait retourner une valeur qui correspond à ce que tu as au registre 54.

Puis on ferme la connexion proprement :

client.close()

Ecriture

Il faut d’abord préparer les données à écrire (on veut écrire 100 à l’adresse 54) :

builder = BinaryPayloadBuilder(byteorder='>', wordorder='>')
builder.add_16bit_int(100)
registers = builder.to_registers()

Ensuite on se reconnecte et on écrit :

client.connect()
client.write_register(address=54, value=registers[0], slave=0)
client.close()

Voici ce que ça a donné sur mon Atlas:

jeedom@JeedomAtlas:~$ python3 --version
Python 3.7.3
jeedom@JeedomAtlas:~$ cd ~www-data/html/plugins/mymodbus/ressources/mymodbusd/
jeedom@JeedomAtlas:/var/www/html/plugins/mymodbus/ressources/mymodbusd$ python3 --version
Python 3.7.3
jeedom@JeedomAtlas:/var/www/html/plugins/mymodbus/ressources/mymodbusd$

Ca se gâte déjà ensuite, (peut être parce que je suis sous une distribution Atlas et/ou que la version de Python est un peu en retard sur la tienne (3.7.x vs 3.9.x) ? )

Même en lançant l’interpréteur avec un sudo, j’obtiens ceci:

>>> from pymodbus.client import ModbusTcpClient
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'ModbusTcpClient' from 'pymodbus.client' (/usr/local/lib/python3.7/dist-packages/pymodbus/client/__init__.py)
>>>

Je n’ m’y connais pas en Python, mais je remarque que le fichier init.py cité est totalement vide, 0 octets. Je ne sais pas si cela signifie quelque chose en rapport avec l’erreur que je rencontre.

Les deux autres import fonctionnent normalement.
Mais ensuite, forcement

>>> client = ModbusTcpClient(host='192.168.0.230', port=502, framer=ModbusSocketFramer)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'ModbusTcpClient' is not defined

Tu peux faire un

ls -als ~www-data/html/plugins/mymodbus/ressources/mymodbusd/

STP ?

jeedom@JeedomAtlas:/usr/local/lib/python3.7/dist-packages/pymodbus$ ls -als ~www-data/html/plugins/mymodbus/ressources/mymodbusd/
total 80
 4 drwxrwxr-x 4 www-data www-data  4096 avril 23 14:45 .
 4 drwxrwxr-x 4 www-data www-data  4096 août  15 16:37 ..
 4 drwxrwxr-x 3 www-data www-data  4096 avril 23 14:45 jeedom
16 -rwxrwxr-x 1 www-data www-data 16039 août  15 16:37 mymodbusd.py
 4 -rwxrwxr-x 1 www-data www-data   978 août  15 16:37 mymodbuslib.py
40 -rwxrwxr-x 1 www-data www-data 37012 août  15 16:37 mymodbus.py
 4 drwxrwxr-x 2 www-data www-data  4096 août  15 16:38 __pycache__
 4 -rwxrwxr-x 1 www-data www-data     7 août  25 23:56 .python-version

Lance ça en premier avant de faire les essais avec les versions de python3 STP (j’ai édité le message pour que ça y soit, si jamais je recopie le post)

export PYENV_ROOT="/var/www/html/plugins/mymodbus/ressources/_pyenv"
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

jeedom@JeedomAtlas:/$ export PYENV_ROOT="/var/www/html/plugins/mymodbus/ressources/_pyenv"
jeedom@JeedomAtlas:/$ command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
jeedom@JeedomAtlas:/$ eval "$(pyenv init -)"
pyenv: cannot rehash: /var/www/html/plugins/mymodbus/ressources/_pyenv/shims isn't writable

MAIS …


jeedom@JeedomAtlas:/$ python3 --version
Python 3.7.3
jeedom@JeedomAtlas:/$ cd ~www-data/html/plugins/mymodbus/ressources/mymodbusd/
jeedom@JeedomAtlas:/var/www/html/plugins/mymodbus/ressources/mymodbusd$ python3 --version
Python 3.9.16
jeedom@JeedomAtlas:/var/www/html/plugins/mymodbus/ressources/mymodbusd$

EDIT: et l’import fonctionne… Je retente la procédure

Ahhhh, me voilà rassuré

edit : je te laisse le temps de refaire les essais :wink:

Alors, en résumé:
La lecture : ça fonctionne parfaitement.
L’ecriture : ça ne renvoie pas d’erreur…
Mais la valeur n’est pas modifiée sur la chaudiere…

J’ai donc essayé autre chose…
Je tente une ecriture répétée…

builder = BinaryPayloadBuilder(byteorder='>', wordorder='>')
builder.add_16bit_int(100)
registers = builder.to_registers()

client.connect()
client.write_register(address=54, value=registers[0], slave=0)
client.write_register(address=54, value=registers[0], slave=0)
client.close()

Et là, miracle…

client.connect()
True
reponse = client.read_holding_registers(address=54, count=1, slave=0)
 decoder = BinaryPayloadDecoder.fromRegisters(reponse.registers, '>', '>')
decoder.decode_16bit_int()
100

J’ai fait plusieurs essais. De temps en temps, une des deux écritures échoue, avec une erreur timeout.
Dans ce cas, la valeur n’est pas écrite. Comme si il fallait absolument que la chaudière reçoive deux fois le message.
Mais ça n’a aucun sens d’après ce que je sais du protocole modbus, d’une part, et surtout, quand j’ai fait mes captures de paquets, les logiciels tiers avec lesquels cela fonctionne n’ont besoin d’envoyer le paquet qu’une seule fois !

Alors pourquoi le plugin devrait envoyer le message deux fois ?

Il faudrait que je remette mon setup pour capturer les paquets, et voir ce qui est envoyé quand je fais les tests a la main avec 1 et 2 ecritures…

Le seul moyen de faire échouer une requête Modbus, c’est qu’il y ait une autre requête en cours et donc un autre maître. C’est le cas avec les chaudières DeDietrich, d’ailleurs… J’ai galéré comme c’est pas possible pour essayer d’arriver à un résultat qui ne me convient pas, mais j’ai pas réussi à faire mieux…

Le truc, c’est que l’option n’est possible qu’en connexion série.


J’ai l’impression que le principe est le même. C’est comme si l’interface ethernet que tu utilises est en fait une passerelle et que derrière, sur le bus série, il y a un autre maître.
En tous cas, c’est vraiment un comportement bizarre !

edit: bien sûr ce n’est pas possible qu’il y ait un bus série, puisque l’adresse esclave est 0… Tu avais essayé avec 1 ?

Ce que tu dis pourrait peut être faire sens:
Dans la chaudière, il y a aussi un serveur http intégré, qui permet de récupérer en local des données en JSON via des requêtes CURL ou GET.
Il y a aussi un Cloud Okofen, qu’on peut joindre via le www ou via une app android, qui permet de récuperer des données.
On pourrait imaginer que ces données sont obtenues via une requete modbus du serveur http… Et que ça se bouscule au portillon.

Mais : dans ce cas, pourquoi ça n’echoue jamais avec Modbus Doctor ?
Et en effet, ça fonctionne quel que soit l’ID d’esclave que je mets (en lecture et en ecriture avec modbus doctor), par ailleurs je n’ai pas coché « RTU sur TCP » dans la config du plugin.

RTU c’est pour l’encodage des données, si ça fonctionne en mode normal, ça ne fonctionnera pas en RTU et vice versa.

edit: autre question : pourquoi ça fonctionne « parfois » ?

Franchement, je suis perdu, je ne sais pas quoi te conseiller…

OK.
Bon, si j’ecris avec slave=8 (sur une double ecriture qui marche), et que je lis sur slave=2, la valeur a bien été modifiée…