[TUTO] Déboguer les erreurs d’expressions Jeedom (PHP) : patch de evaluateExpression() avec journalisation détaillée

Disclaimer important

La procédure ci-dessous modifie un fichier du core Jeedom :
/var/www/html/core/class/jeedom.class.php

:arrow_right: C’est totalement volontaire, mais non supporté officiellement.
:arrow_right: Vous devez savoir ce que vous faites avant de l’appliquer.
:arrow_right: Nécessite PHP 7 minimum.
:arrow_right: La modification est réversible facilement via une simple commande SSH (voir plus bas).
Vérifiez que vous savez lancer une connexion SSH avant de lancer ce patch !!
:arrow_right: Ne modifie pas le fonctionnement de Jeedom (ajoute juste une entrée dans un log).
:arrow_right: Ce Patch sera écrasé à chaque mise à jour de Jeedom (il faudra le réinstaller).


:dart: Pourquoi ce patch ?

Depuis PHP 8, certaines expressions sont devenues invalides ou génèrent des exceptions alors qu’elles étaient tolérées auparavant, par exemple :

  • float + string
  • int + string
  • expressions mal formées dans des virtuels
  • expressions de scénarios contenant du texte concaténé par erreur
  • erreurs de calcul internes non capturées

Le problème :
:point_right: Jeedom ne capture pas certaines exceptions PHP dans les expressions, notamment celles transmises à jeedom::evaluateExpression()
:point_right: Résultat : aucune visibilité sur les erreurs → très difficile de déboguer.

Ce patch ajoute une journalisation détaillée de toutes les erreurs d’expression rencontrées par Jeedom, afin de repérer facilement les commandes ou scénarios fautifs.


:memo: Ce que fait le patch

Il modifie la fonction :

jeedom::evaluateExpression()

pour :

:heavy_check_mark: remplacer catch (Exception $exc) par catch (\Throwable $e)

→ permet de capturer toutes les erreurs PHP 8 (TypeError, ValueError, etc.)

:heavy_check_mark: générer un log détaillé dans :

/var/www/html/log/http.error_evaluateExpression

:heavy_check_mark: afficher une erreur visible en haut à droite dans Jeedom

→ pratique pour repérer immédiatement les expressions cassées

:arrows_counterclockwise: Comment revenir en arrière ? (restaurer le core propre)

En SSH, exécuter simplement :
POUR LA DERNIERE VERSION :

sudo php /var/www/html/install/update.php
puis
sudo chown -R www-data:www-data /var/www/html
puis
sudo reboot

POUR UNE AUTRE VERSION exemple la 4.4.20 :

sudo php /var/www/html/install/update.php mode=force version=4.4.20
puis
sudo chown -R www-data:www-data /var/www/html
puis
sudo reboot

:arrow_right: Cela réinstalle le core Jeedom officiel,
:arrow_right: et restaure le fichier jeedom.class.php intact,


:white_check_mark: Résultat attendu (exemple de sortie de log)

Expression en erreur : #12163#+#14464#
Expression décodée : [Conso EDF][Tableau Electrique Studio][Puissance](816.7)
                      +
                      [Dehors][Portail][Puissance]()
Message : Unsupported operand types: float + string

Grâce à cela, vous pouvez retrouver exactement quelle expression est incorrecte et la corriger en quelques secondes.
Il vous suffit d’aller dans l’équipement qui provoque l’erreur, puis en face de sa commande cliquer sur la roue crantée et voir la section Utilisé par pour savoir où est utilisé la commande et corriger l’erreur.


:rocket: Installation

Créer un scénario, ajouter un bloc code, puis coller le code ci-dessous :
(ne pas mettre de <?php ni ?>)


$file = '/var/www/html/core/class/jeedom.class.php';

$scenario->setLog('--- Patch evaluateExpression() démarré ---');

// Lire le fichier
$content = file_get_contents($file);
if ($content === false) {
    $scenario->setLog("Impossible de lire le fichier : $file");
    return;
}

