{"id":162,"date":"2025-12-17T17:49:35","date_gmt":"2025-12-17T16:49:35","guid":{"rendered":"https:\/\/leblogtech.romain-s.fr\/?page_id=162"},"modified":"2026-01-23T01:30:33","modified_gmt":"2026-01-23T00:30:33","slug":"codes","status":"publish","type":"page","link":"https:\/\/leblogtech.romain-s.fr\/index.php\/codes\/","title":{"rendered":"Codes"},"content":{"rendered":"\n<div class=\"wp-block-group is-layout-constrained wp-block-group-is-layout-constrained\">\n<h1 class=\"wp-block-heading\">ODS &#8211; g\u00e9n\u00e9ralit\u00e9s<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">1. Le probl\u00e8me : Le Temps relatif<\/h2>\n\n\n\n<p class=\"has-medium-font-size\">Dans ce r\u00e9seau UWB (Ultra-Wideband), chaque ancre (A<sub>i<\/sub>) poss\u00e8de une horloge interne qui d\u00e9rive par rapport \u00e0 celle de l&rsquo;ancre de r\u00e9f\u00e9rence (A<sub>R<\/sub>).<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-medium-font-size\">Elles n&rsquo;ont pas le m\u00eame \u00ab\u00a0z\u00e9ro\u00a0\u00bb (Offset).<\/li>\n\n\n\n<li class=\"has-medium-font-size\">Elles ne battent pas la mesure \u00e0 la m\u00eame vitesse (Skew ou d\u00e9rive).<\/li>\n<\/ul>\n\n\n\n<p class=\"has-medium-font-size\">L&rsquo;objectif de ce protocole est de calculer ce <strong>Skew (k<sub>Ri<\/sub><\/strong> ) pour pouvoir convertir n&rsquo;importe quelle dur\u00e9e mesur\u00e9e par l&rsquo;ancre i en une dur\u00e9e \u00ab\u00a0standard\u00a0\u00bb (celle de la r\u00e9f\u00e9rence R).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Le diagramme de s\u00e9quence <\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"603\" height=\"387\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_17h27_54.png\" alt=\"\" class=\"wp-image-169\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_17h27_54.png 603w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_17h27_54-300x193.png 300w\" sizes=\"auto, (max-width: 603px) 100vw, 603px\" \/><\/figure>\n\n\n\n<p class=\"has-medium-font-size\">Figure 1 : ODS-UWB synchronization protocol   \/  <em>source : Mme Dalce<\/em><\/p>\n\n\n\n<p class=\"has-medium-font-size\">Le diagramme montre une s\u00e9quence d&rsquo;\u00e9v\u00e9nements physiques qui d\u00e9limitent un intervalle de temps pr\u00e9cis :<\/p>\n\n\n\n<ul class=\"wp-block-list has-medium-font-size\">\n<li><strong>L&rsquo;\u00e9v\u00e9nement START&nbsp; :<\/strong> Le mobile lance un signal. Tout le monde le re\u00e7oit. C&rsquo;est le \u00ab\u00a0CLAP\u00a0\u00bb de d\u00e9part.\n<ul class=\"wp-block-list\">\n<li>L&rsquo;ancre N le voit \u00e0 t<sup>N<\/sup><sub>1<\/sub>.<\/li>\n\n\n\n<li>L\u2019ancre de r\u00e9f\u00e9rence le voit a t<sup>R<\/sup><sub>1<\/sub><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>L&rsquo;\u00e9v\u00e9nement REQUEST :<\/strong> L&rsquo;ancre de r\u00e9f\u00e9rence attend un peu, puis envoie un message direct aux autres ancres.\n<ul class=\"wp-block-list\">\n<li>L&rsquo;ancre N le voit a t<sup>N<\/sup><sub>2<\/sub>.<\/li>\n\n\n\n<li>Les Ancre r\u00e9pondent chacun leur tour (RESPONSE) avec leurs mesures (t<sup>N<\/sup><sub>1<\/sub>, t<sup>N<\/sup><sub>2<\/sub>) et leur temps d\u2019envoie du RESPONSE (t<sup>N<\/sup><sub>3<\/sub>).<\/li>\n\n\n\n<li>L\u2019ancre de r\u00e9f\u00e9rence re\u00e7oit ce message a t<sup>N<\/sup><sub>4<\/sub><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Synchronisation (TWR)<\/strong>\n<ul class=\"wp-block-list\">\n<li>L\u2019ancre r\u00e9f\u00e9rence obtient les 4 temps cl\u00e9s (t<sup>N<\/sup><sub>1<\/sub>, t<sup>N<\/sup><sub>2<\/sub>, t<sup>N<\/sup><sub>3<\/sub> et t<sup>N<\/sup><sub>4<\/sub>) .<\/li>\n\n\n\n<li>Elle peut donc calculer le&nbsp; Temps de Vol r\u00e9el et effectuer la correction de la d\u00e9rive d&rsquo;horloge (Skew) pour le TDOA.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Code client <\/h3>\n\n\n\n<pre class=\"wp-block-code\" style=\"border-width:3px\"><code>\/\/CLIENT\n\n#include &lt;SPI.h&gt;\n\n\/\/ HARDWARE COMPATIBILITY\n\/\/ Forces the DecaDuino library to use the specific pinout and SPI configuration\n\/\/ for the Decawave DWM1001-DEV module (nRF52832 MCU).\n#define ARDUINO_DWM1001_DEV \n#include &lt;DecaDuino.h&gt;\n\n\/\/ ADDRESSING \n#define ADDR_CLIENT 0x0010 \/\/ Unique ID of this mobile tag\n#define ADDR_S1     0x0001 \/\/ Not used, but kept for protocol consistency\n#define PAN_ID      0xDECA \/\/ Network ID\n#define BROADCAST   0xFFFF \/\/ Broadcast address\n\n\/\/ PROTOCOL DEFINITION \n\/\/ We only need the clap signal.\n#define ODS_MSG_CLAP        0x01\n\n\/\/ PACKET STRUCTURE\n\/\/ __attribute__((packed)) ensures no memory padding is added by the compiler.\nstruct MacHeader { \n    uint16_t fc;   \/\/ Frame Control\n    uint8_t seq;   \/\/ Sequence Number\n    uint16_t pan;  \/\/ PAN ID\n    uint16_t dest; \/\/ Destination Addr\n    uint16_t src;  \/\/ Source Addr\n} __attribute__((packed));\n\nstruct ODS_Packet {\n    MacHeader header;\n    uint8_t   messageType; \n    \/\/ Payload is empty for the clap\n} __attribute__((packed));\n\n\/\/  DRIVER\n#ifdef ARDUINO_DWM1001_DEV\n  DecaDuino decaduino(SS1, DW_IRQ); \/\/ Use DWM1001 specific pins\n#else\n  DecaDuino decaduino; \/\/ Use default Arduino\/Teensy pins\n#endif\n\nuint8_t rxBuffer&#91;128]; \/\/ Not used for RX, but required for init\nuint16_t rxLen;\nint seqID = 0; \/\/ Packet counter\n\n\/\/ SETUP ROUTINE\nvoid setup() {\n  Serial.begin(115200);\n  \/\/ Blocking wait for Serial connection (max 3 seconds) to allow debug monitoring\n  while (!Serial &amp;&amp; millis() &lt; 3000);\n  \n  \n\n  \/\/ Initialize DW1000 radio chip\n  if (!decaduino.init()) { Serial.println(\"Init failed\"); while(1); }\n  \n\n  \n  Serial.println(\"--- CLIENT ODS PRET ---\");\n  delay(1000);\n}\n\n\/\/ --- MAIN LOOP ---\nvoid loop() {\n  \/\/ Process driver engine (check interruptions)\n  decaduino.engine();\n  seqID++; \/\/ Increment sequence number\n\n  Serial.print(\"Blink #\"); Serial.println(seqID);\n  \n  \/\/ 1. PREPARE PACKET\n  ODS_Packet packet;\n  packet.header.fc = 0x8841;      \/\/ Standard Data Frame\n  packet.header.pan = PAN_ID;\n  packet.header.dest = BROADCAST; \/\/ Target: All Anchors\n  packet.header.src = ADDR_CLIENT;\n  packet.header.seq = seqID;\n  packet.messageType = ODS_MSG_CLAP;\n  \n  \/\/ 2. TRANSMISSION (TX)\n  \/\/ pdDataRequest sends the bytes to the radio buffer and triggers TX immediately.\n  \/\/ Size is strictly 10 bytes (Header + Type).\n  decaduino.pdDataRequest((uint8_t*)&amp;packet, 10); \n  \n\n  \/\/ 3. WAIT FOR PHYSICAL TRANSMISSION\n  \/\/ We block here until the radio chip confirms the last bit has left the antenna.\n  while (!decaduino.hasTxSucceeded()) decaduino.engine();\n\n  \/\/ 4. WAIT BEFORE NEXT CLAP\n  \/\/ The loop finishes here. The radio automatically returns to IDLE state (Rx\/Tx OFF).\n  delay(3000); \/\/ Wait 3 seconds before next clap\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Explication code client <\/h2>\n\n\n\n<p class=\"has-medium-font-size\">Le&nbsp; n\u0153ud Client est bas\u00e9 sur une architecture unidirectionnelle asynchrone.<\/p>\n\n\n\n<p class=\"has-medium-font-size\"><strong>Principe de fonctionnement : Le \u00ab\u00a0Blink\u00a0\u00bb<\/strong><\/p>\n\n\n\n<p class=\"has-medium-font-size\">Le n\u0153ud mobile fonctionne selon un mod\u00e8le appel\u00e9 \u00ab\u00a0Blink\u00a0\u00bb (ou Fire and Forget). Son r\u00f4le est exclusivement d&rsquo;\u00e9mettre un signal de pr\u00e9sence p\u00e9riodique, sans se soucier de l&rsquo;infrastructure environnante.<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Le cycle de vie du mobile est le suivant :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-medium-font-size\"><strong>R\u00e9veil :<\/strong> Le processeur sort de veille.<\/li>\n\n\n\n<li class=\"has-medium-font-size\"><strong>\u00c9mission :<\/strong> Il envoie un paquet court (le CLAP) en mode Broadcast.<\/li>\n\n\n\n<li class=\"has-medium-font-size\"><strong>Sommeil :<\/strong> D\u00e8s que l&rsquo;envoi est confirm\u00e9 physiquement, il coupe sa radio et retourne en veille.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Automate client <\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"693\" height=\"317\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_17h13_41.png\" alt=\"\" class=\"wp-image-165\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_17h13_41.png 693w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_17h13_41-300x137.png 300w\" sizes=\"auto, (max-width: 693px) 100vw, 693px\" \/><\/figure>\n\n\n\n<p class=\"has-medium-font-size\">Figure 2 : Automate client<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Code Ancre de r\u00e9f\u00e9rence<\/h2>\n\n\n\n<pre class=\"wp-block-code\" style=\"border-width:3px\"><code>#include &lt;SPI.h&gt;\n\n\/\/ --- HARDWARE COMPATIBILITY ---\n\/\/ Define if using the DWM1001-DEV board\n#define ARDUINO_DWM1001_DEV \n#include &lt;DecaDuino.h&gt;\n\n\/\/ --- CONFIGURATION ---\n#define ADDR_REF     0x0001   \/\/ Address of THIS Reference Anchor (The Time Base)\n#define ADDR_N2      0x0002   \/\/ Address of Secondary Anchor 2 (Neighbor)\n#define ADDR_N3      0x0003   \/\/ Address of Secondary Anchor 3 (Neighbor)\n#define PAN_ID       0xDECA   \/\/ Personal Area Network ID\n#define BROADCAST    0xFFFF   \/\/ Broadcast address\n#define MAX_ANCHORS  5        \/\/ Max capacity of the system (Secondary anchors)\n\n\/\/ DW1000 Time Unit: 1 ms approx 63897600 ticks\n#define TICKS_PER_MS 63897600ULL \n\n\/\/ --- PROTOCOL DEFINITIONS ---\n#define ODS_MSG_CLAP        0x01 \/\/ Step 1: Mobile tag emits signal (Event 0)\n#define ODS_MSG_REQ_TS      0x02 \/\/ Step 2: Reference Anchor requests timestamps\n#define ODS_MSG_RESP_TS     0x03 \/\/ Step 3: Secondary Anchors respond with data\n\/\/ REMOVED: ODS_MSG_FINAL_SYNC (Protocol ends after data collection)\n\n\/\/ --- PACKET STRUCTURES ---\nstruct MacHeader { \n    uint16_t fc;    \/\/ Frame Control\n    uint8_t seq;    \/\/ Sequence Number\n    uint16_t pan;   \/\/ PAN ID\n    uint16_t dest;  \/\/ Destination Address\n    uint16_t src;   \/\/ Source Address\n} __attribute__((packed));\n\nstruct PayloadReqTS { \n    uint8_t count;                   \/\/ Number of anchors targeted\n    uint16_t targetList&#91;MAX_ANCHORS];\/\/ List of IDs (Neighbors)\n} __attribute__((packed));\n\nstruct PayloadRespTS { \n    uint64_t t1_RX_CLAP; \/\/ Time Neighbor received CLAP\n    uint64_t t2_RX_REQ;  \/\/ Time Neighbor received REQ\n    uint64_t t3_TX_RESP; \/\/ Time Neighbor sent RESP\n} __attribute__((packed));\n\nstruct ODS_Packet {\n    MacHeader header;\n    uint8_t   messageType;\n    union {\n        PayloadReqTS  reqInfo;\n        PayloadRespTS respInfo;\n    } payload;\n} __attribute__((packed));\n\n\/\/ --- GLOBAL VARIABLES ---\n\/\/ List of Secondary Anchors (Neighbors) to synchronize\nuint16_t neighborsList&#91;MAX_ANCHORS] = {ADDR_N2, ADDR_N3}; \nint neighborsCount = 2; \n\n\/\/ Storage for timestamps (Index 0 corresponds to neighborsList&#91;0], etc.)\n\/\/ N stands for \"Neighbor\" (Secondary Anchor)\nuint64_t ti1_neighbors&#91;MAX_ANCHORS]; \/\/ t1: CLAP arrival at Neighbor (N)\nuint64_t ti2_neighbors&#91;MAX_ANCHORS]; \/\/ t2: REQ arrival at Neighbor (N)\nuint64_t ti3_neighbors&#91;MAX_ANCHORS]; \/\/ t3: RESP departure from Neighbor (N)\nuint64_t ti4_neighbors&#91;MAX_ANCHORS]; \/\/ t4: RESP arrival at Reference (Ref)\n\n\/\/ R stands for \"Reference\" (This Anchor)\nuint64_t tR1_RxClap = 0; \/\/ Time Reference received CLAP\nuint64_t tR2_TxReq  = 0; \/\/ Time Reference sent REQ\n\nbool responseReceived&#91;MAX_ANCHORS]; \/\/ Track who replied\n\n\/\/ State Machine\nenum State { STATE_IDLE, STATE_WAIT_RESPONSES };\nState currentState = STATE_IDLE;\nunsigned long timeoutStart = 0;\n\n\/\/ Hardware Driver\n#ifdef ARDUINO_DWM1001_DEV\n  DecaDuino decaduino(SS1, DW_IRQ); \n#else\n  DecaDuino decaduino; \n#endif\nuint8_t rxBuffer&#91;128];\nuint16_t rxLen;\n\nvoid setup() {\n  Serial.begin(115200);\n  while (!Serial &amp;&amp; millis() &lt; 3000);\n\n  #ifndef ARDUINO_DWM1001_DEV\n    SPI.setSCK(14); \n  #endif\n\n  if (!decaduino.init()) { Serial.println(\"Init failed\"); while(1); }\n  \n  decaduino.setRxBuffer(rxBuffer, &amp;rxLen);\n  decaduino.plmeRxEnableRequest();\n  Serial.println(\"--- REFERENCE ANCHOR (S1) READY ---\");\n}\n\nvoid loop() {\n  decaduino.engine();\n\n  switch (currentState) {\n    \n    \/\/ --- STATE 1: IDLE (Waiting for Mobile CLAP) ---\n    case STATE_IDLE:\n      if (decaduino.rxFrameAvailable()) {\n        ODS_Packet* pack = (ODS_Packet*)rxBuffer;\n        \n        \/\/ If we receive the CLAP (Reference Event from Mobile)\n        if (pack-&gt;messageType == ODS_MSG_CLAP) {\n            \/\/ 1. Capture exact arrival time at Reference (tR1)\n            tR1_RxClap = decaduino.getLastRxTimestamp();\n            \n            \/\/ 2. Reset data structures for this new cycle\n            for(int i=0; i&lt;MAX_ANCHORS; i++) { \n                ti1_neighbors&#91;i]=0; ti2_neighbors&#91;i]=0; ti3_neighbors&#91;i]=0; ti4_neighbors&#91;i]=0;\n                responseReceived&#91;i] = false; \n            }\n\n            \/\/ 3. Send the Group Request (Delayed) to capture precise tR2\n            sendGroupRequestDelayed();\n            \n            \/\/ 4. Transition to waiting state\n            currentState = STATE_WAIT_RESPONSES;\n            timeoutStart = millis();\n            decaduino.plmeRxEnableRequest();\n        } else {\n             \/\/ Not a CLAP, just listen again\n             decaduino.plmeRxEnableRequest();\n        }\n      }\n      break;\n\n    \/\/ --- STATE 2: COLLECTING RESPONSES FROM SECONDARY ANCHORS ---\n    case STATE_WAIT_RESPONSES:\n      \/\/ Timeout safety (500ms max to wait for responses)\n      if (millis() - timeoutStart &gt; 500) { \n        Serial.println(\"Timeout ! Resetting to IDLE.\");\n        currentState = STATE_IDLE;\n        decaduino.plmeRxEnableRequest();\n        break;\n      }\n\n      if (decaduino.rxFrameAvailable()) {\n         ODS_Packet* pack = (ODS_Packet*)rxBuffer;\n\n         if (pack-&gt;messageType == ODS_MSG_RESP_TS) {\n             uint16_t sender = pack-&gt;header.src;\n             \n             \/\/ Identify which neighbor sent this message\n             for (int i=0; i&lt;neighborsCount; i++) {\n                 if (neighborsList&#91;i] == sender) {\n                     \/\/ Save Timestamps from Payload (Neighbor's local clock)\n                     ti1_neighbors&#91;i] = pack-&gt;payload.respInfo.t1_RX_CLAP;\n                     ti2_neighbors&#91;i] = pack-&gt;payload.respInfo.t2_RX_REQ;\n                     ti3_neighbors&#91;i] = pack-&gt;payload.respInfo.t3_TX_RESP;\n                     \n                     \/\/ Save arrival time at Reference (Reference's local clock)\n                     ti4_neighbors&#91;i] = decaduino.getLastRxTimestamp();\n                     \n                     responseReceived&#91;i] = true;\n                 }\n             }\n             \n             \/\/ Check if ALL neighbors have responded\n             bool allGood = true;\n             for (int i=0; i&lt;neighborsCount; i++) { if(!responseReceived&#91;i]) allGood = false; }\n             \n             if (allGood) {\n                 \/\/ All data collected. Process outputs.\n                 printJSON(); \n                 \n                 \/\/ Calculate Skew for each neighbor using the primitive formula\n                 for(int i=0; i&lt;neighborsCount; i++) {\n                    CalculSkew_Primitive(i);\n                 }\n                 \n                 \/\/ Cycle finished. No ACK sent. Return to IDLE.\n                 currentState = STATE_IDLE;\n             }\n         }\n         decaduino.plmeRxEnableRequest();\n      }\n      break;\n  }\n}\n\n\/\/ Function to send the Request broadcast with a precise delay\nvoid sendGroupRequestDelayed() {\n  ODS_Packet packet;\n  packet.header.fc = 0x8841; packet.header.pan = PAN_ID;\n  packet.header.src = ADDR_REF; packet.header.dest = BROADCAST; \n  packet.messageType = ODS_MSG_REQ_TS;\n  packet.payload.reqInfo.count = neighborsCount;\n  \n  \/\/ Fill target list\n  for(int i=0; i&lt;neighborsCount; i++) packet.payload.reqInfo.targetList&#91;i] = neighborsList&#91;i];\n\n  \/\/ Calculate future time: Now + 20ms\n  \/\/ 20ms * Ticks_per_ms (approx 63897 ticks for 1ms)\n  uint64_t delayTicks = 20000 * 63897; \n  uint64_t now = decaduino.getSystemTimeCounter();\n  \n  \/\/ Align for hardware (mask lower 9 bits)\n  tR2_TxReq = decaduino.alignDelayedTransmissionTS(now + delayTicks);\n  \n  \/\/ Send with Delayed Transmission enabled\n  \/\/ Packet contains the count of targets + list of addresses\n  decaduino.pdDataRequest((uint8_t*)&amp;packet, 11+(2*neighborsCount), true, tR2_TxReq);\n  \n  \/\/ Wait for TX to complete\n  while (!decaduino.hasTxSucceeded()) decaduino.engine();\n}\n\n\/\/ Output data for Python processing\nvoid printJSON() {\n    Serial.println(\"{\");\n    Serial.println(\"  \\\"anchor_Ref\\\": {\");\n    Serial.print(\"    \\\"tR1\\\": \"); decaduino.printUint64(tR1_RxClap); Serial.println(\",\");\n    Serial.print(\"    \\\"tR2\\\": \"); decaduino.printUint64(tR2_TxReq); Serial.println(\"\");\n    Serial.println(\"  },\");\n    Serial.println(\"  \\\"neighbors\\\": &#91;\");\n    \n    for(int i=0; i&lt;neighborsCount; i++) {\n        Serial.println(\"    {\");\n        Serial.print(\"      \\\"id\\\": \\\"0x\"); Serial.print(neighborsList&#91;i], HEX); Serial.println(\"\\\",\");\n        Serial.print(\"      \\\"ti1\\\": \"); decaduino.printUint64(ti1_neighbors&#91;i]); Serial.println(\",\");\n        Serial.print(\"      \\\"ti2\\\": \"); decaduino.printUint64(ti2_neighbors&#91;i]); Serial.println(\",\");\n        Serial.print(\"      \\\"ti3\\\": \"); decaduino.printUint64(ti3_neighbors&#91;i]); Serial.println(\",\");\n        Serial.print(\"      \\\"ti4\\\": \"); decaduino.printUint64(ti4_neighbors&#91;i]); Serial.println(\"\");\n        Serial.print(\"    }\");\n        if(i &lt; neighborsCount-1) Serial.println(\",\"); else Serial.println(\"\");\n    }\n    Serial.println(\"  ]\");\n    Serial.println(\"}\");\n    Serial.println(\"___END_JSON___\");\n}\n\n\/\/ \"Primitive\" Skew Calculation (Demonstration logic)\n\/\/ See \"Problems and Solutions\" page for the Iterative Algorithm\nvoid CalculSkew_Primitive(int i) {\n    uint64_t tR1 = tR1_RxClap;\n    uint64_t tR2 = tR2_TxReq;\n    uint64_t tN1 = ti1_neighbors&#91;i]; \/\/ Neighbor t1\n    uint64_t tN2 = ti2_neighbors&#91;i]; \/\/ Neighbor t2\n    uint64_t tN3 = ti3_neighbors&#91;i]; \/\/ Neighbor t3\n    uint64_t tN4 = ti4_neighbors&#91;i]; \/\/ Neighbor t4 (Received at Ref)\n\n    \/\/ Calculate Round Trip Time (Ref -&gt; Neighbor -&gt; Ref)\n    int64_t T_round = (int64_t)tN4 - (int64_t)tR2;\n    \n    \/\/ Calculate Reply Time at Neighbor (Processing time)\n    \/\/ PROBLEM: This duration is measured in Neighbor's time units (skewed)\n    int64_t T_reply = (int64_t)tN3 - (int64_t)tN2;\n    \n    \/\/ Estimate Time of Flight (Primitive ToF)\n    int64_t tof_us = (T_round - T_reply) \/ 2; \n\n    \/\/ Intervals comparison\n    int64_t ref_interval = (int64_t)tR2 - (int64_t)tR1; \/\/ Interval between events at Reference\n    int64_t neighbor_interval = (int64_t)tN2 - (int64_t)tN1; \/\/ Interval between events at Neighbor\n    \n    \/\/ Primitive correction of reference interval using ToF\n    int64_t denominator = ref_interval - (2 * tof_us); \n    \n    double skew_ratio = 0.0;\n    if (denominator != 0) {\n        \/\/ Ratio = Neighbor_Ticks \/ Reference_Ticks\n        skew_ratio = (double)neighbor_interval \/ (double)denominator;\n    }\n\n    Serial.print(\"Neighbor 0x\"); Serial.print(neighborsList&#91;i], HEX); Serial.println(\":\");\n    Serial.print(\"  ToF (ticks): \"); Serial.println((long)tof_us);\n    Serial.print(\"  Skew (Ratio N\/R) : \");\n    Serial.println(skew_ratio, 6);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Explication<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. Initialisation et \u00c9coute (\u00c9tat <code>IDLE<\/code>)<\/h3>\n\n\n\n<p class=\"has-medium-font-size\">Le module est en \u00e9coute permanente. Le cycle d\u00e9marre d\u00e8s la r\u00e9ception du signal <strong>CLAP<\/strong> \u00e9mis par le mobile (Tag).<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-medium-font-size\"><strong>Action :<\/strong> Capture imm\u00e9diate du timestamp mat\u00e9riel de r\u00e9ception (t<sup>R<\/sup><sub>1<\/sub>).<\/li>\n\n\n\n<li><strong>R\u00e9action :<\/strong> L&rsquo;ancre pr\u00e9pare l&rsquo;envoi d&rsquo;une <strong>REQU\u00caTE<\/strong> \u00e0 toutes les ancres voisines.<\/li>\n\n\n\n<li><strong>Pr\u00e9cision :<\/strong> Elle utilise une transmission diff\u00e9r\u00e9e (<code>sendGroupRequestDelayed<\/code>). Cela lui permet de d\u00e9finir l&rsquo;heure exacte d&rsquo;\u00e9mission future (t<sup>R<\/sup><sub>2<\/sub>) et de l&rsquo;enregistrer avant m\u00eame que l&rsquo;onde radio ne parte.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. Collecte des Donn\u00e9es (\u00c9tat <code>WAIT_RESPONSES<\/code>)<\/h3>\n\n\n\n<p class=\"has-medium-font-size\">L&rsquo;ancre passe en mode attente pour r\u00e9colter les mesures des voisins (N).<\/p>\n\n\n\n<ul class=\"wp-block-list has-medium-font-size\">\n<li><strong>R\u00e9ception :<\/strong> Pour chaque r\u00e9ponse re\u00e7ue, elle extrait les temps mesur\u00e9s par le voisin t<sup>N<\/sup><sub>1<\/sub>, t<sup>N<\/sup><sub>2<\/sub>, t<sup>N<\/sup><sub>3<\/sub> et note l&rsquo;heure d&rsquo;arriv\u00e9e chez elle (t<sup>N<\/sup><sub>4<\/sub>).<\/li>\n\n\n\n<li><strong>Stockage :<\/strong> Les 4 temps cl\u00e9s de chaque voisin sont stock\u00e9s dans des tableaux index\u00e9s.<\/li>\n\n\n\n<li><strong>S\u00e9curit\u00e9 :<\/strong> Un Timeout de 500ms force le retour \u00e0 l&rsquo;\u00e9tat initial si un voisin ne r\u00e9pond pas, \u00e9vitant le blocage du syst\u00e8me.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. Le Calcul du Skew \u00ab\u00a0Primitif\u00a0\u00bb (<code>CalculSkew_Primitive<\/code>)<\/h3>\n\n\n\n<p class=\"has-medium-font-size\">Une fois toutes les r\u00e9ponses re\u00e7ues, le code tente d&rsquo;estimer la d\u00e9rive d&rsquo;horloge (Skew) de chaque voisin via une approche g\u00e9om\u00e9trique directe.<\/p>\n\n\n\n<p class=\"has-medium-font-size\">A. Calcul du Temps de Vol (ToF)<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Il estime la distance entre les ancres par une m\u00e9thode d&rsquo;aller-retour classique :<\/p>\n\n\n\n<p class=\"has-medium-font-size\">ToF = (Temps Aller-retour &#8211; Temps de traitement ) \/ 2 <\/p>\n\n\n\n<ul class=\"wp-block-list has-medium-font-size\">\n<li>Temps Aller-Retour = t<sup>N<\/sup><sub>4 <\/sub>&#8211; t<sup>R<\/sup><sub>2<\/sub> (Vu par la R\u00e9f\u00e9rence)<\/li>\n\n\n\n<li>Temps Traitement = t<sup>N<\/sup><sub>3<\/sub> &#8211; t<sup>N<\/sup><sub>2<\/sub> (Vu par le Voisin)<\/li>\n<\/ul>\n\n\n\n<p class=\"has-medium-font-size\">B. Calcul du Ratio de Skew<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Il compare ensuite les intervalles de temps pour obtenir le ratio de vitesse des horloges :<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Skew = Intervalle Voisin \/ Intervalle R\u00e9f\u00e9rence corrig\u00e9<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-medium-font-size\"><strong>L&rsquo;intervalle Voisin :<\/strong> t<sup>N<\/sup><sub>2<\/sub> &#8211; t<sup>N<\/sup><sub>1<\/sub> (Dur\u00e9e brute mesur\u00e9e par le voisin).<\/li>\n\n\n\n<li class=\"has-medium-font-size\"><strong>L&rsquo;intervalle R\u00e9f\u00e9rence :<\/strong> t<sup>R<\/sup><sub>2<\/sub> &#8211; t<sup>R<\/sup><sub>1 <\/sub> auquel on retire le ToF calcul\u00e9 juste avant (pour compenser le retard d\u00fb \u00e0 la distance).<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">4. Limitation de cette Approche (Pourquoi ce n&rsquo;est pas optimal)<\/h3>\n\n\n\n<p class=\"has-medium-font-size\">Cette m\u00e9thode contient un d\u00e9faut logique appel\u00e9 <strong>\u00ab\u00a0L&rsquo;\u0153uf et la Poule\u00a0\u00bb<\/strong> :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-medium-font-size\">Pour calculer le <strong>ToF<\/strong>, on soustrait le temps de traitement du Voisin (t<sup>N<\/sup><sub>3<\/sub> &#8211; t<sup>N<\/sup><sub>2<\/sub>) <\/li>\n\n\n\n<li class=\"has-medium-font-size\">Or, ce temps est mesur\u00e9 avec l&rsquo;horloge du Voisin <strong>qui est fausse puisqu&rsquo;elle d\u00e9rive.<\/strong><\/li>\n\n\n\n<li class=\"has-medium-font-size\">L&rsquo;erreur de l&rsquo;horloge contamine le calcul de la distance, qui \u00e0 son tour contamine le calcul final du Skew.<\/li>\n<\/ul>\n\n\n\n<p class=\"has-medium-font-size\">Cette m\u00e9thode \u00ab\u00a0primitive\u00a0\u00bb offre une certaine pr\u00e9cision , mais elle est insuffisante pour le TDoA centim\u00e9trique, ce qui justifie l&rsquo;utilisation d&rsquo;un algorithme it\u00e9ratif (d\u00e9taill\u00e9 dans la section probl\u00e8me rencontr\u00e9s et solutions apport\u00e9es.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Automate<\/h4>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"694\" height=\"470\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-21_10h26_37.png\" alt=\"\" class=\"wp-image-188\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-21_10h26_37.png 694w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-21_10h26_37-300x203.png 300w\" sizes=\"auto, (max-width: 694px) 100vw, 694px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Code serveur secondaire<\/h3>\n\n\n\n<pre class=\"wp-block-code\" style=\"border-width:3px\"><code>#include &lt;SPI.h&gt;\n\n\/\/ --- HARDWARE COMPATIBILITY ---\n\/\/ Define for DWM1001-DEV board specific pinout\n#define ARDUINO_DWM1001_DEV \n#include &lt;DecaDuino.h&gt;\n\n\/\/ --- CONFIGURATION ---\n#define MY_ADDR      0x0002   \/\/ Address of this specific Slave (Change for other boards)\n#define ADDR_S1      0x0001   \/\/ Master Address\n#define PAN_ID       0xDECA   \/\/ Personal Area Network ID\n#define BROADCAST    0xFFFF   \/\/ Broadcast address\n\n\/\/ DW1000 Time Unit conversion: approx 63,897,600 ticks = 1 millisecond\n#define TICKS_PER_MS 63897600ULL \n\n\/\/ --- PROTOCOL DEFINITIONS ---\n\/\/ Protocol steps\n#define ODS_MSG_CLAP        0x01 \/\/ Step 1: Reference signal (Reference event)\n#define ODS_MSG_REQ_TS      0x02 \/\/ Step 2: Master requests timestamps\n#define ODS_MSG_RESP_TS     0x03 \/\/ Step 3: Slave responds with data\n\/\/ REMOVED: ODS_MSG_FINAL_SYNC (No longer used)\n\n#define MAX_SLAVES 5 \/\/ Maximum number of slaves addressed in one request\n\n\/\/ --- DATA STRUCTURES (Packed to avoid padding issues) ---\n\n\/\/ Standard 802.15.4 MAC Header\nstruct MacHeader { \n    uint16_t fc;    \/\/ Frame Control\n    uint8_t seq;    \/\/ Sequence Number\n    uint16_t pan;   \/\/ PAN ID\n    uint16_t dest;  \/\/ Destination Address\n    uint16_t src;   \/\/ Source Address\n} __attribute__((packed));\n\n\/\/ Payload for Step 2: Request Timestamps (Sent by Master)\nstruct PayloadReqTS { \n    uint8_t count;                  \/\/ How many slaves are targeted?\n    uint16_t targetList&#91;MAX_SLAVES];\/\/ List of slave addresses to respond\n} __attribute__((packed));\n\n\/\/ Payload for Step 3: Response Timestamps (Sent by Slave)\nstruct PayloadRespTS { \n    uint64_t t1_RX_CLAP; \/\/ Timestamp: When the CLAP was received\n    uint64_t t2_RX_REQ;  \/\/ Timestamp: When the REQUEST was received\n    uint64_t t3_TX_RESP; \/\/ Timestamp: When this RESPONSE is transmitted\n} __attribute__((packed));\n\n\/\/ Main Packet Union: Combines Header and Payloads\nstruct ODS_Packet {\n    MacHeader header;\n    uint8_t   messageType;\n    union {\n        PayloadReqTS  reqInfo;\n        PayloadRespTS respInfo;\n        \/\/ Removed finalInfo\n    } payload;\n} __attribute__((packed));\n\n\/\/ --- GLOBAL VARIABLES ---\n#ifdef ARDUINO_DWM1001_DEV\n  \/\/ Initialize DecaDuino with specific SS and IRQ pins for DWM1001\n  DecaDuino decaduino(SS1, DW_IRQ); \n#else\n  \/\/ Default initialization\n  DecaDuino decaduino; \n#endif\n\nuint8_t rxBuffer&#91;128]; \/\/ Buffer for incoming data\nuint16_t rxLen;        \/\/ Length of received data\n\n\/\/ Variables to store critical timestamps\nuint64_t t1_Clap = 0;\nuint64_t t2_Req  = 0;\n\nvoid setup() {\n  Serial.begin(115200);\n  \/\/ Wait for Serial to be ready (useful for debugging boot issues)\n  while (!Serial &amp;&amp; millis() &lt; 3000);\n\n  #ifndef ARDUINO_DWM1001_DEV\n    SPI.setSCK(14); \/\/ Specific clock pin for generic ESP32 setups\n  #endif\n\n  \/\/ Initialize the UWB radio\n  if (!decaduino.init()) { \n    Serial.println(\"Init failed\"); \n    while(1); \/\/ Halt if hardware fails\n  }\n  \n  \/\/ Set up RX buffer\n  decaduino.setRxBuffer(rxBuffer, &amp;rxLen);\n  \n  \/\/ Enable Receiver immediately\n  decaduino.plmeRxEnableRequest();\n  \n  Serial.print(\"--- SLAVE 0x\"); \n  Serial.print(MY_ADDR, HEX); \n  Serial.println(\" READY ---\");\n}\n\nvoid loop() {\n  \/\/ Main driver engine (handles IRQ and state transitions)\n  decaduino.engine();\n\n  \/\/ Check if a packet has been successfully received\n  if (decaduino.rxFrameAvailable()) {\n    ODS_Packet* pack = (ODS_Packet*)rxBuffer;\n\n    \/\/ --- 1. HANDLE CLAP MSG (Reference Signal) ---\n    if (pack-&gt;messageType == ODS_MSG_CLAP) {\n      \/\/ Hardware automatically captures the exact arrival time\n      t1_Clap = decaduino.getLastRxTimestamp();\n      Serial.println(\"&#91;Slave] t1 (CLAP) saved.\");\n    }\n\n    \/\/ --- 2. HANDLE REQUEST MSG (Master asks for data) ---\n    else if (pack-&gt;messageType == ODS_MSG_REQ_TS) {\n      \/\/ Capture the arrival time of this request immediately\n      t2_Req = decaduino.getLastRxTimestamp(); \n      \n      \/\/ Parse the payload to see if I am in the target list\n      int count = pack-&gt;payload.reqInfo.count;\n      int myIndex = -1;\n      \n      for (int i=0; i&lt;count; i++) {\n          if (pack-&gt;payload.reqInfo.targetList&#91;i] == MY_ADDR) { \n            myIndex = i; \/\/ Found myself! Save the index.\n            break; \n          }\n      }\n\n      \/\/ If I am targeted, calculate response time\n      if (myIndex != -1) {\n          \/\/ --- CALCULATE DELAYED TX TIME (t3) ---\n          \/\/ Logic: Processing Time + (Slot Time * Index)\n          \/\/ 5ms basic delay + 10ms for each position in the list\n          uint64_t delayTicks = (5 + (myIndex * 10)) * TICKS_PER_MS;\n          \n          \/\/ t3 = t2 (Request Arrival) + Delay\n          uint64_t future_t3 = t2_Req + delayTicks;\n          \n          \/\/ IMPORTANT: Hardware requires masking lower 9 bits for delayed TX\n          \/\/ This ensures the timestamp is valid for the DW1000 chip\n          uint64_t aligned_t3 = decaduino.alignDelayedTransmissionTS(future_t3);\n          \n          \/\/ Schedule the response\n          sendResponseDelayed(aligned_t3);\n      }\n    }\n    \n    \/\/ Re-enable Receiver to listen for next messages\n    decaduino.plmeRxEnableRequest();\n  }\n}\n\n\/\/ Function to construct and schedule the packet transmission\nvoid sendResponseDelayed(uint64_t t3) {\n  ODS_Packet packet;\n  \n  \/\/ 1. Fill Header\n  packet.header.fc = 0x8841; \/\/ Standard Data Frame\n  packet.header.pan = PAN_ID;\n  packet.header.dest = ADDR_S1; \n  packet.header.src = MY_ADDR;\n  \n  \/\/ 2. Set Message Type\n  packet.messageType = ODS_MSG_RESP_TS;\n  \n  \/\/ 3. Fill Payload with captured timestamps\n  packet.payload.respInfo.t1_RX_CLAP = t1_Clap; \/\/ When CLAP arrived\n  packet.payload.respInfo.t2_RX_REQ  = t2_Req;  \/\/ When REQ arrived\n  packet.payload.respInfo.t3_TX_RESP = t3;      \/\/ When this packet LEAVES\n\n  \/\/ 4. Send Request to Radio\n  \/\/ Size = Header(9) + Type(1) + 3*8 bytes = 34 bytes\n  \/\/ true = Enable Delayed Transmission\n  \/\/ t3 = The exact time the radio must transmit\n  decaduino.pdDataRequest((uint8_t*)&amp;packet, 34, true, t3);\n  \n  \/\/ 5. Wait for Transmission to complete (Blocking for simplicity)\n  while (!decaduino.hasTxSucceeded()) {\n    decaduino.engine();\n  }\n  Serial.println(\"&#91;Slave] Response scheduled and sent.\");\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Explication<\/h2>\n\n\n\n<p class=\"has-medium-font-size\">Ce code impl\u00e9mente la <strong>machine d&rsquo;\u00e9tat<\/strong> d&rsquo;une ancre esclave. Son r\u00f4le est purement r\u00e9actif : capturer des temps pr\u00e9cis et les transmettre selon un ordonnancement strict.<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Voici les 3 \u00e9tapes fonctionnelles du code :<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Acquisition Temporelle (Timestamping Hardware)<\/h3>\n\n\n\n<p class=\"has-medium-font-size\">Le module est en r\u00e9ception continue (RX Enable).<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-medium-font-size\">\u00c0 la r\u00e9ception du message de r\u00e9f\u00e9rence (<strong>START<\/strong>), il capture le <strong>Timestamp t<sub>1<\/sub><\/strong> (temps d&rsquo;arriv\u00e9e physique du signal).<\/li>\n\n\n\n<li class=\"has-medium-font-size\">\u00c0 la r\u00e9ception de la requ\u00eate (<strong>REQ<\/strong>), il capture le <strong>Timestamp t<sub>2<\/sub><\/strong>.<\/li>\n\n\n\n<li class=\"has-medium-font-size\"><em>Note : Ces valeurs proviennent directement des registres du chip UWB pour une pr\u00e9cision nanoseconde.<\/em><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. Filtrage et Ordonnancement (TDMA)<\/h3>\n\n\n\n<p class=\"has-medium-font-size\">Le code analyse le payload de la requ\u00eate :<\/p>\n\n\n\n<ul class=\"wp-block-list has-medium-font-size\">\n<li><strong>Filtrage d&rsquo;adresse :<\/strong> Il v\u00e9rifie si son <code>MY_ADDR<\/code> est pr\u00e9sent dans la liste cible.<\/li>\n\n\n\n<li><strong>Calcul du Slot :<\/strong> S&rsquo;il est cibl\u00e9, il d\u00e9duit son index i. Il calcule alors l&rsquo;instant futur d&rsquo;\u00e9mission <strong>t<sub>3<\/sub><\/strong> selon une logique <strong>TDMA<\/strong> (Time Division Multiple Access) pour \u00e9viter toute collision RF avec les autres ancres.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. Transmission Diff\u00e9r\u00e9e (Delayed TX)<\/h3>\n\n\n\n<p class=\"has-medium-font-size\">C&rsquo;est l&rsquo;\u00e9tape critique pour le TDoA.<\/p>\n\n\n\n<ul class=\"wp-block-list has-medium-font-size\">\n<li>Le code construit un paquet contenant les donn\u00e9es brutes : {t<sub>1<\/sub>, t<sub>2<\/sub>, t<sub>3<\/sub>}.<\/li>\n\n\n\n<li>Il ne demande pas une \u00e9mission imm\u00e9diate, mais une <strong>Transmission Diff\u00e9r\u00e9e<\/strong> (\u00ab\u00a0Send at t3\u00a0\u00bb).<\/li>\n\n\n\n<li>Le contr\u00f4leur radio attendra mat\u00e9riellement que son horloge interne atteigne exactement t<sub>3<\/sub> pour envoyer le signal.<\/li>\n<\/ul>\n\n\n\n<p class=\"has-medium-font-size\"><strong>En r\u00e9sum\u00e9 :<\/strong> Ce code transforme le microcontr\u00f4leur en un capteur de temps passif, qui ne s&rsquo;active que pour renvoyer ses mesures (t<sub>1<\/sub>, t<sub>2<\/sub>) \u00e0 un instant (t<sub>3<\/sub>)  d\u00e9terministe.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Automate<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"686\" height=\"552\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_17h12_43.png\" alt=\"\" class=\"wp-image-164\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_17h12_43.png 686w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_17h12_43-300x241.png 300w\" sizes=\"auto, (max-width: 686px) 100vw, 686px\" \/><\/figure>\n\n\n\n<p class=\"has-medium-font-size\">Figure 4 : Automate ancre (serveur secondaire)<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">G\u00e9n\u00e9ralit\u00e9s : TDoA (Time Difference of Arrival) <\/h3>\n\n\n\n<p class=\"has-medium-font-size\">Le <strong>TDoA<\/strong> (Time Difference of Arrival) est une technique de localisation par multilat\u00e9ration hyperbolique. Contrairement aux m\u00e9thodes bas\u00e9es sur le temps de vol absolu (ToA) qui n\u00e9cessitent une synchronisation entre le mobile et les ancres, le TDoA exploite la <strong>diff\u00e9rence de temps d&rsquo;arriv\u00e9e<\/strong> d&rsquo;un signal \u00e9mis par le mobile et re\u00e7u par plusieurs ancres synchronis\u00e9es entre elles.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Principe G\u00e9om\u00e9trique : L&rsquo;Intersection d&rsquo;Hyperboles<\/h3>\n\n\n\n<p class=\"has-medium-font-size\">Le principe fondamental repose sur la propri\u00e9t\u00e9 g\u00e9om\u00e9trique suivante : l&rsquo;ensemble des points pour lesquels la diff\u00e9rence de distance par rapport \u00e0 deux points fixes (les foyers) est constante constitue une <strong>hyperbole<\/strong>.<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Dans notre configuration :<\/p>\n\n\n\n<ul class=\"wp-block-list has-medium-font-size\">\n<li><strong>Les Foyers (S) :<\/strong> Ce sont les ancres fixes dont les positions sont connues. (Ancre R = S1, Ancre N = S2, S3)<\/li>\n\n\n\n<li><strong>Le Mobile (M)<\/strong> : C&rsquo;est sa position que l&rsquo;on cherche \u00e0 d\u00e9terminer <\/li>\n\n\n\n<li><strong>La Constante :<\/strong> La diff\u00e9rence de distance mesur\u00e9e est proportionnelle \u00e0 la diff\u00e9rence de temps d&rsquo;arriv\u00e9e des signaux multiplied par la vitesse de la lumi\u00e8re c.<\/li>\n<\/ul>\n\n\n\n<p class=\"has-text-align-center has-medium-font-size\">Distance(M, S1) &#8211; Distance(M, S2) = Constante<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Une seule mesure entre une paire d&rsquo;ancres (ex: S1-S2) place le mobile sur une premi\u00e8re courbe hyperbolique. Pour d\u00e9terminer la position unique (x, y) du mobile M, il est n\u00e9cessaire de g\u00e9n\u00e9rer une seconde courbe via une autre paire (ex: S1-S3). L&rsquo;intersection de ces deux courbes donne la position du mobile.<\/p>\n\n\n\n<h5 class=\"wp-block-heading has-medium-font-size\">2. Formulation math\u00e9matique du probl\u00e8me<\/h5>\n\n\n\n<p class=\"has-medium-font-size\">La r\u00e9solution de la position repose sur l&rsquo;\u00e9galit\u00e9 entre la diff\u00e9rence de temps mesur\u00e9e et la diff\u00e9rence de distance g\u00e9om\u00e9trique. L&rsquo;\u00e9quation fondamentale pour une paire d&rsquo;ancres constitu\u00e9e d&rsquo;une ancre de r\u00e9f\u00e9rence R ( S1) et d&rsquo;une ancre secondaire j (ex: S2 ou S3) est la suivante :<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"323\" height=\"54\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_18h24_42.png\" alt=\"\" class=\"wp-image-170\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_18h24_42.png 323w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2025\/12\/2025-12-14_18h24_42-300x50.png 300w\" sizes=\"auto, (max-width: 323px) 100vw, 323px\" \/><\/figure>\n\n\n\n<p class=\"has-medium-font-size\">O\u00f9 :<\/p>\n\n\n\n<ul class=\"wp-block-list has-medium-font-size\">\n<li><strong>e<sub>R,j<\/sub><\/strong> : La diff\u00e9rence de temps de vol observ\u00e9e.<\/li>\n\n\n\n<li><strong>t<sub>R<\/sub><\/strong> : Le temps de r\u00e9ception du signal \u00ab\u00a0CLAP\u00a0\u00bb par l&rsquo;ancre de r\u00e9f\u00e9rence S1.<\/li>\n\n\n\n<li><strong>t<sub>j<\/sub><\/strong> : Le temps de r\u00e9ception du signal \u00ab\u00a0CLAP\u00a0\u00bb par l&rsquo;ancre secondaire Sj.<\/li>\n\n\n\n<li><strong>(x, y)<\/strong> : La position inconnue du mobile M.<\/li>\n\n\n\n<li><strong>(X<sub>R<\/sub>, Y<sub>R<\/sub><\/strong> <strong>)<\/strong> : La position connue de l&rsquo;ancre de r\u00e9f\u00e9rence S1.<\/li>\n\n\n\n<li><strong>(x<sub>j<\/sub>, y<sub>j<\/sub>)<\/strong> : La position connue de l&rsquo;ancre secondaire Sj.<\/li>\n\n\n\n<li><strong>c<\/strong> : La vitesse de la lumi\u00e8re ( 3 * 10<sup>8<\/sup> m\/s).<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. Le r\u00f4le du protocole ODS : conversion de Fuseau Horaire <\/h3>\n\n\n\n<p class=\"has-medium-font-size\">L&rsquo;\u00e9quation ci-dessus n&rsquo;est valide que si t<sub>R<\/sub> et t<sub>j<\/sub> sont exprim\u00e9s dans le <strong>m\u00eame r\u00e9f\u00e9rentiel temporel<\/strong>. Or, chaque module DWM1001 poss\u00e8de sa propre horloge locale qui d\u00e9rive ind\u00e9pendamment.<\/p>\n\n\n\n<p class=\"has-medium-font-size\">C&rsquo;est ici que le protocole <strong>ODS <\/strong>intervient<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Le timestamp brut t<sub>j<\/sub> mesur\u00e9 par l&rsquo;ancre secondaire est inutilisable tel quel. Gr\u00e2ce aux \u00e9changes de paquets de synchronisation, l&rsquo;algorithme ODS calcule les param\u00e8tres de correction lin\u00e9aire (offset et d\u00e9rive) pour convertir t<sub>j<\/sub> dans le \u00ab\u00a0fuseau horaire\u00a0\u00bb de l&rsquo;ancre de r\u00e9f\u00e9rence S1.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Code python<\/h3>\n\n\n\n<pre class=\"wp-block-code\" style=\"border-width:3px\"><code>import json\nimport re\nimport numpy as np\nfrom scipy.optimize import least_squares\n\n# =============================================================================\n# 1. CONFIGURATION (Mapping logical IDs to physical hardware)\n# =============================================================================\n\n# Physical labels on the DWM1001 boxes\nID_ANCRE_S1_REF = \"14\"   # Master (Reference) anchor\nID_ANCRE_S2_0x2 = \"45\"   # Slave 1\nID_ANCRE_S3_0x3 = \"43\"   # Slave 2\nID_CLIENT_MOB = \"9\"      # Mobile node to locate\n\n# Antenna delay calibration (in meters)\nANTENNA_BIAS_M = -185.0 \n\n# RAW UWB DATA (contains timestamps in hexadecimal and synchronization info)\nRAW_DATA_INPUT = \"\"\"\nSlave 0x3:\n  ToF (ticks): -960\n  T_Master_real (ticks): 1295611255\n  T_Slave (ticks): 1295615541\nSkew (Ratio S\/M) : 1.000003\n{\n  \"anchor_R\": {\n    \"tR1\": 000000615244238b,\n    \"tR2\": 000000619f81128e\n  },\n  \"slaves\": &#91;\n    {\n      \"id\": \"0x2\",\n      \"ti1\": 000000ca6e718cd9,\n      \"ti2\": 000000cabbae6f87,\n      \"ti3\": 000000caceb9a68e,\n      \"ti4\": 00000061b28c54ac\n    },\n    {\n      \"id\": \"0x3\",\n      \"ti1\": 00000053cc6e92a4,\n      \"ti2\": 0000005419ab90ce,\n      \"ti3\": 0000005452ccc88e,\n      \"ti4\": 00000061d8a2481c\n    }\n  ]\n}\n\"\"\"\n\n# =============================================================================\n# 2. COORDINATES DATABASE (X, Y, Z positions of all nodes in the room)\n# =============================================================================\n\nNODES_DB = {\n    \"dwm1001-14\" : np.array(&#91;-1.19, 4.578, 2.658]),\n    \"dwm1001-43\" : np.array(&#91;3.339, 7.565, 2.65]),\n    \"dwm1001-45\" : np.array(&#91;1.311, 8.989, 2.65]),\n    \"dwm1001-9\" : np.array(&#91;-4.005, -1.674, 2.65]),\n    # ... (other nodes omitted for brevity)\n}\n\n# Physical Constants: Speed of light and Decawave time unit (ticks)\nC = 299702547.235\nTIMEBASE = 15.65e-12\n\n# =============================================================================\n# 3. HELPER FUNCTIONS\n# =============================================================================\n\ndef clean_and_parse_json(raw_text):\n    \"\"\" Fixes malformed JSON by adding quotes to hex values and parsing it \"\"\"\n    start_index = raw_text.find('{')\n    if start_index == -1: return None\n    json_part = raw_text&#91;start_index:]\n    pattern = r':\\s*(0x&#91;0-9a-fA-F]+|&#91;0-9a-fA-F]{5,})'\n    fixed_json = re.sub(pattern, r': \"\\1\"', json_part)\n    try:\n        return json.loads(fixed_json)\n    except json.JSONDecodeError as e:\n        print(f\"JSON Error: {e}\")\n        return None\n\ndef get_val(val):\n    \"\"\" Converts a string (hex or dec) into an integer \"\"\"\n    if isinstance(val, str): return int(val, 16)\n    return int(val)\n\ndef tdoa_residuals(vars, anchors_positions, measured_diffs):\n    \"\"\" Cost function for the solver: minimizes the difference between \n        theoretical and measured distance differences (Hyperbolic positioning) \"\"\"\n    x, y = vars\n    est_pos = np.array(&#91;x, y, anchors_positions&#91;0]&#91;2]])\n    ref_pos = anchors_positions&#91;0]\n    \n    residuals = &#91;]\n    for i in range(1, len(anchors_positions)):\n        slave_pos = anchors_positions&#91;i]\n        measure = measured_diffs&#91;i-1] \n        dist_ref = np.linalg.norm(est_pos - ref_pos)\n        dist_slave = np.linalg.norm(est_pos - slave_pos)\n        # Residual = (Estimated distance difference) - (Measured distance difference)\n        residuals.append((dist_slave - dist_ref) - measure)\n        \n    return residuals\n\n# =============================================================================\n# 4. MAIN EXECUTION (Synchronization logic and Solver)\n# =============================================================================\n\nif __name__ == \"__main__\":\n    print(f\"=== TDOA CALCULATION WITH ODS CORRECTION ===\")\n    \n    try:\n        # Mapping JSON roles to their physical locations in the database\n        ROLE_TO_PHYSICAL = {\n            \"anchor_R\": ID_ANCRE_S1_REF,\n            \"0x2\": ID_ANCRE_S2_0x2,\n            \"0x3\": ID_ANCRE_S3_0x3\n        }\n\n        pos_S1 = NODES_DB&#91;f\"dwm1001-{ID_ANCRE_S1_REF}\"]\n        anchors_solver = &#91;pos_S1]\n        dist_diffs_solver = &#91;]\n\n    except KeyError as e:\n        print(f\"ERROR: Physical ID {e} not found in database.\")\n        exit()\n\n    data = clean_and_parse_json(RAW_DATA_INPUT)\n    \n    if data:\n        # Reference timestamps from the Master Anchor\n        tR1 = get_val(data&#91;\"anchor_R\"]&#91;\"tR1\"]) # Start message arrival\n        tR2 = get_val(data&#91;\"anchor_R\"]&#91;\"tR2\"]) # Request message departure\n        \n        for slave in data&#91;\"slaves\"]:\n            role_json = slave&#91;\"id\"]\n            \n            if role_json in ROLE_TO_PHYSICAL:\n                id_physique = ROLE_TO_PHYSICAL&#91;role_json]\n                db_key = f\"dwm1001-{id_physique}\"\n                \n                if db_key in NODES_DB:\n                    # Slave timestamps\n                    ti1 = get_val(slave&#91;\"ti1\"]) # Start message arrival (Slave clock)\n                    ti2 = get_val(slave&#91;\"ti2\"]) # Request message arrival (Slave clock)\n                    ti3 = get_val(slave&#91;\"ti3\"]) # Response message departure (Slave clock)\n                    ti4 = get_val(slave&#91;\"ti4\"]) # Response message arrival (Master clock)\n                    \n                    pos_Si = NODES_DB&#91;db_key]\n                    \n                    # 1. SYNCHRONIZATION: Calculate Time of Flight (ToF) between Master and Slave\n                    round_trip = ti4 - tR2\n                    processing = ti3 - ti2\n                    tof_ticks = (round_trip - processing) \/ 2.0\n                    tof_raw_m = (tof_ticks * TIMEBASE) * C\n                    # Apply antenna delay correction\n                    tof_corrected_m = tof_raw_m - ANTENNA_BIAS_M\n                    tof_corr_ticks = tof_corrected_m \/ C \/ TIMEBASE\n                    \n                    # 2. ODS LOGIC: Normalize Slave timestamp to the Master's time reference\n                    arrival_req = tR2 + tof_corr_ticks\n                    ti1_norm = arrival_req - (ti2 - ti1)\n                    \n                    # 3. TDOA: Calculate distance difference (Mobile to Slave vs Mobile to Master)\n                    tdoa_dist = (ti1_norm - tR1) * TIMEBASE * C\n                    \n                    anchors_solver.append(pos_Si)\n                    dist_diffs_solver.append(tdoa_dist)\n\n        # 4. SOLVER: find the best (X,Y) coordinates\n        if len(dist_diffs_solver) &gt;= 2:\n            # Initial guess: mean position of anchors\n            guess = np.mean(anchors_solver, axis=0)&#91;:2]\n            res = least_squares(tdoa_residuals, guess, args=(anchors_solver, dist_diffs_solver))\n            \n            print(f\"\\nRESULT: X={res.x&#91;0]:.3f}, Y={res.x&#91;1]:.3f}\")\n            \n            # Error calculation compared to real coordinates\n            client_key = f\"dwm1001-{ID_CLIENT_MOB}\"\n            if client_key in NODES_DB:\n                real = NODES_DB&#91;client_key]\n                err = np.linalg.norm(res.x - real&#91;:2])\n                print(f\"REAL POSITION ERROR: {err:.3f} m\")<\/code><\/pre>\n\n\n\n<div class=\"wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-6c531013 wp-block-group-is-layout-flex\">\n<div class=\"wp-block-uagb-advanced-heading uagb-block-4f71c635\"><h3 class=\"uagb-heading-text\">1. Pr\u00e9paration et nettoyage des donn\u00e9es<\/h3><\/div>\n<\/div>\n\n\n\n<p class=\"has-medium-font-size\">Le code commence par \u00ab\u00a0nettoyer\u00a0\u00bb les donn\u00e9es brutes qui sortent de la plateforme FitIotLab. Comme les cartes envoient des messages un peu bruts avec des grands nombres en hexad\u00e9cimal, on utilise des expressions r\u00e9guli\u00e8res (Regex) dans la fonction <code>clean_and_parse_json<\/code>. Le but est de transformer ce texte en un format JSON propre que Python peut manipuler facilement comme un dictionnaire.<\/p>\n\n\n\n<div class=\"wp-block-uagb-advanced-heading uagb-block-19dc2817\"><h3 class=\"uagb-heading-text\">2. Le c\u0153ur du protocole : La synchronisation ODS<\/h3><\/div>\n\n\n\n<p class=\"has-medium-font-size\">C&rsquo;est la partie la plus critique. Comme les horloges de nos ancres (S1, S2, S3) ne sont pas synchronis\u00e9es, on ne peut pas comparer leurs timestamps directement.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-medium-font-size\">Calcul du ToF (Time of Flight) : Le code calcule le temps que met un message pour faire l&rsquo;aller-retour entre l&rsquo;ancre Ma\u00eetre (S1) et une ancre Esclave (Si).<\/li>\n\n\n\n<li class=\"has-medium-font-size\">Normalisation : En connaissant ce temps de vol, on peut recalculer \u00e0 quel moment pr\u00e9cis l&rsquo;esclave a re\u00e7u le signal du mobile (ti1), mais en l&rsquo;exprimant dans le r\u00e9f\u00e9rentiel de temps du Ma\u00eetre. C&rsquo;est cette \u00e9tape qui permet de \u00ab\u00a0gommer\u00a0\u00bb le d\u00e9calage entre les horloges.<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-uagb-advanced-heading uagb-block-820898b8\"><h3 class=\"uagb-heading-text\">3. Calcul du TDoA (Time Difference of Arrival)<\/h3><\/div>\n\n\n\n<p class=\"has-medium-font-size\">Une fois que tout le monde parle la \u00ab\u00a0m\u00eame langue temporelle\u00a0\u00bb, on calcule la diff\u00e9rence de temps d&rsquo;arriv\u00e9e du signal du mobile entre le Ma\u00eetre et chaque Esclave. On multiplie ce temps par la vitesse de la lumi\u00e8re c pour obtenir une diff\u00e9rence de distance. C&rsquo;est ce qu&rsquo;on appelle la mesure TDoA<\/p>\n\n\n\n<div class=\"wp-block-uagb-advanced-heading uagb-block-8f42c6e5\"><h3 class=\"uagb-heading-text\">4. R\u00e9solution<\/h3><\/div>\n\n\n\n<p class=\"has-medium-font-size\">Pour trouver la position (x, y) finale, on utilise la fonction <code>least_squares<\/code>.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-medium-font-size\"><strong>Pourquoi ?<\/strong> Parce qu&rsquo;avec les obstacles et les impr\u00e9cisions radio, nos hyperboles de localisation ne se croisent jamais parfaitement en un seul point.<\/li>\n\n\n\n<li class=\"has-medium-font-size\"><strong>Comment ?<\/strong> L&rsquo;algorithme part d&rsquo;une estimation (le \u00ab\u00a0guess\u00a0\u00bb) et ajuste les coordonn\u00e9es (x, y) petit \u00e0 petit. Il cherche le point qui minimise la somme des carr\u00e9s des erreurs de distance. C&rsquo;est ce qui nous donne la position la plus probable statistiquement.<\/li>\n<\/ul>\n<\/div>\n\n\n\n<p><\/p>\n\n\n\n<div class=\"wp-block-group is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-uagb-advanced-heading uagb-block-a8cdb34b\"><h2 class=\"uagb-heading-text\">Tests r\u00e9alis\u00e9s<\/h2><\/div>\n\n\n\n<p class=\"has-medium-font-size\">Pour \u00e9valuer la fiabilit\u00e9 de notre syst\u00e8me, on a r\u00e9alis\u00e9 une s\u00e9rie de tests en faisant varier la topologie des n\u0153uds, c\u2019est-\u00e0-dire leur disposition et leur distance par rapport au mobile.<\/p>\n\n\n\n<p class=\"has-medium-font-size\">Voici ce qu\u2019on a observ\u00e9 sur le terrain :<\/p>\n\n\n\n<div class=\"wp-block-uagb-advanced-heading uagb-block-cc2ea123\"><h3 class=\"uagb-heading-text\">L&rsquo;influence de la distance (Test 0)<\/h3><\/div>\n\n\n\n<p class=\"has-medium-font-size\">Au d\u00e9but, on a test\u00e9 une configuration o\u00f9 le mobile \u00e9tait assez \u00e9loign\u00e9 des ancres (n\u0153ud 9 versus 14, 45, 43). Le r\u00e9sultat a \u00e9t\u00e9 assez brutal avec une erreur de plus de 10 m\u00e8tres, ce qui nous a montr\u00e9 que la g\u00e9om\u00e9trie du r\u00e9seau est super importante pour la pr\u00e9cision du TDoA.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"518\" height=\"624\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image.png\" alt=\"\" class=\"wp-image-218\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image.png 518w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-249x300.png 249w\" sizes=\"auto, (max-width: 518px) 100vw, 518px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"320\" height=\"89\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-1.png\" alt=\"\" class=\"wp-image-219\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-1.png 320w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-1-300x83.png 300w\" sizes=\"auto, (max-width: 320px) 100vw, 320px\" \/><\/figure>\n\n\n\n<div class=\"wp-block-uagb-advanced-heading uagb-block-74b764bd\"><h3 class=\"uagb-heading-text\">Optimisation par axes (Tests 1 et 2)<\/h3><\/div>\n\n\n\n<p class=\"has-medium-font-size\">On a ensuite essay\u00e9 de regrouper les ancres plus pr\u00e8s du mobile (n\u0153ud 43) en les alignant sur des axes sp\u00e9cifiques. En se focalisant sur l\u2019axe X, on est descendu \u00e0 <strong>1,315 m d&rsquo;erreur<\/strong> , et sur l\u2019axe Y, on a obtenu notre meilleur score avec seulement <strong>1 m\u00e8tre d&rsquo;\u00e9cart<\/strong>.<\/p>\n\n\n\n<div class=\"wp-block-uagb-container uagb-block-0ef4bd08 alignfull uagb-is-root-container\"><div class=\"uagb-container-inner-blocks-wrap\">\n<div class=\"wp-block-uagb-container uagb-block-271dbef9\">\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<div class=\"wp-block-uagb-advanced-heading uagb-block-ca7c0eb5\"><h4 class=\"uagb-heading-text\">1<\/h4><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"503\" height=\"485\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-3.png\" alt=\"\" class=\"wp-image-221\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-3.png 503w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-3-300x289.png 300w\" sizes=\"auto, (max-width: 503px) 100vw, 503px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"278\" height=\"100\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-7.png\" alt=\"\" class=\"wp-image-226\"\/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<div class=\"wp-block-uagb-advanced-heading uagb-block-3b2cc982\"><h4 class=\"uagb-heading-text\">2<\/h4><\/div>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"576\" height=\"489\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-4.png\" alt=\"\" class=\"wp-image-222\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-4.png 576w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-4-300x255.png 300w\" sizes=\"auto, (max-width: 576px) 100vw, 576px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"286\" height=\"82\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-5.png\" alt=\"\" class=\"wp-image-224\"\/><\/figure>\n<\/div>\n<\/div>\n<\/div>\n<\/div><\/div>\n\n\n\n<div class=\"wp-block-uagb-advanced-heading uagb-block-fafc2ed5\"><h3 class=\"uagb-heading-text\">L&rsquo;impact des obstacles (Test 3)<\/h3><\/div>\n\n\n\n<p class=\"has-medium-font-size\">On a voulu voir comment le syst\u00e8me r\u00e9agissait face \u00e0 une cloison. En pla\u00e7ant les ancres derri\u00e8re des murs, l&rsquo;erreur est remont\u00e9e \u00e0 5,809 m. \u00c7a confirme que les obstacles physiques perturbent pas mal le signal UWB et les calculs de temps de vol.<\/p>\n\n\n\n<div class=\"wp-block-uagb-container uagb-block-df5d3bb8 alignfull uagb-is-root-container\"><div class=\"uagb-container-inner-blocks-wrap\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"629\" height=\"699\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-8.png\" alt=\"\" class=\"wp-image-227\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-8.png 629w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-8-270x300.png 270w\" sizes=\"auto, (max-width: 629px) 100vw, 629px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"337\" height=\"97\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-9.png\" alt=\"\" class=\"wp-image-228\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-9.png 337w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-9-300x86.png 300w\" sizes=\"auto, (max-width: 337px) 100vw, 337px\" \/><\/figure>\n<\/div><\/div>\n\n\n\n<div class=\"wp-block-uagb-advanced-heading uagb-block-f9353d21\"><h3 class=\"uagb-heading-text\">Les limites du syst\u00e8me (Test 4)<\/h3><\/div>\n\n\n\n<p class=\"has-medium-font-size\">Enfin, on a tent\u00e9 une topologie \u00ab\u00a0extr\u00eame\u00a0\u00bb avec des ancres tr\u00e8s \u00e9loign\u00e9es les unes des autres \u00e0 travers tout le b\u00e2timent. L\u00e0, on a atteint les limites de notre protocole : le script a fini en \u00ab\u00a0Timeout\u00a0\u00bb, car les messages RESPONSE n&rsquo;arrivaient plus \u00e0 temps ou \u00e9taient perdus \u00e0 cause de la distance trop grande.<\/p>\n\n\n\n<div class=\"wp-block-uagb-container uagb-block-ab22bfbb alignfull uagb-is-root-container\"><div class=\"uagb-container-inner-blocks-wrap\">\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"561\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-10-1024x561.png\" alt=\"\" class=\"wp-image-229\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-10-1024x561.png 1024w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-10-300x164.png 300w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-10-768x421.png 768w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-10.png 1149w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"359\" src=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-11-1024x359.png\" alt=\"\" class=\"wp-image-230\" srcset=\"https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-11-1024x359.png 1024w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-11-300x105.png 300w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-11-768x269.png 768w, https:\/\/leblogtech.romain-s.fr\/wp-content\/uploads\/2026\/01\/image-11.png 1496w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/div><\/div>\n\n\n\n<div class=\"wp-block-uagb-advanced-heading uagb-block-5cbc547c\"><h3 class=\"uagb-heading-text\">Synth\u00e8se<\/h3><\/div>\n\n\n\n<p>Pour r\u00e9sumer, ces tests nous ont permis de comprendre que pour avoir une localisation pr\u00e9cise, il ne suffit pas que le code soit bon, il faut aussi que le placement des n\u0153uds soit coh\u00e9rent par rapport \u00e0 la zone qu&rsquo;on veut couvrir.<\/p>\n<\/div>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>ODS &#8211; g\u00e9n\u00e9ralit\u00e9s 1. Le probl\u00e8me : Le Temps relatif Dans ce r\u00e9seau UWB (Ultra-Wideband), chaque ancre (Ai) poss\u00e8de une [&hellip;]<\/p>\n","protected":false},"author":5,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_uag_custom_page_level_css":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"class_list":["post-162","page","type-page","status-publish","hentry"],"uagb_featured_image_src":{"full":false,"thumbnail":false,"medium":false,"medium_large":false,"large":false,"1536x1536":false,"2048x2048":false},"uagb_author_info":{"display_name":"romain-s","author_link":"https:\/\/leblogtech.romain-s.fr\/index.php\/author\/romain-s\/"},"uagb_comment_info":0,"uagb_excerpt":"ODS &#8211; g\u00e9n\u00e9ralit\u00e9s 1. Le probl\u00e8me : Le Temps relatif Dans ce r\u00e9seau UWB (Ultra-Wideband), chaque ancre (Ai) poss\u00e8de une [&hellip;]","_links":{"self":[{"href":"https:\/\/leblogtech.romain-s.fr\/index.php\/wp-json\/wp\/v2\/pages\/162","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/leblogtech.romain-s.fr\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/leblogtech.romain-s.fr\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/leblogtech.romain-s.fr\/index.php\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/leblogtech.romain-s.fr\/index.php\/wp-json\/wp\/v2\/comments?post=162"}],"version-history":[{"count":14,"href":"https:\/\/leblogtech.romain-s.fr\/index.php\/wp-json\/wp\/v2\/pages\/162\/revisions"}],"predecessor-version":[{"id":245,"href":"https:\/\/leblogtech.romain-s.fr\/index.php\/wp-json\/wp\/v2\/pages\/162\/revisions\/245"}],"wp:attachment":[{"href":"https:\/\/leblogtech.romain-s.fr\/index.php\/wp-json\/wp\/v2\/media?parent=162"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}