Logging on to Omada via node Red

I had some buggy Matter Wifi devices that kept dropping out but a “reconnect” in Omada would fix everything. I saw the Omada integration had a reconnect function but I couldn’t find it. But anyway, for those of you who want the Node Red flow of how to get Omada to log on and reconnect a device here it is. You can use your own trigger as an input. I expanded this to get a trigger from Uptime Kuma that was monitor my HA status and then get the MAC address from the Omada device to pass into this flow.

Check the functions as you need to enter the IP address of your Omada and then I also put the IP in multiple other functions as a fallback. I also have a spot in a function for your user name and password for Omada.

[
    {
        "id": "ddb285d4938a9181",
        "type": "tab",
        "label": "How to log on to Omada",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "03035395c2e164a5",
        "type": "function",
        "z": "ddb285d4938a9181",
        "name": "Sore MAC address",
        "func": "flow.set('client.mac', msg.data.attributes.mac);\n\n// Get the MAC from flow context\nlet mac = flow.get('client.mac');\n\n// Transform it: uppercase + replace ':' with '-'\nmac = mac.toUpperCase().replace(/:/g, '-');\n\n//Store it back to original flow variable\nflow.set('client.mac', mac);\n\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 190,
        "y": 220,
        "wires": [
            [
                "2b612295ec81cc22"
            ]
        ]
    },
    {
        "id": "4891ebcdabe80fa7",
        "type": "inject",
        "z": "ddb285d4938a9181",
        "name": "Start",
        "props": [
            {
                "p": "data.attributes.mac",
                "v": "8C-87-D0-23-25-04",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 170,
        "y": 100,
        "wires": [
            [
                "03035395c2e164a5"
            ]
        ]
    },
    {
        "id": "2b612295ec81cc22",
        "type": "http request",
        "z": "ddb285d4938a9181",
        "name": "GET /api/info",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "https://10.1.0.18:8043/api/info",
        "tls": "tls-omada",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 390,
        "y": 220,
        "wires": [
            [
                "8d303129172b170b"
            ]
        ]
    },
    {
        "id": "8d303129172b170b",
        "type": "json",
        "z": "ddb285d4938a9181",
        "name": "to JSON",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 560,
        "y": 220,
        "wires": [
            [
                "d939eec95b3c915c"
            ]
        ]
    },
    {
        "id": "d939eec95b3c915c",
        "type": "function",
        "z": "ddb285d4938a9181",
        "name": "store CONTROLLER_ID + creds",
        "func": "// Read omadacId and store in flow context; also prepare login creds\nconst id = msg.payload?.result?.omadacId;\nif (!id) {\n    node.error('No omadacId in /api/info');\n    return null;\n}\nflow.set('omada.baseUrl', 'https://your omada ip:8043');\nflow.set('omada.controllerId', id);\n\n// Pull credentials from environment variables (recommended)\nconst user = env.get('OMADA_USER') || 'your omada user';\nconst pass = env.get('OMADA_PASS') || 'your omada password';\n\nreturn { payload: { username: user, password: pass } };",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 790,
        "y": 220,
        "wires": [
            [
                "b5fa5688c2747dec",
                "a6129ea36a0d5614"
            ]
        ]
    },
    {
        "id": "b5fa5688c2747dec",
        "type": "function",
        "z": "ddb285d4938a9181",
        "name": "set login URL/body",
        "func": "// Build the login URL and ensure JSON body\nconst base = flow.get('omada.baseUrl') || 'https://your omada ip:8043';\nconst id   = flow.get('omada.controllerId');\nif (!base || !id) {\n    node.error('Missing baseUrl or controllerId');\n    return null;\n}\nmsg.url = `${base}/${id}/api/v2/login`;\nmsg.headers = { 'Content-Type': 'application/json' };\nmsg.payload = JSON.stringify(msg.payload);\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1050,
        "y": 220,
        "wires": [
            [
                "441ad8c5dadfa1ec",
                "88779500f140958a"
            ]
        ]
    },
    {
        "id": "a6129ea36a0d5614",
        "type": "debug",
        "z": "ddb285d4938a9181",
        "name": "before POST payload",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "statusVal": "",
        "statusType": "auto",
        "x": 980,
        "y": 160,
        "wires": []
    },
    {
        "id": "441ad8c5dadfa1ec",
        "type": "http request",
        "z": "ddb285d4938a9181",
        "name": "POST /<id>/api/v2/login",
        "method": "POST",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "",
        "tls": "tls-omada",
        "persist": true,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 210,
        "y": 320,
        "wires": [
            [
                "f702471c25a143a2"
            ]
        ]
    },
    {
        "id": "88779500f140958a",
        "type": "debug",
        "z": "ddb285d4938a9181",
        "name": "request to POST",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "statusVal": "",
        "statusType": "auto",
        "x": 1230,
        "y": 160,
        "wires": []
    },
    {
        "id": "f702471c25a143a2",
        "type": "json",
        "z": "ddb285d4938a9181",
        "name": "to JSON",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 440,
        "y": 320,
        "wires": [
            [
                "63016b3332fec821",
                "3141f5a217e7d270"
            ]
        ]
    },
    {
        "id": "63016b3332fec821",
        "type": "debug",
        "z": "ddb285d4938a9181",
        "name": "login response",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "statusVal": "",
        "statusType": "auto",
        "x": 560,
        "y": 280,
        "wires": []
    },
    {
        "id": "3141f5a217e7d270",
        "type": "function",
        "z": "ddb285d4938a9181",
        "name": "store TOKEN",
        "func": "// Save the CSRF token from login\nconst token = msg.payload?.result?.token;\nif (!token) {\n    node.error('No token in login response');\n    return null;\n}\nflow.set('omada.token', token);\n\n// Save the session cookie\nconst cookies = msg.responseCookies;\nif (cookies && cookies.TPOMADA_SESSIONID?.value) {\n    flow.set('omada.session', cookies.TPOMADA_SESSIONID.value);\n} else {\n    node.warn('No TPOMADA_SESSIONID cookie found');\n}\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 630,
        "y": 320,
        "wires": [
            [
                "2fb11a789fb2c3af"
            ]
        ]
    },
    {
        "id": "2fb11a789fb2c3af",
        "type": "function",
        "z": "ddb285d4938a9181",
        "name": "set URL + headers (loginStatus)",
        "func": "// Build loginStatus URL with Csrf-Token header\nconst base = flow.get('omada.baseUrl') || 'https://your omada ip:8043';\nconst id    = flow.get('omada.controllerId');\nconst token = flow.get('omada.token');\nif (!id || !token) {\n    node.error('Missing id or token');\n    return null;\n}\nmsg.url = `${base}/${id}/api/v2/loginStatus?token=${encodeURIComponent(token)}`;\nmsg.headers = { 'Csrf-Token': token, 'Content-Type': 'application/json' };\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 870,
        "y": 320,
        "wires": [
            [
                "a1c48e73e8b23c32"
            ]
        ]
    },
    {
        "id": "a1c48e73e8b23c32",
        "type": "http request",
        "z": "ddb285d4938a9181",
        "name": "GET loginStatus",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "",
        "tls": "tls-omada",
        "persist": true,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 200,
        "y": 420,
        "wires": [
            [
                "890a4d343e2ca6f3"
            ]
        ]
    },
    {
        "id": "890a4d343e2ca6f3",
        "type": "json",
        "z": "ddb285d4938a9181",
        "name": "to JSON",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 380,
        "y": 420,
        "wires": [
            [
                "9aa1309dee23977f",
                "7ee6be922ade0cf5"
            ]
        ]
    },
    {
        "id": "9aa1309dee23977f",
        "type": "debug",
        "z": "ddb285d4938a9181",
        "name": "status",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "statusVal": "",
        "statusType": "auto",
        "x": 510,
        "y": 380,
        "wires": []
    },
    {
        "id": "7ee6be922ade0cf5",
        "type": "function",
        "z": "ddb285d4938a9181",
        "name": "Build Reconnect Request",
        "func": "const base = flow.get('omada.baseUrl');\nconst controllerId = flow.get('omada.controllerId');\nconst token = flow.get('omada.token');\nconst session = flow.get('omada.session'); // store after login\n\nif (!base || !controllerId || !token || !session) {\n    node.error('Missing login/session info');\n    return null;\n}\n\n\n\nconst clientMac = flow.get('client.mac');\nconst siteId = '689dcd0c8d151d57e79b8eec'; // your site ID\n\nmsg.url = `${base}/${controllerId}/api/v2/sites/${siteId}/cmd/clients/${clientMac}/reconnect`;\nmsg.method = 'POST';\nmsg.headers = {\n    'Content-Type': 'application/json',\n    'csrf-token': token,              // lowercase, exactly as browser sends\n    'X-Requested-With': 'XMLHttpRequest', // required\n    'Cookie': `TPOMADA_SESSIONID=${session}`\n};\nmsg.payload = '';\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 230,
        "y": 600,
        "wires": [
            [
                "be717ec724491b58",
                "8535fe9b9350c1f5"
            ]
        ]
    },
    {
        "id": "be717ec724491b58",
        "type": "http request",
        "z": "ddb285d4938a9181",
        "name": "POST /reconnect",
        "method": "use",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "",
        "tls": "tls-omada",
        "persist": false,
        "proxy": "",
        "authType": "",
        "x": 490,
        "y": 600,
        "wires": [
            [
                "cb9c3f26b73dc827",
                "3378354ed030f890"
            ]
        ]
    },
    {
        "id": "8535fe9b9350c1f5",
        "type": "debug",
        "z": "ddb285d4938a9181",
        "name": "debug Build Reconnect Request",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 410,
        "y": 540,
        "wires": []
    },
    {
        "id": "cb9c3f26b73dc827",
        "type": "json",
        "z": "ddb285d4938a9181",
        "name": "Parse Response",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 730,
        "y": 600,
        "wires": [
            [
                "63dc0eadf4c4271a"
            ]
        ]
    },
    {
        "id": "3378354ed030f890",
        "type": "debug",
        "z": "ddb285d4938a9181",
        "name": "debug Post /reconnect",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 680,
        "y": 540,
        "wires": []
    },
    {
        "id": "63dc0eadf4c4271a",
        "type": "debug",
        "z": "ddb285d4938a9181",
        "name": "Reconnect Response",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "x": 940,
        "y": 560,
        "wires": []
    },
    {
        "id": "tls-omada",
        "type": "tls-config",
        "name": "Omada self-signed",
        "cert": "",
        "key": "",
        "ca": "",
        "certname": "",
        "keyname": "",
        "caname": "",
        "servername": "",
        "verifyservercert": false,
        "alpnprotocol": ""
    }
]