// Si déjà patché, on sort
if (strpos($content, 'http.error_evaluateExpression') !== false) {
    $scenario->setLog('Patch déjà présent, rien à faire.');
    return;
}

// 1) Trouver la fonction evaluateExpression
$funcSignature = 'public static function evaluateExpression(';
$funcPos = strpos($content, $funcSignature);
if ($funcPos === false) {
    $scenario->setLog('Signature de evaluateExpression() introuvable.');
    return;
}

// 2) Chercher le catch à partir de la position de la fonction
$catchSearch = "} catch (Exception \$exc) {";
$catchPos = strpos($content, $catchSearch, $funcPos);
if ($catchPos === false) {
    $scenario->setLog('Bloc "} catch (Exception $exc) {" introuvable dans evaluateExpression().');
    return;
}

// 3) Vérifier que ce catch est bien avant le "return $_input;"
$returnSearch = 'return $_input;';
$returnPos = strpos($content, $returnSearch, $funcPos);
if ($returnPos === false) {
    $scenario->setLog('"return $_input;" introuvable après evaluateExpression().');
    return;
}

if (!($catchPos < $returnPos)) {
    $scenario->setLog('Incohérence : le catch n\'est pas avant "return $_input;". Abandon.');
    return;
}

// 4) Préparer le nouveau bloc catch (on ne remplace QUE la ligne de catch)
$newCatch = <<<PHP
//} catch (Exception \$exc) {
\t\t} catch (\\Throwable \$e) {

\t\t\t// --- Décodage des #CMD_ID# en noms humains ---
\t\t\t\$_input_names = \$_input;
\t\t\tif (is_string(\$_input)) {
\t\t\t\t\$_input_names = \$_input;
\t\t\t\tif (preg_match_all('/#(\\d+)#/', \$_input, \$m)) {
\t\t\t\t\tforeach (\$m[1] as \$cmdId) {
\t\t\t\t\t\t\$cmd = cmd::byId(\$cmdId);
\t\t\t\t\t\tif (is_object(\$cmd)) {
\t\t\t\t\t\t\t\$name = \$cmd->getHumanName() . '(' . \$cmd->execCmd() . ')';
\t\t\t\t\t\t} else {
\t\t\t\t\t\t\t\$name = '[Commande inconnue ID=' . \$cmdId . ']';
\t\t\t\t\t\t}
\t\t\t\t\t\t\$_input_names = str_replace('#' . \$cmdId . '#', \$name, \$_input_names);
\t\t\t\t\t}
\t\t\t\t}
\t\t\t}

\t\t\tlog::add(
\t\t\t\t'http.error_evaluateExpression',
\t\t\t\t'error',
\t\t\t\t"Expression en erreur : " . print_r(\$_input, true) . "\\n" .
\t\t\t\t"Expression décodée : " . print_r(\$_input_names, true) . "\\n" .
\t\t\t\t((\$_scenario!==null) ? "Scenario : " . print_r(\$_scenario, true) . "\\n" : "") .
\t\t\t\t"Message : " . \$e->getMessage() . "\\n"
\t\t\t);
PHP;

// 5) Remplacement uniquement de la ligne "} catch (Exception $exc) {"
$newContent = substr_replace($content, $newCatch, $catchPos, strlen($catchSearch));

if ($newContent === null) {
    $scenario->setLog('Erreur lors du substr_replace().');
    return;
}

// 6) Sauvegarde
if (file_put_contents($file, $newContent) === false) {
    $scenario->setLog("Impossible d'écrire dans le fichier : $file");
    return;
}

$scenario->setLog('Patch evaluateExpression() appliqué avec succès.');
$scenario->setLog('--- Fin du patch ---');
17 « J'aime »

Et les premiers résultats commencent à tomber :wave:t5:

0000|[2025-12-04 16:30:22] ERROR  Expression en erreur : time_diff(#[Wallplug Teleinfo communication]#,now,m)
0001|Expression décodée : time_diff(#[Wallplug Teleinfo communication]#,now,m)
0002|Message : Failed to parse time string (#[Wallplug Teleinfo communication]#) at position 0 (#): Unexpected character

Merci pour cette modif indispensable.
Ce soir il y a aura des « Ouf super je vais pas tout me taper à la main » suivi de « Ah quand même, eh bien j’ai du boulot »

Corrigé et c’était une faute de copié/collé dans un virtuel où je n’aurais jamais pensé à aller voir.

2 « J'aime »

Bonjour Mrgreen,
J’espère que ton patch ne sera pas temporaire et qu’il sera adopté et repris par l’équipe Jeedom dés la version 4.5.1 en stable car c’est tellement utile que l’on se demande pourquoi c’était bridé.
Je corrige au fil de leurs apparitions des erreurs qui ne se limitent pas aux équipements zigbee mais également zwave, suiviconso.
Difficile pour nous qui ne sommes pas dev d’imaginer en écrivant une expression et en la testant dans le testeur de penser qu’un jour pour une raison ou une autre un des paramétres pourrait être not available et bloquer tout :face_with_head_bandage:

1 « J'aime »

Bonjour et merci pour ce patch.

Les « tolérances » (transtypage implicite) de PHP m’ont toujours un peu agacés, ainsi que parfois celle de Jeedom (mais je m’y fais. :wink:)

Bonjour,

vous pourriez proposer une PR pour intégrer cette fonction sans devoir patcher le script.

A+
Michel

1 « J'aime »

Mauvaise info, la dernière mise à jour du plugin stabilise le client et le remonte automatiquement dans la seconde s’il plante. Il faut faire cette dernière mise à jour, c’est important.
voir ce sujet Depuis la maj 4.5 Le service client MQTT "Zigbee" est arrêté - #195 par MrGreen

Pourriez-vous ne pas faire passer de fake info, merci !

Le patch est une sorte de test. Et dans l’idée, oui, il faudra faire un PR (sauf si l’équipe Jeedom veut s’en occuper bien entendu) :slight_smile:

3 « J'aime »

Bonsoir Mr Green,

Est-ce valable aussi pour la version 4.4.20 ou uniquement pour la 4.5 ?

Bonsoir, je pense que cela passe aussi en 4.4.2, de toute manière si le patch détecte un problème, il ne patch pas, et au pire tu peux revenir en arrière (tout est décrit dans le post)

1 « J'aime »

Pour info
après être revenue en arrière

, ça installe la dernière version de jeedom , pour ce qu’y veule revenir à leur version d’avant voilà ce qu’il faut faire.
Exemple pour la V 4.4.20

sudo php /var/www/html/install/update.php mode=force version=4.4.20

Attention, après une mise à jour en ligne de commande, il faut ré-appliquer les droits sur le dossier Jeedom :

sudo chown -R www-data:www-data /var/www/html

sudo :slightly_smiling_face:

1 « J'aime »

Merci pour l’info @sudo, je viens de mettre à jour le post avec ce que tu as mis :slight_smile:

1 « J'aime »

Bonjour,
Est-ce toujours d’actualité / fonctionnel avec la version 4.5.2 de jeedom car j’ai aucun fichier qui se crée … j’ai mon daemon zigbee qui se coupe tous les jours a 15h01 et j’aimerais bien mettre le doigt sur le pb

Bonjour,
Il te dit bien dans le log du scenario qu’il est appliqué ?
Tu dois avoir un nouveau log http.error_evaluateExpression
Cordialement

Hello @rennais35000

A priori oui

Dans le core cela semble ok aussi

Mais pas de nouveau fichier de log

Et je pense que je dois avoir des erreurs

une idée ?

Aucune idée, peut-être que ce log n’est créé que s’il trouve une erreur à y mettre et qu’en fait tes erreurs ne sont de cette sorte :man_shrugging:t5:
Moi ça fait un moment qu’il est vide après une chasse acharnée, et le client s’arrête toujours.

j’ai un doute que mon jeedom soit si propre que ça

je viens de faire un test

jeedom::evaluateExpression("1/0");

et cela fonctionne

donc tout est ok
merci

1 « J'aime »