# Guide de sélection : passerelles

URL : https://smartbuildings.fr/guide-selection-passerelle/

Guide de sélection

# Choisir sa passerelle : protocoles GTB, compteurs et capteurs IoT

Répondez à quelques questions simples, une à la fois : le guide vous propose la passerelle adaptée à vos équipements et à votre supervision, avec le pourquoi, les accessoires à prévoir et les points d'attention. Un conseiller reste joignable à chaque étape.

- Caractéristiques vérifiées chez les fabricants
- Sans inscription
- Sélection à retrouver par e-mail

Question **1**

## Que souhaitez-vous relier ?

Choisissez ce qui décrit le mieux votre projet.

## Votre projet

1. Vos réponses s'afficheront ici. Vous pourrez revenir sur chacune d'elles.

## Besoin d'un avis ?

Un cas particulier, plusieurs sites, une contrainte forte : un conseiller vous aide à choisir.

Vous cherchez plutôt à relier un site à Internet ou à distance (4G, 5G, VPN, Wi-Fi) ? [Ouvrir le guide des routeurs](https://smartbuildings.fr/guide-selection-routeur/)

## Ce guide vous aide si vous voulez :

- Faire dialoguer des équipements qui parlent des protocoles différents : Modbus, BACnet, KNX, DALI, M-Bus, PROFINET, EtherNet/IP.
- Remonter des automates, des régulateurs, de l'éclairage ou des compteurs dans une GTB ou une supervision.
- Collecter des capteurs sans fil LoRaWAN, Enless, AKCP ou Bluetooth, y compris sur un site sans réseau filaire.
- Relever des compteurs M-Bus filaires, sans fil (wM-Bus) ou à impulsions.
- Accéder à un port série RS-232 ou RS-485 par le réseau, le convertir, le prolonger par fibre ou le brancher en USB.
- Raccorder des équipements BACnet MS/TP à un réseau BACnet/IP.
- Collecter et traiter des données sur site avec une passerelle programmable (Node-RED, Docker).

### Lexique des termes du guide

- **Passerelle** : Équipement qui traduit les données d'un protocole dans un autre, par exemple de Modbus vers BACnet.
- **GTB** : Gestion technique du bâtiment : le système qui supervise le chauffage, la ventilation, l'éclairage et les comptages.
- **SCADA** : Logiciel de supervision et de télé-conduite des installations industrielles, d'eau et d'énergie.
- **Maître et esclave** : Sur un bus série (Modbus RTU, PROFIBUS, M-Bus), le maître interroge et les esclaves répondent. Une passerelle est souvent maître côté équipements et esclave côté supervision.
- **Client et serveur** : Sur un réseau IP (Modbus TCP, BACnet/IP, IEC 104, OPC UA), le client lit ou écrit les données que le serveur met à disposition.
- **Contrôleur et équipement PROFINET** : Le contrôleur, souvent l'automate, pilote les équipements (devices). Selon le modèle, une passerelle est l'un ou l'autre.
- **Scanner et adaptateur EtherNet/IP** : Le scanner, souvent l'automate, échange avec les adaptateurs. Une passerelle adaptateur est lue par l'automate.
- **Point de données** : Une valeur échangée par la passerelle : mesure, état, alarme ou consigne.
- **BACnet/IP** : Protocole de GTB sur réseau Ethernet, défini par la norme ISO 16484-5.
- **BACnet MS/TP** : Variante de BACnet sur bus série RS-485, fréquente sur les régulateurs CVC.
- **BACnet/SC** : BACnet sécurisé : communications chiffrées (TLS) par l'intermédiaire d'un concentrateur.
- **Routeur BACnet** : Relie des réseaux BACnet entre eux, par exemple MS/TP et IP, sans traduire les objets.
- **Certification BTL** : Certification BACnet délivrée par BACnet Testing Laboratories après des essais de conformité.
- **Modbus RTU** : Protocole maître et esclave sur liaison série, le plus souvent RS-485.
- **Modbus TCP** : Modbus sur réseau Ethernet, en client et serveur.
- **M-Bus** : Bus filaire de télérelève des compteurs (EN 13757), qui alimente aussi les compteurs.
- **wM-Bus** : Version radio du M-Bus (868 MHz), utilisée par de nombreux compteurs d'eau et de chaleur.
- **Charge unitaire** : Consommation de référence d'un appareil sur le bus M-Bus (1,5 mA) ; un compteur compte en général pour une charge.
- **Sortie impulsions (S0)** : Sortie d'un compteur qui émet une impulsion par quantité mesurée ; elle se lit par une entrée impulsions ou un convertisseur vers M-Bus.
- **LoRaWAN** : Réseau radio longue portée et basse consommation pour capteurs, en bande 868 MHz en France.
- **Serveur de réseau LoRaWAN** : Logiciel qui gère les capteurs LoRaWAN, leurs clés et le décodage des données ; il peut être intégré à la passerelle.
- **Packet forwarder** : Mode où la passerelle relaie simplement les trames LoRaWAN vers un serveur de réseau externe.
- **KNX** : Bus d'automatisation du bâtiment pour l'éclairage, les stores et le chauffage, avec sa propre alimentation de bus.
- **DALI** : Protocole de commande de l'éclairage (IEC 62386).
- **LON** : Réseau de terrain LonWorks, présent dans des bâtiments plus anciens.
- **LCN** : Système d'automatisation du bâtiment à bus propriétaire.
- **VRF** : Climatisation à débit de réfrigérant variable : plusieurs unités intérieures reliées à une unité extérieure.
- **CAN, CANopen** : Bus de terrain utilisé dans les machines, les véhicules et certains équipements de puissance.
- **PROFINET** : Réseau industriel sur Ethernet, avec contrôleurs et équipements (devices).
- **PROFIBUS DP** : Bus de terrain industriel sur RS-485, avec maîtres et esclaves.
- **EtherNet/IP** : Réseau industriel sur Ethernet, avec scanners et adaptateurs.
- **S7** : Protocole des automates S7 sur Ethernet ; la passerelle les lit en client S7.
- **DF1** : Protocole série d'automates et d'équipements plus anciens, encore présent sur des installations existantes.
- **ASCII** : Trames texte envoyées par des appareils série simples : balances, lecteurs de codes-barres, afficheurs.
- **HART** : Communication numérique superposée au signal 4-20 mA des instruments de mesure.
- **IEC 61850** : Norme de communication des postes électriques, entre IED et systèmes de contrôle-commande.
- **IED** : Équipement électronique d'un poste électrique : relais de protection, contrôleur de travée, centrale de mesure.
- **IEC 61850-3** : Exigences d'environnement et de compatibilité électromagnétique pour les équipements installés en poste électrique.
- **IEC 60870-5-101 et 104** : Protocoles de télé-conduite des réseaux électriques et d'eau : 101 sur liaison série, 104 sur réseau IP.
- **DNP3** : Protocole de télé-conduite utilisé dans l'énergie et l'eau, entre maître et outstations.
- **OPC UA** : Standard d'échange de données industriel et sécurisé, indépendant des fabricants.
- **MQTT** : Protocole léger de publication de messages vers un serveur (broker), courant pour les plateformes IoT.
- **SNMP** : Protocole de supervision des équipements réseau et informatiques, utilisé par les logiciels de supervision et de DCIM.
- **DCIM** : Logiciel de gestion de l'infrastructure d'un data center : énergie, climatisation, capacité.
- **IEC 62443-4-2** : Norme de cybersécurité des composants d'automatisme ; le niveau de sécurité (SL) indique la robustesse visée.
- **ATEX** : Réglementation européenne des équipements utilisables en atmosphère explosive ; la zone et la catégorie se vérifient référence par référence.
- **Rail DIN** : Rail métallique normalisé de 35 mm sur lequel se fixent les équipements d'une armoire électrique.
- **Plage de température étendue** : Dans ce guide : fonctionnement annoncé par le fabricant d'au moins -30 à 70 °C.
- **PoE** : Alimentation de l'équipement par le câble réseau (norme 802.3af ou 802.3at).
- **RS-232, RS-485, RS-422** : Liaisons série : RS-232 point à point sur courte distance, RS-485 en bus multipoint jusqu'à environ 1 200 m, RS-422 en point à point sur 4 fils.
- **Real COM** : Pilote qui crée sur un PC un port COM virtuel relié au port série distant.

'; chargerProduits().then(rendre, function () { zone.innerHTML = '

La sélection n\'a pas pu se charger.

'; }); return; } rendreResultat(); } function rendreRecap() { var p = etat().posees; recapN.textContent = p.length ? '(' + p.length + ')' : ''; recapEl.innerHTML = p.length ? p.map(function (q) { var t = QM[q].titre, v = libelleOption(QM[q], S.rep[q]); return '
-
'; }).join('') : '
- Vos réponses s\'afficheront ici. Vous pourrez revenir sur chacune d\'elles.
'; } function lexique(textes) { var t = textes.join(' ').toLowerCase(), vus = {}; var l = (P.glossaire || []).filter(function (x) { var k = x.terme.toLowerCase(); if (vus[k] || t.indexOf(k) < 0) return false; vus[k] = 1; return true; }); if (!l.length) return ''; return '

### Lexique : ' + esc(l.map(function (x) { return x.terme; }).join(', ')) + '

- **' + esc(x.terme) + '** : ' + esc(x.definition) + '

'; } function rendreQuestion(q, n) { var multi = q.type === 'multiple', nombre = q.type === 'nombre', h = ''; h += '

Question **' + n + '**

## ' + esc(q.titre) + '

'; if (q.aide) h += '

' + esc(q.aide) + '

'; if (nombre) { var v = S.rep[q.id] != null ? S.rep[q.id] : ''; h += '

' + (q.unite ? '' + esc(q.unite) + '' : '') + '

'; } else { if (multi && !/plusieurs (r[ée]ponses|choix)/i.test(q.aide || '')) h += '

Plusieurs réponses possibles.

'; h += '

'; var deja = multi && Array.isArray(S.rep[q.id]) ? S.rep[q.id] : []; M.optionsVisibles(q, etat().rep).forEach(function (o) { var nsp = estNsp(o); h += ''; }); h += '

'; if (multi) h += '

'; } h += '

' + (n > 1 ? '' : '') + '

' + (multi || nombre ? '' : '') + '

'; h += lexique([q.titre, q.aide || ''].concat((q.options || []).map(function (o) { return o.libelle + ' ' + (o.aide || ''); }))); h += '

'; zone.innerHTML = h; var ok = zone.querySelector('[data-sbg-suite]'); if (multi) { var maj = function () { var sel = [].slice.call(zone.querySelectorAll('.sbg-opt[aria-pressed=true]')).map(function (b) { return b.dataset.sbgO; }); ok.disabled = !sel.length; zone.querySelector('.sbg-conseils').innerHTML = q.options.filter(function (o) { return sel.indexOf(o.id) >= 0 && o.conseil; }).map(function (o) { return '

' + esc(o.conseil) + '

'; }).join(''); return sel; }; zone.querySelectorAll('.sbg-opt').forEach(function (b) { b.addEventListener('click', function () { var on = b.getAttribute('aria-pressed') !== 'true'; b.setAttribute('aria-pressed', String(on)); if (on) zone.querySelectorAll('.sbg-opt[aria-pressed=true]').forEach(function (x) { if (x !== b && (x.hasAttribute('data-sbg-nsp') || b.hasAttribute('data-sbg-nsp'))) x.setAttribute('aria-pressed', 'false'); }); maj(); }); }); ok.addEventListener('click', function () { var sel = maj(); if (sel.length) repondre(q.id, sel); }); maj(); } else if (nombre) { var inp = zone.querySelector('#sbg-n'); var valider = function () { var x = parseFloat(inp.value); if (isNaN(x) || x < q.min || x > q.max) { inp.classList.add('is-bad'); inp.focus(); return; } repondre(q.id, x); }; ok.addEventListener('click', valider); inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); valider(); } }); } else { zone.querySelectorAll('.sbg-opt').forEach(function (b) { b.addEventListener('click', function () { repondre(q.id, b.dataset.sbgO); }); }); } placer(); } /* « Pourquoi ce choix » : arguments des critères demandés (exigés, groupes, préférences), complétés jusqu'à 3. */ function pourquoi(r, eff) { var p = r.produit, s = r.tags, arg = p.arguments || {}, ordre = []; var ajout = function (t) { if (s.indexOf(t) >= 0 && arg[t] && ordre.indexOf(t) < 0) ordre.push(t); }; eff.exige.forEach(ajout); eff.groupes.forEach(function (g) { g.forEach(ajout); }); Object.keys(eff.prefere).filter(function (t) { return eff.prefere[t] > 0; }).sort(function (a, b) { return eff.prefere[b] - eff.prefere[a]; }).forEach(ajout); var l = ordre.slice(0, 5); if (l.length < 3) Object.keys(arg).forEach(function (t) { if (l.length < 3 && l.indexOf(t) < 0 && s.indexOf(t) >= 0) l.push(t); }); return l.map(function (t) { return arg[t]; }); } /* Accessoires du produit retenu : règles du parcours (option sauf niveau « necessaire ») et accessoires requis par la fiche (nécessaires sauf niveau « option »). Un accessoire nécessaire est coché d'office ; une option ne l'est jamais. */ function accessoiresDe(r) { var vus = {}, out = []; var req = (r.produit.accessoires_requis || []).map(function (a) { return { id: a.id, raison: a.raison, niveau: a.niveau || 'necessaire' }; }); (r.accessoires || []).concat(req).forEach(function (a) { if (a.id === r.id) return; var f = fiche(a.id); if (!f) return; var nec = a.niveau === 'necessaire'; if (vus[a.id]) { if (nec && !vus[a.id].nec) { vus[a.id].nec = true; vus[a.id].raison = a.raison; } return; } vus[a.id] = { f: f, raison: a.raison, nec: nec }; out.push(vus[a.id]); }); return out; } function carte(r, i, eff) { var p = r.produit, f = fiche(p.id) || { url: '#', img: '' }, v = r.version, top = i === 0, acc = accessoiresDe(r), why = pourquoi(r, eff), h = ''; h += '

'; h += '

' + (top ? ' Notre choix ' : '') + (f.img ? '' : '') + '

'; h += '

' + esc(p.marque) + (p.serie ? ' · ' + esc(p.serie) : '') + '

'; h += '

### [' + esc(f.nom || p.nom) + ']('%20+%20esc%28f.url%29%20+%20')

'; if (v) h += '

Version conseillée : **' + esc(v.libelle) + '**' + (v.sku ? ' `' + esc(v.sku) + '`' : '') + '

'; else if ((p.variantes || []).length > 1) h += '

Versions : **toutes conviennent à vos réponses**, à préciser sur la fiche ou dans votre demande.

'; if (why.length) h += '

#### Pourquoi ce choix

- ' + esc(w) + '

'; if (p.limites) h += '

**Bon à savoir :**' + esc(p.limites) + '

'; var li = function (a) { var idc = 'sbg-a' + p.id + '-' + a.f.id; return '
-
'; }; var nec = acc.filter(function (a) { return a.nec; }), opt = acc.filter(function (a) { return !a.nec; }); if (nec.length) h += '

#### À prévoir avec ce produit

'; if (opt.length) h += '

#### En option, selon votre installation

Cochez ce qui vous concerne : la case ajoute l\'accessoire à votre demande de devis.

'; var faits = p.faits || {}, fk = Object.keys(faits); if (fk.length) h += '

### Caractéristiques vérifiées (' + fk.length + ')

- **' + esc(k) + '** : ' + esc(faits[k]) + '

' + ((p.sources || []).length ? '

Source' + (p.sources.length > 1 ? 's' : '') + ' fabricant : ' + p.sources.slice(0, 3).map(function (u, k) { return '[document ' + (k + 1) + ']('%20+%20esc%28u%29%20+%20')'; }).join(', ') + '

' : '') + '

'; h += '

[Voir la fiche]('%20+%20esc%28f.url%29%20+%20')

'; return h + '

'; } function resume() { return etat().posees.map(function (q) { return '- ' + QM[q].titre + ' : ' + libelleOption(QM[q], S.rep[q]); }).join('\n'); } function resumeResultat(res) { if (!res || !res.produits.length) return ''; return 'Proposition du guide : ' + res.produits.map(function (r) { return r.produit.nom + (r.version ? ' (' + r.version.libelle + (r.version.sku ? ', réf. ' + r.version.sku : '') + ')' : ''); }).join(' ; '); } function rendreResultat() { var res = M.resultat({ produits: F.produits }, P, S.rep, 3), eff = res.effets, exp = P.expert || {}, h = ''; dernier = res; h += '

Résultat

## ' + (res.produits.length && !res.expert ? 'Notre recommandation pour votre projet' : 'Votre projet mérite un échange avec un conseiller') + '

' + '

' + (res.produits.length ? 'Sélection établie à partir de vos réponses et des caractéristiques publiées par les fabricants.' : 'Aucun produit du catalogue ne répond à tous vos critères à la fois.') + '

' + '

'; if (res.conseiller) { h += '

### ' + esc(exp.titre || 'Parlons de votre projet') + '

' + esc(exp.texte || 'Un conseiller SmartBuildings étudie votre besoin et vous propose la bonne solution.') + '

' + (res.produits.length ? '

Une première piste figure ci-dessous ; un conseiller confirmera le choix avec vous.

' : '') + '

'; } var cs = res.conseils.filter(function (c, i, a) { return a.indexOf(c) === i; }); if (cs.length) h += '

### Points d\'attention

- ' + esc(c) + '

'; if (res.produits.length) { h += carte(res.produits[0], 0, eff); if (res.produits.length > 1) h += '

### ' + (res.produits.length > 2 ? 'Autres possibilités' : 'Autre possibilité') + '

' + res.produits.slice(1).map(function (r, i) { return carte(r, i + 1, eff); }).join('') + '

'; } h += '

' + formEmail() + formConseil(false) + '

'; zone.innerHTML = h; brancherFormulaires(); evt('guide_resultat', { famille: F.id, produits: res.produits.length, conseiller: res.conseiller ? 1 : 0 }); placer('#sbg-rt'); } function rendreConseil() { zone.innerHTML = '

' + formConseil(true) + '

'; brancherFormulaires(); placer('#sbg-ct'); } /* ── Formulaires ── */ // texte du libellé dans un seul span : le lien reste dans la phrase (le libellé est en flex) function accord(t) { return ''; } var POT = ''; function formEmail() { return ''; } function formConseil(seul) { var joint = [MARQUE + ' :', resume(), resumeResultat(S.vue === 'resultat' ? dernier : null)].filter(Boolean); return ''; } function verifier(form) { form.querySelectorAll('.is-bad').forEach(function (x) { x.classList.remove('is-bad'); }); var bad = [].slice.call(form.querySelectorAll('input[required],textarea[required]')).filter(function (x) { return x.type === 'checkbox' ? !x.checked : (!x.checkValidity() || !x.value.trim()); }); bad.forEach(function (x) { x.classList.add('is-bad'); }); return bad; } function brancherFormulaires() { t0Form = Date.now(); zone.querySelectorAll('form[data-sbg-form]').forEach(function (form) { form.addEventListener('submit', function (e) { e.preventDefault(); var msg = form.querySelector('.sbg-msg'), btn = form.querySelector('button[type=submit]'), bad = verifier(form); msg.className = 'sbg-msg'; msg.textContent = ''; if (bad.length) { msg.className = 'sbg-msg ko'; msg.textContent = 'Merci de renseigner les champs obligatoires (*) et de cocher l\'accord.'; bad[0].focus(); return; } if (form.dataset.sbgForm === 'email') envoyerEmail(form, msg, btn); else envoyerConseil(form, msg, btn); }); }); } function envoyerEmail(form, msg, btn) { var corps = { email: form.email.value.trim(), consent: 1, rappel: form.rappel.checked ? 1 : 0, website: form.website.value, duree: Date.now() - t0Form, version: D.version, famille: F.id, reponses: S.rep, produits: (dernier ? dernier.produits : []).map(function (r) { return { id: r.id, vid: r.variante || 0 }; }) }; btn.disabled = true; fetch(C.rest + 'guide/enregistrer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(corps), credentials: 'same-origin' }) .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); }) .then(function (x) { btn.disabled = false; if (!x.ok || !x.j.ok) { msg.className = 'sbg-msg ko'; msg.textContent = (x.j && x.j.message) || 'Enregistrement impossible, merci de réessayer.'; return; } msg.className = 'sbg-msg ok'; msg.textContent = x.j.envoye ? 'C\'est noté : le lien vient de partir vers ' + corps.email + '.' : (x.j.apercu ? 'Aperçu : sélection enregistrée, e-mail non envoyé. Lien de reprise ci-dessous.' : 'Sélection enregistrée. Gardez le lien ci-dessous.'); if (x.j.lien) { var z = form.querySelector('.sbg-retour-lien'); z.innerHTML = '

'; var inp = z.querySelector('input'), b = z.querySelector('button'); b.addEventListener('click', function () { var fini = function () { b.textContent = 'Copié'; setTimeout(function () { b.textContent = 'Copier'; }, 2000); }; if (navigator.clipboard) navigator.clipboard.writeText(inp.value).then(fini, function () { inp.select(); }); else inp.select(); }); } evt('guide_enregistre', { famille: F.id, rappel: corps.rappel }); }) .catch(function () { btn.disabled = false; msg.className = 'sbg-msg ko'; msg.textContent = 'Enregistrement impossible, merci de réessayer.'; }); } function envoyerConseil(form, msg, btn) { var texte = [form.question.value.trim(), MARQUE + ' :', resume(), resumeResultat(S.vue === 'resultat' ? dernier : null)].filter(Boolean).join('\n\n').slice(0, 3900); if (C.apercu) { msg.className = 'sbg-msg ok'; msg.textContent = 'Aperçu : rien n\'est envoyé. Voici ce que recevrait l\'équipe SmartBuildings.'; var pre = form.querySelector('.sbg-pre-apercu') || document.createElement('pre'); pre.className = 'sbg-pre sbg-pre-apercu'; pre.textContent = 'Nom : ' + form.name.value + '\nSociété : ' + (form.company.value.trim() || 'Non précisé') + '\nE-mail : ' + form.email.value + '\nTéléphone : ' + form.phone.value + '\n\n' + texte; msg.after(pre); return; } var fd = new FormData(); fd.append('name', form.name.value.trim()); fd.append('company', form.company.value.trim() || 'Non précisé'); fd.append('email', form.email.value.trim()); fd.append('phone', form.phone.value.trim()); fd.append('message', texte); fd.append('consent', '1'); fd.append('items', '[]'); fd.append('website', form.website.value); fd.append('t0', String(t0Form)); fd.append('page', location.href.split('?')[0]); btn.disabled = true; fetch(C.devis, { method: 'POST', body: fd, credentials: 'same-origin' }) .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); }) .then(function (x) { if (x.ok && x.j.ok) { form.innerHTML = '

### Message envoyé

Merci. Un conseiller SmartBuildings vous répond par e-mail' + (fd.get('phone') ? ' ou par téléphone' : '') + '. Un accusé de réception vient de partir vers ' + esc(fd.get('email')) + '.

'; form.querySelector('h3').focus(); evt('generate_lead', { source: 'guide_' + F.id }); return; } btn.disabled = false; msg.className = 'sbg-msg ko'; msg.textContent = (x.j && x.j.message) || 'Envoi impossible, merci de réessayer.'; }) .catch(function () { btn.disabled = false; msg.className = 'sbg-msg ko'; msg.textContent = 'Envoi impossible, merci de réessayer ou d\'écrire à info@smartbuildings.fr.'; }); } /* ── Demande de devis du site (snippet 5) : même stockage, même règle de fusion (produit et version) ── */ function ajouterDevis(pid) { var r = (dernier ? dernier.produits : []).filter(function (x) { return x.id === pid; })[0]; if (!r) return; var a = lire(CLE_DEVIS) || [], f = fiche(pid) || {}, v = r.version; var mettre = function (it) { var k = it.pid + ':' + (it.vid || 0); if (!a.some(function (x) { return x.pid + ':' + (x.vid || 0) === k; })) a.push(it); }; mettre({ pid: pid, vid: v ? v.variation_id : 0, name: f.nom || r.produit.nom, variant: v ? v.libelle : ((r.produit.variantes || []).length > 1 ? 'Version à préciser' : ''), img: f.img || '', qty: 1 }); var carteEl = zone.querySelector('.sbg-card[data-pid="' + pid + '"]'); if (carteEl) carteEl.querySelectorAll('[data-sbg-acc]').forEach(function (c) { if (!c.checked) return; var fa = fiche(+c.dataset.sbgAcc); if (fa) mettre({ pid: fa.id, vid: 0, name: fa.nom, variant: '', img: fa.img || '', qty: 1 }); }); ecrire(CLE_DEVIS, a.slice(0, 50)); var ta = document.querySelector('#sb-devis textarea[name=message]'); if (ta && (!ta.value.trim() || ta.value.indexOf('Projet décrit avec le guide') === 0)) ta.value = MARQUE + ' :\n' + resume(); if (!document.getElementById('sb-devis')) { var b = zone.querySelector('[data-sbg-devis="' + pid + '"]'); if (b) b.textContent = 'Ajouté à votre demande'; } evt('guide_devis', { famille: F.id, produit: pid }); } /* ── Événements ── */ app.addEventListener('click', function (e) { var t = e.target, b; if ((b = t.closest('[data-sbg-devis]'))) { ajouterDevis(+b.dataset.sbgDevis); return; } // le script du devis (snippet 5) ouvre ensuite sa fenêtre (data-sb-open) if ((b = t.closest('[data-sbg-modif]'))) { modifierDepuis(b.dataset.sbgModif); return; } if (t.closest('[data-sbg-retour]')) { retour(); return; } if (t.closest('[data-sbg-recommencer]')) { recommencer(); return; } if (t.closest('[data-sbg-recharger]')) { pret = null; rendre(); return; } if (t.closest('[data-sbg-conseil]')) { interagi = true; var fc = S.vue === 'resultat' && zone.querySelector('form[data-sbg-form=conseil]'); if (fc) { fc.style.scrollMarginTop = '80px'; fc.scrollIntoView({ behavior: 'smooth', block: 'start' }); var n = fc.querySelector('input'); if (n) n.focus({ preventScroll: true }); return; } S.vue = 'conseil'; rendre(); } }); /* ── Démarrage : lien de reprise (?g=…), sinon dernier parcours de ce navigateur (7 jours, mêmes données) ── */ function demarrer() { var g = (location.search.match(/[?&]g=([A-Za-z0-9]{20,64})/) || [])[1]; if (g && C.rest) { zone.innerHTML = '

Chargement de votre sélection…

'; fetch(C.rest + 'guide/selection/' + g, { credentials: 'same-origin' }) .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); }) .then(function (x) { var bon = x.ok && x.j.ok && x.j.famille === F.id; if (bon) S = { rep: x.j.reponses || {}, vue: 'question' }; rendre(); if (!bon) zone.insertAdjacentHTML('afterbegin', '

Ce lien de sélection n\'est plus valide. Vous pouvez refaire le parcours en quelques questions.

'); }).catch(function () { rendre(); }); } else { var m = lire(CLE); if (m && m.v === D.version && Date.now() - (m.t || 0) < 7 * 864e5) S = { rep: m.rep || {}, vue: 'question' }; rendre(); } if (!F.produits) setTimeout(function () { chargerProduits().catch(function () { /* nouvel essai au moment du résultat */ }); }, 400); } demarrer(); })();
