Hier ein PHP Skript, welches das tut, was im Titel steht. Im „DRY-RUN“ Modus wird einfach eine gut formatierte Tabelle aller Zigbee-Devices erstellt.
<?php
declare(strict_types=1);
/**
* Zigbee2MQTT: change MQTTBaseTopic for device instances + show table
*
* Modes:
* - DRY_RUN = true : simulate for ALL Zigbee2MQTT Device instances (no changes)
* - DRY_RUN = <int> : wet-run for ONLY that instance id
* - DRY_RUN = false : wet-run for ALL instances
*
* After run: prints table
* ID | Friendly_Name | IEEE | base_topic | instance_topic
*/
const DEVICE_MODULE_FILTER = 'Zigbee2MQTT Device';
const NEW_BASE_TOPIC = 'zigbee2mqtt2065';
// DRY_RUN can be: true / false / int
$DRY_RUN = true; // <-- set to true, false, or e.g. 53693
// Table formatting
const TABLE_WIDTH_FRIENDLY = 42;
const TABLE_WIDTH_TOPIC = 44;
// ---------------- helpers ----------------
function moduleName(int $iid): string {
try {
$inst = IPS_GetInstance($iid);
$mid = $inst['ModuleInfo']['ModuleID'] ?? '';
if ($mid === '') return '';
$m = IPS_GetModule($mid);
return (string)($m['ModuleName'] ?? '');
} catch (Throwable $e) {
return '';
}
}
function safeConfig(int $iid): array {
$cfg = IPS_GetConfiguration($iid); // JSON string
$arr = json_decode($cfg, true);
return is_array($arr) ? $arr : [];
}
function extractIEEE(string $friendly, array $cfg): string {
if (!empty($cfg['IEEE']) && is_string($cfg['IEEE'])) return strtolower(trim($cfg['IEEE']));
if (preg_match('/0x[0-9a-fA-F]{16}/', $friendly, $m)) return strtolower($m[0]);
return '';
}
function getMqttBaseTopic(int $iid): string {
$cfg = safeConfig($iid);
return (isset($cfg['MQTTBaseTopic']) && is_string($cfg['MQTTBaseTopic'])) ? trim($cfg['MQTTBaseTopic']) : '';
}
function getMqttTopic(int $iid): string {
$cfg = safeConfig($iid);
// Your module uses MQTTTopic (no space)
if (isset($cfg['MQTTTopic']) && is_string($cfg['MQTTTopic']) && trim($cfg['MQTTTopic']) !== '') {
return trim($cfg['MQTTTopic']);
}
// fallback: instance name
return IPS_GetName($iid);
}
function setMqttBaseTopic(int $iid, string $newBaseTopic): void {
// Property name is confirmed: MQTTBaseTopic
IPS_SetProperty($iid, 'MQTTBaseTopic', $newBaseTopic);
IPS_ApplyChanges($iid);
}
function pad(string $s, int $w): string {
if (mb_strlen($s) > $w) return mb_substr($s, 0, $w - 1) . '…';
return str_pad($s, $w);
}
function printTable(array $deviceIds): void {
$rows = [];
foreach ($deviceIds as $iid) {
$friendly = IPS_GetName($iid);
$cfg = safeConfig($iid);
$ieee = extractIEEE($friendly, $cfg);
$base = getMqttBaseTopic($iid);
$topic = getMqttTopic($iid);
$instanceTopic = '';
if ($base !== '' && $topic !== '') {
$instanceTopic = rtrim($base, '/') . '/' . ltrim($topic, '/');
}
$rows[] = [
'ID' => (string)$iid,
'Friendly_Name' => $friendly,
'IEEE' => $ieee,
'base_topic' => $base,
'instance_topic' => $instanceTopic,
];
}
usort($rows, fn($a, $b) => strcasecmp($a['Friendly_Name'], $b['Friendly_Name']) ?: ((int)$a['ID'] <=> (int)$b['ID']));
$widths = [
'ID' => 7,
'Friendly_Name' => TABLE_WIDTH_FRIENDLY,
'IEEE' => 20,
'base_topic' => 18,
'instance_topic' => TABLE_WIDTH_TOPIC,
];
$sep = '+';
foreach ($widths as $w) $sep .= str_repeat('-', $w + 2) . '+';
$header = '|';
foreach ($widths as $k => $w) $header .= ' ' . pad($k, $w) . ' |';
echo "\nZigbee2MQTT Device Enumeration (table)\n";
echo "=====================================\n";
echo "Found " . count($rows) . " devices\n\n";
echo $sep . "\n";
echo $header . "\n";
echo $sep . "\n";
foreach ($rows as $r) {
echo '| '
. pad($r['ID'], $widths['ID']) . ' | '
. pad($r['Friendly_Name'], $widths['Friendly_Name']) . ' | '
. pad($r['IEEE'], $widths['IEEE']) . ' | '
. pad($r['base_topic'], $widths['base_topic']) . ' | '
. pad($r['instance_topic'], $widths['instance_topic']) . " |\n";
}
echo $sep . "\n";
}
// ---------------- main ----------------
$deviceIds = [];
foreach (IPS_GetInstanceList() as $iid) {
if (stripos(moduleName($iid), DEVICE_MODULE_FILTER) === false) continue;
$deviceIds[] = $iid;
}
sort($deviceIds);
echo "Zigbee2MQTT MQTTBaseTopic changer\n";
echo "================================\n";
echo "Devices found: " . count($deviceIds) . "\n";
echo "New MQTTBaseTopic: " . NEW_BASE_TOPIC . "\n";
$targets = [];
$apply = false;
if ($DRY_RUN === true) {
$targets = $deviceIds; // simulate all
$apply = false;
echo "Mode: DRY-RUN (simulate all)\n\n";
} elseif ($DRY_RUN === false) {
$targets = $deviceIds; // apply all
$apply = true;
echo "Mode: WET-RUN (apply all)\n\n";
} elseif (is_int($DRY_RUN)) {
$targets = [$DRY_RUN]; // apply one
$apply = true;
echo "Mode: WET-RUN (apply single instance {$DRY_RUN})\n\n";
} else {
echo "ERROR: DRY_RUN must be bool or int.\n";
return;
}
$changed = 0;
$skipped = 0;
$failed = 0;
foreach ($targets as $iid) {
if (!IPS_ObjectExists($iid)) {
echo "FAIL {$iid}: instance does not exist\n";
$failed++;
continue;
}
if (stripos(moduleName($iid), DEVICE_MODULE_FILTER) === false) {
echo "SKIP {$iid} (" . IPS_GetName($iid) . "): not a Zigbee2MQTT Device\n";
$skipped++;
continue;
}
$cur = getMqttBaseTopic($iid);
if ($cur === NEW_BASE_TOPIC) {
echo "OK {$iid} (" . IPS_GetName($iid) . "): already " . NEW_BASE_TOPIC . "\n";
continue;
}
if ($apply) {
try {
setMqttBaseTopic($iid, NEW_BASE_TOPIC);
$after = getMqttBaseTopic($iid);
echo "CHG {$iid} (" . IPS_GetName($iid) . "): MQTTBaseTopic {$cur} -> {$after}\n";
$changed++;
} catch (Throwable $e) {
echo "FAIL {$iid} (" . IPS_GetName($iid) . "): " . $e->getMessage() . "\n";
$failed++;
}
} else {
echo "DRY {$iid} (" . IPS_GetName($iid) . "): WOULD set MQTTBaseTopic {$cur} -> " . NEW_BASE_TOPIC . "\n";
}
}
echo "\nSummary:\n";
echo " Changed: {$changed}\n";
echo " Skipped: {$skipped}\n";
echo " Failed : {$failed}\n";
// Always show full table after run
printTable($deviceIds);
echo "Done.\n";