Selected work

ESP32-C6-ARDUINO-UNO-AC

  • C++

Access OS

A dual-microcontroller, RFID access-control system with a phone-style operating system, dual OLED displays, a captive-portal WiFi setup and a live web dashboard.

Built by Seleste Technologies on an Arduino Uno and an ESP32-C6.

Platform Framework Language Status


Table of Contents

  1. Overview
  2. Features
  3. System Architecture
  4. Tech Stack
  5. Bill of Materials
  6. Wiring and Connections
  7. Inter-Board Protocol
  8. Software Setup
  9. First Run and Usage
  10. Keypad Reference
  11. Applications
  12. Web Interface and API
  13. Behaviour Reference
  14. Project Structure
  15. Configuration
  16. Troubleshooting
  17. Security Notes
  18. Roadmap
  19. License

Overview

Access OS splits one access-control system across two boards, each doing what it is best at:

BoardRoleResponsibilities
ESP32-C6BrainUI "operating system", two OLEDs, keypad, WiFi, captive portal, web server, card database, PIN and settings, access decisions
Arduino UnoMuscleRFID reader, servo door latch, buzzer, status LEDs, two ultrasonic sensors, IR beam, exit button

The boards talk over a simple line-based serial protocol. The Uno reports events (a card was scanned, someone is near the door, the exit button was pressed). The ESP32 decides, and tells the Uno to grant or deny.


Features

Access control

  • RFID card enrollment and removal, stored in flash (up to 16 cards)
  • Servo-driven door latch with configurable auto-relock (3, 5, 10 or 20 s)
  • IR beam holds the door open while someone stands in the doorway and relocks shortly after they pass
  • Exit push button for inside release
  • Outside and inside ultrasonic sensors with a "visitor detected" event that wakes the screens
  • PIN protection for door opening, card management and settings
  • Event log (last 10 events, timestamped via NTP)

Operating system

  • Icon-based launcher with 8 apps on a 128x64 OLED
  • Phone-style multi-tap text entry for passwords and PINs
  • Screensaver after 40 s of inactivity
  • Persistent settings in flash (NVS)
  • Four FreeRTOS tasks: UI, network, UART link and second display

Dual OLED

  • OLED A is the interactive screen
  • OLED B is a live dashboard (clock and status, sensor bars, event log)
  • Wallpapers are drawn on one virtual 256x64 canvas, so animations travel across both displays

Connectivity

  • Captive-portal WiFi provisioning (Seleste-Setup hotspot), plus on-device WiFi setup from the Settings app
  • Web dashboard with live status, remote lock and unlock, and event log
  • mDNS: reachable at http://seleste.local
  • NTP time sync (UTC+3 by default)

System Architecture

flowchart LR
    subgraph ESP["ESP32-C6 (brain)"]
        UI["UI task (loop)\nlauncher, apps, keypad"]
        NET["net task\nWiFi, DNS, web server"]
        LNK["link task\nUART parser"]
        OB["oledB task\nsecond display"]
        NVS[("NVS flash\ncards, PIN, WiFi, settings")]
    end
    subgraph UNO["Arduino Uno (muscle)"]
        RFID["RC522 RFID"]
        SRV["SG90 servo latch"]
        SENS["2x HC-SR04, IR, exit button"]
        OUT["Buzzer, 3 LEDs"]
    end
    KP["4x4 keypad"] --> UI
    UI --> OLA["OLED A"]
    OB --> OLB["OLED B"]
    NET <--> PHONE["Phone / browser"]
    LNK <-- "UART 9600 baud" --> UNO
    UI --- NVS
    RFID --> UNO
    SENS --> UNO
    UNO --> SRV
    UNO --> OUT

Decision flow for a card scan

  1. The RC522 reads a tag and the Uno sends C:<UID> to the ESP32.
  2. The ESP32 looks the UID up in its card list.
  3. The ESP32 replies G (grant) or X (deny).
  4. On grant, the Uno moves the servo, lights the green LED, plays the success tone and starts the relock timer.
  5. Both boards log the result, and OLED B and the web dashboard update.

Tech Stack

LayerTechnology
MicrocontrollersESP32-C6 (RISC-V, WiFi 6, 2.4 GHz), ATmega328P (Arduino Uno)
LanguageC++ (Arduino framework)
RTOSFreeRTOS (ESP32 Arduino core 3.x) with tasks and recursive mutexes
GraphicsU8g2 (full-buffer mode, hardware and software I2C)
RFIDMFRC522 library (RC522, 13.56 MHz, SPI)
NetworkingWiFi, WebServer, DNSServer (captive portal), ESPmDNS, NTP via configTime
StoragePreferences (NVS flash) for cards, PIN, WiFi credentials and settings
ActuationServo library, tone() for the passive buzzer
Inter-board linkUART, 9600 baud, 8N1, ASCII line protocol (SoftwareSerial on Uno, Serial1 on ESP32)
Web front endVanilla HTML, CSS and JavaScript served from flash (no external dependencies)
ToolchainArduino IDE 2.x (or arduino-cli)

