Application web pour réaliser un réseau, comme l'interface de Zigbee2MQTT

Bonjour à tous,

Tous ceux qui utilisent Zigbee2MQTT connaissent ce réseau qui est affiché de manière assez fluide.

Je voudrais savoir avec quel outil cela est réalisé, mon idée est de créer une page qui affiche mon réseau, avec les IP… mais je voudrais que ce soit dynamique, je ne vais pas un draw ou un photoshop qui me génère une image.

Je regarde donc ce qui existe avec si possible un affichage en page web si possible.

Merci si vous avez une idée.

Salut à toi , je n’est pas de réponse précise à tes interrogations, je ne sais pas si tu a déjà vu mais il existe sur hacs un addon qui fait un peut la même chose Z2m network map

Salut

En regardant le code source de z2m : https://github.com/Koenkk/zigbee2mqtt/blob/cc31ceabd1c29bc1f266640cc6e6bea95d9d6db3/lib/extension/networkMap.ts#L40

Le réseau zigbee est créé avec : https://www.planttext.com/

1 « J'aime »

Hello,

J’utilise https://mermaid.live/

Tu peux générer une page web et intégrer ton diagramme. Il est très personnalisable.

Merci pour toutes vos idées ou liens.
J’ai continué à creuser également et j’ai trouvé que Zigbee2MQTT utilisait la librairie D3

En quelques lignes de code, j’arrive à ce résultat :
https://www.sigalou-domotique.fr/reseau

Il y a encore un peu de boulot mais finalement assez simple.
J’ai intégré les icones mdi du coup aussi.

6 « J'aime »

Hello @Sigalou
Je trouve ta représentation visuelle plus fun que la mienne. :sweat_smile: Est-ce pour la mettre dans ton dashboard ha ?
On est d’accord que ça n’est pas automatisé ?
Si jamais c’est partageable, je serais intéressé de voir tes codes :sweat_smile:

Je ne connaissais pas

merci du partage

Idem si tu peux montrer le code pour voir un peu ce que ça donne…

Par contre, sur ton site pour info la carte ne s’affiche pas en totalité :

Salut :wave:t2:
Je suis moi aussi preneur du code , j’aime beaucoup cette représentation du réseau :grinning:
Ça permet de récupérer tous les périphériques réseaux ? Même ceux en wifi ?

non, pas forcement à mettre sur HA, si je l’ai quelque part, ça me suffit.
Dans HA, ce serait cool dans ce cas de travailler avec des ping pour mettre en couleur les équipements qui ne ping pas

1 « J'aime »

oui, j’ai jeté un code rapide de test, faut que je travaille le cadre d’affichage

oui, pourquoi cela ? ils ne bougent pas chez toi ?

C’est à dire « ne bouge pas » ?

En tout cas on est plusieurs à bien vouloir le code :grin:

1 « J'aime »

pas de souci, je vous le donne dès que je suis chez moi

3 « J'aime »

Voici le code

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Diagramme de Réseau Domestique</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/MaterialDesign-Webfont/7.2.96/css/materialdesignicons.min.css" rel="stylesheet">
    
    <!-- Styles -->
    <style>
        #network-diagram {
            width: 100%;
            height: 800px;
            position: relative;
            overflow: hidden;
            background: transparent;
        }
        .node {
            cursor: move;
        }
        .icon-container {
            width: 48px;
            height: 48px;
            background: white;
            border: 2px solid #666;
            border-radius: 50%;
            display: flex;
            justify-content: center;
            align-items: center;
            transition: all 0.3s ease;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }

        .icon-container:hover {
            transform: scale(1.1);
            border-color: #4CAF50;
            box-shadow: 0 4px 12px rgba(76,175,80,0.3);
            background: rgba(76,175,80,0.1);
        }
        .mdi {
            font-size: 20px;
            color: #4CAF50;
        }
        .ip-label {
            font-size: 10px;
            text-anchor: middle;
            fill: currentColor;
        }
        .title-label {
            font-size: 12px;
            text-anchor: middle;
            font-weight: bold;
            fill: currentColor;
        }
        foreignObject {
            overflow: visible;
        }
			
			.link-highlight {
					stroke: #4CAF50 !important;
					stroke-opacity: 1 !important;
					stroke-width: 2px !important;
			}
    </style>
