AI Thinker BU03 pinout

AI Thinker BU03 pinout

<aside> <img src="/icons/light-bulb_gray.svg" alt="/icons/light-bulb_gray.svg" width="40px" />

Tech Document

AI thinker BU03-kit specification

https://www.makerfabs.cc/article/esp32-uwb-indoor-positioning-test.html

https://core-electronics.com.au/guides/getting-started-with-ultra-wideband-and-measuring-distances-arduino-and-pico-guide/#JQL99Y3

https://fcniufr8ibx1.feishu.cn/wiki/TpEiwDZlRiPHyTkZzrecZohFnVf

https://fcniufr8ibx1.feishu.cn/wiki/VjNfwjJugii6mjklQqxc59jDnhg

https://github.com/au5ton/KeilForDocker

</aside>

https://docs.google.com/presentation/d/1z5fIBbtb-PHsTmm5DOIb_CDVoF5X1-BGCwXxVB5uRxU/edit?usp=share_link

Test Run

<aside> <img src="/icons/cellular_gray.svg" alt="/icons/cellular_gray.svg" width="40px" />

reading from board:

Raw data: b'\\xAA%\\x01\\xEE\\x07\\x00\\x00\\xD8\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'

Base Station Distances:

BS0: 2.030m

BS1: 1.240m

BS2: Not visible

BS3: Not visible

BS4: Not visible

BS5: Not visible

BS6: Not visible

BS7: Not visible

Raw data: b'\\xAA%\\x01\\xB2\\x07\\x00\\x00\\xD8\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'

Base Station Distances:

BS0: 1.970m

BS1: 1.240m

BS2: Not visible

BS3: Not visible

BS4: Not visible

BS5: Not visible

</aside>

#include <math.h>
#if defined(ARDUINO_UNOWIFIR4)
  #include <WiFiS3.h>
#else
  #include <WiFiNINA.h>
#endif
#include <ArduinoHttpClient.h>

const char* ssid = "sandbox370";
const char* pass = "+s0a+s03!2gether?";
const char* endpoint = "<http://10.23.10.67:3000/update>";
const char* serverAddress = "10.23.10.67";
int port = 3000;

const char* TAG_ID = "TAG_A";

float baseDistance = 2.3;

WiFiClient wifi;
HttpClient client(wifi, serverAddress, port);

unsigned long lastSend = 0;
float wifiRequestCoolDown = 1000;

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);

  Serial.print("base distance:");
  Serial.println(baseDistance);

  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\\nWiFi connected!");
}

void sendLocationRequest(float x, float y) {
  String payload = String("{\\"id\\":\\"") + TAG_ID + "\\",\\"x\\":" + x + ",\\"y\\":" + y + "}";

  Serial.println("POSTing: " + payload);

  client.beginRequest();
  client.post("/update");
  client.sendHeader("Content-Type", "application/json");
  client.sendHeader("Content-Length", payload.length());
  client.beginBody();
  client.print(payload);
  client.endRequest();

  int statusCode = client.responseStatusCode();
  String response = client.responseBody();

  Serial.print("Status: "); Serial.println(statusCode);
  Serial.print("Response: "); Serial.println(response);
  Serial.println();
}

bool decodeUwbDistances(uint8_t* data, int dataLen, float* distances) {
  // Initialize all distances to -1 (equivalent to None)
  for (int i = 0; i < 8; i++) {
    distances[i] = -1.0;
  }
  
  if (dataLen < 35) {
    return false;
  }
  
  // Check for header pattern
  if (data[0] != 0xaa || data[1] != 0x25 || data[2] != 0x01) {
    return false;
  }
  
  // Extract distance data (skip header, process 4-byte chunks)
  for (int i = 0; i < 8; i++) {  // 8 base stations (0-7)
    int byteOffset = 3 + (i * 4);  // Each distance is 4 bytes
    if (byteOffset + 3 < dataLen) {
      // Read as little-endian 32-bit integer
      uint32_t distanceRaw = data[byteOffset] | 
                            (data[byteOffset + 1] << 8) | 
                            (data[byteOffset + 2] << 16) | 
                            (data[byteOffset + 3] << 24);
      
      // Convert to meters
      if (distanceRaw > 0) {
        distances[i] = distanceRaw / 1000.0;
      } else {
        distances[i] = -1.0;  // No signal/not visible
      }
    }
  }
  
  return true;
}

// <https://www.makerfabs.cc/article/esp32-uwb-indoor-positioning-test.html>
void calculateXY(float left_distance, float right_distance, float &x, float &y) {
  float cos_a = (left_distance * left_distance + baseDistance * baseDistance - right_distance * right_distance) / (2 * left_distance * baseDistance);
  x = left_distance * cos_a;
  y = left_distance * sqrt(1 - cos_a * cos_a);
}

void printDistances(float* distances, bool validData) {
  if (!validData) {
    Serial.println("Invalid data received");
    return;
  }
  
  Serial.println("Base Station Distances:");
  for (int i = 0; i < 8; i++) {
    if (distances[i] > 0) {
      Serial.print("  BS");
      Serial.print(i);
      Serial.print(": ");
      Serial.print(distances[i], 3);
      Serial.println("m");
    } else {
      Serial.print("  BS");
      Serial.print(i);
      Serial.println(": Not visible");
    }
  }
  Serial.println("------------------------------");
}

void loop() {
  static uint8_t buffer[256];
  static int bufferIndex = 0;
  static bool messageStarted = false;
  unsigned long now = millis();
  
  // Check if anything is available in buffer
  while (Serial1.available()) {
    uint8_t incomingByte = Serial1.read();
    
    // Look for start of message
    if (!messageStarted && incomingByte == 0xAA) {
      messageStarted = true;
      bufferIndex = 0;
      buffer[bufferIndex++] = incomingByte;
    } else if (messageStarted) {
      buffer[bufferIndex++] = incomingByte;
      
      // Check if we have a complete message (35+ bytes expected)
      if (bufferIndex >= 35) {
        // Print raw data
        Serial.print("Raw data: b'");
        for (int i = 0; i < bufferIndex; i++) {
          if (buffer[i] >= 32 && buffer[i] <= 126) {
            Serial.print((char)buffer[i]);
          } else {
            Serial.print("\\\\x");
            if (buffer[i] < 16) Serial.print("0");
            Serial.print(buffer[i], HEX);
          }
        }
        Serial.println("'");
        
        // Decode distances
        float distances[8];
        bool validData = decodeUwbDistances(buffer, bufferIndex, distances);
        printDistances(distances, validData);

        if (distances[0] != -1 && distances[1] != -1 && now - lastSend >= wifiRequestCoolDown) {
          lastSend = now;
          float x, y;
          calculateXY(distances[0], distances[1], x, y);
          sendLocationRequest(x, y);
        }
        
        // Reset for next message
        messageStarted = false;
        bufferIndex = 0;
      }
      
      // Prevent buffer overflow
      if (bufferIndex >= 256) {
        messageStarted = false;
        bufferIndex = 0;
      }
    }
  }
}

IMG_8312.JPG

Screen Recording 2025-10-23 at 9.07.35 PM.mov