Indoor Positioning System with MaUWB ESP32S3 DW3000
Table of Contents
Indoor Positioning System:
Indoor Positioning System with MaUWB ESP32S3 DW3000- In my last article, I showed how two MaUWB_ESP32S3 UWB modules can communicate with each other. I used one module as an Anchor and the other as a Tag. On the Anchor’s OLED display, I was able to successfully monitor the Tag’s distance. Although this worked really well, but still there was something missing; we could only see how far the Tag was, but we didn’t know its exact position; like which side of the Anchor it was on. To track the Tag’s exact position and to build a complete Indoor Positioning system; we need at least 3 or more Anchors.
After reading this article, you will not only be able to track the position of a tag on a computer application designed in Python; but you will also be able to wirelessly monitor the Tag’s position on your system and smart phone.
So, without any further delay, let’s get started!!!
Amazon Links:
MaUWB ESP32S3 UWB Modules “Product Official page”
ESP32 WiFi + Bluetooth Module (Recommended)
*Please Note: These are affiliate links. I may make a commission if you buy the components through these links. I would appreciate your support in this way!
Initial Setup:
Right now, I only have these two modules. For my initial experiments, I am going to use these two modules as Anchors A0 and A1. And I am going to create a tag myself using the MakerFabs MaUWB DW3000 chipset and my newly created ESP32S3 based development board.
To interface the MaUWB DW3000 chipset with the ESP32S3, first; we need to solder the wires.
You can follow these connections.
Tracking the Position on Straight Line:
The MaUWB DW3000 chipset has been connected to the ESP32S3, and now I can use it as either a Tag or an Anchor. This is how you can create multiple Tags and Anchors.
Before building the complete Indoor Positioning System, first; you need to understand how it actually works. For this, first I am going to explain, how to track the position of a Tag on a straight line “on X Co-ordinate”.
Since I already have two MaUWB ESP32S3 modules, which I am going to use as Anchors, I will use the one I created as the Tag.
Tip: order yourself at least 5 of these MaUWB ESP32S3 modules, this way you won’t need to make your own tags and anchors.
Connect the Tag-side ESP32S3 controller board to the laptop.
Open the following program in the Arduino IDE. For Tag 0 (“T0”), make sure the UWB_INDEX is set to 0; for Tag T1, set it to 1, and so on.
I have already explained how to install these libraries In my previous tutorial.
Adafruit_GFX
Adafruit_SSD1306
I also changed the SDA and SCL pins to GPIOs 01 and 02 because, on my ESP32S3 development board, the SSD1306 OLED display module is connected to GPIOs 01 and 02. You don’t need to change anything else.
Tag0 Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
/* Data transmit Demo UWB Module Firmware later than 1.1.0 is required Use 2.0.0 Wire Use 1.11.7 Adafruit_GFX_Library Use 1.14.4 Adafruit_BusIO Use 2.0.0 SPI Use 2.5.7 Adafruit_SSD1306 */ // User config ------------------------------------------ #define UWB_INDEX 0 #define TAG // #define ANCHOR #define FREQ_850K // #define FREQ_6800K #define UWB_TAG_COUNT 10 // User config end ------------------------------------------ #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Arduino.h> #define SERIAL_LOG Serial #define SERIAL_AT mySerial2 HardwareSerial SERIAL_AT(2); #define RESET 16 #define IO_RXD2 18 #define IO_TXD2 17 #define I2C_SDA 01 #define I2C_SCL 02 Adafruit_SSD1306 display(128, 64, &Wire, -1); void setup() { pinMode(RESET, OUTPUT); digitalWrite(RESET, HIGH); SERIAL_LOG.begin(115200); SERIAL_LOG.print(F("Hello! ESP32-S3 AT command V1.0 Test")); SERIAL_AT.begin(115200, SERIAL_8N1, IO_RXD2, IO_TXD2); SERIAL_AT.println("AT"); Wire.begin(I2C_SDA, I2C_SCL); delay(1000); // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 SERIAL_LOG.println(F("SSD1306 allocation failed")); for (;;) ; // Don't proceed, loop forever } display.clearDisplay(); logoshow(); sendData("AT?", 2000, 1); sendData("AT+RESTORE", 5000, 1); sendData(config_cmd(), 2000, 1); sendData(cap_cmd(), 2000, 1); sendData("AT+SETRPT=0", 2000, 1); sendData("AT+SAVE", 2000, 1); sendData("AT+RESTART", 2000, 1); } long int runtime = 0; int data_count = 0; String response = ""; void loop() { if ((millis() - runtime) > 500) { char data_str[80]; sprintf(data_str, "AT+DATA=32,UWB_T0_Data:%d", data_count * 2); data_count++; sendData(data_str, 2000, 1); runtime = millis(); } // put your main code here, to run repeatedly: while (SERIAL_LOG.available() > 0) { SERIAL_AT.write(SERIAL_LOG.read()); yield(); } while (SERIAL_AT.available() > 0) { char c = SERIAL_AT.read(); if (c == '\r') continue; else if (c == '\n' || c == '\r') { SERIAL_LOG.println(response); response = ""; } else response += c; } } // SSD1306 void logoshow(void) { display.clearDisplay(); display.setTextSize(1); // Normal 1:1 pixel scale display.setTextColor(SSD1306_WHITE); // Draw white text display.setCursor(0, 0); // Start at top-left corner display.println(F("MaUWB DW3000")); display.setCursor(0, 20); // Start at top-left corner // display.println(F("with STM32 AT Command")); display.setTextSize(2); String temp = ""; #ifdef TAG temp = temp + "T" + UWB_INDEX; #endif #ifdef ANCHOR temp = temp + "A" + UWB_INDEX; #endif #ifdef FREQ_850K temp = temp + " 850k"; #endif #ifdef FREQ_6800K temp = temp + " 6.8M"; #endif display.println(temp); display.setCursor(0, 40); temp = "Total: "; temp = temp + UWB_TAG_COUNT; display.println(temp); display.display(); delay(2000); } String sendData(String command, const int timeout, boolean debug) { String response = ""; // command = command + "\r\n"; SERIAL_LOG.println(command); SERIAL_AT.println(command); // send the read character to the SERIAL_LOG long int time = millis(); while ((time + timeout) > millis()) { while (SERIAL_AT.available()) { // The esp has data so display its output to the serial window char c = SERIAL_AT.read(); // read the next character. response += c; } } if (debug) { SERIAL_LOG.println(response); } return response; } String config_cmd() { String temp = "AT+SETCFG="; // Set device id temp = temp + UWB_INDEX; // Set device role #ifdef TAG temp = temp + ",0"; #endif #ifdef ANCHOR temp = temp + ",1"; #endif // Set frequence 850k or 6.8M #ifdef FREQ_850K temp = temp + ",0"; #endif #ifdef FREQ_6800K temp = temp + ",1"; #endif // Set range filter temp = temp + ",1"; return temp; } String cap_cmd() { String temp = "AT+SETCAP="; // Set Tag capacity temp = temp + UWB_TAG_COUNT; // Time of a single time slot #ifdef FREQ_850K temp = temp + ",25,1"; #endif #ifdef FREQ_6800K temp = temp + ",10"; #endif return temp; } |
Next, connect one of the MaUWB ESP32S3 UWB boards to the laptop that you want to use as Anchor A0, and then open the following program. On the MakerFabs MaUWB_ESP32S3 UWB module, the SSD1306 OLED display is connected to GPIOs 39 and 38.
If I were using my ESP32S3 development board, I would change it to GPIOs 01 and 02. If you are using different I2C pins, just go ahead and change these pins. If you scroll down, you will see that it’s already defined as A0, so no further changes are needed in the code. Let me also mention that this code is specifically for Anchor A0. For all the other anchors, we will use a different code, which I will show you in a few seconds.
Additionally, Anchor A0 will remain connected to the laptop because it directly communicates with the computer application designed for real-time Tag tracking. I know it sounds unprofessional but this is how it’s done by the company. But, don’t worry; later I will make it completely wireless. Then you won’t need to physically connect any board to the Laptop.
Anyway, just go ahead and upload this program to the Anchor A0.
Get Range, Anchor “A0” Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
/* Anchor A0, get range */ #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Arduino.h> HardwareSerial mySerial2(2); #define RESET 16 #define IO_RXD2 18 #define IO_TXD2 17 #define I2C_SDA 39 #define I2C_SCL 38 Adafruit_SSD1306 display(128, 64, &Wire, -1); void setup() { pinMode(RESET, OUTPUT); digitalWrite(RESET, HIGH); Serial.begin(115200); Serial.print(F("Hello! ESP32-S3 AT command V1.0 Test")); mySerial2.begin(115200, SERIAL_8N1, IO_RXD2, IO_TXD2); mySerial2.println("AT"); Wire.begin(I2C_SDA, I2C_SCL); delay(1000); // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 Serial.println(F("SSD1306 allocation failed")); for (;;) ; // Don't proceed, loop forever } display.clearDisplay(); logoshow(); } long int runtime = 0; String response = ""; String rec_head = "AT+RANGE"; void loop() { // put your main code here, to run repeatedly: while (Serial.available() > 0) { mySerial2.write(Serial.read()); yield(); } while (mySerial2.available() > 0) { char c = mySerial2.read(); if (c == '\r') continue; else if (c == '\n' || c == '\r') { // if(0) if (response.indexOf(rec_head) != -1) { range_analy(response); // Serial.println("-----------Get range msg-----------"); // String result = response.substring(response.indexOf(rec_head) + rec_head.length()); // Serial.println(result); // Serial.println("-----------Over-----------"); } else { Serial.println(response); } response = ""; } else response += c; } } // SSD1306 void logoshow(void) { display.clearDisplay(); display.setTextSize(2); // Normal 1:1 pixel scale display.setTextColor(SSD1306_WHITE); // Draw white text display.setCursor(0, 0); // Start at top-left corner display.println(F("Get Range")); // display.setTextSize(1); display.setCursor(0, 20); display.println(F("JSON")); display.setCursor(0, 40); display.println(F("A0")); display.display(); delay(2000); } // AT+RANGE=tid:1,mask:04,seq:63,range:(0,0,30,0,0,0,0,0),rssi:(0.00,0.00,-77.93,0.00,0.00,0.00,0.00,0.00) void range_analy(String data) { String id_str = data.substring(data.indexOf("tid:") + 4, data.indexOf(",mask:")); String range_str = data.substring(data.indexOf("range:"), data.indexOf(",rssi:")); String rssi_str = data.substring(data.indexOf("rssi:")); // Serial.println(id_str); // Serial.println(range_str); // Serial.println(rssi_str); // range:(0,0,51,0,0,0,0,0) // rssi:(0.00,0.00,-78.54,0.00,0.00,0.00,0.00,0.00) int range_list[8]; double rssi_list[8]; int count = 0; count = sscanf(range_str.c_str(), "range:(%d,%d,%d,%d,%d,%d,%d,%d)", &range_list[0], &range_list[1], &range_list[2], &range_list[3], &range_list[4], &range_list[5], &range_list[6], &range_list[7]); if (count != 8) { Serial.println("RANGE ANALY ERROR"); Serial.println(count); return; } count = sscanf(rssi_str.c_str(), "rssi:(%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf)", &rssi_list[0], &rssi_list[1], &rssi_list[2], &rssi_list[3], &rssi_list[4], &rssi_list[5], &rssi_list[6], &rssi_list[7]); if (count != 8) { Serial.println("RSSI ANALY ERROR"); Serial.println(count); return; } // Serial Port // Serial.print("TAG "); // Serial.println(id_str); // for (int i = 0; i < 8; i++) // { // Serial.print("A"); // Serial.print(i); // Serial.print(" RANGE "); // Serial.print(range_list[i]); // Serial.print(" RSSI "); // Serial.println(rssi_list[i]); // } // Serial Port Json String json_str = ""; json_str = json_str + "{\"id\":" + id_str + ","; json_str = json_str + "\"range\":["; for (int i = 0; i < 8; i++) { if (i != 7) json_str = json_str + range_list[i] + ","; else json_str = json_str + range_list[i] + "]}"; } Serial.println(json_str); } |
Now, let’s connect another MaUWB_ESP32S3 UWB Module to the laptop. Open this program.
Anchor A1 Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
/* Data transmit Demo UWB Module Firmware later than 1.1.0 is required Use 2.0.0 Wire Use 1.11.7 Adafruit_GFX_Library Use 1.14.4 Adafruit_BusIO Use 2.0.0 SPI Use 2.5.7 Adafruit_SSD1306 */ // User config ------------------------------------------ #define UWB_INDEX 1 // #define TAG #define ANCHOR #define FREQ_850K // #define FREQ_6800K #define UWB_TAG_COUNT 10 // User config end ------------------------------------------ #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Arduino.h> #define SERIAL_LOG Serial #define SERIAL_AT mySerial2 HardwareSerial SERIAL_AT(2); #define RESET 16 #define IO_RXD2 18 #define IO_TXD2 17 #define I2C_SDA 39 #define I2C_SCL 38 Adafruit_SSD1306 display(128, 64, &Wire, -1); void setup() { pinMode(RESET, OUTPUT); digitalWrite(RESET, HIGH); SERIAL_LOG.begin(115200); SERIAL_LOG.print(F("Hello! ESP32-S3 AT command V1.0 Test")); SERIAL_AT.begin(115200, SERIAL_8N1, IO_RXD2, IO_TXD2); SERIAL_AT.println("AT"); Wire.begin(I2C_SDA, I2C_SCL); delay(1000); // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 SERIAL_LOG.println(F("SSD1306 allocation failed")); for (;;) ; // Don't proceed, loop forever } display.clearDisplay(); logoshow(); sendData("AT?", 2000, 1); sendData("AT+RESTORE", 5000, 1); sendData(config_cmd(), 2000, 1); sendData(cap_cmd(), 2000, 1); sendData("AT+SETRPT=0", 2000, 1); sendData("AT+SAVE", 2000, 1); sendData("AT+RESTART", 2000, 1); } long int runtime = 0; int data_count = 0; String response = ""; void loop() { if ((millis() - runtime) > 1000) { char data_str[80]; sprintf(data_str, "AT+DATA=32,UWB_A0_Data:%d", data_count * 2 + 1); data_count++; sendData(data_str, 2000, 1); runtime = millis(); } // put your main code here, to run repeatedly: while (SERIAL_LOG.available() > 0) { SERIAL_AT.write(SERIAL_LOG.read()); yield(); } while (SERIAL_AT.available() > 0) { char c = SERIAL_AT.read(); if (c == '\r') continue; else if (c == '\n' || c == '\r') { SERIAL_LOG.println(response); response = ""; } else response += c; } } // SSD1306 void logoshow(void) { display.clearDisplay(); display.setTextSize(1); // Normal 1:1 pixel scale display.setTextColor(SSD1306_WHITE); // Draw white text display.setCursor(0, 0); // Start at top-left corner display.println(F("MaUWB DW3000")); display.setCursor(0, 20); // Start at top-left corner // display.println(F("with STM32 AT Command")); display.setTextSize(2); String temp = ""; #ifdef TAG temp = temp + "T" + UWB_INDEX; #endif #ifdef ANCHOR temp = temp + "A" + UWB_INDEX; #endif #ifdef FREQ_850K temp = temp + " 850k"; #endif #ifdef FREQ_6800K temp = temp + " 6.8M"; #endif display.println(temp); display.setCursor(0, 40); temp = "Total: "; temp = temp + UWB_TAG_COUNT; display.println(temp); display.display(); delay(2000); } String sendData(String command, const int timeout, boolean debug) { String response = ""; // command = command + "\r\n"; SERIAL_LOG.println(command); SERIAL_AT.println(command); // send the read character to the SERIAL_LOG long int time = millis(); while ((time + timeout) > millis()) { while (SERIAL_AT.available()) { // The esp has data so display its output to the serial window char c = SERIAL_AT.read(); // read the next character. response += c; } } if (debug) { SERIAL_LOG.println(response); } return response; } String config_cmd() { String temp = "AT+SETCFG="; // Set device id temp = temp + UWB_INDEX; // Set device role #ifdef TAG temp = temp + ",0"; #endif #ifdef ANCHOR temp = temp + ",1"; #endif // Set frequence 850k or 6.8M #ifdef FREQ_850K temp = temp + ",0"; #endif #ifdef FREQ_6800K temp = temp + ",1"; #endif // Set range filter temp = temp + ",1"; return temp; } String cap_cmd() { String temp = "AT+SETCAP="; // Set Tag capacity temp = temp + UWB_TAG_COUNT; // Time of a single time slot #ifdef FREQ_850K temp = temp + ",25,1"; #endif #ifdef FREQ_6800K temp = temp + ",15,1"; #endif return temp; } |
Since we are using this as Anchor A1, the UWB_INDEX is set to 1. For Anchor A2, you would change it to 2, and so on. This way, you can create multiple Anchors. There is no need to change anything else except the I2C pins if you are using your own controller board. Anyway, you can ahead and upload the program.
Now, the Tag and two Anchors are ready.
Next, for the real-time Tag monitoring on the computer application, you will need to install the latest versions of
- PyCharm and
- Python
Let’s start with Python; for this, open the official python website, scroll down, and download the latest version.
I have already downloaded Python 3.13.0, so let’s proceed with the installation.
Python Installation:
- Right click on the file and select “Run as Administrator”.
- Make sure you check these Boxes, Add, python.exe to PATH is must be checked. Finally, click on the Install Now.
Once the Python is successfully installed! To double-check, open the command prompt and type Python.
If you see Python and its version displayed, congratulations; you have installed it correctly.
Next, we will install PyCharm.
Go to the JetBrains website and download the latest version of PyCharm. I have already downloaded it, so let’s proceed with the installation.
The installation process is straightforward:
Make sure to check all the necessary boxes during setup.
Click the Next button and wait for the installation to complete.
Once installed, you will see a prompt indicating that your computer needs to restart to complete the PyCharm installation. Select Reboot now and click Finish.
After rebooting your system, you will see the Pycharm Icon on the Desktop.
This is my PyCharm project file, but before opening this file, first; let’s define the positions of the Anchors.
Anchor A0, as you can see, is connected to the laptop, and its position is (0,0), meaning X = 0 cm and Y = 0 cm.
Similarly, the position of Anchor A1 is (114,0), meaning X = 114 cm and Y = 0 cm. The two Anchors should be horizontally aligned.
I have powered the Anchor A1 using my 5V 3A power supply and 4S lithium-Ion Battery pack. I could connect a small lipo battery to that while port on the UWB module, but unfortunately I don’t have one at the moment. Anyway, you can see it has no physical connection to the laptop.
Once the Anchor positions are set, then we can go ahead and open the PyCharm project file. copy and paste the following code.
Python application Code for two Anchors:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
import pygame import serial import serial.tools.list_ports import json import time import math RED = [255, 0, 0] BLACK = [0, 0, 0] WHITE = [255, 255, 255] class UWB: def __init__(self, name, type): self.name = name self.type = type self.x = 0 self.y = 0 self.status = False self.list = [] if self.type == 1: self.color = RED else: self.color = BLACK def set_location(self, x, y): self.x = x self.y = y self.status = True def cal(self): if len(self.list) >= 2 and self.list[0] != 0 and self.list[1] != 0: x, y = self.three_point_uwb(0, 1) self.set_location(int(x), int(y)) self.status = True def three_point_uwb(self, a_id, b_id): x, y = self.three_point(anc[a_id].x, anc[a_id].y, anc[b_id].x, anc[b_id].y, self.list[a_id], self.list[b_id]) return x, y def three_point(self, x1, y1, x2, y2, r1, r2): p2p = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) if r1 + r2 <= p2p: temp_x = x1 + (x2 - x1) * r1 / (r1 + r2) temp_y = y1 + (y2 - y1) * r1 / (r1 + r2) else: dr = p2p / 2 + (r1**2 - r2**2) / (2 * p2p) temp_x = x1 + (x2 - x1) * dr / p2p temp_y = y1 + (y2 - y1) * dr / p2p return temp_x, temp_y def get_frist_com(): port_list = serial.tools.list_ports.comports() if len(port_list) <= 0: print("No COM") return "" else: print("First COM") for com in port_list: print(com) return list(com)[0] def draw_uwb(uwb): pixel_x = int(uwb.x * cm2p + x_offset) pixel_y = SCREEN_Y - int(uwb.y * cm2p + y_offset) if uwb.status: r = 10 temp_str = uwb.name + " (" + str(uwb.x) + "," + str(uwb.y) + ")" font = pygame.font.SysFont("Consola", 24) surf = font.render(temp_str, True, uwb.color) screen.blit(surf, [pixel_x, pixel_y]) pygame.draw.circle(screen, uwb.color, [pixel_x + 20, pixel_y + 50], r, 0) def read_data(): line = ser.readline().decode('UTF-8').replace('\n', '') try: data = json.loads(line) print(data) tag[data['id']].list = data['range'] tag[data['id']].cal() except ValueError: print("[LOG]" + line) def fresh_page(): runtime = time.time() screen.fill(WHITE) for uwb in anc: draw_uwb(uwb) for uwb in tag: draw_uwb(uwb) pygame.draw.line(screen, BLACK, (CENTER_X_PIEXL, 0), (CENTER_X_PIEXL, SCREEN_Y), 1) pygame.draw.line(screen, BLACK, (0, CENTER_Y_PIEXL), (SCREEN_X, CENTER_Y_PIEXL), 1) pygame.display.flip() print("Fresh Over, Use Time:") print(time.time() - runtime) def distance(x1, y1, x2, y2): return math.sqrt((x2 - x1)**2 + (y2 - y1)**2) # Main Function ............................................................. SCREEN_X = 800 SCREEN_Y = 800 pygame.init() screen = pygame.display.set_mode([SCREEN_X, SCREEN_Y]) ser = serial.Serial(get_frist_com(), 115200) anc = [] tag = [] anc_count = 2 tag_count = 8 A0X, A0Y = 0, 0 A1X, A1Y = 146, 0 # 114cm + 32 centimeters CENTER_X = (A0X + A1X) / 2 CENTER_Y = (A0Y + A1Y) / 2 r0 = distance(A0X, A0Y, CENTER_X, CENTER_Y) r1 = distance(A1X, A1Y, CENTER_X, CENTER_Y) r = max(r0, r1) cm2p = SCREEN_X / 2 * 0.9 / r x_offset = SCREEN_X / 2 - CENTER_X * cm2p y_offset = SCREEN_Y / 2 - CENTER_Y * cm2p CENTER_X_PIEXL = CENTER_X * cm2p + x_offset CENTER_Y_PIEXL = CENTER_Y * cm2p + y_offset for i in range(anc_count): name = "ANC " + str(i) anc.append(UWB(name, 0)) for i in range(tag_count): name = "TAG " + str(i) tag.append(UWB(name, 1)) anc[0].set_location(A0X, A0Y) anc[1].set_location(A1X, A1Y) fresh_page() ser.write("begin".encode('UTF-8')) ser.reset_input_buffer() runtime = time.time() while True: read_data() if (time.time() - runtime) > 0.5: fresh_page() runtime = time.time() ser.reset_input_buffer() |
After opening the position project file, you will need to configure some settings. Here’s how:
- Go to the File menu and click on Settings.
- Navigate to Project: position.py and select Python Interpreter.
- Click on Add Interpreter.
- Next, choose Add Local Interpreter.
- Next, choose Select existing.
- Under Python path, choose Python 3.13.0, which we installed earlier.
- Finally, click the OK button.
Next, we have to install some packages;
- numpy
- pygame and
- pyserial
If you will like you are stuck, you can watch the video tutorial given at the end of this article.
About the code:
I actually downloaded this code from MakerFab’s product official page. They had designed it for four anchors, but I modified it for two anchors. Anyway, to specify the anchors positions, you need to scroll down.
A0X, A0Y = 0, 0
A1X, A1Y = 146, 0 # 114cm + 32 centimeters
Set the A0X and A0Y to 0,0 and also set the position of Anchor A1; And let me tell you, no matter what the actual value is, you need to add 32 cm from your side. Because I practically tested it, the tag position was a little bit off. So, after adding 32 cm to the actual distance value, the tag position accuracy greatly improved. Instead of writing 114, you should write 146. So, you need to add 32 cm to all the anchors, in case you want to use more anchors. Apart from setting the anchor positions, you don’t need to make any other changes to the code.
Before you click on the Play button, you need to make sure that Anchor A0 is connected to the laptop, Tag T0 is ON, and Anchor A1 is also ON.
You can see that the distance between Anchor A0 and A1 is 114 cm. Everything looks good, so let’s go ahead and click the Play button.
As you can see, it can impressively track the position of Tag T0 in real time between the two anchors. You can see how accurately it tracks the position as I move the tag left and right.
The problem with using only two anchors is that we only have the X-coordinate; the Y-coordinate is missing. That’s why, if I move the tag forward or backward, it will still show the position of the tag on a straight line.
So, if you are working on a project where you need to track the position of something on a straight line or, let’s say, only on the X-coordinate, then you can use two anchors. But if you want to precisely measure the position of an object or human in an area, you will need to use at least 3 to 4 anchors. You can increase the number of anchors if you want to track the position within an irregular area. But initially, you can start with 3 to 4 anchors.
Creating the Indoor Positioning System:
To explain this, I created two more anchors, A2 and A3. This time, I am using simple ESP32 Dev Modules. For the connections, you can follow this circuit diagram.
Anchor A2 Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
/* * ESP32 Dev Module Data transmit Demo UWB Module Firmware later than 1.1.0 is required Use 2.0.0 Wire Use 1.11.7 Adafruit_GFX_Library Use 1.14.4 Adafruit_BusIO Use 2.0.0 SPI Use 2.5.7 Adafruit_SSD1306 */ // User config ------------------------------------------ #define UWB_INDEX 2 // #define TAG #define ANCHOR #define FREQ_850K // #define FREQ_6800K #define UWB_TAG_COUNT 10 // User config end ------------------------------------------ #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Arduino.h> #define SERIAL_LOG Serial #define SERIAL_AT mySerial2 HardwareSerial SERIAL_AT(2); #define RESET 5 //16 #define IO_RXD2 4 #define IO_TXD2 2 #define I2C_SDA 21 #define I2C_SCL 22 Adafruit_SSD1306 display(128, 64, &Wire, -1); void setup() { pinMode(RESET, OUTPUT); digitalWrite(RESET, HIGH); SERIAL_LOG.begin(115200); SERIAL_LOG.print(F("Hello! ESP32-S3 AT command V1.0 Test")); SERIAL_AT.begin(115200, SERIAL_8N1, IO_RXD2, IO_TXD2); SERIAL_AT.println("AT"); Wire.begin(I2C_SDA, I2C_SCL); delay(1000); // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 SERIAL_LOG.println(F("SSD1306 allocation failed")); for (;;) ; // Don't proceed, loop forever } display.clearDisplay(); logoshow(); sendData("AT?", 2000, 1); sendData("AT+RESTORE", 5000, 1); sendData(config_cmd(), 2000, 1); sendData(cap_cmd(), 2000, 1); sendData("AT+SETRPT=0", 2000, 1); sendData("AT+SAVE", 2000, 1); sendData("AT+RESTART", 2000, 1); } long int runtime = 0; int data_count = 0; String response = ""; void loop() { if ((millis() - runtime) > 1000) { char data_str[80]; sprintf(data_str, "AT+DATA=32,UWB_A0_Data:%d", data_count * 2 + 1); data_count++; sendData(data_str, 2000, 1); runtime = millis(); } // put your main code here, to run repeatedly: while (SERIAL_LOG.available() > 0) { SERIAL_AT.write(SERIAL_LOG.read()); yield(); } while (SERIAL_AT.available() > 0) { char c = SERIAL_AT.read(); if (c == '\r') continue; else if (c == '\n' || c == '\r') { SERIAL_LOG.println(response); response = ""; } else response += c; } } // SSD1306 void logoshow(void) { display.clearDisplay(); display.setTextSize(1); // Normal 1:1 pixel scale display.setTextColor(SSD1306_WHITE); // Draw white text display.setCursor(0, 0); // Start at top-left corner display.println(F("MaUWB DW3000")); display.setCursor(0, 20); // Start at top-left corner // display.println(F("with STM32 AT Command")); display.setTextSize(2); String temp = ""; #ifdef TAG temp = temp + "T" + UWB_INDEX; #endif #ifdef ANCHOR temp = temp + "A" + UWB_INDEX; #endif #ifdef FREQ_850K temp = temp + " 850k"; #endif #ifdef FREQ_6800K temp = temp + " 6.8M"; #endif display.println(temp); display.setCursor(0, 40); temp = "Total: "; temp = temp + UWB_TAG_COUNT; display.println(temp); display.display(); delay(2000); } String sendData(String command, const int timeout, boolean debug) { String response = ""; // command = command + "\r\n"; SERIAL_LOG.println(command); SERIAL_AT.println(command); // send the read character to the SERIAL_LOG long int time = millis(); while ((time + timeout) > millis()) { while (SERIAL_AT.available()) { // The esp has data so display its output to the serial window char c = SERIAL_AT.read(); // read the next character. response += c; } } if (debug) { SERIAL_LOG.println(response); } return response; } String config_cmd() { String temp = "AT+SETCFG="; // Set device id temp = temp + UWB_INDEX; // Set device role #ifdef TAG temp = temp + ",0"; #endif #ifdef ANCHOR temp = temp + ",1"; #endif // Set frequence 850k or 6.8M #ifdef FREQ_850K temp = temp + ",0"; #endif #ifdef FREQ_6800K temp = temp + ",1"; #endif // Set range filter temp = temp + ",1"; return temp; } String cap_cmd() { String temp = "AT+SETCAP="; // Set Tag capacity temp = temp + UWB_TAG_COUNT; // Time of a single time slot #ifdef FREQ_850K temp = temp + ",25,1"; #endif #ifdef FREQ_6800K temp = temp + ",15,1"; #endif return temp; } |
This is the modified code for Anchor A2. I set the UWB_INDEX to 2, and I also changed the pin numbers. This time, I am using GPIOs 4 and 2. On the ESP32 Dev Module, the SDA and SCL pins are 21 and 22.
Similarly, for Anchor A3, set the UWB_INDEX to 3.
These are the only changes you need to make. After that, you can go ahead and upload the programs. Make sure you remove the wires connected to the Tx and Rx pins of the ESP32 Dev Module before uploading.
The Tag T0 and all four anchors, A0, A1, A2, and A3, are ready. Now, let’s go ahead and take a look at the Python code.
Python application Code for 4 Anchors:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
import pygame import serial import serial.tools.list_ports import json import csv import os import time import math # import site # print(site.getsitepackages()) RED = [255, 0, 0] BLACK = [0, 0, 0] WHITE = [255, 255, 255] class UWB: def __init__(self, name, type): self.name = name self.type = type self.x = 0 self.y = 0 self.status = False self.list = [] if self.type == 1: self.color = RED else: self.color = BLACK def set_location(self, x, y): self.x = x self.y = y self.status = True def cal(self): count = 0 anc_id_list = [] for range in self.list: if range != 0: anc_id_list.append(count) count = count + 1 # print(anc_id_list) if count >= 3: x = 0.0 y = 0.0 temp_x, temp_y = self.three_point_uwb( anc_id_list[0], anc_id_list[1]) x += temp_x y += temp_y temp_x, temp_y = self.three_point_uwb( anc_id_list[0], anc_id_list[2]) x += temp_x y += temp_y temp_x, temp_y = self.three_point_uwb( anc_id_list[2], anc_id_list[1]) x += temp_x y += temp_y x = int(x / 3) y = int(y / 3) self.set_location(x, y) self.status = True def three_point_uwb(self, a_id, b_id): x, y = self.three_point(anc[a_id].x, anc[a_id].y, anc[b_id].x, anc[b_id].y, self.list[a_id], self.list[b_id]) return x, y def three_point(self, x1, y1, x2, y2, r1, r2): temp_x = 0.0 temp_y = 0.0 p2p = (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2) p2p = math.sqrt(p2p) if r1 + r2 <= p2p: temp_x = x1 + (x2 - x1) * r1 / (r1 + r2) temp_y = y1 + (y2 - y1) * r1 / (r1 + r2) else: dr = p2p / 2 + (r1 * r1 - r2 * r2) / (2 * p2p) temp_x = x1 + (x2 - x1) * dr / p2p temp_y = y1 + (y2 - y1) * dr / p2p return temp_x, temp_y def get_frist_com(): port_list = serial.tools.list_ports.comports() if len(port_list) <= 0: print("No COM") return "" else: print("First COM") for com in port_list: print(com) return list(com)[0] def draw_uwb(uwb): # pixel_x = int(uwb.x * cm2p + x_offset) # pixel_y = int(uwb.y * cm2p + y_offset) pixel_x = int(uwb.x * cm2p + x_offset) pixel_y = SCREEN_Y - int(uwb.y * cm2p + y_offset) if uwb.status: r = 10 temp_str = uwb.name + " (" + str(uwb.x) + "," + str(uwb.y)+")" font = pygame.font.SysFont("Consola", 24) surf = font.render(temp_str, True, uwb.color) screen.blit(surf, [pixel_x, pixel_y]) pygame.draw.circle(screen, uwb.color, [ pixel_x + 20, pixel_y + 50], r, 0) # {'id': 1, 'range': [0, 53, 423, 0, 0, 0, 0, 0]} def read_data(): # ser.reset_input_buffer() line = ser.readline().decode('UTF-8').replace('\n', '') # print(line) try: data = json.loads(line) print(data) print(data['id']) # print(data['range']) tag[data['id']].list = data['range'] tag[data['id']].cal() except ValueError: print("[LOG]" + line) def fresh_page(): runtime = time.time() screen.fill(WHITE) for uwb in anc: draw_uwb(uwb) for uwb in tag: draw_uwb(uwb) pygame.draw.line(screen, BLACK, (CENTER_X_PIEXL, 0), (CENTER_X_PIEXL, SCREEN_Y), 1) pygame.draw.line(screen, BLACK, (0, CENTER_Y_PIEXL), (SCREEN_X, CENTER_Y_PIEXL), 1) pygame.display.flip() print("Fresh Over, Use Time:") print(time.time() - runtime) def distance(x1, y1, x2, y2): return math.sqrt((x2-x1) ** 2 + (y2 - y1)**2) # Main Function ............................................................. SCREEN_X = 800 SCREEN_Y = 800 pygame.init() screen = pygame.display.set_mode([SCREEN_X, SCREEN_Y]) ser = serial.Serial(get_frist_com(), 115200) anc = [] tag = [] anc_count = 4 tag_count = 8 # A0X, A0Y = 2066, 514 # A1X, A1Y = 2243, 1418 # A2X, A2Y = 427, 1726 # A3X, A3Y = 458, 112 A0X, A0Y = 0, 0 A1X, A1Y = 146, 0 #114cm +32 = 146 we add 32cm for calibration A2X, A2Y = 146, 102 # 70cm + 32cm A3X, A3Y = 0, 102 CENTER_X = int((A0X+A1X+A2X)/3) CENTER_Y = int((A0Y+A1Y+A2Y)/3) r0 = distance(A0X, A0Y, CENTER_X, CENTER_Y) r1 = distance(A1X, A1Y, CENTER_X, CENTER_Y) r2 = distance(A2X, A2Y, CENTER_X, CENTER_Y) r3 = distance(A3X, A3Y, CENTER_X, CENTER_Y) r = max(r0, r1, r2, r3) cm2p = SCREEN_X / 2 * 0.9 / r # meter to pixel # 1000 cm = 1000 piexl # cm2p = 1 # 1000 cm = 500 piexl # cm2p = 0.5 x_offset = SCREEN_X / 2 - CENTER_X * cm2p y_offset = SCREEN_Y / 2 - CENTER_Y * cm2p CENTER_X_PIEXL = CENTER_X * cm2p + x_offset CENTER_Y_PIEXL = CENTER_Y * cm2p + y_offset for i in range(anc_count): name = "ANC " + str(i) anc.append(UWB(name, 0)) for i in range(tag_count): name = "TAG " + str(i) tag.append(UWB(name, 1)) anc[0].set_location(A0X, A0Y) anc[1].set_location(A1X, A1Y) anc[2].set_location(A2X, A2Y) anc[3].set_location(A3X, A3Y) fresh_page() ser.write("begin".encode('UTF-8')) ser.reset_input_buffer() runtime = time.time() while True: read_data() if (time.time() - runtime) > 0.5: fresh_page() runtime = time.time() ser.reset_input_buffer() # Package Command # pyinstaller --onefile .\position.py |
This is the original code written for four anchors. For this application, you should have at least three anchors but the recommended number of Anchors are 4, if you want to build a highly accurate indoor positioning system. In this code, I only changed the XY values of the anchors, as I already explained in the previous example. Anyway, let’s go ahead and click the Play button.
Now, you can see that I can track the position of Tag T0 in the XY plane. You can change the coordinates in the code, and then you will be able to track the position of this tag within a larger area let’s say a house or office, ground etc. I am sure you got the idea of how to make a real-time human/object/pet indoor positioning system.
Although this application is simply fantastic, the only thing I don’t like is that Anchor A0 is connected to the system, which makes the entire setup feel quite unprofessional. Every time you want to track the position of a human, pet, or any object, you have to connect Anchor A0 to your laptop/PC. So, I decided to make it completely wireless.
Wireless Indoor Positioning System:
For the wireless indoor positioning system, you need to upload the following code into the Anchor A0.
Modified Code for the Anchor A0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
#include <WiFi.h> #include <HTTPClient.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Arduino.h> // Wi-Fi credentials const char* ssid = "Engr Fahad"; // Replace with your Wi-Fi SSID const char* password = "x73S3234"; // Replace with your Wi-Fi password // Web server URL (Replace with your Flask server IP) const char* serverURL = "http://192.168.100.3:5000/api/uwb"; // Example: Flask server running on IP 192.168.100.3 HardwareSerial mySerial2(2); // Use hardware serial 2 for communication with the UWB sensor #define RESET 16 #define IO_RXD2 18 #define IO_TXD2 17 #define I2C_SDA 39 #define I2C_SCL 38 Adafruit_SSD1306 display(128, 64, &Wire, -1); // OLED display initialization String response = ""; String rec_head = "AT+RANGE"; void setup() { pinMode(RESET, OUTPUT); digitalWrite(RESET, HIGH); Serial.begin(115200); // Start the serial communication for debugging mySerial2.begin(115200, SERIAL_8N1, IO_RXD2, IO_TXD2); // Initialize communication with UWB sensor Wire.begin(I2C_SDA, I2C_SCL); // Initialize I2C for OLED display // Initialize OLED display if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed")); for (;;); // Infinite loop if display initialization fails } display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println(F("UWB Ready")); display.display(); delay(2000); // Connect to Wi-Fi WiFi.begin(ssid, password); display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.println("Connecting to Wi-Fi..."); display.display(); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi connected"); display.println("Wi-Fi Connected"); display.display(); } void loop() { // Read data from UWB sensor while (mySerial2.available() > 0) { char c = mySerial2.read(); if (c == '\r') continue; else if (c == '\n') { if (response.indexOf(rec_head) != -1) { range_analy(response); // Analyze range data when response header is found } response = ""; // Reset response for the next data } else { response += c; // Accumulate the response from the sensor } } } // Analyze range data and send it to the web server void range_analy(String data) { String id_str = data.substring(data.indexOf("tid:") + 4, data.indexOf(",mask:")); String range_str = data.substring(data.indexOf("range:"), data.indexOf(",rssi:")); int range_list[8]; int count = sscanf(range_str.c_str(), "range:(%d,%d,%d,%d,%d,%d,%d,%d)", &range_list[0], &range_list[1], &range_list[2], &range_list[3], &range_list[4], &range_list[5], &range_list[6], &range_list[7]); if (count != 8) { Serial.println("RANGE ANALY ERROR"); return; } String json_str = "{\"id\":" + id_str + ",\"range\":["; for (int i = 0; i < 8; i++) { json_str += String(range_list[i]); if (i < 7) json_str += ","; } json_str += "]}"; Serial.println(json_str); // Print the JSON data to the Serial monitor sendDataToServer(json_str); // Send data to the Flask server } // Send JSON data to the Flask server void sendDataToServer(String jsonData) { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; http.begin(serverURL); // Connect to the Flask server http.addHeader("Content-Type", "application/json"); int httpResponseCode = http.POST(jsonData); // Send POST request with JSON data if (httpResponseCode > 0) { String response = http.getString(); // Get the server's response Serial.println("Server Response: " + response); } else { Serial.println("Error sending data. HTTP Response Code: " + String(httpResponseCode)); } http.end(); // End the HTTP request } else { Serial.println("Wi-Fi disconnected"); } } |
This is the modified code for Anchor A0. You can see the WiFi credentials. Now, it will send the data of all four anchors to the webpage through WiFi. There is no need to modify the code for Tag T0 or the anchors A1, A2, and A3. Anyway, I have already uploaded this program. Now, let’s take a look at the Python and HTML code.
Make sure to keep the Python project file and the templates folder in the same directory.
Inside the templates folder, we have an HTML file named index.
If you want to make any changes to the code, simply right-click on the index file and select “Edit in Notepad.”
HTML Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>UWB Monitoring</title> <style> body { font-family: Arial, sans-serif; text-align: center; padding: 50px; } .data-container { margin-top: 30px; font-size: 1.2em; } .data-container h2 { margin-bottom: 20px; } .data-container .range { font-weight: bold; } .data-container .id { font-weight: normal; } /* Styling the map */ .map { width: 800px; /* Map width */ height: 400px; /* Map height */ background-color: #f0f0f0; position: relative; margin: 30px auto; border: 1px solid #ccc; display: flex; justify-content: center; align-items: center; } /* Origin point */ .origin { position: absolute; bottom: -20px; left: 50%; transform: translateX(-50%); font-size: 14px; color: #000; } /* Anchor visualization */ .anchor { width: 20px; height: 20px; background-color: black; border-radius: 50%; position: absolute; } /* Tag visualization */ .tag { width: 20px; height: 20px; background-color: green; border-radius: 50%; position: absolute; top: 50%; /* Keep tag at the same vertical position */ transform: translate(-50%, -50%); } /* Anchor positions */ .anchor.a0 { left: 0%; /* Anchor A0 at the bottom-left */ top: 100%; transform: translate(-50%, -50%); } .anchor.a1 { left: 100%; /* Anchor A1 at the bottom-right */ top: 100%; transform: translate(-50%, -50%); } .anchor.a2 { left: 100%; /* Anchor A2 at the top-right */ top: 0%; transform: translate(-50%, -50%); } .anchor.a3 { left: 0%; /* Anchor A3 at the top-left */ top: 0%; transform: translate(-50%, -50%); } </style> </head> <body> <h1>Welcome to the UWB Monitoring Server!</h1> <div class="data-container"> <h2>Latest UWB Data:</h2> <p><span class="id">Tag ID: <span id="tag-id">Loading...</span></span></p> <p><span class="range">Range Data: <span id="range-data">Loading...</span></span></p> </div> <!-- Map to display the anchors and tag's position --> <div class="map"> <!-- Anchor A0 --> <div class="anchor a0" title="Anchor A0 (0,0)"></div> <!-- Anchor A1 --> <div class="anchor a1" title="Anchor A1 (146,0)"></div> <!-- Anchor A2 --> <div class="anchor a2" title="Anchor A2 (146,102)"></div> <!-- Anchor A3 (imaginary, top-left corner) --> <div class="anchor a3" title="Anchor A3 (0,102)"></div> <!-- Tag --> <div id="tag" class="tag"></div> <!-- Origin Label --> <div class="origin">Origin (0, 0)</div> </div> <script> // Function to fetch and update the tag's position and range data function fetchLatestData() { fetch('/latest_data') // Fetch new data from Flask server .then(response => response.json()) // Parse JSON response .then(data => { console.log("Fetched data:", data); // Update the tag's position on the map const tagElement = document.getElementById('tag'); const mapWidth = 800; // Map width in pixels const mapHeight = 400; // Map height in pixels // Scaling factor based on the actual coordinates of the anchors const scaleFactorX = mapWidth / 146; // 146 is the horizontal distance between A0 and A1 const scaleFactorY = mapHeight / 102; // 102 is the height between A0 and A2 // Scale x and y to map dimensions; keep tag's position relative const scaledX = data.x * scaleFactorX; const scaledY = data.y * scaleFactorY; // Set the tag's position tagElement.style.left = `${scaledX}px`; tagElement.style.top = `${scaledY}px`; // Update the Range Data document.getElementById('range-data').textContent = data.range; document.getElementById('tag-id').textContent = data.id; }) .catch(error => { console.error("Error fetching data:", error); }); } // Refresh the tag's position and range data every 1 second (1000 milliseconds) setInterval(fetchLatestData, 1000); </script> </body> </html> |
Python Code for Wireless indoor Positioning System:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
from flask import Flask, render_template, request, jsonify import math # Initialize the Flask app app = Flask(__name__) # Initialize data variable latest_data = {} @app.route('/') def home(): # Render the index.html template and pass the latest_data variable to it return render_template('index.html', data=latest_data) @app.route('/api/uwb', methods=['POST']) def uwb_data(): global latest_data try: # Receive data from the ESP32 received_data = request.get_json() print(f"Received data: {received_data}") # Parse the range data range_data = received_data.get('range', []) if len(range_data) < 4: return jsonify({"status": "error", "message": "Insufficient range data"}), 400 # Distances to Anchors d1 = range_data[3] # Distance to Anchor 1 (0, 0) d2 = range_data[2] # Distance to Anchor 2 (146, 0) d3 = range_data[1] # Distance to Anchor 3 (146, 102) d4 = range_data[0] # Distance to Anchor 4 (0, 102) # Anchor positions x1, y1 = 0, 0 # Anchor 1 position x2, y2 = 146, 0 # Anchor 2 position x3, y3 = 146, 102 # Anchor 3 position x4, y4 = 0, 102 # Anchor 4 position # Using the trilateration method to calculate x, y (approximation for 4 anchors) # Set up equations derived from the distances to each anchor: # d1^2 = x^2 + y^2 # d2^2 = (x - x2)^2 + (y - y2)^2 # d3^2 = (x - x3)^2 + (y - y3)^2 # d4^2 = (x - x4)^2 + (y - y4)^2 # Subtract equations to eliminate quadratic terms and solve the linear system # For simplicity, we apply a basic approximation approach to calculate x and y # Equation for x and y based on distances A = 2 * (x2 - x1) B = 2 * (y2 - y1) C = d1**2 - d2**2 - x1**2 + x2**2 - y1**2 + y2**2 x = C / A # Simplified equation for x # Equation for y using the first equation and calculated x y = math.sqrt(d1**2 - x**2) # Solve for y using the first equation # Update the latest data with the tag's position and range latest_data = { 'id': received_data.get('id'), 'range': range_data, # Send range data 'x': x, 'y': y } # Respond with a success status return jsonify({"status": "success"}), 200 except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/latest_data') def latest_data_endpoint(): # Serve the latest data as JSON for the frontend to fetch return jsonify(latest_data) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True) |
This is the Python code for wirelessly tracking the position of a tag in an area. Make sure to install all the required packages as explained earlier. This time, you will also need to install the Flask package.
All four anchors are powered up, and Anchor A0 is connected to the WiFi. So, let’s click the Play button.
You can see that we are receiving data from all four anchors. To open the webpage, we can click on this IP address.
After clicking on the IP address, it will open a web page for you; as you can see in the image below.
Now, I can precisely track the position of a human, object, or pet wirelessly; not only on my laptop but also on my cell phone. I no longer need to sit in front of my laptop.
In upcoming articles, I will use this setup for load automation, security, car, and bike control. So, consider subscribing if you don’t want to miss those videos and articles.