I wasn't aware that the Trigger node supported topic-dependent repeating.
Anyway that allowed me to simplify the flow a lot (see below).
And a NEW FEATURE has been added: when an alert is detected in the flow, the camera view will be opened automatically in the floorplan (with a red border around it). This reduces the amount of steps you need to execute in the dashboard, to analyze what is going on in your property:
And here is the flow to achieve this:
[{"id":"58329d91.3fc564","type":"ui_svg_graphics","z":"42b7b639.325dd8","group":"f014eb03.a3c618","order":1,"width":"14","height":"10","svgString":"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" preserveAspectRatio=\"none meet\" x=\"0\" y=\"0\" viewBox=\"0 0 900 710\" width=\"100%\" height=\"100%\">\n <rect id=\"svgEditorBackground\" x=\"0\" y=\"0\" width=\"900\" height=\"710\" style=\"fill:none;stroke:none;\" />\n <image width=\"889\" height=\"703\" id=\"background\" xlink:href=\"https://www.roomsketcher.com/wp-content/uploads/2016/10/1-Bedroom-Floor-Plans.jpg\" />\n <image id=\"cam_living_video\" x=\"395\" y=\"130\" width=\"320\" height=\"180\" xlink:href=\"\" stroke=\"blue\" stroke-width=\"2\" visibility=\"hidden\" style=\"outline: 6px solid white;\"/>\n <image id=\"cam_garage_video\" x=\"580\" y=\"480\" width=\"320\" height=\"180\" xlink:href=\"\" stroke=\"blue\" stroke-width=\"2\" visibility=\"hidden\" style=\"outline: 6px solid white;\"/>\n <text id=\"cam_living_icon\" x=\"690\" y=\"280\" font-family=\"FontAwesome\" fill=\"blue\" stroke=\"white\" stroke-width=\"2\" font-size=\"35\" text-anchor=\"middle\" alignment-baseline=\"middle\">fa-video-camera</text>\n <text id=\"cam_garage_icon\" x=\"760\" y=\"640\" font-family=\"FontAwesome\" fill=\"blue\" stroke=\"white\" stroke-width=\"2\" font-size=\"35\" text-anchor=\"middle\" alignment-baseline=\"middle\">fa-video-camera</text>\n</svg>","clickableShapes":[{"targetId":"#cam_living_icon","action":"click","payload":"toggle","payloadType":"str","topic":"#cam_living_video"},{"targetId":"#cam_garage_icon","action":"click","payload":"toggle","payloadType":"str","topic":"#cam_garage_video"}],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":true,"showBrowserEvents":false,"enableJsDebugging":false,"sendMsgWhenLoaded":false,"outputField":"","editorUrl":"http://drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"name":"","x":1720,"y":1020,"wires":[["503a621e.d0b47c"]]},{"id":"e5ef519e.37daa","type":"function","z":"42b7b639.325dd8","name":"Next image URL","func":"// Each SVG 'image' element gets its own counter (to store the number of the last displayed image from the image sequence)\nconst counterName = 'img_sequence_' + msg.topic;\n\nvar count = context.get(counterName);\n\n// When the last image from the image sequence has been read, start again with the first image of the sequence\nif (!count || count > 47) {\n count = 1;\n}\n\n// Convert the image number to a 3-digit number with leading zeros (e.g. 3 to \"003\")\nvar countAsString = \"000000000\" + count;\ncountAsString = countAsString.slice(-3);\n\n// Each SVG 'image' element has its own image sequence on my \"node-red-static-resources\" Github repository\nswitch(msg.topic) {\n case \"#cam_garage_video\":\n msg.url = `https://github.com/bartbutenaers/node-red-static-resources/blob/main/image_sequence/garage/ezgif-frame-${countAsString}.jpg?raw=true`;\n break;\n case \"#cam_living_video\":\n msg.url = `https://github.com/bartbutenaers/node-red-static-resources/blob/main/image_sequence/living/ezgif-frame-${countAsString}.jpg?raw=true`;\n break;\n}\n\n// Make sure we fetch the next image, the next time that we arrive in this function node\ncontext.set(counterName, count + 2);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1100,"y":960,"wires":[["694228d5.81f3f8"]]},{"id":"694228d5.81f3f8","type":"http request","z":"42b7b639.325dd8","name":"Get image","method":"GET","ret":"bin","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","x":1290,"y":960,"wires":[["394bde6d.062452"]]},{"id":"394bde6d.062452","type":"function","z":"42b7b639.325dd8","name":"Update image in SVG","func":"// Convert the (base64 encoded) image as a data URL\nvar imageAsBuffer = new Buffer(msg.payload);\nvar imageAsBase64 = imageAsBuffer.toString('base64');\nvar imageAsDataUrl = \"data:image/jpg;base64,\" + imageAsBase64;\n\n// Update the data URL of the SVG 'image' element in the SVG\nmsg = {\n payload: {\n \"command\": \"set_attribute\",\n \"selector\": msg.topic,\n \"attributeName\": \"xlink:href\",\n \"attributeValue\": imageAsDataUrl\n }\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1500,"y":960,"wires":[["58329d91.3fc564"]]},{"id":"40f2ce95.0a6b8","type":"trigger","z":"42b7b639.325dd8","name":"Repeat","op1":"1","op2":"0","op1type":"str","op2type":"str","duration":"-250","extend":false,"overrideDelay":false,"units":"ms","reset":"stop","bytopic":"topic","topic":"topic","outputs":1,"x":920,"y":960,"wires":[["e5ef519e.37daa"]]},{"id":"503a621e.d0b47c","type":"link out","z":"42b7b639.325dd8","name":"SVG output","links":["e4d546fb.caf698"],"x":1855,"y":1020,"wires":[]},{"id":"e4d546fb.caf698","type":"link in","z":"42b7b639.325dd8","name":"","links":["503a621e.d0b47c"],"x":595,"y":1020,"wires":[["bb362ae5.2ccc38"]]},{"id":"bb362ae5.2ccc38","type":"function","z":"42b7b639.325dd8","name":"Toggle start/stop","func":"msg.payload = msg.alert || context.get(msg.topic) || \"start\";\n\n// Toggle the start/stop of the trigger node, to start/stop pushing images to the dashboard\nif (msg.payload === \"start\") {\n context.set(msg.topic, \"stop\");\n}\nelse {\n context.set(msg.topic, \"start\");\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":740,"y":960,"wires":[["63a3fd45.302dc4","40f2ce95.0a6b8"]]},{"id":"63a3fd45.302dc4","type":"function","z":"42b7b639.325dd8","name":"Show/hide image","func":"var visibility;\ndebugger;\n\nif (msg.payload === \"start\") {\n visibility = \"visible\";\n}\nelse {\n visibility = \"hidden\";\n}\n\nvar newMsg = {\n payload: [{\n \"command\": \"set_style\",\n \"selector\": msg.topic,\n \"attributeName\": \"visibility\",\n \"attributeValue\": visibility \n }]\n}\n\nif (msg.alert) {\n switch (msg.alert) {\n case \"start\":\n newMsg.payload.push({\n \"command\": \"set_style\",\n \"selector\": msg.topic,\n \"attributeName\": \"outline\",\n \"attributeValue\": \"red solid 6px\" \n });\n break;\n case \"stop\":\n newMsg.payload.push({\n \"command\": \"set_style\",\n \"selector\": msg.topic,\n \"attributeName\": \"outline\",\n \"attributeValue\": \"white solid 6px\" \n });\n break;\n }\n}\n\nreturn newMsg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":950,"y":1020,"wires":[["58329d91.3fc564"]]},{"id":"7decac8e.ca5fc4","type":"inject","z":"42b7b639.325dd8","name":"Alert living","props":[{"p":"alert","v":"start","vt":"str"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"#cam_living_video","x":540,"y":900,"wires":[["bb362ae5.2ccc38"]]},{"id":"c3041ad9.5fdef8","type":"inject","z":"42b7b639.325dd8","name":"Normal living","props":[{"p":"alert","v":"stop","vt":"str"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"#cam_living_video","x":550,"y":960,"wires":[["bb362ae5.2ccc38"]]},{"id":"f014eb03.a3c618","type":"ui_group","name":"Floorplan test","tab":"80068970.6e2868","order":1,"disp":true,"width":"14","collapse":false},{"id":"80068970.6e2868","type":"ui_tab","name":"SVG","icon":"dashboard","disabled":false,"hidden":false}]
If anybody has seen other useful features (e.g. in "E-map" software tools), please share it here...