This week’s assignment was to design, build, and connect wired or wireless nodes with network addresses and input/output capabilities. My custom fish PCB uses the XIAO ESP32S3, so adding WiFi networking was straightforward.

1. Turning My Fish Board into a WiFi UDP Node

My fish board ready for WiFi.
Board successfully connected to WiFi.
Button press → LED on → UDP message sent.

ESP32S3 WiFi Node Code


#include 
#include 

const char* ssid = "iPhone";
const char* password = "11111111";

const int BTN_PIN = D0;
const int LED_PIN = LED_BUILTIN;

WiFiUDP udp;
const unsigned int localPort = 1234;
const unsigned int sendPort = 1234;

IPAddress pcIP(172, 20, 10, 3);

int lastState = HIGH;

void setup() {
  pinMode(BTN_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);

  Serial.begin(115200);
  delay(1000);

  Serial.println();
  Serial.println("Node 1 booting...");

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

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

  Serial.println("WiFi connected!");
  Serial.println(WiFi.localIP());

  udp.begin(localPort);
}

void loop() {
  int s = digitalRead(BTN_PIN);

  if (s == LOW)
    digitalWrite(LED_PIN, HIGH);
  else
    digitalWrite(LED_PIN, LOW);

  if (lastState == HIGH && s == LOW) {
    const char* msg = "node 1 button 0";

    Serial.println(msg);

    udp.beginPacket(pcIP, sendPort);
    udp.print(msg);
    udp.endPacket();
    Serial.println("UDP message sent.");
  }

  lastState = s;

  int packetSize = udp.parsePacket();
  if (packetSize > 0) {
    char buf[128];
    int len = udp.read(buf, sizeof(buf) - 1);
    if (len > 0) buf[len] = 0;

    Serial.print("Received UDP: ");
    Serial.println(buf);
  }
}
      

Python UDP Listener

Python listener receiving UDP packets.

import socket

PORT = 1234

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', PORT))

print("Listening UDP on port", PORT)

while True:
    data, addr = s.recvfrom(1024)
    print("From", addr, "->", data.decode(errors='ignore'))
      

2. Group Assignment — Two Boards Exchanging Data

Justin’s board uses an HC-SR04 ultrasonic sensor and sends distance values (e.g., DIST:23.4) to my board via UDP. Both boards connect to my hotspot, forming a shared local network.

Justin’s distance data → my OLED display updates.

My Fish Board Receiver (OLED Display)


#include 
#include 
#include 
#include 
#include 

#define W 128
#define H 64

const int SDA_PIN = D8;
const int SCL_PIN = D7;
TwoWire I2C(1);
Adafruit_SSD1306 display(W, H, &I2C, -1);

const int BTN[4] = { D0, D1, D2, D3 };
const char* LABEL[4] = { "RIVER", "LAKE", "OCEAN", "???" };

const char* ssid = "iPhone";
const char* password = "11111111";

WiFiUDP udp;
const unsigned int localPort = 1234;

void showText(const char* s) {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);

  int16_t x1, y1;
  uint16_t w, h;
  display.getTextBounds(s, 0, 0, &x1, &y1, &w, &h);
  int x = (W - (int)w) / 2;
  int y = (H - (int)h) / 2;
  display.setCursor(x, y);
  display.println(s);
  display.display();
}

void updateDisplayFromDistance(float d) {
  if (d < 0)
    showText("NO DATA");
  else if (d < 10)
    showText("TOO CLOSE");
  else if (d < 30)
    showText("NEAR");
  else if (d < 100)
    showText("FAR");
  else
    showText("OUT");
}

void setup() {
  Serial.begin(115200);
  delay(200);

  I2C.begin(SDA_PIN, SCL_PIN, 100000);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  for (int i = 0; i < 4; i++) pinMode(BTN[i], INPUT_PULLUP);

  showText("BOOTING");

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(300);

  showText("READY");

  udp.begin(localPort);
}

void loop() {
  static uint8_t last[4] = {HIGH, HIGH, HIGH, HIGH};
  static unsigned long lastDebounce[4] = {0,0,0,0};
  const unsigned long DEBOUNCE_MS = 30;

  int packetSize = udp.parsePacket();
  if (packetSize > 0) {
    char buf[64];
    int len = udp.read(buf, sizeof(buf) - 1);
    if (len > 0) buf[len] = 0;

    if (strncmp(buf, "DIST:", 5) == 0) {
      float d = atof(buf + 5);
      updateDisplayFromDistance(d);
    }
  }
}
      

Justin’s Ultrasonic Sender Node


#include 
#include 

const char* ssid = "iPhone";
const char* password = "11111111";

const int TRIG_PIN = D4;
const int ECHO_PIN = D5;

WiFiUDP udp;
const unsigned int sendPort = 1234;

IPAddress fishIP(172, 20, 10, 2);

const unsigned long MEASURE_INTERVAL = 500;
unsigned long lastMeasureTime = 0;

float measureDistanceCm() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  long duration = pulseIn(ECHO_PIN, HIGH, 30000);
  if (duration == 0) return -1.0;

  float distance = duration / 58.0;
  return distance;
}

void setup() {
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  Serial.begin(115200);
  delay(1000);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) delay(500);

  Serial.println("Sense ready");
}

void loop() {
  unsigned long now = millis();
  if (now - lastMeasureTime >= MEASURE_INTERVAL) {
    lastMeasureTime = now;

    float d = measureDistanceCm();

    char msg[32];
    if (d < 0)
      snprintf(msg, sizeof(msg), "DIST:-1");
    else
      snprintf(msg, sizeof(msg), "DIST:%.1f", d);

    udp.beginPacket(fishIP, sendPort);
    udp.write((const uint8_t*)msg, strlen(msg));
    udp.endPacket();

    Serial.print("Sent: ");
    Serial.println(msg);
  }
}