[HOW-TO][Pro][Cloud] - Tool to FIX (advanced) flows after removing and re-adding devices

Hello
I have tried Your script testing in a new Homey Pro early 2023, updated to the latest experimental sw verion (10.0.01.rc1 as far as I remember) I followed the steps above where I had configured a Virtual Device button and connected it to a flow. I read the ID before deleting the device and configured a new button. Read the ID in the new button. I filled in the old and new IDs in the script and tried to run it in several ways with softRun both as true and false.
Everything looks great. The logging says that I got a correct flow match and it says that its i updateing. No error messages. The problem is that nothing happens. The broken card in the flow is not updated.

Log:
:white_check_mark: Script Success
:leftwards_arrow_with_hook: Returned: undefined (not sure what to expect here)
Checking flow “broken flow”…
Checking flow “test pushover”…
Found old device ID in URI of trigger card in flow “test pushover”
Updating flow “test pushover” with new trigger…
Checking flow “Test cast”…
Checking flow “update flow”…

Any recomendations?

@Tony_Johansen hard to say :sweat_smile:

It should fix any card it finds for that ID. So maybe the old ID is not correct.

You can see the device ID if you hover over your broken card in your flow (in the webap)

Thank You for very quick reply.
IDs are correct. Checked both using “Homey.devices.getDevices();” in web api and hovering as You described.
Could it be some kind of authorization issues? It was mentioned in the dialog above that it’s best to run the script in the Web API and not in Homey Script. I have tried both.
What is exactly the difference between softRun true and false?

Just to be sure that I understand this correctly. After running the script, will the broken card in the flow automatically look OK again? I don’t have to do some extra actions?

Just load them again - eg. when on Web, switch to another flow and back…it shall “looks okay” if fixed.

1 Like

Homeyscript has less rights than the webapi playground

softRun allows you to run the script to see what flows are affected without actually fixing them

I will do a factory reset and eventually a software up or downgrade. Which Homey software version have You run this script successfully on?

@Tony_Johansen its version independent.

@martijnpoppen Thank you so much for this great script!, saved me from losing my hair :slight_smile:

the “Homey.devices.getDevices();” was too heavy for my browser so i made this, might be helpful for others :slight_smile:

it will filter all the other devices and lines showing only ID and name

let searchname = “devicenamehere”;
Homey.devices.getDevices().then(devices =>
Object.values(devices)
.filter(device => device.name.toLowerCase().includes(searchname.toLowerCase()))
.map(device => ({ id: device.id, name: device.name }))
);

3 Likes

@late4marshmellow nice! I’ll add that :slight_smile:

glad you liked it… lazy as i am i actually took it a bit further… and combined it with your script(s)
Basically one can now do a search, if only one ID is found on each old and new search.
The script goes on and uses those id’s as replacements. if several are found it stops executing the rest and just outputs the result in API Playground. so one manually can copy/paste the correct ID avoiding mistakes

or if old and new id is filled it will go straight to doing the magic.

was thinking about adding a code in the end that deletes the “deleteme” device - since i rename the old device beforei run this, then manually delete it in the app. though if you are interested i can look into adding that too. hope its helpfull :slight_smile:

const finishedmsg = "finished update flow";
// you can either search for devices first or add the id's if they are familiar directly below.
const oldSearch = "deleteme";
const newSearch = "Master bedroom Night Lamp";

 // It's recommended to only use 1 id at a time. This will also fix your variables.
let oldIds = [];
let newIds = [];

// Set the softRun variable to true or false to determine whether changes should be logged or actually executed
const softRun = false; // true | false

// Open the console in your browser and the result will be shown there
// Howto open console: https://balsamiq.com/support/faqs/browserconsole/