</head>
<body>
    <div id="network-diagram"></div>

    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script>
    // =============================================
    // DÉFINITION DES ÉQUIPEMENTS
    // =============================================
    const deviceGroups = {
        network: { name: "Réseau", icon: "mdi-router-network", devices: [
            { name: "SynologyNAS-13", ip: "192.168.1.2", icon: "mdi-nas" },
            { name: "MR2200ac-BWQQT0-Chambre", ip: "192.168.1.254", icon: "mdi-wifi" },
            { name: "MR2200ac-PRCAJV-Piscine", ip: "192.168.1.253", icon: "mdi-wifi" },
            { name: "XiaoMiRepeater_V2", ip: "192.168.1.251", icon: "mdi-wifi" }
        ]},
        cameras: { name: "Caméras", icon: "mdi-cctv", devices: [
            { name: "CameraAxisPiscine-POE4", ip: "192.168.1.7", icon: "mdi-cctv" },
            { name: "CameraGarage-POE1", ip: "192.168.1.3", icon: "mdi-cctv" },
            { name: "CameraMobile", ip: "192.168.1.8", icon: "mdi-cctv" },
            { name: "CameraMobotixBassin", ip: "192.168.1.4", icon: "mdi-cctv" },
            { name: "CameraPorche-10", ip: "192.168.1.5", icon: "mdi-cctv" },
            { name: "CameraVoitures-POE2", ip: "192.168.1.6", icon: "mdi-cctv" }
        ]},
        domotique: { name: "Domotique", icon: "mdi-home-automation", devices: [
            { name: "HomeAssistant", ip: "192.168.1.222", icon: "mdi-home-assistant" },
            { name: "Jeedom-Maitre", ip: "192.168.1.100", icon: "mdi-home-automation" },
            { name: "Jeedom-Esclave", ip: "192.168.1.21", icon: "mdi-home-automation" },
            { name: "jeedomchauffage", ip: "192.168.1.150", icon: "mdi-home-automation" }
        ]},
        alexa: { name: "Alexas", icon: "mdi-amazon-alexa", devices: [
            { name: "ChambreParents", ip: "192.168.1.142", icon: "mdi-amazon-alexa" },
            { name: "Coralie", ip: "192.168.1.136", icon: "mdi-amazon-alexa" },
            { name: "CouloirEtage", ip: "192.168.1.143", icon: "mdi-amazon-alexa" },
            { name: "Cuisine", ip: "192.168.1.137", icon: "mdi-amazon-alexa" },
            { name: "Rose", ip: "192.168.1.138", icon: "mdi-amazon-alexa" },
            { name: "SalledeBains", ip: "192.168.1.135", icon: "mdi-amazon-alexa" },
            { name: "SalonRdc", ip: "192.168.1.139", icon: "mdi-amazon-alexa" }
        ]},
        computers: { name: "Ordinateurs", icon: "mdi-desktop-classic", devices: [
            { name: "PC-Christel-Eth", ip: "192.168.1.35", icon: "mdi-desktop-classic" },
            { name: "PC-Christel-Wifi", ip: "192.168.1.34", icon: "mdi-desktop-classic" },
            { name: "PC-Coralie", ip: "192.168.1.29", icon: "mdi-desktop-classic" },
            { name: "PC-Jeedom-Cuisine", ip: "192.168.1.31", icon: "mdi-desktop-classic" },
            { name: "PC-Lionel-2", ip: "192.168.1.30", icon: "mdi-desktop-classic" },
            { name: "OrdiCoralie", ip: "192.168.1.28", icon: "mdi-desktop-classic" }
        ]},
        mobile: { name: "Mobiles", icon: "mdi-cellphone", devices: [
            { name: "Galaxy-J5-2016", ip: "192.168.1.163", icon: "mdi-cellphone" },
            { name: "Galaxy-S10", ip: "192.168.1.164", icon: "mdi-cellphone" },
            { name: "Galaxy-Tab-A", ip: "192.168.1.171", icon: "mdi-tablet" },
            { name: "GalaxyJ7-Christel", ip: "192.168.1.160", icon: "mdi-cellphone" },
            { name: "GalaxyS10-Lionel", ip: "192.168.1.161", icon: "mdi-cellphone" },
            { name: "GalaxyS7-Coralie", ip: "192.168.1.162", icon: "mdi-cellphone" },
            { name: "iPad-de-user", ip: "192.168.1.170", icon: "mdi-tablet-ipad" }
        ]},
        appliances: { name: "Appareils", icon: "mdi-television", devices: [
            { name: "Apple-TV", ip: "192.168.1.232", icon: "mdi-apple" },
            { name: "Apple-TV-Wifi", ip: "192.168.1.233", icon: "mdi-apple" },
            { name: "FireTV-Chambre", ip: "192.168.1.146", icon: "mdi-fire" },
            { name: "FireTV-Chambre-Eth", ip: "192.168.1.144", icon: "mdi-fire" },
            { name: "TVChambre-Wifi", ip: "192.168.1.147", icon: "mdi-television" }
        ]},
        automation: { name: "Automatisation", icon: "mdi-home", devices: [
            { name: "Arrosage", ip: "192.168.1.91", icon: "mdi-sprinkler-variant" },
            { name: "Chauffage", ip: "192.168.1.116", icon: "mdi-thermostat" },
            { name: "ChauffeEau", ip: "192.168.1.89", icon: "mdi-water-boiler" },
            { name: "Portail", ip: "192.168.1.82", icon: "mdi-gate" },
            { name: "WallBox", ip: "192.168.1.133", icon: "mdi-ev-station" }
        ]},
			diy: { name: "DIY", icon: "mdi-chip", devices: [
    { name: "DIYSalon", ip: "192.168.1.211", icon: "mdi-home-automation" },
    { name: "DIYSalon2", ip: "192.168.1.212", icon: "mdi-home-automation" },
    { name: "DIYPenderie", ip: "192.168.1.214", icon: "mdi-home-automation" },
    { name: "DIYCuisine", ip: "192.168.1.215", icon: "mdi-home-automation" },
    { name: "DIYExtCuisine", ip: "192.168.1.216", icon: "mdi-home-automation" },
    { name: "DIYExtGarage", ip: "192.168.1.217", icon: "mdi-home-automation" },
    { name: "DIYChParents", ip: "192.168.1.220", icon: "mdi-home-automation" },
    { name: "DIYPOWR3", ip: "192.168.1.221", icon: "mdi-home-automation" }
]},
other: { name: "Autres", icon: "mdi-devices", devices: [
    { name: "PorteEntree", ip: "192.168.1.9", icon: "mdi-door" },
    { name: "Portier-POE3", ip: "192.168.1.10", icon: "mdi-doorbell" },
    { name: "PasserelleTuyaZigbee", ip: "192.168.1.245", icon: "mdi-zigbee" },
    { name: "thermomix-236844", ip: "192.168.1.246", icon: "mdi-blender" },
    { name: "S685IP-Telephones-8", ip: "192.168.1.249", icon: "mdi-phone" },
    { name: "Energy", ip: "192.168.1.225", icon: "mdi-flash" },
    { name: "Eau", ip: "192.168.1.230", icon: "mdi-water" }
]}
    };