Required libraries

LibraryUsed onSource
esp32 board package (Espressif), version 3.xESP32-C6Boards Manager
U8g2 (olikraus)ESP32-C6Library Manager
MFRC522 (GithubCommunity / Miguel Balboa)UnoLibrary Manager
Servo, SoftwareSerial, SPIUnoBuilt in
WiFi, WebServer, DNSServer, ESPmDNS, Preferences, WireESP32-C6Built in with the ESP32 core

Bill of Materials

QtyComponentNotes
1Arduino UnoAccess node
1ESP32-C6 dev boardMain controller
20.96" SSD1306 OLED, 128x64, I2CFor 1.3" SH1106 modules, change the constructors
14x4 membrane keypadNavigation and text entry
1RC522 RFID reader with tags3.3V device
1SG90 micro servoDoor latch
2HC-SR04 ultrasonic sensorsOutside and inside
1IR obstacle sensor (FC-51 type)Doorway beam, LOW when blocked
1Passive buzzerDriven with tone()
3LEDs (green, red, yellow)Each with a 220 ohm resistor
1Push buttonExit button
3220 ohm resistorsOne per LED
1 + 11 kohm and 2 kohm resistorsLevel shifter on the Uno-to-C6 UART line
1470 uF capacitorAcross servo power, recommended

Wiring and Connections

All grounds must be common: connect Uno GND to ESP32-C6 GND. Power each board from its own USB connection.

Arduino Uno

FunctionComponent pinUno pin
Link RX (receives from ESP32)n/aD2 (from C6 GPIO18)
Link TX (sends to ESP32)n/aD3 (to C6 GPIO21 via divider)
Buzzer+ leg (other leg to GND)D4
Servo signalOrange or yellow wireD5
Green LED (door open)Anode via 220 ohmD6
Red LED (door locked)Anode via 220 ohmD7
Yellow LED (activity)Anode via 220 ohmD8
RC522 RSTRSTD9
RC522 SSSDAD10
RC522 MOSIMOSID11
RC522 MISOMISOD12
RC522 SCKSCKD13
Ultrasonic 1 (outside)TRIG / ECHOA0 / A1
Ultrasonic 2 (inside)TRIG / ECHOA2 / A3
IR obstacle sensorOUTA4
Exit buttonOne leg (other to GND, internal pull-up)A5

Power connections (Uno)

ComponentSupply
RC522 VCC3.3V pin (not 5V)
Servo (red wire)5V, with a 470 uF capacitor across power and ground
HC-SR04 x2 VCC5V
IR sensor VCC5V
All groundsGND

ESP32-C6

FunctionComponent pinC6 GPIO
OLED A SDASDAGPIO22
OLED A SCLSCLGPIO23
OLED B SDASDAGPIO20
OLED B SCLSCLGPIO19
Keypad row 1Pin 1GPIO0
Keypad row 2Pin 2GPIO1
Keypad row 3Pin 3GPIO2
Keypad row 4Pin 4GPIO3
Keypad column 1Pin 5GPIO6
Keypad column 2Pin 6GPIO7
Keypad column 3Pin 7GPIO10
Keypad column 4Pin 8GPIO11
Link TX (to Uno D2)n/aGPIO18
Link RX (from Uno D3)n/aGPIO21 (via divider)
OLED A and B VCCVCC3V3
OLED A and B GNDGNDGND

All 14 GPIOs used are safe, non-strapping pins. Keypad pin order can differ between membranes. If keys appear scrambled, verify rows and columns with a multimeter and swap the groups.

UART link and level shifter

The Uno's TX is 5V logic, but the ESP32-C6 is not 5V tolerant. Use a resistor divider on that one line:

Uno D3 ──[ 1 kΩ ]──┬── ESP32-C6 GPIO21
                   │
                 [ 2 kΩ ]
                   │
                  GND

ESP32-C6 GPIO18 ───────────── Uno D2        (3.3V into the Uno is read as HIGH, no shifter needed)

RC522 note

The RC522 is a 3.3V device. Many builders drive it directly from the Uno's 5V logic and it works, but for long-term reliability add 1 kohm / 2 kohm dividers on MOSI, SCK, SS and RST.

Pin budget at a glance

Uno:      D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 A0 A1 A2 A3 A4 A5   (18 of 18 used; D0/D1 left free for USB)
ESP32-C6: 0 1 2 3 6 7 10 11 18 19 20 21 22 23                          (14 GPIOs, strapping pins avoided)