async function searchDevices() {
    if (!oldIds.length || !newIds.length) {
        try {
            const devices = await Homey.devices.getDevices();
            const allDevices = Object.values(devices);

            let oldDevices = allDevices
                .filter(device => device.name.toLowerCase().includes(oldSearch.toLowerCase()))
                .map(device => ({ id: device.id, name: device.name }));

            let newDevices = allDevices
                .filter(device => device.name.toLowerCase().includes(newSearch.toLowerCase()))
                .map(device => ({ id: device.id, name: device.name }));

            if (oldDevices.length > 1 || newDevices.length > 1) {
                return { oldDevices, newDevices };
            }

            if (oldDevices.length === 1 && newDevices.length === 1) {
                oldIds = [oldDevices[0].id];
                newIds = [newDevices[0].id];
            }
        } catch (error) {
            console.error(error);
        }
    }

const main = async function () {
    console.log('Starting main function...');

    // Retrieve all of the existing flows
    const flows = await Homey.flow.getFlows();

    // Iterate through each flow
    Object.values(flows).forEach(async (f) => {
        console.log(`Checking flow "${f.name}"...`);

        // If the flow has a trigger with a URI or ID
        if (f.trigger && (f.trigger.uri || f.trigger.id)) {
            const trigger = f.trigger;

            let replaceValue = '';
            let cardsChanged = false;

            if (oldIds.length === 1) {
                replaceValue = oldIds[0];
            } else if (trigger.uri && trigger.uri.includes('homey:device:')) {
                replaceValue = trigger.uri.replace('homey:device:', '');
            } else if (trigger.id && trigger.id.includes('homey:device:')) {
                replaceValue = trigger.id.replace('homey:device:', '');
            }

            if (replaceValue.includes('|')) {
                replaceValue = replaceValue.split('|')[0];
            } else if (replaceValue.includes(':')) {
                replaceValue = replaceValue.split(':')[0];
            }

            // If the flow contains the old device ID
            if (oldIds.includes(replaceValue)) {
                const replacer = new RegExp(replaceValue, 'g');
                const index = oldIds.findIndex((o) => o.includes(replaceValue));

                // Replace the old device ID with the corresponding new device ID in the trigger
                if (trigger.uri) {
                    const oldToken = trigger.uri;
                    trigger.uri = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== trigger.uri) {
                        console.log(`Found old device ID in URI of trigger card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                } else if (trigger.id) {
                    const oldToken = trigger.id;
                    trigger.id = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== trigger.id) {
                        console.log(`Found old device ID in ID of trigger card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                }

                // Replace the old device ID with the corresponding new device ID in the trigger droptoken
                if (trigger.droptoken) {
                    const oldToken = trigger.droptoken;
                    trigger.droptoken = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== trigger.droptoken) {
                        console.log(`Found old device ID in droptoken of trigger card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                }

                // If softRun is false, update the flow with the new trigger
                // If any cards in the advanced flow were changed
                if (cardsChanged) {
                    // If softRun is false, update the advanced flow with the new cards

                    if (!softRun) {
                        console.log(`Updating flow "${f.name}" with new trigger...`);
                        await Homey.flow.updateFlow({
                            id: f.id,
                            flow: { id: f.id, trigger: trigger }
                        });
                        // If softRun is true, log the change that would be made
                    } else {
                        console.log(`Would update flow "${f.name}" with new trigger:`, {
                            id: f.id,
                            flow: { id: f.id, trigger: trigger }
                        });
                    }
                }
            }
        }

        // Iterate through each action in the flow
        f.actions.forEach(async (a, i) => {
            let replaceValue = '';
            let cardsChanged = false;

            if (oldIds.length === 1) {
                replaceValue = oldIds[0];
            } else if (a.uri && a.uri.includes('homey:device:')) {
                replaceValue = a.uri.replace('homey:device:', '');
            } else if (a.id && a.id.includes('homey:device:')) {
                replaceValue = a.id.replace('homey:device:', '');
            }

            if (replaceValue.includes('|')) {
                replaceValue = replaceValue.split('|')[0];
            } else if (replaceValue.includes(':')) {
                replaceValue = replaceValue.split(':')[0];
            }

            // If the flow contains the old device ID
            if (oldIds.includes(replaceValue)) {
                const replacer = new RegExp(replaceValue, 'g');
                const actions = f.actions;
                const index = oldIds.findIndex((o) => o.includes(replaceValue));

                // Replace the old device ID with the corresponding new device ID in the action
                if (actions[i].uri) {
                    const oldToken = actions[i].uri;
                    actions[i].uri = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== actions[i].uri) {
                        console.log(`Found old device ID in URI of action card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                } else if (actions[i].id) {
                    const oldToken = actions[i].id;
                    actions[i].id = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== actions[i].id) {
                        console.log(`Found old device ID in ID of action card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                }

                // Replace the old device ID with the corresponding new device ID in the action droptoken
                if (actions[i].args && actions[i].args.value && typeof actions[i].args.value === 'string') {
                    const oldToken = actions[i].args.value;
                    actions[i].args.value = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== actions[i].args.value) {
                        console.log(`Found old device ID in ID of action card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                }

                if (actions[i].args && actions[i].args.text && typeof actions[i].args.text === 'string') {
                    const oldToken = actions[i].args.text;
                    actions[i].args.text = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== actions[i].args.text) {
                        console.log(`Found old device ID in ID of action card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                }

                if (actions[i].args && actions[i].args.message && typeof actions[i].args.message === 'string') {
                    const oldToken = actions[i].args.message;
                    actions[i].args.message = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== actions[i].args.message) {
                        console.log(`Found old device ID in ID of action card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                }

                if (cardsChanged) {
                    // If softRun is false, update the flow with the new action
                    if (!softRun) {
                        console.log(`Updating flow "${f.name}" with new action...`);
                        await Homey.flow.updateFlow({
                            id: f.id,
                            flow: { id: f.id, actions: actions }
                        });
                        // If softRun is true, log the change that would be made
                    } else {
                        console.log(`Would update flow "${f.name}" with new action:`, {
                            id: f.id,
                            flow: { id: f.id, actions: actions }
                        });
                    }
                }
            }
        });

        // Iterate through each condition in the flow
        f.conditions.forEach(async (a, i) => {
            let replaceValue = '';
            let cardsChanged = false;

            // If the condition URI includes the device ID
            if (oldIds.length === 1) {
                replaceValue = oldIds[0];
            } else if (a.uri && a.uri.includes('homey:device:')) {
                replaceValue = a.uri.replace('homey:device:', '');
            } else if (a.id && a.id.includes('homey:device:')) {
                replaceValue = a.id.replace('homey:device:', '');
            }

            if (replaceValue.includes('|')) {
                replaceValue = replaceValue.split('|')[0];
            } else if (replaceValue.includes(':')) {
                replaceValue = replaceValue.split(':')[0];
            }

            // If the flow contains the old device ID
            if (oldIds.includes(replaceValue)) {
                const replacer = new RegExp(replaceValue, 'g');
                const conditions = f.conditions;
                const index = oldIds.findIndex((o) => o.includes(replaceValue));

                // Replace the old device ID with the corresponding new device ID in the condition
                if (conditions[i].uri) {
                    const oldToken = conditions[i].uri;
                    conditions[i].uri = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== conditions[i].uri) {
                        console.log(`Found old device ID in URI of condition card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                } else if (conditions[i].id) {
                    const oldToken = conditions[i].id;
                    conditions[i].id = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== conditions[i].id) {
                        console.log(`Found old device ID in ID of condition card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                }

                // Replace the old device ID with the corresponding new device ID in the condition droptoken
                if (conditions[i].droptoken) {
                    const oldToken = conditions[i].droptoken;
                    conditions[i].droptoken = oldToken.replace(replacer, newIds[index]);

                    if (oldToken !== conditions[i].droptoken) {
                        console.log(`Found old device ID in Droptoken of condition card in flow "${f.name}"`);
                        cardsChanged = true;
                    }
                }

                if (cardsChanged) {
                    // If softRun is false, update the flow with the new condition
                    if (!softRun) {
                        console.log(`Updating flow "${f.name}" with new condition...`);
                        await Homey.flow.updateFlow({
                            id: f.id,
                            flow: { id: f.id, conditions: conditions }
                        });
                        // If softRun is true, log the change that would be made
                    } else {
                        console.log(`Would update flow "${f.name}" with new condition:`, {
                            id: f.id,
                            flow: { id: f.id, conditions: conditions }
                        });
                    }
                }
            }
        });
    });

    console.log('--------------------------------------')

    // Retrieve all of the existing advanced flows
    const advanced_flows = Object.values(await Homey.flow.getAdvancedFlows());

    // Iterate through each advanced flow
    advanced_flows.forEach(async (af) => {
        console.log(`Checking advanced flow "${af.name}"...`);

        const cards = af.cards;
        let cardsChanged = false;

        // Iterate through each card in the advanced flow
        for (const c in af.cards) {
            let replaceValue = null;

            if (oldIds.length === 1) {
                replaceValue = oldIds[0];
            } else if (cards[c].ownerUri && cards[c].ownerUri.includes('homey:device:')) {
                replaceValue = cards[c].ownerUri.replace('homey:device:', '');
            }

            if (replaceValue.includes('|')) {
                replaceValue = replaceValue.split('|')[0];
            } else if (replaceValue.includes(':')) {
                replaceValue = replaceValue.split(':')[0];
            }

            const replacer = new RegExp(replaceValue, 'g');
            const index = oldIds.findIndex((o) => o.includes(replaceValue));

            if (cards[c].args && cards[c].droptoken) {
                const oldToken = cards[c].droptoken;
                cards[c].droptoken = cards[c].droptoken.replace(replacer, newIds[index]);

                if (oldToken !== cards[c].droptoken) {
                    console.log(`Found old device ID in droptoken of card in advanced flow "${af.name}"`);
                    cardsChanged = true;
                }
            }

            if (cards[c].args && cards[c].args.value && typeof cards[c].args.value === 'string') {
                const oldToken = cards[c].args.value;
                cards[c].args.value = cards[c].args.value.replace(replacer, newIds[index]);

                if (oldToken !== cards[c].args.value) {
                    console.log(`Found old device ID in value of card in advanced flow "${af.name}"`);
                    cardsChanged = true;
                }
            }

            if (cards[c].args && cards[c].args.text && typeof cards[c].args.text === 'string') {
                const oldToken = cards[c].args.text;
                cards[c].args.text = cards[c].args.text.replace(replacer, newIds[index]);

                if (oldToken !== cards[c].args.text) {
                    console.log(`Found old device ID in test of card in advanced flow "${af.name}"`);
                    cardsChanged = true;
                }
            }

            if (cards[c].args && cards[c].args.message && typeof cards[c].args.message === 'string') {
                const oldToken = cards[c].args.message;
                cards[c].args.message = cards[c].args.message.replace(replacer, newIds[index]);

                if (oldToken !== cards[c].args.message) {
                    console.log(`Found old device ID in message of card in advanced flow "${af.name}"`);
                    cardsChanged = true;
                }
            }

            if (cards[c].ownerUri) {
                const oldToken = cards[c].ownerUri;
                cards[c].ownerUri = cards[c].ownerUri.replace(replacer, newIds[index]);

                if (oldToken !== cards[c].ownerUri) {
                    console.log(`Found old device ID in ownerUri of card in advanced flow "${af.name}"`);
                    cardsChanged = true;
                }
            }

            if (cards[c].id) {
                const oldToken = cards[c].id;
                cards[c].id = cards[c].id.replace(replacer, newIds[index]);

                if (oldToken !== cards[c].id) {
                    console.log(`Found old device ID in ownerUri of card in advanced flow "${af.name}"`);
                    cardsChanged = true;
                }
            }
        }

        // If any cards in the advanced flow were changed
        if (cardsChanged) {
            // If softRun is false, update the advanced flow with the new cards

            if (!softRun) {
                console.log(`Updating advanced flow "${af.name}" with new cards...`);
                const response = await Homey.flow.updateAdvancedFlow({
                    id: af.id,
                    advancedflow: { cards: cards }
                });
                console.log(`Update response: ${JSON.stringify(response)}`);
            } else {
                console.log(`Would update advanced flow "${af.name}" with new cards:`, {
                    id: af.id,
                    advancedflow: { cards: cards }
                });
            }
        }
    });

console.log(finishedmsg);
};

// Call the main function to execute the flow ID replacements
main();

}

searchDevices();
1 Like

Remember me ? Problems making the script working. As I described, I started with an empty Homey early 2023, configured 2 devices and a few flows. After testing that the devices worked OK with the flows, I deleted the devices and configured new ones. After that I trie Your script, but nothing was updated.
Today I did the following:
I started from scratch again, but this time I restored a backup from my Homey Early 2019.
I first tried a repair on a couple of devices, but those failed.
Then I followed the same procedure as before and according to Your description, using the script.
Voila, it worked perfectly. Changed a lot of flows that used my 26 zigbee devices.

Why? I don’t know. Just worked.
Thank You for all Your help.

1 Like

I tried this script only it gives me this message : “root”:{

1 item

“error”:

string"Invalid character ‘\u201c’"

}

Best script ever❗️

It has helped me so many times. Thanks @martijnpoppen

3 Likes

Yes tried in in scripts. Didnt work either

@Robin_De_Lange did you try the script in the first post? Or a other one

Martijn I mean this script which does not do it in the web api

Since I need to transfer quite a lot of devices, I thought it would be easy to get a small overview.

If I use your method with all the info my laptop crashes but it works😂 and is a bit to much info

@Robin_De_Lange ah. yeah, didn’t look into that yet.

but you can try this:

const searchname = "devicenamehere";
Homey.devices.getDevices().then(devices => Object.values(devices).filter(device => device.name.toLowerCase().includes(searchname.toLowerCase())).map(device => ({ id: device.id, name: device.name })));

1 Like

@Robin_De_Lange also added that to the top post :slight_smile:

1 Like

@Robin_De_Lange
I’ve looked at my search script and seems the the quotes for searchname somehow turned into curly quotes, rendering this to fail…

try this, your browser should promt you with an input :slight_smile:


function ensureQuotes(str) {
    str = str.replace(/“|”|‘|’|'|`/g, "");

    if (!str.startsWith('"') || !str.endsWith('"')) {
        str = '"' + str + '"';
    }

    return str;
}

const userInput = prompt("Enter the device name to search:");
const correctedInput = ensureQuotes(userInput);
let searchname = correctedInput.replace(/"/g, '');

Homey.devices.getDevices().then(devices =>
    Object.values(devices)
        .filter(device => device.name.toLowerCase().includes(searchname.toLowerCase()))
        .map(device => ({ id: device.id, name: device.name }))
);
2 Likes