Skip to content

ESP-NOW Applied Exercises with Peripherals

Exercise 1 — One-to-one: wireless button to LED

Pattern: one-to-one
Peripherals: pushbutton on Board A, LED on Board B

Goal

When the student presses a button on Board A, Board B turns an LED on. When the button is released, the LED turns off.


Exercise 2 — One-to-one: potentiometer-controlled dimmer

Pattern: one-to-one
Peripherals: potentiometer on Board A, PWM LED on Board B

Goal

Board A reads an analog potentiometer and sends the value to Board B. Board B adjusts the brightness of an LED using PWM.


Exercise 3 — Two-way: ping and acknowledgment with buzzer

Pattern: two-way / symmetric
Peripherals: pushbutton on Board A, buzzer on Board B, status LED on Board A

Goal

When the user presses a button on Board A, it sends a command to Board B. Board B activates a buzzer briefly and returns an ACK. Board A lights a status LED only if the ACK arrives before timeout.


Exercise 4 — One-to-many: one controller, three actuators

Pattern: one-to-many
Peripherals: one controller board with 3 buttons, three receiver boards with different outputs

  • Board B: LED
  • Board C: buzzer
  • Board D: servo

Goal

One master board sends commands to three different boards. Each button triggers a different remote actuator.

Example behavior

  • Button 1 toggles the LED on Board B
  • Button 2 activates a short beep on Board C
  • Button 3 moves the servo on Board D to a defined angle

Exercise 5 — One-to-many with targeted NeoPixel control from terminal

Pattern: one-to-many
Peripherals: one sender board connected to the serial terminal, multiple receiver boards each with a NeoPixel ring

Goal

The sender board reads commands from the serial terminal and sends them to a selected ESP node so it changes one pixel on its NeoPixel ring.

Terminal command format

esp#, pixel#, R, G, B

Example commands

esp1, 0, 255, 0, 0
esp2, 5, 0, 255, 0
esp3, 11, 0, 0, 255

Meaning: - esp1 selects receiver board 1 - pixel# selects which LED in the ring to modify - R, G, B are the color values from 0 to 255

Required setup

  • One transmitter board connected to the computer
  • At least 3 receiver ESP boards
  • One NeoPixel ring or circle array per receiver board
  • Add the NeoPixel component to each receiver project with: idf.py add-dependency "zorxx/neopixel"
  • A lookup table on the transmitter that maps:
  • esp1 → MAC of Board B
  • esp2 → MAC of Board C
  • esp3 → MAC of Board D

NeoPixel quick reference

Add the dependency to your project with

idf.py add-dependency "zorxx/neopixel"
from the terminal in your project directory.

The zorxx/neopixel library exposes three things you need: an init function, a pixel-set function, and a color macro.

#include "neopixel.h"

#define NEOPIXEL_PIN   GPIO_NUM_8   // data pin wired to the ring's DIN
#define NEOPIXEL_COUNT 12           // number of LEDs in the ring

// 1. Initialize once, e.g. inside app_main()
tNeopixelContext np = neopixel_Init(NEOPIXEL_COUNT, NEOPIXEL_PIN);

// 2. To set a single pixel, fill a tNeopixel struct and call neopixel_SetPixel()
tNeopixel pixel;
pixel.index = 5;                        // which LED in the ring (0-based)
pixel.rgb   = NP_RGB(255, 0, 0);        // red — NP_RGB(R, G, B) packs the color

neopixel_SetPixel(np, &pixel, 1);       // last argument = number of pixels in the array

// 3. To set multiple pixels in one call, use an array
tNeopixel pixels[3] = {
    { .index = 0,  .rgb = NP_RGB(255, 0,   0)   },   // red
    { .index = 1,  .rgb = NP_RGB(0,   255, 0)   },   // green
    { .index = 2,  .rgb = NP_RGB(0,   0,   255) },   // blue
};
neopixel_SetPixel(np, pixels, 3);

// 4. To turn a pixel off, set its color to black
pixel.index = 5;
pixel.rgb   = NP_RGB(0, 0, 0);
neopixel_SetPixel(np, &pixel, 1);

Exercise 6 — Many-to-one: sensor hub printed in terminal

Pattern: many-to-one
Peripherals: - Board B: LDR - Board C: potentiometer - Board D: temperature sensor or second analog source - Board A: serial terminal monitor

Goal

Three sensor nodes send measurements to one gateway. The gateway prints the latest data from all nodes in the terminal.

Example terminal output

Node 1 | Light = 742
Node 2 | Pot = 1810
Node 3 | Temp = 24.6 C

Exercise 7 — Everyone-to-everyone: distributed quiz buzzer

Pattern: everyone-to-everyone
Peripherals: pushbutton and LED on every board

Goal

Each board has a button. When any student presses their button, all boards receive the event and display who buzzed first by lighting LEDs in a defined pattern.

Example behavior

  • Every board has a local button and LED
  • Pressing a button sends a "buzz" message to every other board
  • The first received buzz wins
  • All boards indicate the winner
  • Later presses are ignored until reset

Exercise 8 — Group Assignment: range-extended chat system

Pattern: everyone-to-everyone with relay forwarding
Peripherals: serial terminal on every board

Goal

Each board can send and receive messages to/from every other board. The user types a message in the terminal of any board and it is broadcast to all other boards, which print it in their terminals. Boards that are out of direct range of the sender can still receive the message because intermediate boards repeat it, extending the network's reach.

Example behavior

  • Each board has a unique name (e.g., Oscar, Sumie, Carlos)
  • The user types a message in the terminal prefixed by the board's name (e.g., Oscar: Hello everyone!)
  • The message is broadcast to all reachable neighbors
  • Any board that receives the message prints it to its own terminal and rebroadcasts it once, so boards beyond direct range also receive it
  • To prevent the message from bouncing forever, include a TTL (time-to-live) counter in the packet struct — each board decrements it before forwarding, and discards the packet when it reaches zero

Packet struct hint

typedef struct {
    char     sender[16];   // board name, e.g. "Oscar"
    char     text[64];     // message body
    uint8_t  ttl;          // decrement before forwarding; discard when 0
} __attribute__((packed)) chat_pkt_t;