Inter-Board Protocol

ASCII lines terminated by \n, 9600 baud, 8N1.

Uno to ESP32-C6

MessageMeaning
H:1Hello, sent at boot. The ESP32 responds by re-sending configuration.
C:<UIDHEX>RFID card scanned, UID in uppercase hex
D:<out>,<in>Ultrasonic distances in cm (400 means no echo), every 300 ms
I:0 or I:1IR beam state (1 = blocked)
B:1Exit button pressed
S:0 or S:1Lock state (1 = locked), sent on change and as a 5 s heartbeat

ESP32-C6 to Uno

CommandMeaning
GGrant: unlock, start relock timer, success tone
XDeny: red flash and error tone
LLock now
S<n>Play sound n (0 click, 1 success, 2 error, 3 alarm, 4 boot)
R<sec>Set relock time in seconds (1 to 120)
M0 / M1Unmute / mute the buzzer

The ESP32 treats the link as down if nothing is received for 3 seconds.


Software Setup

1. Install tools

  1. Install the Arduino IDE 2.x.
  2. Add the Espressif board URL in Preferences, then install esp32 by Espressif Systems (version 3.0 or newer) from the Boards Manager.
  3. In the Library Manager, install U8g2 and MFRC522.

2. Flash the Uno

  1. Open uno_access_node/uno_access_node.ino.
  2. Select Arduino Uno and its COM port.
  3. Upload. The buzzer plays a boot melody and the red LED lights up (locked).

3. Flash the ESP32-C6

  1. Open esp32c6_seleste_os/esp32c6_seleste_os.ino.
  2. Select ESP32C6 Dev Module and its port. Enable USB CDC On Boot if you want serial logs on the native USB port.
  3. Upload. Both OLEDs show the boot splash, then the launcher appears on OLED A.

The two sketches cannot be merged into one file. The Uno and ESP32 use different architectures and toolchains, so each board gets its own sketch.


First Run and Usage

  1. PIN. The default PIN is 1234. Change it in Settings > Change PIN.
  2. WiFi (captive portal). With no saved credentials, the C6 starts the open hotspot Seleste-Setup. Join it from your phone. The setup page opens automatically (or browse to 192.168.4.1). Choose your network, enter the password and tap Connect.
  3. WiFi (on-device). Open Settings > Join WiFi network, choose a network from the scan list and type the password with the keypad.
  4. Dashboard. Once connected, open http://seleste.local or the IP shown in the Info app.
  5. Add a card. Open Cards (PIN required), choose [+] Add new card and tap a tag on the reader.
  6. Use the door. Tap an enrolled card, press the exit button, or use the Door app or the dashboard.

Keypad Reference

KeyNavigationText entry mode
2UpLetters (multi-tap)
8DownLetters (multi-tap)
4LeftLetters (multi-tap)
6RightLetters (multi-tap)
5 or #OK / select# confirms
*BackBackspace
AHome (any screen)Cancel
BCycle OLED B viewToggle upper/lower case
CLock the door nowToggle abc / 123 mode
DMute / unmute soundShow / hide text

