Contrat API — Maîtrise de consommation (bridage)
Base : https://live.jmoai.net/api/v1/radius.
Authentification : JWT client + licence RADIUS active + abonnement Suivi du
Matériel actif, sauf mention contraire. La licence annuelle STARLINK X MikroTik
donne la capacité RADIUS/MikroTik ; le suivi mensuel donne l’accès au bridage,
à son journal et à ses mesures.
Ce que fait le service
Le gérant décrit une intention ; le backend l’applique et la maintient sur son routeur, en continu, sans que l’application ait besoin d’être ouverte. Trois leviers se combinent :
| Levier | Déclencheur | Effet |
|---|---|---|
| Bridage programmé | dates ou jours de semaine choisis | plafond global sur le routeur |
| Bridage adaptatif | quota atteint ou pic de consommation détecté | même plafond, hors jours prévus |
| Bridage ciblé | un usager dépasse son quota individuel | ce seul usager est ralenti, sur tous ses appareils |
Les deux populations d’usagers
Le service fonctionne à l’identique sur un routeur piloté par RADIUS et sur un routeur dont les utilisateurs sont créés en local par l’application — ce sont deux moitiés du parc, et aucune n’est traitée comme un cas particulier.
- Le bridage global (programmé et adaptatif) est indifférent à qui s’authentifie : ce sont des files sur le routeur, elles s’appliquent au trafic quelle qu’en soit l’origine.
- Le bridage ciblé lit deux sources et les fusionne : l’accounting RADIUS (
radacct, qui couvre le hotspot RADIUS et le PPPoE) et/ip hotspot activelu directement sur le routeur (qui couvre le hotspot local). Un routeur qui n’a que l’une des deux fonctionne exactement pareil.
Un usager vu des deux côtés — cas normal d’un hotspot RADIUS — n’est pas compté deux fois :
la clé de dédoublonnage est le couple (usager, adresse), et c’est le plus grand des deux
volumes qui est retenu, jamais leur somme. Le champ sources de reason indique d’où vient
la mesure retenue.
Si aucune des deux sources ne répond, les bridages ciblés en cours sont laissés en l’état. Traiter ce silence comme « plus personne ne consomme » libérerait tout le monde au premier incident réseau.
Rien n’est jamais bloqué ni déconnecté. Sous bridage, le trafic essentiel (messagerie, réseaux sociaux, DNS) garde une priorité haute et un débit garanti ; tout le reste passe en priorité basse. Hors congestion, ce « reste » atteint quand même le plafond : la priorité ne se manifeste qu’au moment où le tuyau est plein. Un client qui consulte WhatsApp ne verra pas la différence entre un jour bridé et un jour normal ; celui qui télécharge la verra.
GET /bandwidth/classes
Le catalogue des classes de trafic préservées, pour l’écran de réglage. JWT seul, pas de licence requise.
{
"classes": [
{ "id": "dns", "libelle": "Résolution DNS", "plages": 5 },
{ "id": "meta", "libelle": "WhatsApp, Messenger, Instagram, Facebook", "plages": 12 },
{ "id": "telegram", "libelle": "Telegram", "plages": 8 },
{ "id": "x", "libelle": "X (Twitter)", "plages": 3 }
]
}
⚠️ La classification se fait par adresse IP. Elle attrape la signalisation, les messages et les API, pas toujours la vidéo, qui est servie depuis des CDN mutualisés. C’est voulu : la vidéo d’un réseau social est de la consommation lourde et a sa place dans la file basse. TikTok et Snapchat sont volontairement absents pour la même raison — les inscrire reviendrait à prioriser au hasard tout ce qui partage leurs CDN.
PUT /bandwidth/program
Enregistre ou remplace le programme d’un routeur. Quota : 5 appels/minute et par utilisateur.
{
"nas_id": "uuid",
"enabled": true,
"mode": "BOTH",
"limit_mbps": 10,
"essential_share": 0.25,
"essential_classes": ["dns", "meta", "telegram", "x"],
"scheduled_dates": ["2026-09-05", "2026-09-12"],
"schedule_weekdays": [2, 3],
"daily_quota_gb": 80,
"monthly_quota_gb": 1500,
"user_daily_quota_gb": 5,
"user_limit_mbps": 1,
"max_throttled_users": 10
}
| Champ | Obligatoire | Règle |
|---|---|---|
nas_id |
oui | routeur du client authentifié |
limit_mbps |
oui | > 0 et ≤ 10000 |
mode |
non | SCHEDULED, ADAPTIVE ou BOTH (défaut) |
essential_share |
non | strictement entre 0 et 1 (défaut 0,25) |
essential_classes |
non | identifiants de GET /bandwidth/classes ; les inconnus sont ignorés |
scheduled_dates |
non | YYYY-MM-DD, dédoublonnées et triées par le serveur |
schedule_weekdays |
non | entiers 0–6, 0 = dimanche |
daily_quota_gb, monthly_quota_gb |
non | > 0 ; déclenchent le mode adaptatif |
user_daily_quota_gb + user_limit_mbps |
non | vont ensemble ou pas du tout ; quota jugé sur le total de l’usager, tous appareils confondus (jusqu’à 16 adresses par file) |
max_throttled_users |
non | entier 0–100 (défaut 10) |
Réponse 200 :
{ "nas_id": "uuid", "enabled": true, "mode": "BOTH", "revision": 3, "application": "PLANIFIEE" }
application: "PLANIFIEE" est un engagement, pas une esquive : la pose sur le routeur a lieu
à la réconciliation suivante (au plus 5 minutes). Faire échouer l’enregistrement parce que le
routeur est hors ligne priverait le gérant de la seule chose qu’il maîtrise — son intention —
et l’obligerait à réessayer sans savoir quand. L’état réellement constaté se lit sur
GET /bandwidth/program.
Codes d’erreur
| Code | Sens |
|---|---|
BW_NAS_REQUIRED |
nas_id absent |
BW_NAS_NOT_OWNED |
le NAS n’appartient pas au client authentifié |
BW_LIMIT_REQUIRED / BW_LIMIT_INVALID |
plafond absent ou hors bornes |
BW_MODE_INVALID |
mode inconnu |
BW_SCHEDULE_EMPTY |
mode SCHEDULED sans aucune date ni jour — ne briderait jamais |
BW_DATE_INVALID / BW_WEEKDAY_INVALID |
format de date ou jour hors 0–6 |
BW_SHARE_INVALID |
essential_share hors ]0, 1[ |
BW_CLASSES_INVALID |
aucune classe connue dans la liste fournie |
BW_USER_PAIR_REQUIRED |
quota individuel et plafond individuel séparés |
BW_MAX_USERS_INVALID |
max_throttled_users hors 0–100 |
CLICK_SPAM_PROTECTION |
quota de 5 appels/minute dépassé |
GET /bandwidth/program
L’état de tous les routeurs du client.
{
"programmes": [
{
"nas_id": "uuid",
"enabled": true,
"mode": "BOTH",
"limit_mbps": 10,
"etat_souhaite": "ON",
"etat_constate": "ON",
"timezone": "Africa/Porto-Novo",
"derniere_verification": "2026-08-29T14:32:11Z",
"en_erreur": false,
"jours_programmes_restants": 2,
"consommation": {
"octetsJour": 64424509440,
"octetsDeuxHeures": 8589934592,
"octetsMois": 1073741824000,
"dernierReleve": "2026-08-29T14:30:00Z"
},
"usagers_brides": 2
}
],
"usagers_brides": [
{
"nas_id": "uuid",
"username": "TM-ABC123",
"limit_mbps": 1,
"framed_ips": ["10.5.0.23", "10.5.0.30"],
"observed_bytes": 12884901888,
"applied_at": "2026-08-29T13:05:00Z"
}
]
}
Points d’attention pour l’affichage :
etat_souhaitevsetat_constate: un écart signifie que la convergence est en cours ou que le routeur est injoignable.en_erreur: trueconfirme le second cas.etat_constatevautUNKNOWNtant que le routeur n’a jamais répondu,ABSENTquand le programme a été retiré du routeur.timezoneest le fuseau du ROUTEUR, relevé sur l’appareil à la pose du plan. C’est lui qui décide de quel jour est « le 29 » — ni le serveur, ni le téléphone du gérant. L’afficher évite le malentendu classique : un gérant en déplacement qui coche une date depuis un autre fuseau. Il vautnulltant que le plan n’a jamais été posé ; la décision retombe alors sur le fuseau du serveur, ce qui peut décaler le bridage d’une heure ou deux.jours_programmes_restantsvautnullquand un régime hebdomadaire est en place : il ne s’épuise jamais, la question n’a pas de sens. Quand il est un nombre et qu’il passe sous 7, la liste de dates arrive à sa fin — l’application doit inviter le gérant à la prolonger, sans quoi le mode s’arrêtera sans rien dire.consommationpeut êtrenull: aucune télémétrie reçue pour ce routeur. Le mode adaptatif est alors inerte, et l’application doit le signaler plutôt que d’afficher 0 Go.
DELETE /bandwidth/program/{nasId}
Désactive le programme et nettoie le routeur : plan global et bridages ciblés sont retirés. Quota : 5 appels/minute.
{ "disabled": true, "nettoye": true }
nettoye: false signifie que le routeur était injoignable. La désactivation est enregistrée
quand même — le gérant a demandé l’arrêt, il l’obtient — et un balayage de nettoyage retirera
les files au retour du routeur. Sans ce balayage, un client resterait bridé indéfiniment après
avoir demandé l’arrêt.
GET /bandwidth/activity
Ce que le système a fait, raconté en français. C’est l’écran « activité » de l’application.
Paramètres : limite (1–200, défaut 50), avant (curseur), nas_id (filtre facultatif).
{
"activites": [
{
"id": "uuid",
"nas_id": "uuid",
"type": "THROTTLE_ON",
"date": "2026-08-29T18:04:12Z",
"texte": "Bridage activé — pic de consommation détecté (+120 % sur 2 h).",
"detail": { "motif": "PIC_DE_CONSOMMATION", "mesures": { "acceleration_pct": 120 } }
},
{
"id": "uuid",
"nas_id": "uuid",
"type": "USER_THROTTLED",
"date": "2026-08-29T18:04:15Z",
"texte": "TM-C4D835 ralenti à 1 Mbps — 19.9 Go consommés sur 6 appareils.",
"detail": { "username": "TM-C4D835", "observe_gb": 19.9, "adresses": 6 }
}
],
"suivant": "2026-08-29T18:04:15Z"
}
- Affiche
texte, pastype. Le champdetailest là pour un écran de diagnostic ou un export, pas pour être reconstitué en phrase côté application — la phrase changerait alors à chaque version du serveur sans que l’app le sache. - Pagination par curseur : rappeler l’appel avec
avant = suivant.suivant: nullmarque la fin. On n’utilise pas d’offsetparce que le journal reçoit des lignes pendant qu’on le feuillette : les pages se décaleraient et le gérant verrait deux fois la même ligne. - Un
typeinconnu de votre version reste affiché tel quel danstexte. Ne masquez pas la ligne : un journal à trous inquiète plus qu’une ligne technique.
GET /bandwidth/impact
Ce que le bridage a changé. C’est l’écran qui justifie la fonction auprès du gérant.
Paramètres : jours (7–180, défaut 30), nas_id (facultatif).
{
"fenetre_jours": 30,
"programmes": 1,
"timezone": "Africa/Porto-Novo",
"impact": {
"courbe": [
{ "jour": "2026-08-01", "gb": 98.4, "bride": false },
{ "jour": "2026-08-02", "gb": 61.2, "bride": true }
],
"jours_brides": 8,
"jours_normaux": 21,
"moyenne_jour_bride_gb": 61.2,
"moyenne_jour_normal_gb": 98.4,
"reduction_pct": 38,
"economie_estimee_gb": 297.6,
"fiabilite": "BONNE",
"fiabilite_motif": null,
"total_gb": 2451.8
},
"usagers": {
"distincts": 12,
"interventions": 34,
"actuellement_brides": 2,
"volume_observe_gb": 412.7
}
}
Comment la réduction est calculée — methode
Depuis le 2026-08-29, la comparaison apparie les jours de la semaine : samedi contre samedi, mardi contre mardi. La consommation d’un hotspot est fortement saisonnière à la semaine, et l’on bride justement les jours chargés — comparer la moyenne des jours bridés à celle des jours normaux produisait donc un biais systématiquement flatteur.
| Champ | Sens |
|---|---|
methode: "JOURS_APPARIES" |
samedi comparé à samedi. Chiffre à afficher. |
methode: "MOYENNE_BRUTE" |
aucun jour de semaine n’avait les deux sortes de jours — repli assumé |
jours_semaine_apparies |
combien de jours de semaine ont pu être appariés (2 minimum) |
reduction_brute_pct |
l’ancien calcul, exposé À CÔTÉ |
Un écart important entre reduction_pct et reduction_brute_pct n’est pas une anomalie : il
mesure le poids de la saisonnalité. Affichez reduction_pct ; gardez l’autre pour un écran de
diagnostic.
⚠️ La règle d’affichage la plus importante de ce contrat
Quand fiabilite vaut INSUFFISANTE, n’affichez NI reduction_pct NI
economie_estimee_gb — ils valent alors null, et fiabilite_motif porte la phrase à
montrer à la place (« il faut au moins 3 jours bridés et 3 jours normaux pour comparer
(actuellement 1 et 2) »).
Comparer des jours bridés à des jours normaux est une corrélation, pas une preuve : la consommation varie d’elle-même — week-ends, congés, un gros client qui part. Avec deux jours d’un côté et trois de l’autre, un écart de 40 % est du bruit. Un chiffre flatteur et faux détruit la confiance le jour où le gérant s’en aperçoit ; un chiffre modeste et honnête la construit. C’est pourquoi le serveur refuse de conclure plutôt que de renvoyer un zéro.
reduction_pct peut être négatif : les jours bridés ont alors consommé plus que les
normaux. Affichez-le tel quel. Le masquer donnerait un tableau de bord qui ne sait dire que
de bonnes nouvelles, donc auquel personne ne croira.
economie_estimee_gb est une estimation, jamais une mesure : on ne sait pas ce qui aurait
été consommé sans bridage. Le libellé côté application doit le dire.
Autres points
courbeest triée par jour croissant, découpée dans le fuseau du routeur (timezone). Chaque point portebride, ce qui permet de colorer les barres concernées.- Les jours bridés viennent du journal — ce qui a eu lieu — et non du calendrier — ce qui était prévu. Un jour programmé pendant lequel le routeur était hors ligne n’a bridé personne ; le compter fausserait la comparaison, et dans le sens flatteur.
usagers.distinctscompte des personnes,interventionscompte des applications de la règle. Un usager bridé cinq jours de suite fait 1 personne et 5 interventions.impact: nullsignifie qu’aucun programme n’existe, ou que la série est indisponible. Affichez un état vide, pas des zéros.
Notifications
Le gérant reçoit une notification (base + push FCM) sur les transitions uniquement :
bridage activé, bridage levé, pic détecté, routeur injoignable. Elles portent
metadata.type = "BANDWIDTH" et metadata.nas_id.
Ce qui ne déclenche pas de notification, volontairement : le bridage d’un usager individuel. Ils sont fréquents, et une notification par personne noierait le gérant — le journal d’activité les porte tous.
Réglages, dans qoe_notification_preferences (mêmes heures de silence que les rapports QoE,
pour ne pas obliger le gérant à les régler deux fois) :
| Champ | Effet |
|---|---|
bandwidth_push |
coupe toutes les notifications de bridage |
quiet_start / quiet_end |
heures de silence, respectées sauf pour un routeur injoignable |
Une même alerte n’est pas répétée avant 6 heures pour un routeur donné. La réconciliation passant toutes les 5 minutes, une fenêtre plus courte que cette cadence ne dédoublonnerait rien : c’est le mécanisme exact qui a produit 330 alertes en 48 h et fait tomber les messages destinés aux clients.
GET /admin/bandwidth/fleet (administration)
{
"programmes": { "total": 842, "actifs": 610, "en_bridage": 47, "injoignables": 23 },
"usagers_actuellement_brides": 118,
"evenements_24h": { "THROTTLE_ON": 51, "USER_THROTTLED": 204, "ROUTER_UNREACHABLE": 12 },
"evenements_24h_tronques": false
}
evenements_24h_tronques: true signifie que la répartition est calculée sur les 1 000
premiers événements seulement. Affichez-le : une répartition tronquée présentée comme un
total est un chiffre faux.
POST /admin/bandwidth/reconcile (administration)
Force un passage de réconciliation sans attendre le cron. ?limite=1..200, défaut 40.
Sert à valider un programme en production immédiatement après l’avoir enregistré.
{
"traites": 40,
"poses": 3,
"bascules": 1,
"conformes": 5,
"inchanges": 30,
"injoignables": 1,
"usagers_brides": 2,
"usagers_liberes": 0
}
inchanges est le chemin économe : la décision n’a pas bougé et aucune session n’a été
ouverte vers ces routeurs. C’est ce qui rend le balayage tenable sur plusieurs milliers
d’appareils.
Adresse MAC de substitution — starlink_x
📱 Dev de l’app mobile : la note d’intégration pas à pas est sur
/docs/CONTRAT-API-ANTI-RESTRICTION-APP— ordre de pose, vérifications, recette. Ce chapitre-ci reste la référence des routes.
C’est l’application qui pose l’adresse, depuis le réseau local du client. Le backend ne la pose jamais.
Ce n’est pas une limitation temporaire. Changer la MAC de l’interface WAN renégocie le bail
DHCP : si le fournisseur lie la session à l’adresse — ce que fait la parabole Starlink — le
lien tombe. Le tunnel de gestion et la voie de secours Back To Home empruntent ce même lien :
le filet de sécurité disparaîtrait en même temps que la panne qu’il devait rattraper, et plus
rien ne pourrait déclencher un retour en arrière. Depuis le LAN, l’opérateur garde un chemin
vers l’appareil quoi qu’il arrive au WAN. /interface ethernet reste donc un chemin sensible
de routerWriteGuard, sans catalogue : une tentative de pose côté serveur est refusée.
Le backend garantit trois choses : l’adresse est déterministe (dérivée du numéro de série,
donc identique d’une consultation à l’autre), l’adresse d’origine est enregistrée avant
d’être remplacée, et chaque geste entre dans le journal GET /bandwidth/activity
(MAC_SUBSTITUTED, MAC_RESTORED, MAC_POSE_FAILED).
Masquer la MAC ne suffit pas
Un routeur MikroTik s’annonce sur son lien par quatre signaux. L’adresse n’en est qu’un, et ce n’est pas le plus bavard :
| Signal | Ce que le fournisseur voit | Traité ? |
|---|---|---|
| Adresse MAC | « fabricant inconnu » | ✅ mac_substitution |
| Découverte de voisinage (MNDP/CDP/LLDP) | constructeur, version de RouterOS et identité du client, en clair | ✅ limitée au LAN |
| Nom d’hôte DHCP (option 12) | RouterOS envoie l’identité système dans sa demande de bail | ✅ retiré ou remplacé |
| Empreinte DHCP (option 55) | ordre des options demandées, caractéristique de RouterOS | ❌ ne se change pas |
La quatrième ligne est à dire au gérant. Un camouflage annoncé comme total et qui ne l’est pas est pire qu’un camouflage partiel assumé : il ferait prendre des risques qu’on croit couverts.
On restreint la découverte, on ne la coupe pas : elle reste active sur les interfaces locales, sans quoi un technicien ne retrouverait plus le routeur dans WinBox depuis le LAN — c’est ainsi qu’on le dépanne quand tout le reste est tombé.
Et l’on ne renomme pas le routeur : /system identity reste le vrai nom, celui que le
technicien lit. Le nom d’hôte envoyé au fournisseur est une option DHCP séparée.
GET /starlink/mac/{nasId}/consigne
| Paramètre | Rôle |
|---|---|
interface |
l’interface WAN, telle que l’application la lit sur le routeur |
interfaces_locales |
les interfaces où la découverte reste permise, séparées par des virgules |
nom_hote |
nom d’hôte de substitution ; absent = aucun nom d’hôte envoyé |
Sans interface, la commande n’est pas délivrée : le serveur ne devine pas quelle interface
porte le WAN, et se tromper poserait l’adresse sur le LAN. Sans interfaces_locales, le bloc
discretion vaut null avec discretion_motif: "INVENTAIRE_INTERFACES_MANQUANT".
Le cas normal : nommer le pont
Les gérants de hotspot relient leurs ports sous un pont unique, sur lequel tourne le serveur
hotspot. Il suffit alors de nommer ce pont : interfaces_locales=bridge-hotspot. Inutile
d’énumérer ether2, ether3, wlan1 — la découverte sur un pont atteint tous ses membres.
Et c’est aussi la limite : un pont ne se découpe pas. Un pont est un domaine de diffusion unique ; on ne peut pas activer la découverte sur ether2 mais pas sur ether3 quand les deux en sont membres. C’est le pont entier, ou rien.
Conséquence concrète, que l’application doit dire au gérant : découverte sur le pont du hotspot = les clients payants voient « MikroTik, RouterOS 7.19.6, nom du gérant » dans WinBox. Le serveur le signale de lui-même dans la réponse :
"discretion": {
"portee": "PONT",
"avertissement": "La découverte restera visible de tout ce qui est relié à bridge-hotspot — y compris les clients de votre hotspot. Pour ne la garder que pour vous, nommez une interface de gestion hors du pont."
}
portee vaut PONT, INTERFACES_NOMMEES ou AUCUNE. Affichez avertissement quand il
n’est pas null — c’est le seul moment où le gérant peut encore choisir une interface de
gestion hors du pont.
⚠️ L’inventaire vient de l’application, et c’est délibéré. Le serveur ne connaît pas la topologie du routeur. Sur un routeur dont le WAN est membre d’un pont, mettre ce pont dans les interfaces locales ferait ressortir la découverte par le pont, donc par le WAN — le contraire du but. N’incluez pas un pont qui contient l’interface WAN. Le serveur écarte malgré tout l’interface WAN de la liste, par sécurité.
{
"nas_id": "uuid",
"identifiant": "HH40AF7YVNP",
"mac_substitution": "02:AB:CD:EF:01:02",
"interface": "ether1",
"commande": "/interface ethernet set [find name=ether1] mac-address=02:AB:CD:EF:01:02",
"commande_retour": null,
"pose_par": "APP_LAN",
"precautions": ["À exécuter depuis le réseau local du routeur. …"],
"discretion": {
"commandes": [
":if ([:len [/interface list find name=JSLOCAL]] = 0) do={/interface list add name=JSLOCAL comment=JSLOCAL}",
":if ([:len [/interface list member find list=JSLOCAL interface=ether2]] = 0) do={/interface list member add list=JSLOCAL interface=ether2}",
"/ip neighbor discovery-settings set discover-interface-list=JSLOCAL",
"/ip dhcp-client set [find interface=ether1] dhcp-options=clientid"
],
"interfaces_locales": ["ether2", "wlan1"],
"nom_hote": null,
"limites": ["L’empreinte DHCP de RouterOS reste reconnaissable : elle ne se change pas.", "…"]
},
"discretion_motif": null,
"discretion_appliquee": false,
"etat": null,
"droit": { "autorise": false, "motif": "STARLINK_X_REQUIRED", "enforced": false }
}
-
Posez
commandeETdiscretion.commandesdans le même passage sur le LAN. Les séparer ferait poser l’adresse par un gérant qui remettrait le reste « à plus tard » — et un routeur masqué qui continue de crier son constructeur et son identité sur le lien n’est pas masqué. -
Les commandes de
discretionsont idempotentes : une consigne rejouée après un échec partiel ne casse rien et ne crée pas de doublon. -
Elles sont toutes refusées au backend par
routerWriteGuard(interface list,ip neighbor,ip dhcp-client), exactement comme la commande de MAC. -
Affichez
precautionsavant le bouton, pas dans un écran d’aide. Une consigne exécutable sans avoir lu l’avertissement finit par être exécutée sans l’avoir lu. -
commande_retourest renseignée dès qu’une pose a été déclarée. C’est le geste de secours ; il doit rester atteignable hors ligne, gardez-le côté application. -
droitobéit à la même règle que le reste du contrat : bouton actif sienforcedest faux.
POST /starlink/mac/{nasId}
Ce que l’application déclare avoir fait. Le serveur ne peut pas le vérifier — le routeur est peut-être injoignable précisément parce que la pose a réussi.
{
"resultat": "POSEE",
"interface": "ether1",
"mac_origine": "48:A9:8A:11:22:33",
"mac_posee": "02:AB:CD:EF:01:02",
"internet_confirme": true,
"discretion_appliquee": true,
"interfaces_locales": ["ether2", "wlan1"],
"nom_hote": null,
"motif": null
}
discretion_appliquee: true exige interfaces_locales : un registre qui affirme sans rien
prouver ne servirait pas le support, qui doit pouvoir répondre à « ce routeur crie-t-il encore
son nom ? » sans aller le voir. L’événement WAN_DISCRETION_APPLIED entre dans le journal.
Réponse immédiate — l’application enchaîne sur l’écran de suivi sans second appel :
{
"nas_id": "uuid",
"etat": "POSEE",
"commande_retour": "/interface ethernet set [find name=ether1] mac-address=48:A9:8A:11:22:33",
"service_etat": "INITIALISATION",
"titre": "Service Anti-Restriction en cours d’initialisation…"
}
mac_origine est exigée même quand resultat vaut ECHEC : c’est justement quand la pose
a mal tourné qu’il faut savoir vers quoi revenir. resultat vaut POSEE, RESTAUREE ou
ECHEC ; un ECHEC ne porte pas de mac_posee.
| Code | Sens |
|---|---|
MAC_RESULT_INVALID |
resultat hors des trois valeurs |
MAC_INTERFACE_INVALID |
interface absente ou non nommable |
MAC_ORIGIN_INVALID |
mac_origine absente ou mal formée |
MAC_POSED_NOT_UNICAST |
adresse de groupe — une carte ne peut pas la porter en source |
MAC_NO_EFFECT |
adresse posée identique à l’origine : le lien tomberait sans rien masquer |
MAC_MISMATCH |
l’adresse déclarée n’est pas celle calculée pour ce routeur |
MAC_SERIAL_UNKNOWN |
numéro de série inconnu — l’adresse ne peut pas être dérivée |
Le parcours du bouton, côté application
Le bouton « Activer l’Anti-Restriction » est un déclencheur, pas un interrupteur. Il ne bascule pas un réglage : il lance une séquence dont chaque étape a une durée réelle. L’écran doit suivre cette séquence, sinon le gérant conclura à une panne pendant la minute où tout se passe normalement.
1. GET /starlink/mac/{nasId}/consigne?interface=ether1 → l'adresse et la commande
2. (application, sur le LAN) pose la commande sur le routeur
3. (application) attend le retour d'Internet et le VÉRIFIE
4. POST /starlink/mac/{nasId} { resultat: "POSEE", internet_confirme: true }
← réponse immédiate : service_etat "INITIALISATION"
← notification push : « Service Anti-Restriction en cours d'initialisation… »
5. GET /starlink/mac/{nasId}/statut (toutes les 5 s, tant que INITIALISATION)
← étapes : ROUTEUR, SAUVEGARDE, PROGRAMME
6. quand service_etat = "OPERATIONNEL"
← notification push : « Système Anti-Restriction actif sur SN…… »
internet_confirme n’est pas une formalité. C’est lui qui déclenche la reprise en main.
Ne le posez qu’après avoir constaté, depuis le LAN, que le routeur a retrouvé Internet — un
true optimiste ferait démarrer la séquence sur un routeur encore en train de renégocier son
bail DHCP, et le gérant lirait un échec là où il n’y avait qu’une attente.
Les deux moments sont séparés parce qu’ils le sont vraiment. Entre la pose et le service opérationnel, le bail se renégocie, le tunnel remonte, la sauvegarde se rapatrie, le programme se pose. Afficher « actif » dès l’étape 4 mentirait pendant la seule minute où quelque chose peut encore mal tourner — et c’est la minute où le gérant regarde son écran.
GET /starlink/mac/{nasId}/statut
Lecture ouverte, sans licence ni quota : c’est l’écran qui suit le bouton, il est interrogé en boucle, et le couper laisserait une barre qui tourne sans jamais conclure.
{
"nas_id": "uuid",
"router_serial": "HH40AF7YVNP",
"service_etat": "INITIALISATION",
"titre": "Service Anti-Restriction en cours d’initialisation…",
"etapes": [
{ "cle": "ROUTEUR", "libelle": "Le routeur répond de nouveau", "etat": "OK" },
{ "cle": "SAUVEGARDE", "libelle": "Sauvegarde de la configuration", "etat": "EN_ATTENTE" },
{ "cle": "PROGRAMME", "libelle": "Maîtrise de consommation en place", "etat": "EN_ATTENTE" }
],
"demarre_le": "2026-08-29T07:31:02Z",
"operationnel_depuis": null,
"peut_relancer": false
}
service_etat |
Ce que l’application affiche |
|---|---|
EN_ATTENTE |
le bouton d’activation |
INITIALISATION |
« Service Anti-Restriction en cours d’initialisation… » + les étapes |
OPERATIONNEL |
« Système Anti-Restriction actif sur SN…… », avec operationnel_depuis |
DEGRADE |
l’adresse est posée mais la reprise n’a pas abouti — bouton « Réessayer » |
- Affichez
titre, passervice_etat. La phrase vient du serveur pour rester la même partout, y compris dans la notification. - Une étape peut valoir
OK,EN_ATTENTE,IGNOREEouECHEC.IGNOREEn’est pas un problème : une sauvegarde refusée ou l’absence de programme de bridage n’empêchent pas le service d’être opérationnel. Seule l’étapeROUTEURest bloquante. peut_relancerest calculé par le serveur. Ne le recalculez pas : l’expiration d’une reprise interrompue (10 min) est une règle serveur, et deux versions de l’application la calculeraient différemment. Un backend redémarré en pleine reprise laisse une ligneINITIALISATIONque le statut convertit de lui-même enDEGRADErelançable — c’est pour ça que ce champ existe.- Cadence de sondage : 5 s pendant
INITIALISATION, puis arrêtez. La reprise dure typiquement moins d’une minute, sauvegarde comprise.
POST /starlink/mac/{nasId}/relancer
Le bouton « Réessayer ». Sous licence, comme toute action. Rend le même objet que /statut,
avec relance: true si la reprise a bien redémarré. Relancer une reprise déjà en cours et non
expirée renvoie relance: false : deux sauvegardes concurrentes sur le même routeur ne
serviraient à rien.
Notifications
Deux poussées, de type STARLINK_X, en plus de l’écran :
event_type |
Texte |
|---|---|
SERVICE_INITIALIZING |
Service Anti-Restriction en cours d’initialisation… |
SERVICE_READY |
Système Anti-Restriction actif sur SN…… |
SERVICE_DEGRADED |
L’adresse est posée, mais le routeur n’a pas encore répondu au serveur |
Elles ne respectent pas les heures silencieuses : c’est le gérant qui vient d’appuyer sur le bouton, il attend la réponse à l’heure qu’il est. Le dédoublonnage se fait par activation (la clé porte l’horodatage de début), donc une relance après un échec notifie de nouveau — sans quoi le gérant croirait que rien ne s’est passé.
Les trois événements entrent aussi dans GET /bandwidth/activity, avec la même phrase.
GET /starlink/mac
Les routeurs masqués du client. Lecture ouverte, sans licence : un gérant dont la licence a expiré doit pouvoir constater quelle adresse porte son routeur, et la restaurer.
Ce que le backend écrit sur le routeur
Uniquement /queue simple et /queue type, sous le commentaire JSBRIDAGE.
Le plan compte une file par plage préservée, plus un parent et une file « reste » — soit
25 à 30 files selon les classes choisies dans essential_classes. Ce n’est pas un choix de
conception : sur RouterOS 7, le paramètre dst d’une file simple n’accepte qu’une seule
valeur. Les files sont posées en un seul aller-retour groupé (mesuré : 2,4 s pour 28 files),
partagent toutes le même commentaire, et se retirent en une commande. Jamais de
/system script, /system scheduler, /ip firewall filter ni /ip firewall nat : ces
chemins sont refusés par routerWriteGuard, et le calendrier vit donc dans le backend plutôt
que dans un planificateur embarqué. Une file mal réglée ralentit ; elle ne peut pas rendre un
routeur injoignable — c’est ce qui autorise le backend à écrire ici sans humain sur le LAN.
Le retrait ne vise jamais que le commentaire JSBRIDAGE : aucune file du client, du hotspot
ou d’un revendeur n’est touchée.