diff --git a/core/ajax/AbeilleModelChange.ajax.php b/core/ajax/AbeilleModelChange.ajax.php index db93915f62..150afa7ba8 100644 --- a/core/ajax/AbeilleModelChange.ajax.php +++ b/core/ajax/AbeilleModelChange.ajax.php @@ -37,12 +37,10 @@ throw new Exception('401 Unauthorized'); } -define('devicesDir', __DIR__ . '/../config/devices/'); // Abeille's supported devices -define('devicesLocalDir', __DIR__ . '/../config/devices_local/'); // Unsupported/user devices +require_once __DIR__ . '/../php/AbeilleModels.php'; // Libray to deal with models // Perform the requested action $action = $_POST['action']; - if (function_exists($action)) { $action(); } else { @@ -53,12 +51,11 @@ * Returns in JSON the list of existing models (known by Abeille). * Includes built-in models and local models (if any). */ -function getModelChoiceList() -{ +function getModelChoiceList() { // We allow the choice of local models and official models // Associative array modelID => model data - $list = getDevicesList("local"); - $list = array_merge($list, getDevicesList("Abeille")); + $list = getModelsList("local"); + $list = array_merge($list, getModelsList("Abeille")); // Retrieve the current model of the equipment (if it has one) $eqId = (int) $_POST['eqId']; @@ -86,8 +83,7 @@ function getModelChoiceList() * forces it to 'manual' so that the model is no longer modified automatically * from zigbee signature at equipment announcement. */ -function setModelToDevice() -{ +function setModelToDevice() { // Retrieving equipment $eqId = (int) $_POST['eqId']; $eqLogic = eqLogic::byId($eqId); @@ -156,8 +152,7 @@ function setModelToDevice() * Restores normal (automatic) model selection for equipment. * (= the model can be detected again by Abeille at the next announcement of the equipment) */ -function disableManualModelForDevice() -{ +function disableManualModelForDevice() { // Retrieving equipment $eqId = (int) $_POST['eqId']; $eqLogic = eqLogic::byId($eqId); @@ -182,70 +177,68 @@ function disableManualModelForDevice() } - /** Code from .tools\gen_devices_list.php */ // TODO refactor to avoid code duplication /* Get list of supported devices ($from="Abeille"), or user/custom ones ($from="local") Returns: Associative array; $devicesList[$identifier] = array(), or false if error */ -function getDevicesList($from = "Abeille") -{ - $devicesList = []; - - if ($from == "Abeille") - $rootDir = devicesDir; - else if ($from == "local") - $rootDir = devicesLocalDir; - else { - echo ("ERROR: Emplacement JSON '" . $from . "' invalide\n"); - return false; - } - - $dh = opendir($rootDir); - if ($dh === false) { - echo ('ERROR: getDevicesList(): opendir(' . $rootDir . ')\n'); - return false; - } - while (($dirEntry = readdir($dh)) !== false) { - /* Ignoring some entries */ - if (in_array($dirEntry, array(".", ".."))) - continue; - $fullPath = $rootDir . $dirEntry; - if (!is_dir($fullPath)) - continue; - - $fullPath = $rootDir . $dirEntry . '/' . $dirEntry . ".json"; - if (!file_exists($fullPath)) - continue; // No local JSON model. Maybe just an auto-discovery ? - - $dev = array( - 'modelName' => $dirEntry, - 'modelSource' => $from - ); - - /* Check if config is compliant with other device identification */ - $content = file_get_contents($fullPath); - $devConf = json_decode($content, true); - $devConf = $devConf[$dirEntry]; // Removing top key - $dev['manufacturer'] = isset($devConf['manufacturer']) ? $devConf['manufacturer'] : ''; - $dev['model'] = isset($devConf['model']) ? $devConf['model'] : ''; - $dev['type'] = $devConf['type']; - $dev['icon'] = $devConf['configuration']['icon']; - $devicesList[$dirEntry] = $dev; - - if (isset($devConf['alternateIds'])) { - $idList = explode(',', $devConf['alternateIds']); - foreach ($idList as $id) { - echo ("getDevicesList(): Alternate ID '" . $id . "' for '" . $dirEntry . "'\n"); - $dev = array( - 'modelName' => $dirEntry, - 'modelSource' => $from - ); - $devicesList[$id] = $dev; - } - } - } - closedir($dh); - - return $devicesList; -} +// function getModelsList($from = "Abeille") { +// $devicesList = []; + +// if ($from == "Abeille") +// $rootDir = modelsDir; +// else if ($from == "local") +// $rootDir = modelsLocalDir; +// else { +// echo ("ERROR: Emplacement JSON '" . $from . "' invalide\n"); +// return false; +// } + +// $dh = opendir($rootDir); +// if ($dh === false) { +// echo ('ERROR: getModelsList(): opendir(' . $rootDir . ')\n'); +// return false; +// } +// while (($dirEntry = readdir($dh)) !== false) { +// /* Ignoring some entries */ +// if (in_array($dirEntry, array(".", ".."))) +// continue; +// $fullPath = $rootDir . $dirEntry; +// if (!is_dir($fullPath)) +// continue; + +// $fullPath = $rootDir . $dirEntry . '/' . $dirEntry . ".json"; +// if (!file_exists($fullPath)) +// continue; // No local JSON model. Maybe just an auto-discovery ? + +// $dev = array( +// 'modelName' => $dirEntry, +// 'modelSource' => $from +// ); + +// /* Check if config is compliant with other device identification */ +// $content = file_get_contents($fullPath); +// $devConf = json_decode($content, true); +// $devConf = $devConf[$dirEntry]; // Removing top key +// $dev['manufacturer'] = isset($devConf['manufacturer']) ? $devConf['manufacturer'] : ''; +// $dev['model'] = isset($devConf['model']) ? $devConf['model'] : ''; +// $dev['type'] = $devConf['type']; +// $dev['icon'] = $devConf['configuration']['icon']; +// $devicesList[$dirEntry] = $dev; + +// if (isset($devConf['alternateIds'])) { +// $idList = explode(',', $devConf['alternateIds']); +// foreach ($idList as $id) { +// echo ("getModelsList(): Alternate ID '" . $id . "' for '" . $dirEntry . "'\n"); +// $dev = array( +// 'modelName' => $dirEntry, +// 'modelSource' => $from +// ); +// $devicesList[$id] = $dev; +// } +// } +// } +// closedir($dh); + +// return $devicesList; +// } diff --git a/core/class/AbeilleTools.class.php b/core/class/AbeilleTools.class.php index d9863e5a25..2e105e9e82 100755 --- a/core/class/AbeilleTools.class.php +++ b/core/class/AbeilleTools.class.php @@ -87,6 +87,7 @@ public static function deamonlogFilter($loglevel = 'NONE', $pluginName, $loggerN 'modelName' => model file 'modelSource' => model file location ) */ + // Note: OBSOLETE !! Use getModelsList() from AbeilleModels.php library public static function getDevicesList($from = "Abeille") { $devicesList = []; diff --git a/core/config/Abeille.config.php b/core/config/Abeille.config.php index abd13b9977..68170be3b9 100644 --- a/core/config/Abeille.config.php +++ b/core/config/Abeille.config.php @@ -8,6 +8,8 @@ $resourcePath = realpath(__DIR__.'/../../resources'); define('wifiLink', '/tmp/zigateWifi'); // For WIFI: Socat output define('otaDir', 'tmp/fw_ota'); // OTA FW location relative to Abeille's root + define('modelsDir', __DIR__ . '/devices/'); // Abeille's supported devices + define('modelsLocalDir', __DIR__ . '/devices_local/'); // Unsupported/user devices /* Inter-daemons queues: array[''] = array("id" => queueId, "max" => maxMsgSize) */ diff --git a/core/config/devices/LISEZMOI.txt b/core/config/devices/LISEZMOI.txt index a99cfbddec..bd427a7bce 100644 --- a/core/config/devices/LISEZMOI.txt +++ b/core/config/devices/LISEZMOI.txt @@ -3,21 +3,28 @@ *** Répertoire "core/config/devices" pour modèles supportés *** -Ce répertoire contient les modèles d'équipement supportés par Abeille. - ATTENTION !! Toute modification de ce répertoire sera écrasée lors des mises-à-jour du plugin. -Pendant la phase d'inclusion, Abeille va chercher les modèles dans l'ordre suivant -- dans le répertoire 'devices_local' pour les équipements locaux/custom ou en cours de dev + +Ce répertoire contient les modèles d'équipement supportés par Abeille. + +Pendant la phase d'inclusion, Abeille va tenter d'identifier le modèle correspondant à la signature Zigbee, dans l'ordre suivant +- dans le répertoire 'devices_local' pour les équipements locaux/custom ou en cours de developpement - puis dans ce répertoire officiel 'devices' - et enfin si toujours pas trouvé, le modèle 'defaultUnknown.json' sera utilisé - Ce répertoire doit suivre la structure /.json ATTENTION !! Voir 'identificateur' plus bas dans le format du fichier. +Notes +- Plusieurs signatures Zigbee peuvent utiliser le même modèle (cas d'un équipement vendu sous differentes marques) +- Pour de très rares cas, d'autres modèles (à forcer à la main apres appairage) peuvent être utilisés. + Par ex l'équipement a toujours la meme signature mais une incompatibilité dans l'evolution de son firmware rendre + le modèle par défaut inutilisable (ex: Cas sonde TSH317 Owon: EP=03 pour les anciens, 01 pour les nouveaux). + + Format de fichier ================= diff --git a/core/config/devices/THS317-ET_OWON/THS317-ET_OWON-old.json b/core/config/devices/THS317-ET_OWON/THS317-ET_OWON-old.json new file mode 100644 index 0000000000..5f1242b36e --- /dev/null +++ b/core/config/devices/THS317-ET_OWON/THS317-ET_OWON-old.json @@ -0,0 +1,55 @@ +{ + "THS317-ET_OWON-old": { + "comment": "Special model for old devices with EP 03. To be used as a 'forced model'", + "manufacturer": "Owon", + "zbManufacturer": "OWON", + "model": "THS317-ET", + "type": "Owon multi-sensor 1st gen", + "genericType": "Environment", + "timeout": "60", + "category": { + "heating": "1" + }, + "comment": "WARNING: Recent devices are using EP01 while old one are on EP03 (see #2379)", + "configuration": { + "icon": "Owon-THS317-ET", + "mainEP": "03", + "batteryType": "2x1.5V AAA" + }, + "commands": { + "Battery-Percent": { + "use": "inf_zbAttr-0001-BatteryPercent" + }, + "Identify": { + "use": "act_zbCmdC-Identify", + "nextLine": "after" + }, + "Temperature": { + "use": "inf_zbAttr-0402-MeasuredValue", + "isVisible": "1" + }, + "Bind 0402-ToZigate": { + "use": "act_zbBindToZigate", + "params": "clustId=0402", + "execAtCreation": "yes" + }, + "Bind 0001-ToZigate": { + "use": "act_zbBindToZigate", + "params": "clustId=0001", + "execAtCreation": "yes" + }, + "SetReporting 0402-0000": { + "use": "act_zbConfigureReporting2", + "params": "clustId=0402&attrType=29&attrId=0000&minInterval=300&maxInterval=600", + "execAtCreation": "yes", + "comment": "Reporting every 5 to 10mins" + }, + "SetReporting 0001-0021": { + "use": "act_zbConfigureReporting2", + "params": "clustId=0001&attrId=0021&attrType=20&minInterval=1800&maxInterval=3300", + "execAtCreation": "yes", + "comment": "Reporting every 30 to 55mins" + } + } + } +} \ No newline at end of file diff --git a/core/php/AbeilleModels.php b/core/php/AbeilleModels.php new file mode 100644 index 0000000000..d71a7b6825 --- /dev/null +++ b/core/php/AbeilleModels.php @@ -0,0 +1,178 @@ + model signature + 'modelName' => model file name WITHOUT '.json' extension + 'modelSource' => model file location ('Abeille' or 'local') + 'modelPath' => OPTIONAL: Path relative to 'modelSource' (default='/.json') + 'manufacturer' => equipment manufacturer + 'model' => equipment model + 'type' => equipment short description + 'icon' => equipment associated icon + ) */ + function getModelsList($from = "Abeille") { + $devicesList = []; + + if ($from == "Abeille") + $rootDir = modelsDir; + else if ($from == "local") + $rootDir = modelsLocalDir; + else { + log::add('Abeille', 'error', "getModelsList(): Emplacement JSON '".$from."' invalide"); + return false; + } + + $dh = opendir($rootDir); + if ($dh === false) { + log::add('Abeille', 'error', 'getModelsList(): opendir('.$rootDir.')'); + return false; + } + while (($dirEntry = readdir($dh)) !== false) { + /* Ignoring some entries */ + if (in_array($dirEntry, array(".", ".."))) + continue; + $fullPath = $rootDir.$dirEntry; + if (!is_dir($fullPath)) + continue; + + /* Supporting multiple variant of a model (rare case) + /.json + /-.json + /-.json */ + $dh2 = opendir($fullPath); + $deLen = strlen($dirEntry); + while (($dirEntry2 = readdir($dh2)) !== false) { + if (in_array($dirEntry2, array(".", ".."))) + continue; + logDebug("dirEntry2='${dirEntry2}'"); + // Model and its optional variants is named '[-].json' + if (substr($dirEntry2, 0, $deLen) != $dirEntry) + continue; // Discovery or other file but not a model + + $dirEntry2 = substr($dirEntry2, 0, -5); // Removing trailing '.json' + $dev = array( + 'modelSig' => $dirEntry, + 'modelName' => $dirEntry2, + 'modelSource' => $from + ); + if ($dirEntry2 != $dirEntry) + $dev['modelPath'] = $dirEntry.'/'.$dirEntry2.'.json'; // It's a variant + + $fullPath2 = $rootDir.$dirEntry.'/'.$dirEntry2.'.json'; + $content = file_get_contents($fullPath2); + $devMod = json_decode($content, true); + $devMod = $devMod[$dirEntry2]; // Removing top key + + $dev['manufacturer'] = isset($devMod['manufacturer']) ? $devMod['manufacturer'] : ''; + $dev['model'] = isset($devMod['model']) ? $devMod['model']: ''; + $dev['type'] = isset($devMod['type']) ? $devMod['type'] : ''; + $dev['icon'] = isset($devMod['configuration']['icon']) ? $devMod['configuration']['icon'] : ''; + $devicesList[$dirEntry2] = $dev; + + /* Any other equipments using the same model ? */ + if (isset($devMod['alternateIds'])) { + $ai = $devMod['alternateIds']; + /* Reminder: + "alternateIds": { + "alternateSigX": { + "manufacturer": "manufX", // Optional + "model": "modelX", // Optional + "type": "typeX" // Optional + "icon": "iconX" // Optional + }, + "alternateSigY": {}, + "alternateSigZ": {} + } */ + foreach ($ai as $aId => $aIdVal) { + log::add('Abeille', 'debug', "getModelsList(): Alternate ID '".$aId."' for '".$dirEntry2."'"); + $devA = $dev; // modelName, modelSource & modelPath do not change + $devA['modelSig'] = $aId; + + // manufacturer, model, type or icon can be overload + if (isset($aIdVal['manufacturer'])) + $devA['manufacturer'] = $aIdVal['manufacturer']; + if (isset($aIdVal['model'])) + $devA['model'] = $aIdVal['model']; + if (isset($aIdVal['type'])) + $devA['type'] = $aIdVal['type']; + if (isset($aIdVal['icon'])) + $devA['icon'] = $aIdVal['icon']; + $devicesList[$aId] = $devA; + } + } + } + closedir($dh2); + + // $fullPath = $rootDir.$dirEntry.'/'.$dirEntry.".json"; + // if (!file_exists($fullPath)) + // continue; // No model. Maybe just an auto-discovery ? + + // $dev = array( + // 'modelSig' => $dirEntry, + // 'modelName' => $dirEntry, + // 'modelSource' => $from + // ); + + // $content = file_get_contents($fullPath); + // $devMod = json_decode($content, true); // Device model + // $devMod = $devMod[$dirEntry]; // Removing top key + + // $dev['manufacturer'] = isset($devMod['manufacturer']) ? $devMod['manufacturer'] : ''; + // $dev['model'] = isset($devMod['model']) ? $devMod['model']: ''; + // $dev['type'] = isset($devMod['type']) ? $devMod['type'] : ''; + // $dev['icon'] = isset($devMod['configuration']['icon']) ? $devMod['configuration']['icon'] : ''; + // $devicesList[$dirEntry] = $dev; + + // /* Any other equipments using the same model ? */ + // if (isset($devMod['alternateIds'])) { + // $ai = $devMod['alternateIds']; + // /* Reminder: + // "alternateIds": { + // "alternateSigX": { + // "manufacturer": "manufX", // Optional + // "model": "modelX", // Optional + // "type": "typeX" // Optional + // "icon": "iconX" // Optional + // }, + // "alternateSigY": {}, + // "alternateSigZ": {} + // } */ + // foreach ($ai as $aId => $aIdVal) { + // log::add('Abeille', 'debug', "getModelsList(): Alternate ID '".$aId."' for '".$dirEntry."'"); + // $devA = $dev; // modelName, modelSource & modelPath do not change + // $devA['modelSig'] = $aId; + + // // manufacturer, model, type or icon can be overload + // if (isset($aIdVal['manufacturer'])) + // $devA['manufacturer'] = $aIdVal['manufacturer']; + // if (isset($aIdVal['model'])) + // $devA['model'] = $aIdVal['model']; + // if (isset($aIdVal['type'])) + // $devA['type'] = $aIdVal['type']; + // if (isset($aIdVal['icon'])) + // $devA['icon'] = $aIdVal['icon']; + // $devicesList[$aId] = $devA; + // } + // } + } + closedir($dh); + + // Default sorting... by alphabetic order of manufacturers + // TODO + + return $devicesList; + } // End getModelsList() +?> diff --git a/desktop/js/Abeille.js b/desktop/js/Abeille.js index a7ce36093c..127168aa4c 100755 --- a/desktop/js/Abeille.js +++ b/desktop/js/Abeille.js @@ -70,11 +70,11 @@ function refreshEqInfos() { eqBatteryType = eq.batteryType; curEq = { zigbee: { - addr: eq.addr + addr: eq.addr, }, model: { - modelName: eq.model.modelName - } + modelName: eq.model.modelName, + }, }; // console.log("idEqName=", document.getElementById("idEqName")); @@ -829,15 +829,15 @@ function migrateEq() { }); } -/* Attempt to detect devices on network but unknown to Jeedom. */ -function recoverDevices() { - console.log("recoverDevices()"); +// /* Attempt to detect devices on network but unknown to Jeedom. */ +// function recoverDevices() { +// console.log("recoverDevices()"); - $("#md_modal").dialog({ title: "{{Récupération d'équipements fantômes}}" }); - $("#md_modal") - .load("index.php?v=d&plugin=Abeille&modal=AbeilleRecovery.modal") - .dialog("open"); -} +// $("#md_modal").dialog({ title: "{{Récupération d'équipements fantômes}}" }); +// $("#md_modal") +// .load("index.php?v=d&plugin=Abeille&modal=AbeilleRecovery.modal") +// .dialog("open"); +// } /* Confirm unknown zigate must be accepted. */ function acceptNewZigate() { @@ -1124,25 +1124,26 @@ $("#idModelChangeBtn").on("click", function () { }, dataType: "json", global: false, - success: function (lstModels) { + success: function (modelsList) { + console.log("modelsList=", modelsList); // Populate html5 datalist - Object.values(lstModels).forEach((model) => { + Object.values(modelsList).forEach((model) => { var str = ""; // Zigbee signature if ( typeof model.manufacturer == "string" && model.manufacturer != "" - ) { + ) str += "[" + model.manufacturer + "] "; - } + else str += "[?] "; if ( typeof model.model == "string" && model.model != "" && model.model != "?" - ) { + ) str += model.model + " "; - } + else str += "? "; // Label if (str != "") { @@ -1151,8 +1152,16 @@ $("#idModelChangeBtn").on("click", function () { str += model.type; // JSON id (including location) - str += - " (" + model.jsonLocation + "/" + model.jsonId + ".json)"; + if (typeof model.modelPath != "undefined") + // modelPath is optional + str += " (" + model.modelPath + ")"; + else + str += + " (" + + model.modelName + + "/" + + model.modelName + + ".json)"; // Adding to datalist var $opt = $(""); diff --git a/docs/fr_FR/Changelog.rst b/docs/fr_FR/Changelog.rst index f57df67973..8361a068e7 100644 --- a/docs/fr_FR/Changelog.rst +++ b/docs/fr_FR/Changelog.rst @@ -4,6 +4,8 @@ ChangeLog - Interne: Cmd: Regulation revue. - Profalux BSO: Modèle revu. Suppression 'lift' (idem 'Set Level'). - Interne: Cmd: Plus que 3 niveaux de priorités. +- Owon-THS317-ET: Ajout d'un modèle pour les vieux équipements (EP=03 au lieu de 01, voir 2319). +- Interne: Nouvelle lib 'AbeilleModels.php'. 231231-STABLE-1 ---------------