Store & Forward Meshtastic : mémoire tampon hors réseau
Réponse rapide
Store & Forward (S&F) est le module Meshtastic qui permet à un nœud bien placé de conserver l’historique des messages texte du mesh, pour que les nœuds éteints ou hors de portée puissent demander une relecture à leur retour. Un serveur (un appareil ESP32 en rôle ROUTER/ROUTER_LATE, ou avec is_server activé, disposant d’au moins 1 Mo de PSRAM libre) stocke chaque message texte qu’il entend; un client demande l’historique sur le port STORE_FORWARD_APP (65) et reçoit par défaut jusqu’à 25 messages stockés des 240 dernières minutes, cadencés à un paquet par 5000 ms. Chaque nombre de cette page est cité du code source du micrologiciel.
Télécharger le CSV Télécharger le JSON API REST →
Transcrit mot à mot depuis meshtastic/firmware → src/modules/StoreForwardModule.cpp / .h
et meshtastic/protobufs → storeforward.proto / module_config.proto / portnums.proto
(en date du 2026-07-22). Les valeurs par défaut, les seuils et les valeurs d’énumération
sont des littéraux exacts de ces fichiers; les lignes du tableau et les fichiers de données restent en anglais parce que
ce sont des citations littérales des sources. Notez que portnums.proto marque toujours
STORE_FORWARD_APP « (Work in Progress) » — le comportement peut changer entre les versions
du micrologiciel; la date ci-dessus fait foi.
Comment fonctionne le Store & Forward
- Un nœud devient la mémoire du mesh. Un nœud capable d’agir comme serveur (ESP32 avec PSRAM, en rôle
ROUTER/ROUTER_LATEou avecis_serveractivé) écoute tout le trafic et copie chaque message texte entendu dans un tampon circulaire en PSRAM (historyAdd()). Les positions et la télémétrie ne sont pas stockées —wantPacket()n’accepte queTEXT_MESSAGE_APPetSTORE_FORWARD_APP. - La capacité est dimensionnée d’après la PSRAM. À moins que
recordssoit défini, le tampon est dimensionné à(((getFreePsram() / 4) * 3) / sizeof(PacketHistoryStruct))— jusqu’aux trois quarts de la PSRAM libre. Une fois plein, le serveur journalise « S&F - PSRAM Full. Starting overwrite » et boucle en écrasant les enregistrements les plus anciens. - Les clients demandent une relecture. Un client envoie
CLIENT_HISTORY(avec, en option, unewindowen minutes) sur le port 65; sans fenêtre, le serveur applique son défaut de240minutes. Le serveur répondROUTER_HISTORY(combien de messages arrivent, la fenêtre utilisée, etlast_requestpour que les requêtes répétées sautent les paquets déjà livrés), puis rejoue chaque message stocké enROUTER_TEXT_BROADCASTouROUTER_TEXT_DIRECT. - La relecture est polie par conception. Au plus
historyReturnMax(25 par défaut) messages par requête, un paquet parpacketTimeMax(5000 ms), et seulement tant que la barrière d’utilisation du canal le permet (le commentaire du code la plafonne à « moins de 25 % d’utilisation »). Les paquets rejoués partent en prioritéBACKGROUNDavecwant_ack = false. - Le filtre de relecture protège les frontières de vie privée. Un client ne reçoit que des diffusions ou des messages directs qui lui sont adressés — jamais ses propres messages ni les messages directs des autres — et les demandes d’historique sur le canal public par défaut sont refusées d’emblée.
Chaque réglage, valeur par défaut et code de protocole
| Groupe | Élément | Valeur / nº | Unité / type | Source | Description (en anglais — littérale) |
|---|---|---|---|---|---|
| Requirement | Supported architectures |
ARCH_ESP32, ARCH_PORTDUINO |
compile guard | StoreForwardModule.cpp |
The whole module body is wrapped in "#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)" — S&F only exists on ESP32 devices and the Linux-native (Portduino) build. nRF52 devices cannot run it. |
| Requirement | Server role gate |
ROUTER, ROUTER_LATE, or is_server |
device role | StoreForwardModule.cpp |
Server mode initializes only when config.device.role is ROUTER or ROUTER_LATE, or moduleConfig.store_forward.is_server is set. Every other enabled node becomes a client (is_client = true). |
| Requirement | PSRAM presence |
memGet.getPsramSize() > 0 |
condition | StoreForwardModule.cpp |
A server-mode device without PSRAM logs "S&F: device doesn't have PSRAM, Disable" and disables the module. |
| Requirement | Minimum free PSRAM |
1024 * 1024 |
bytes (1 MB) | StoreForwardModule.cpp |
Server startup requires memGet.getFreePsram() >= 1024 * 1024; below that it logs "S&F: not enough PSRAM free, Disable". |
| Firmware default | historyReturnMax |
25 |
records | StoreForwardModule.h |
"Return maximum of 25 records by default." — the most messages a server will replay per history request unless history_return_max overrides it. |
| Firmware default | historyReturnWindow |
240 |
minutes (4 h) | StoreForwardModule.h |
"Return history of last 4 hours by default." — the replay window used when a client does not specify one, unless history_return_window overrides it. |
| Firmware default | records |
0 (auto-calculated) |
records | StoreForwardModule.h |
Defaults to 0, meaning populatePSRAM() computes capacity as (((memGet.getFreePsram() / 4) * 3) / sizeof(PacketHistoryStruct)) — up to 3/4 of free PSRAM. |
| Firmware default | heartbeat |
false |
bool | StoreForwardModule.h |
"No heartbeat." — the server broadcasts no heartbeat unless moduleConfig.store_forward.heartbeat enables it. |
| Firmware default | heartbeatInterval |
900 |
seconds (15 min) | StoreForwardModule.h |
When heartbeat is enabled, the server broadcasts a ROUTER_HEARTBEAT every heartbeatInterval seconds (checked as heartbeatInterval * 1000 ms); the period is echoed in variant.heartbeat.period. |
| Firmware default | packetTimeMax |
5000 |
ms | StoreForwardModule.h |
"Interval between sending history packets as a server." — runOnce() paces replay so stored messages go out at most one per 5 seconds. |
| Firmware behaviour | Channel-utilization gate |
isTxAllowedChannelUtil(true) |
condition | StoreForwardModule.cpp |
Replay and heartbeat transmissions only happen when airtime permits: "Only send packets if the channel is less than 25% utilized and until historyReturnMax". |
| Firmware behaviour | Stored payload types |
TEXT_MESSAGE_APP only |
PortNum | StoreForwardModule.cpp |
historyAdd() runs on TEXT_MESSAGE_APP packets — the history holds text messages, not positions or telemetry. wantPacket() accepts only TEXT_MESSAGE_APP and STORE_FORWARD_APP. |
| Firmware behaviour | History buffer overwrite |
wraps to 0 when full |
ring buffer | StoreForwardModule.cpp |
When packetHistoryTotalCount reaches records the server logs "S&F - PSRAM Full. Starting overwrite", resets the counter to 0 and starts overwriting the oldest slots. |
| Firmware behaviour | Replay filter |
from != dest && (to == BROADCAST || to == dest) |
condition | StoreForwardModule.cpp |
A client is only sent packets it did not author itself, and only broadcasts or direct messages addressed to it. |
| Firmware behaviour | Public-channel refusal |
"S&F not permitted on the public channel." |
text reply | StoreForwardModule.cpp |
History requests arriving on the default (public) channel are refused with this text message (channels.isDefaultChannel check). |
| Firmware behaviour | Busy refusal |
"S&F - Busy. Try again shortly." |
text reply | StoreForwardModule.cpp |
While the server is replaying to one client it answers other history requests with this text message; CLIENT_STATS gets a ROUTER_BUSY instead. |
| Firmware behaviour | Client retry delay |
available_packets * packetTimeMax * (2 if ROUTER_ERROR else 1) |
ms | StoreForwardModule.cpp |
On ROUTER_BUSY / ROUTER_ERROR a client computes retry_delay = millis() + getNumAvailablePackets(...) * packetTimeMax, doubled for ROUTER_ERROR. |
| Firmware behaviour | Legacy text trigger |
"SF" + 0x00 |
text message | StoreForwardModule.cpp |
A direct text message whose payload starts with bytes 'S', 'F', 0x00 is a "Legacy Request to send" — the server replays historyReturnWindow * 60 seconds of history to the sender. |
| Firmware behaviour | Replay want_ack |
false |
bool | StoreForwardModule.cpp |
Replayed and protocol packets are sent with want_ack = false and priority BACKGROUND: "Let's assume that if the server received the S&F request that the client is in range." |
| Config field | enabled |
1 |
bool | module_config.proto |
"Enable the Store and Forward Module" — off by default, like other Meshtastic modules. |
| Config field | heartbeat |
2 |
bool | module_config.proto |
Enables the periodic ROUTER_HEARTBEAT broadcast on a server (firmware default: off). |
| Config field | records |
3 |
uint32 | module_config.proto |
Maximum number of records to store in memory; 0 lets the firmware auto-size from free PSRAM. |
| Config field | history_return_max |
4 |
uint32 | module_config.proto |
Overrides the maximum number of records returned per history request (firmware default 25). |
| Config field | history_return_window |
5 |
uint32 | module_config.proto |
Overrides the history window in minutes (firmware default 240 = 4 hours). |
| Config field | is_server |
6 |
bool | module_config.proto |
"Set to true to let this node act as a server that stores received messages and resends them upon request." — lets a non-router role run the server. |
| Protocol | STORE_FORWARD_APP |
65 |
PortNum | portnums.proto |
The S&F protocol port — flagged "(Work in Progress)" in portnums.proto. See the PortNum registry. |
| RequestResponse | UNSET |
0 |
enum | storeforward.proto |
Unset/unused. |
| RequestResponse | ROUTER_ERROR |
1 |
enum | storeforward.proto |
Router is in an error state. Codes 001–063 are from the router. |
| RequestResponse | ROUTER_HEARTBEAT |
2 |
enum | storeforward.proto |
Router heartbeat — carries a Heartbeat variant with period (seconds) and secondary (0 = primary router). |
| RequestResponse | ROUTER_PING |
3 |
enum | storeforward.proto |
Router has requested the client respond; works as an "are you there" message. |
| RequestResponse | ROUTER_PONG |
4 |
enum | storeforward.proto |
The response to a "Ping". A client treats it like receiving a heartbeat. |
| RequestResponse | ROUTER_BUSY |
5 |
enum | storeforward.proto |
Router is currently busy; please try again later. |
| RequestResponse | ROUTER_HISTORY |
6 |
enum | storeforward.proto |
Router is responding to a request for history — carries history_messages (count), window and last_request. |
| RequestResponse | ROUTER_STATS |
7 |
enum | storeforward.proto |
Router is responding to a request for stats — carries the Statistics variant. |
| RequestResponse | ROUTER_TEXT_DIRECT |
8 |
enum | storeforward.proto |
Router replays a stored text message that was a direct message. |
| RequestResponse | ROUTER_TEXT_BROADCAST |
9 |
enum | storeforward.proto |
Router replays a stored text message that was a broadcast. |
| RequestResponse | CLIENT_ERROR |
64 |
enum | storeforward.proto |
Client is in an error state. Codes 064–127 are from the client; the server aborts an in-progress replay to that client. |
| RequestResponse | CLIENT_HISTORY |
65 |
enum | storeforward.proto |
Client has requested a replay from the router — may carry a History variant whose window is in minutes. |
| RequestResponse | CLIENT_STATS |
66 |
enum | storeforward.proto |
Client has requested stats from the router. |
| RequestResponse | CLIENT_PING |
67 |
enum | storeforward.proto |
Client has requested the router respond; works as an "are you there" message. |
| RequestResponse | CLIENT_PONG |
68 |
enum | storeforward.proto |
The response to a "Ping". |
| RequestResponse | CLIENT_ABORT |
106 |
enum | storeforward.proto |
Client has requested that the router abort processing the client's request. |
| Message field | rr |
1 |
RequestResponse | storeforward.proto |
The request/response code — every S&F packet carries one. |
| Message field | stats (oneof variant) |
2 |
Statistics | storeforward.proto |
Server statistics: messages_total, messages_saved, messages_max, up_time (s), requests, requests_history, heartbeat, return_max, return_window (minutes). |
| Message field | history (oneof variant) |
3 |
History | storeforward.proto |
History metadata: history_messages (count to be sent), window (the filter window used) and last_request (index of the last message previously sent, so a client can avoid duplicates). |
| Message field | heartbeat (oneof variant) |
4 |
Heartbeat | storeforward.proto |
Heartbeat payload: period (seconds between heartbeats) and secondary ("If set, this is not the primary Store & Forward router on the mesh"). |
| Message field | text (oneof variant) |
5 |
bytes | storeforward.proto |
Text from a replayed history message (used with ROUTER_TEXT_DIRECT / ROUTER_TEXT_BROADCAST). |
Demander l’historique en pratique
- Depuis une app cliente : envoyez un protobuf
StoreAndForwardavecrr = CLIENT_HISTORY(65) surSTORE_FORWARD_APP. Incluezhistory.windowen minutes pour outrepasser le défaut de 4 heures du serveur. Utilisezhistory.last_requestde la réponseROUTER_HISTORYprécédente pour sauter les messages déjà reçus. - Déclencheur hérité : un message texte direct dont la charge utile commence par les octets
‘S’, ‘F’, 0x00déclenche aussi une relecture de la fenêtre par défaut. - Vivacité :
CLIENT_PING→ROUTER_PONGconfirme qu’un serveur est joignable; si le heartbeat du serveur est activé, il diffuseROUTER_HEARTBEATtoutes les 900 s par défaut. - Santé :
CLIENT_STATS→ROUTER_STATSretournemessages_savedvsmessages_max, le temps de fonctionnement, les compteurs de requêtes et lesreturn_max/return_windowactifs du serveur. - Si le serveur est occupé à rejouer pour quelqu’un d’autre, vous recevez le message texte « busy » (ou
ROUTER_BUSYpour les stats); le client patienteavailable_packets × 5000 ms, doublé après unROUTER_ERROR.
Limites et mises en garde
- Messages texte seulement. L’historique stocke les charges
TEXT_MESSAGE_APP— pas de positions, de télémétrie ni d’autres ports. - ESP32 (ou build Linux natif) avec PSRAM seulement. Le corps du module ne compile que pour
ARCH_ESP32/ARCH_PORTDUINO, et le mode serveur exige ≥ 1 Mo de PSRAM libre — les nœuds nRF52 et les cartes sans PSRAM ne peuvent pas servir. - De la RAM, pas de la flash. Le tampon circulaire vit en PSRAM — un redémarrage du serveur perd l’historique stocké.
- Un seul serveur par mesh est l’intention de conception. Le champ
secondarydu heartbeat existe, mais le micrologiciel commente « we always have one primary router for now ». - Pas sur le canal public. Les demandes d’historique sur le canal par défaut sont refusées; faites tourner le S&F sur un canal privé.
- Travail en cours.
portnums.protomarque le port « (Work in Progress) »; validez contre la version du micrologiciel que vous exécutez réellement.
Pourquoi ça compte pour des comms souveraines hors réseau
Un mesh dont les messages s’évaporent dès qu’un nœud s’éteint est un réseau de walkies-talkies; un mesh avec Store & Forward est un répondeur qui vous appartient. Pour la couche de communications résilientes d’une pile de souveraineté, la différence est structurelle : un nœud routeur (en anglais) solaire en hauteur avec le S&F activé signifie qu’un appareil éteint pendant une diffusion d’urgence peut rejoindre le mesh et demander « qu’est-ce que j’ai manqué? » — sans opérateur, sans nuage et sans compte. C’est le même principe d’auto-garde que nous appliquons à l’infrastructure Bitcoin : l’historique de vos communications devrait vivre sur du matériel que vous contrôlez. Voir aussi l’entrée de glossaire store-and-forward (en anglais) pour le concept réseau général, et les communications d’urgence par mesh au Canada (en anglais) pour le contexte de déploiement.
Pages reliées (en anglais)
Registre des PortNum Meshtastic (le port 65 vit ici) · Référence MeshPacket & champ Data (l’enveloppe des paquets S&F) · Rôles des nœuds Meshtastic (ROUTER / ROUTER_LATE) · Préréglages du modem LoRa · Meshtastic via MQTT (l’alternative reliée à internet) · Pôle mesh souverain (en français).
Crédit : Store & Forward est conçu et maintenu par le projet Meshtastic — le code du module dans meshtastic/firmware et le schéma du protocole dans meshtastic/protobufs (GPL-3.0). Cette page est un index daté, lisible par les humains et les machines, de ces fichiers.
Produits, réparations et guides connexes
- hub de souveraineté pour Bitcoiners
- la stack souveraine des plebs
- Nostr pour les Bitcoiners
- héberger votre propre relais Nostr
- débuter avec Meshtastic
- Bitcoin sur les réseaux mesh Meshtastic
- répertoire d’outils matériels open source
- minage Bitcoin hors réseau
Dernière révision: 22 juillet 2026.