In text entry, digit keys cycle through letters like an old mobile phone: 2 = a, b, c, 2. Key 1 holds punctuation (. , @ - _ ! ? # $ % and &) and 0 holds space and 0.


Applications

IconAppDescriptionPIN
PadlockDoorLive lock state and distances. # opens or closes the lock.To open
CardCardsList, enroll and remove RFID cardsYes
RadarRadarBars for both distances and IR beam stateNo
DocumentLogScrollable event historyNo
PictureWallpaperPick a wallpaper, shown across both screensNo
SnakeSnakeClassic game, speeds up as you scoreNo
GearSettingsWiFi, portal, PIN, relock time, sound, brightness, rebootYes
InfoInfoIP, WiFi name, link status, heap, uptimeNo

A successful PIN entry stays valid for 60 seconds.

Wallpapers

Stars, Rain, Waves, Ball, Grid and Sweep. Each is rendered against a 256x64 virtual canvas. OLED A draws the left half and OLED B the right half, so motion continues seamlessly between screens.

OLED B views (key B)

ViewContent
0Clock, door state, network and last event
1Distance bars, IR state, Uno link status
2Recent events
3Right half of the wallpaper

Web Interface and API

MethodPathDescription
GET/Dashboard (or the WiFi setup page while in portal mode)
GET/setupWiFi setup page
POST/saveSubmit WiFi credentials (ssid, manual, pass)
GET/api/statusJSON status
POST/api/unlockUnlock, form field pin
POST/api/lockLock, form field pin

Example status response:

{
  "locked": 1,
  "d1": 123,
  "d2": 400,
  "ir": 0,
  "link": 1,
  "cards": 3,
  "log": ["14:02 OK 3A9F01C2", "14:00 Door locked"]
}

Three wrong PINs lock out web commands for 30 seconds.


Behaviour Reference

LEDs

LEDMeaning
RedDoor locked (flashes quickly on denied access)
GreenDoor unlocked
YellowCard read, or a person within 40 cm of the outside sensor

Sounds

IDSoundUsed for
0Short clickKey presses, card read
1Rising two-toneAccess granted, card added
2Low descending toneAccess denied, wrong PIN
3Alarm sweepReserved for alerts
4Three rising notesBoot

Door logic

  • After a grant, the door relocks when the timer expires.
  • While the IR beam is blocked, the timer is held, so the door never relocks on someone in the doorway.
  • Once the beam clears after a person passes, the door relocks after about 1.5 s.
  • The servo only receives power while moving and detaches afterwards to avoid jitter and heat.
  • The ESP32 logs a "Visitor" event when the outside sensor reads under 35 cm (rate limited to once per 15 s).

FreeRTOS tasks (ESP32-C6)

TaskPriorityStackPurpose
loop (UI)1defaultKeypad scan, app logic, OLED A rendering
link23 KBParse messages from the Uno
net110 KBWiFi state machine, DNS, web server
oledB14 KBOLED B rendering

Shared state (log, card list, settings) is protected by a recursive mutex.


Project Structure

seleste-access-os/
├── README.md
├── esp32c6_seleste_os/
│   └── esp32c6_seleste_os.ino     # ESP32-C6: OS, UI, WiFi, web, link
└── uno_access_node/
    └── uno_access_node.ino        # Uno: RFID, servo, sensors, buzzer, LEDs

Configuration

Compile-time options at the top of each sketch.

ESP32-C6

DefineDefaultPurpose
OLED2_SHARED_BUS0Set to 1 to put both OLEDs on one I2C bus (second OLED jumpered to address 0x3D)
UTC_OFFSET_SEC3 * 3600Time zone offset for NTP (UTC+3, Nairobi)
OLED_A_*, OLED_B_*, LINK_*, rowPins, colPinssee wiringPin assignments

Uno

ConstantDefaultPurpose
ANG_LOCKED / ANG_OPEN10 / 100Servo angles; adjust to your latch
relockMs5000Default relock time (overridden by the ESP32 setting)

Troubleshooting

SymptomLikely cause and fix
Link shows "DOWN"Check common ground and that TX and RX are crossed. Confirm both sketches use 9600 baud.
Garbled link dataServo motion can disturb SoftwareSerial. Add the 470 uF capacitor and keep the baud at 9600.
An OLED stays blankCheck I2C address (0x3C or 0x3D), SDA/SCL pins, and whether the module is SH1106 instead of SSD1306.
Both OLEDs share one imageYou enabled OLED2_SHARED_BUS but both modules have the same address.
Keypad keys wrongRow and column groups swapped. Swap the two arrays or the wires.
RFID never readsRC522 must be on 3.3V. Recheck SPI wiring (D10 to D13) and RST on D9.
Servo jitters or Uno resetsServo draws surge current. Add the capacitor or power the servo from a separate 5V supply sharing ground.
Setup page does not pop upBrowse manually to 192.168.4.1. Some phones suppress captive pages on networks without internet.
Wrong timeNTP needs WiFi. Check UTC_OFFSET_SEC. Before sync, the clock shows uptime.
Cannot unlock from the webConfirm the PIN. After 3 wrong attempts, wait 30 s.

Security Notes

  • Change the default PIN 1234 before real use.
  • The web dashboard uses plain HTTP and sends the PIN unencrypted. Keep it on a trusted local network, never exposed to the internet.
  • The setup hotspot is open while in portal mode. Complete setup promptly.
  • The ESP32 makes the access decisions. If it is powered off, the Uno denies all cards (no offline fallback yet).
  • RFID UIDs identify tags but can be cloned, so this system suits hobby, lab and light-duty use rather than high-security doors.
  • The servo latch is a prototype mechanism. Do not rely on it for life-safety or fire-exit doors.

Roadmap

  • [ ] Flash the Uno from the ESP32 over serial (STK500) for combined deployments
  • [ ] Offline fallback: a master card stored on the Uno
  • [ ] Per-card names, schedules and access logs in flash
  • [ ] HTTPS or token-based authentication for the dashboard
  • [ ] Card management from the web interface
  • [ ] OTA updates for the ESP32-C6
  • [ ] MQTT integration

License

Released under the MIT License. Add a LICENSE file to your repository to apply it.


Seleste Technologies

Visit project ↗

← All projects