Sinds kort is de nieuwe Philips Hue Bridge Pro beschikbaar. Deze ondersteunt tot wel 150 lampen en 50 accessoires, en maakt het mogelijk om Hue-lampen als bewegingssensor te gebruiken. Helaas ondersteunt de Homey Pro op dit moment de nieuwe Hue Bridge Pro nog niet via de officiële Philips Hue app. Gelukkig kun je de bridge toch al koppelen via Matter.
In dit artikel lees je stap voor stap hoe je dat doet — zónder je apparaten of flows kwijt te raken.
Waarom Matter?
De Philips Hue Bridge Pro ondersteunt Matter, een nieuwe smart home-standaard waarmee apparaten platform-onafhankelijk kunnen samenwerken. Homey ondersteunt Matter al sinds versie 10.3.1 van Homey Pro (2023), en daarmee kun je dus nu ook je Hue-lampen via Matter koppelen.
Belangrijk: verwijder nog niets!
Voordat je begint:
Ga naar 👉 https://tools.developer.homey.app/tools/api-playground en vul bij request het volgende in:
Homey.devices.getDevices();
klik op run. je ziet nu alle devices die je hebt. Kopieer de response en zet deze in bijv. kladblok.
- ❌ Verwijder je oude Hue Bridge nog niet
- ❌ Verwijder de Philips Hue app nog niet uit Homey
- ✅ Zorg dat al je lampen bereikbaar zijn voor de nieuwe bridge
- ✅ Zorg dat je nieuwe Hue Bridge Pro al gekoppeld is aan je Hue-account
Stap 1: Koppel de Hue Bridge Pro aan Homey via Matter
Volg deze tutorial om je bridge aan Homey te koppelen:
🔗 Tutorial: Philips Hue koppelen via Matter aan Homey Pro
Na het koppelen verschijnen je Hue-lampen als Matter-apparaten in Homey.
Stap 2: Zet je apparaten handmatig in de juiste zones
De apparaten komen allemaal onder “thuis” terecht. Ga in Homey naar elk apparaat en kies handmatig de juiste kamer of zone waarin het thuishoort.
Dit is belangrijk, want de automatische migratie (zie stap 3) gebruikt deze zones als één van de match-criteria.
Stap 3: Koppel je oude Hue-apparaten aan de nieuwe Matter-apparaten
LET OP! Dit script is getest met de apparaten overzetten (aan/uit), maar niet met bijv. helderheid en dimniveau instellingen in de flows. Dit zou je eventueel later handmatig kunnen aanpassen met het andere script. Voer dit script uit op eigen verantwoordelijkheid en alleen als je (in ieder geval een beetje) weet wat je aan het doen bent! Ik raad altijd aan om Homey backups te gebruiken als je een ingrijpende verandering gaat doen in je Homey setup!
Ga naar:
👉 https://tools.developer.homey.app/tools/api-playground
- Log in op je Homey account
- Kies linksboven je Homey Pro
- Plak onderstaande code in het “Request”-veld:
Je kunt er voor kiezen om nu hetgene wat je in kladblok hebt geplakt, hier in te plakken: const OLD_DEVICES_JSON = “; in plaats van “. Mochten de apparaten via de philips hue app al verwijderd zijn uit Homey, heb je deze nog steeds bij de hand.
(async () => {
const softRun = true; // ← eerst runnen zonder dat er iets wordt omgezet!
const MAX_REPLACEMENTS_PER_FLOW_LOG = 50;
// ── Config: plak hier je oude snapshot (optioneel) ─────────────────
// Plak de ruwe JSON string van een eerdere `Homey.devices.getDevices()` call.
// Dit mag ofwel het hele object { id: device, ... } zijn of een array met devices.
const OLD_DEVICES_JSON = ``; // ← laat zo staan als je geen oude snapshot wilt gebruiken
// ── Handmatige mapping (optioneel) ─────────────────────────────────────────
// Oude HUE deviceId → Nieuwe MATTER deviceId (forceert vervanging, overschrijft heuristiek)
const MANUAL_PAIRS = {
// "old-hue-uuid": "new-matter-uuid",
};
// Naam-aliases (optioneel): “oude naam” → “nieuwe naam” (helpt matching)
const NAME_ALIASES = {
// "Eetkamerlamp 1": "Eetkamerlamp 1", // vul bij afwijkende namen
};
// ── Herkenning & helpers ───────────────────────────────────────────────────
const isHue = d => typeof d?.driverId === "string" && d.driverId.startsWith("homey:app:nl.philips.hue");
const isMatter = d => {
const s = String(d?.driverId || "").toLowerCase();
return s.includes(":matter:") || s.includes("virtualdrivermatter");
};
const isLightLike = d => {
if (d?.class === "light") return true;
const caps = Array.isArray(d?.capabilities) ? d.capabilities : [];
return caps.includes("onoff") || caps.includes("dim");
};
const norm = s => (s || "")
.normalize("NFKC")
.toLowerCase()
.replace(/[^\p{L}\p{N}\s-]/gu, " ")
.replace(/\s+/g, " ")
.trim();
const stripCommon = s => {
let t = " " + s + " ";
t = t.replace(/\bhue\b/g, " ");
t = t.replace(/\blamp(en)?\b/g, " ");
t = t.replace(/\bspot(s)?\b/g, " ");
t = t.replace(/\bledstrip\b/g, " ");
t = t.replace(/\bphilips\b/g, " ");
return norm(t);
};
// Belangrijk: we gebruiken een mutable alias map die we later kunnen overschrijven met suggesties.
let CURRENT_NAME_ALIASES = { ...NAME_ALIASES };
const tokenize = s => stripCommon(norm(CURRENT_NAME_ALIASES[s] || s)).split(" ").filter(Boolean);
const capSet = d => new Set(Array.isArray(d?.capabilities) ? d.capabilities : []);
// score helpers
const tokenScore = (aTokens, bTokens) => {
if (!aTokens.length || !bTokens.length) return 0;
const a = new Set(aTokens);
const b = new Set(bTokens);
let intersect = 0;
for (const x of a) if (b.has(x)) intersect++;
return intersect * 2 + (intersect / Math.max(a.size, b.size));
};
const classScore = (a, b) => (a && b && a === b ? 2 : 0);
const capsScore = (aCaps, bCaps) => Math.min([...aCaps].filter(x => bCaps.has(x)).length, 3);
const toArrayDevices = objOrArr => {
if (!objOrArr) return [];
if (Array.isArray(objOrArr)) return objOrArr;
if (typeof objOrArr === "object") return Object.values(objOrArr);
return [];
};
const buildIndex = devices => devices.map(d => ({
d,
id: d.id,
name: d.name || "",
normName: norm(d.name || ""),
tokens: tokenize(d.name || ""),
zone: d.zone || null,
class: d.class || null,
caps: capSet(d),
}));
function suggestFromSnapshots(oldDevices, newDevices, {
restrictToLightLikes = true,
scoreThreshold = 3,
} = {}) {
const oldList = oldDevices.filter(d => isHue(d) && (!restrictToLightLikes || isLightLike(d)));
const newList = newDevices.filter(d => isMatter(d) && (!restrictToLightLikes || isLightLike(d)));
const indexNew = buildIndex(newList);
const pairs = [];
const unresolved = [];
for (const h of oldList) {
const hName = h.name || "";
const hTokens = tokenize(hName);
const hZone = h.zone || null;
const hClass = h.class || null;
const hCaps = capSet(h);
const hNorm = norm(CURRENT_NAME_ALIASES[hName] || hName);
// exact
let candidates = indexNew.filter(x => x.name === hName);
if (candidates.length === 1) {
pairs.push({ old: h, neu: candidates[0].d, reason: "exact-name", score: 100 });
continue;
}
// normalized
candidates = indexNew.filter(x => x.normName === hNorm);
if (candidates.length === 1) {
pairs.push({ old: h, neu: candidates[0].d, reason: "normalized-name", score: 90 });
continue;
}
// fuzzy
const scored = indexNew.map(x => {
let s = 0;
s += tokenScore(hTokens, x.tokens);
s += classScore(hClass, x.class);
s += capsScore(hCaps, x.caps);
if (hZone && x.zone && hZone === x.zone) s += 1.5;
if (x.normName.includes(hNorm) || hNorm.includes(x.normName)) s += 0.5;
return { x, s };
}).sort((a, b) => b.s - a.s);
if (scored.length && scored[0].s >= scoreThreshold) {
pairs.push({ old: h, neu: scored[0].x.d, reason: "fuzzy", score: scored[0].s });
} else {
unresolved.push(h);
}
}
// 1:1
const seenNew = new Set();
const finalPairs = [];
const collisions = [];
for (const p of pairs) {
if (seenNew.has(p.neu.id)) collisions.push(p);
else { seenNew.add(p.neu.id); finalPairs.push(p); }
}
// bouw suggestie-objecten
const SUGGESTED_MANUAL_PAIRS = {};
const SUGGESTED_NAME_ALIASES = {};
for (const p of finalPairs) {
SUGGESTED_MANUAL_PAIRS[p.old.id] = p.neu.id;
const oldName = p.old.name || "";
const newName = p.neu.name || "";
if (norm(oldName) !== norm(newName)) {
SUGGESTED_NAME_ALIASES[oldName] = newName;
}
}
return { SUGGESTED_MANUAL_PAIRS, SUGGESTED_NAME_ALIASES, collisions, unresolved, matches: finalPairs };
}
// ── Data (huidige status) ──────────────────────────────────────────────────
const devicesObj = await Homey.devices.getDevices();
const devices = Object.values(devicesObj);
const hueList = devices.filter(d => isHue(d) && isLightLike(d));
const matterList = devices.filter(d => isMatter(d) && isLightLike(d));
if (!hueList.length) { console.log("Geen HUE-lampen gevonden in je Homey. Niks te vervangen."); return; }
if (!matterList.length) { console.log("Geen MATTER-lampen gevonden in je Homey. Vervanging niet mogelijk."); return; }
// ── (NIEUW) Suggesties uit oude snapshot (indien aanwezig) ─────────────────
let suggestedManual = {};
let suggestedAliases = {};
if (OLD_DEVICES_JSON && OLD_DEVICES_JSON.trim()) {
try {
const parsed = JSON.parse(OLD_DEVICES_JSON);
const oldDevices = toArrayDevices(parsed);
// Gebruik eerst je handmatige aliases tijdens suggestie (kan helpen)
CURRENT_NAME_ALIASES = { ...NAME_ALIASES };
const { SUGGESTED_MANUAL_PAIRS, SUGGESTED_NAME_ALIASES, collisions, unresolved, matches } =
suggestFromSnapshots(oldDevices, devices);
suggestedManual = SUGGESTED_MANUAL_PAIRS;
suggestedAliases = SUGGESTED_NAME_ALIASES;
console.log("── Voorstel uit oude snapshot ─────────────────────────────");
matches.forEach(p =>
console.log(`• ${p.old.name} (${p.old.id}) → ${p.neu.name} (${p.neu.id}) [${p.reason}, score=${p.score}]`));
if (collisions.length) {
console.warn("\n⚠️ Collisions (zelfde MATTER kandidaat voor meerdere oude HUE’s):");
collisions.forEach(p =>
console.warn(`- ${p.old.name} → ${p.neu.name} [${p.reason}, score=${p.score}] (NIET toegepast)`));
}
if (unresolved.length) {
console.warn("\n⚠️ Onopgeloste oude HUE-devices (geen goede match):");
unresolved.forEach(h => console.warn(`- ${h.name} (${h.id})`));
}
console.log("──────────────────────────────────────────────────────────");
} catch (e) {
console.warn("Kon OLD_DEVICES_JSON niet parsen; snapshot-suggestie overgeslagen.", e);
}
}
// Merge: suggesties → jouw handmatige input (handmatig heeft voorrang)
const _MANUAL_PAIRS = { ...suggestedManual, ...MANUAL_PAIRS };
CURRENT_NAME_ALIASES = { ...suggestedAliases, ...NAME_ALIASES };
// ── Indexeer MATTER (na CURRENT_NAME_ALIASES set) ──────────────────────────
const indexMatter = matterList.map(d => ({
d,
name: d.name || "",
normName: norm(d.name || ""),
tokens: tokenize(d.name || ""),
zone: d.zone || null,
class: d.class || null,
caps: capSet(d),
}));
// ── Matching HUE → MATTER ──────────────────────────────────────────────────
const pairs = []; // { old: Device, neu: Device, reason, score }
const unresolved = [];
for (const h of hueList) {
if (_MANUAL_PAIRS[h.id]) {
const m = devices.find(x => x.id === _MANUAL_PAIRS[h.id]);
if (m) {
pairs.push({ old: h, neu: m, reason: "manual", score: 999 });
continue;
}
}
const hName = h.name || "";
const hNorm = norm(CURRENT_NAME_ALIASES[hName] || hName);
const hTokens = tokenize(hName);
const hZone = h.zone || null;
const hClass = h.class || null;
const hCaps = capSet(h);
// 1) exacte naam
let candidates = indexMatter.filter(x => x.d.name === hName);
if (candidates.length === 1) {
pairs.push({ old: h, neu: candidates[0].d, reason: "exact-name", score: 100 });
continue;
}
// 2) genormaliseerde naam
candidates = indexMatter.filter(x => x.normName === hNorm);
if (candidates.length === 1) {
pairs.push({ old: h, neu: candidates[0].d, reason: "normalized-name", score: 90 });
continue;
}
// 3) fuzzy
let scored = indexMatter.map(x => {
let s = 0;
s += tokenScore(hTokens, x.tokens); // naam-overlap
s += classScore(hClass, x.class); // zelfde class
s += capsScore(hCaps, x.caps); // cap-overlap
if (hZone && x.zone && hZone === x.zone) s += 1.5; // bonus voor zelfde zone
if (x.normName.includes(hNorm) || hNorm.includes(x.normName)) s += 0.5;
return { x, s };
}).sort((a, b) => b.s - a.s);
if (scored.length && scored[0].s >= 3) {
pairs.push({ old: h, neu: scored[0].x.d, reason: "fuzzy", score: scored[0].s });
} else {
unresolved.push(h);
}
}
// Verwijder dubbel toewijzen van dezelfde MATTER lamp (1:1 is gewenst)
const seenNew = new Set();
const finalPairs = [];
const collisions = [];
for (const p of pairs) {
if (seenNew.has(p.neu.id)) collisions.push(p);
else { seenNew.add(p.neu.id); finalPairs.push(p); }
}
// ── Logging: voorstel (incl. bron van overrides) ───────────────────────────
console.log("── Voorstel HUE → MATTER ───────────────────────────────────");
for (const p of finalPairs) {
console.log(`• ${p.old.name} (${p.old.id}) → ${p.neu.name} (${p.neu.id}) [${p.reason}, score=${p.score}]`);
}
if (Object.keys(suggestedManual).length || Object.keys(suggestedAliases).length) {
console.log("\n(Info) Suggesties uit oude snapshot toegepast (kun je overschrijven met MANUAL_PAIRS/NAME_ALIASES bovenin).");
if (Object.keys(suggestedManual).length) {
console.log("SUGGESTED_MANUAL_PAIRS:", JSON.stringify(suggestedManual, null, 2));
}
if (Object.keys(suggestedAliases).length) {
console.log("SUGGESTED_NAME_ALIASES:", JSON.stringify(suggestedAliases, null, 2));
}
}
if (collisions.length) {
console.warn("\n⚠️ Meervoudige toewijzingen (één MATTER kandidaat voor meerdere HUE’s):");
collisions.forEach(p => console.warn(`- ${p.old.name} → ${p.neu.name} [${p.reason}, score=${p.score}] (NIET toegepast)`));
}
if (unresolved.length) {
console.warn("\n⚠️ Onopgeloste HUE-devices (geen goede match gevonden):");
unresolved.forEach(h => console.warn(`- ${h.name} (${h.id})`));
}
// ── Flow scanning & vervangen ──────────────────────────────────────────────
const mapOldToNew = new Map(finalPairs.map(p => [p.old.id, p.neu.id]));
const escapeReg = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const replaceEverywhere = (node, from, to) => {
const r = new RegExp(escapeReg(from), "g");
if (typeof node === "string") return node.replace(r, to);
if (node && typeof node === "object") {
for (const k of Object.keys(node)) {
const v = node[k];
if (typeof v === "string") node[k] = v.replace(r, to);
else if (v && typeof v === "object") replaceEverywhere(v, from, to);
}
}
return node;
};
const flowsObj = await Homey.flow.getFlows();
const flows = Object.values(flowsObj);
const advFlows = Object.values(await Homey.flow.getAdvancedFlows());
const changedFlows = [];
const changedAdv = [];
// Gewone flows verwerken
for (const f of flows) {
const before = JSON.stringify(f);
const clone = JSON.parse(before);
for (const [oldId, newId] of mapOldToNew.entries()) {
if (clone.trigger) replaceEverywhere(clone.trigger, oldId, newId);
if (Array.isArray(clone.actions)) clone.actions.forEach(c => replaceEverywhere(c, oldId, newId));
if (Array.isArray(clone.conditions)) clone.conditions.forEach(c => replaceEverywhere(c, oldId, newId));
}
const after = JSON.stringify(clone);
if (before !== after) {
changedFlows.push({ id: f.id, name: f.name });
if (!softRun) {
try {
await Homey.flow.updateFlow({
id: f.id,
flow: {
id: f.id,
...(clone.trigger ? { trigger: clone.trigger } : {}),
...(clone.actions ? { actions: clone.actions } : {}),
...(clone.conditions ? { conditions: clone.conditions } : {}),
},
});
} catch (e1) {
try {
await Homey.flow.updateFlow({ id: f.id, ...clone });
} catch (e2) {
console.error(`❌ updateFlow faalde voor ${f.name} (${f.id})`, e2);
}
}
}
}
}
// Advanced flows verwerken
for (const af of advFlows) {
const before = JSON.stringify({ cards: af.cards, connections: af.connections });
const cards = JSON.parse(JSON.stringify(af.cards));
const connections = JSON.parse(JSON.stringify(af.connections || []));
for (const key of Object.keys(cards || {})) {
for (const [oldId, newId] of mapOldToNew.entries()) {
replaceEverywhere(cards[key], oldId, newId);
}
}
const after = JSON.stringify({ cards, connections });
if (before !== after) {
changedAdv.push({ id: af.id, name: af.name });
if (!softRun) {
try {
await Homey.flow.updateAdvancedFlow({
id: af.id,
advancedflow: { cards, connections },
});
} catch (e1) {
try {
await Homey.flow.updateAdvancedFlow({ id: af.id, cards, connections });
} catch (e2) {
console.error(`❌ updateAdvancedFlow faalde voor ${af.name} (${af.id})`, e2);
}
}
}
}
}
// ── Rapport ────────────────────────────────────────────────────────────────
console.log("\n──────── RESULTAAT ────────");
console.log(`Dry-run: ${softRun ? "JA (niets opgeslagen)" : "NEE (wijzigingen opgeslagen)"}`);
console.log(`HUE→MATTER paren toegepast: ${finalPairs.length}`);
if (unresolved.length) console.log(`Onopgelost: ${unresolved.length} (zie waarschuwing boven)`);
if (collisions.length) console.log(`Conflicten (dubbele doelkandidaat): ${collisions.length} (niet toegepast)`);
console.log(`Gewone Flows aangepast: ${changedFlows.length}`);
changedFlows.slice(0, MAX_REPLACEMENTS_PER_FLOW_LOG).forEach(f => console.log(`• ${f.name} (${f.id})`));
if (changedFlows.length > MAX_REPLACEMENTS_PER_FLOW_LOG) console.log(`… +${changedFlows.length - MAX_REPLACEMENTS_PER_FLOW_LOG} meer`);
console.log(`Advanced Flows aangepast: ${changedAdv.length}`);
changedAdv.slice(0, MAX_REPLACEMENTS_PER_FLOW_LOG).forEach(f => console.log(`• ${f.name} (${f.id})`));
if (changedAdv.length > MAX_REPLACEMENTS_PER_FLOW_LOG) console.log(`… +${changedAdv.length - MAX_REPLACEMENTS_PER_FLOW_LOG} meer`);
console.log("───────────────────────────");
})();
- Klik met de rechtermuisknop in het venster → klik op Inspecteren
- Open het tabblad Console
- Klik op Run
Het script laat in de console zien welke apparaten vervangen zullen worden en welke flows worden aangepast. Controleer dit goed!
Stap 4: Vervang je apparaten in je flows
Als je tevreden bent met het resultaat, wijzig je de softRun regel in het script naar false:
const softRun = false;
Klik nogmaals op Run. Het script vervangt nu al je Hue-apparaten in Homey flows door de gekoppelde Matter-apparaten.
Stap 5: Controleer of alles werkt
- Ga je flows na
- Controleer je zones
- Test of je lampen reageren via Homey
- EXTRA: als je dit script in de api playground van Homey zet, krijg je een overzicht in de console van de flows die missende apparaten hebben. In deze flows zou je dan nog handmatig de apparaten kunnen terugzetten.
(async () => {
const SHOW_WHERE = false; // zet op true als je per flow een beknopte lijst wilt in welke KAART-keys de missers zitten
// (optioneel) oude devices export (id -> device) om oude namen te tonen
const OLD_DEVICES = {
// "uuid-van-oud-device": { id:"...", name:"Oude lampnaam", ... },
};
// ── helpers ────────────────────────────────────────────────────────────────
const toArr = (x) => Array.isArray(x) ? x : Object.values(x || {});
const oldById = new Map(toArr(OLD_DEVICES).map(d => [d.id, d]));
const devicesObj = await Homey.devices.getDevices();
const curDevices = toArr(devicesObj);
const curIds = new Set(curDevices.map(d => d.id));
const ownerUriToDevId = (s) => {
if (typeof s !== "string") return null;
const m = s.match(/^homey:device:([0-9a-f-]{36})$/i);
return m ? m[1] : null;
};
// Herken alléén velden die echt naar devices verwijzen (geen willekeurige UUID-scan!)
function extractDeviceRefsFromCard(card) {
const hits = [];
// 1) ownerUri: "homey:device:<uuid>"
const byOwner = ownerUriToDevId(card.ownerUri);
if (byOwner) hits.push({ id: byOwner, where: "ownerUri" });
// 2) gangbare arg-vormen
const nodesToCheck = [];
// vaak: { device: { id } }
if (card.args && card.args.device) nodesToCheck.push({ node: card.args.device, where: "args.device" });
// soms: { target: { id } } of { targets: [ { id } ] }
if (card.args && card.args.target) nodesToCheck.push({ node: card.args.target, where: "args.target" });
if (card.args && Array.isArray(card.args.targets)) nodesToCheck.push({ node: card.args.targets, where: "args.targets[]" });
// soms: { devices: [ { id } ] } of direct array met id’s
if (card.args && Array.isArray(card.args.devices)) nodesToCheck.push({ node: card.args.devices, where: "args.devices[]" });
if (card.devices) nodesToCheck.push({ node: card.devices, where: "card.devices[]" });
if (card.targets) nodesToCheck.push({ node: card.targets, where: "card.targets[]" });
const collectIds = (node, where) => {
if (!node) return;
if (typeof node === "string") {
// soms staat het id direct als string
if (/^[0-9a-f-]{36}$/i.test(node)) hits.push({ id: node, where });
} else if (Array.isArray(node)) {
for (const v of node) collectIds(v, where);
} else if (typeof node === "object") {
if (typeof node.id === "string" && /^[0-9a-f-]{36}$/i.test(node.id)) {
hits.push({ id: node.id, where });
}
}
};
for (const n of nodesToCheck) collectIds(n.node, n.where);
// dedup
const seen = new Set();
return hits.filter(h => {
const key = h.id + "|" + h.where;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
function summarizeMissingRefs(refs) {
const byId = new Map();
for (const r of refs) {
if (!byId.has(r.id)) byId.set(r.id, new Set());
byId.get(r.id).add(r.where);
}
return [...byId.entries()].map(([id, wheres]) => ({
id,
oldName: oldById.get(id)?.name || null,
where: SHOW_WHERE ? [...wheres] : undefined
}));
}
// ── gewone flows ───────────────────────────────────────────────────────────
const flowsObj = await Homey.flow.getFlows();
const flows = toArr(flowsObj);
const flowReport = [];
for (const f of flows) {
const refs = [];
// trigger/conditions/actions hebben meestal kaarten met ownerUri/id/args
const scanCards = (cards, label) => {
if (!cards) return;
const arr = Array.isArray(cards) ? cards : [cards];
for (const c of arr) {
const hits = extractDeviceRefsFromCard(c);
for (const h of hits) refs.push({ ...h, cardType: label });
}
};
scanCards(f.trigger, "trigger");
scanCards(f.conditions, "condition");
scanCards(f.actions, "action");
// filter op missende device-id’s
const missing = refs.filter(r => !curIds.has(r.id));
if (missing.length) {
flowReport.push({
id: f.id,
name: f.name,
missingDevices: summarizeMissingRefs(missing)
});
}
}
// ── advanced flows ─────────────────────────────────────────────────────────
const advFlows = toArr(await Homey.flow.getAdvancedFlows());
const advReport = [];
for (const af of advFlows) {
const refs = [];
const cards = af.cards || {};
for (const key of Object.keys(cards)) {
const card = cards[key];
const hits = extractDeviceRefsFromCard(card);
for (const h of hits) refs.push({ ...h, cardKey: key });
}
const missing = refs.filter(r => !curIds.has(r.id));
if (missing.length) {
const grouped = new Map(); // id -> set(cardKeys/wheres)
for (const m of missing) {
if (!grouped.has(m.id)) grouped.set(m.id, new Set());
if (SHOW_WHERE) grouped.get(m.id).add(m.cardKey || m.where || "?");
}
advReport.push({
id: af.id,
name: af.name,
missingDevices: [...grouped.entries()].map(([id, set]) => ({
id,
oldName: oldById.get(id)?.name || null,
where: SHOW_WHERE ? [...set] : undefined
}))
});
}
}
// ── output ─────────────────────────────────────────────────────────────────
if (!flowReport.length && !advReport.length) {
console.log("✅ Geen flows met missende apparaten gevonden.");
return;
}
console.log("=== Gewone flows met missende apparaten ===");
if (flowReport.length) {
for (const r of flowReport) {
console.log(`• ${r.name} (${r.id})`);
for (const m of r.missingDevices) {
const label = m.oldName ? `${m.oldName} (${m.id})` : m.id;
console.log(` - ${label}${SHOW_WHERE && m.where?.length ? " ← " + m.where.join(", ") : ""}`);
}
}
} else {
console.log("— geen —");
}
console.log("\n=== Advanced flows met missende apparaten ===");
if (advReport.length) {
for (const r of advReport) {
console.log(`• ${r.name} (${r.id})`);
for (const m of r.missingDevices) {
const label = m.oldName ? `${m.oldName} (${m.id})` : m.id;
console.log(` - ${label}${SHOW_WHERE && m.where?.length ? " ← " + m.where.join(", ") : ""}`);
}
}
} else {
console.log("— geen —");
}
// (optioneel) export JSON
const exportJson = JSON.stringify({ flows: flowReport, advancedFlows: advReport }, null, 2);
console.log("\n=== EXPORT JSON (kopieer indien gewenst) ===");
console.log(exportJson);
})();
Is alles goed overgezet? Dan kun je de oude Philips Hue app in Homey verwijderen. De apparaten zelf worden bij het verwijderen meestal automatisch opgeruimd, maar controleer dit eventueel handmatig.
Conclusie
De overstap naar de Philips Hue Bridge Pro is eenvoudig, mits je de juiste stappen volgt. Dankzij Matter en het slimme migratiescript kun je zonder gedoe je flows en instellingen behouden.
Veel succes!
Sommige links op deze pagina zijn affiliatelinks. Dit betekent dat ik een kleine commissie verdien wanneer je via deze links een aankoop doet, zonder dat het jou extra kost. Dit helpt mij om deze website draaiende te houden en nuttige content te blijven aanbieden.
Amazon Affiliate Disclaimer
Als Amazon Affiliate verdien ik aan kwalificerende aankopen. Voor meer informatie, bekijk de volledige Affiliate Disclaimer.