// =============================================
    // CODE DE VISUALISATION
    // =============================================
    document.addEventListener("DOMContentLoaded", function() {
        const svg = d3.select("#network-diagram")
            .append("svg")
            .attr("width", "100%")
            .attr("height", "100%")
            .attr("viewBox", "-400 -400 800 800");

        // Créer les nœuds et liens
        const nodes = [];
        const links = [];

        // Ajouter Internet
        nodes.push({ 
            id: "Internet",
            group: "internet",
            icon: "mdi-web",
            name: "Internet",
            fx: -50,
            fy: -200
        });

        // Ajouter la Freebox
        nodes.push({ 
            id: "Freebox",
            name: "Freebox",
            ip: "192.168.1.255",
            icon: "mdi-router",
            group: "internet-devices",
            fx: -25,
            fy: -100
        });

        // Ajouter le routeur principal
        nodes.push({ 
            id: "Router", 
            group: "center", 
            icon: "mdi-router-wireless", 
            name: "Routeur Principal",
            ip: "192.168.1.1",
            fx: 0,
            fy: 0
        });

        // Ajouter les liens Internet -> Freebox -> Router
        links.push({ source: "Internet", target: "Freebox", type: "internet" });
        links.push({ source: "Freebox", target: "Router" });

        // Ajouter les groupes et leurs appareils
        let angle = 0;
        const angleStep = (2 * Math.PI) / Object.keys(deviceGroups).length;
        const groupRadius = 80;

        Object.entries(deviceGroups).forEach(([key, group]) => {
            const groupX = Math.cos(angle) * groupRadius;
            const groupY = Math.sin(angle) * groupRadius;
            
            nodes.push({
                id: key,
                group: "group",
                icon: group.icon,
                name: group.name,
                x: groupX,
                y: groupY
            });
            
            links.push({ source: "Router", target: key });

            const deviceAngleStep = (2 * Math.PI) / group.devices.length;
            group.devices.forEach((device, i) => {
                const deviceAngle = angle + i * deviceAngleStep;
                nodes.push({
                    id: device.name,
                    group: key,
                    icon: device.icon,
                    name: device.name,
                    ip: device.ip
                });
                links.push({ source: key, target: device.name });
            });

            angle += angleStep;
        });

        // Configuration de la simulation
        const simulation = d3.forceSimulation(nodes)
            .force("link", d3.forceLink(links).id(d => d.id)
                .distance(d => {
                    if (d.type === "internet") return 100;
                    if (d.source.group === "center") {
                        return 50;
                    } else if (d.source.group === "group") {
                        return 150;
                    }
                    return 50;
                }))
            .force("charge", d3.forceManyBody().strength(-300))
            .force("center", d3.forceCenter(0, 0))
            .force("collision", d3.forceCollide().radius(30));

// Dessiner les liens
const link = svg.append("g")
    .selectAll("line")
    .data(links)
    .join("line")
    .attr("stroke", d => d.type === "internet" ? "#4CAF50" : "#666")
    .attr("stroke-opacity", d => d.type === "internet" ? 1 : 0.4)
    .attr("stroke-width", d => {
        if (d.type === "internet") return 3;
        return d.source.group === "center" ? 3 : 3;
    })
    .attr("stroke-dasharray", d => d.type === "internet" ? "5,5" : "none");

// Fonction de drag
const drag = d3.drag()
    .on("start", (event, d) => {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
    })
    .on("drag", (event, d) => {
        d.fx = event.x;
        d.fy = event.y;
    })
    .on("end", (event, d) => {
        if (!event.active) simulation.alphaTarget(0);
        if (d.id !== "Router" && d.id !== "Internet" && d.id !== "Freebox") {
            d.fx = null;
            d.fy = null;
        }
    });

// Dessiner les nœuds
const node = svg.append("g")
    .selectAll("g")
    .data(nodes)
    .join("g")
    .attr("class", "node")
    .call(drag)
    .on("mouseover", (event, d) => {
        // Mettre en surbrillance les liens connectés
        link.classed("link-highlight", l => 
            l.source.id === d.id || l.target.id === d.id
        );
    })
    .on("mouseout", () => {
        // Retirer la surbrillance
        link.classed("link-highlight", false);
    });

        // Ajouter les icônes
        node.append("foreignObject")
            .attr("width", 40)
            .attr("height", 40)
            .attr("x", -20)
            .attr("y", -20)
            .append("xhtml:div")
            .attr("class", "icon-container")
            .html(d => `<i class="mdi ${d.icon}"></i>`);

        // Ajouter les étiquettes de nom
        node.append("text")
            .attr("class", "title-label")
            .attr("dy", -25)
            .text(d => d.name);

        // Ajouter les étiquettes IP
        node.append("text")
            .attr("class", "ip-label")
            .attr("dy", 35)
            .text(d => d.ip || "");

        // Mise à jour de la position
        simulation.on("tick", () => {
            link
                .attr("x1", d => d.source.x)
                .attr("y1", d => d.source.y)
                .attr("x2", d => d.target.x)
                .attr("y2", d => d.target.y);

            node.attr("transform", d => `translate(${d.x},${d.y})`);
        });
    });
    </script>
</body>
</html>
1 « J'aime »

Pas besoin d’installer quoi que ce soit, les librairies sont récupérées dans le code

4 « J'aime »