Welcome to the Bird-IoT Platform! This project is designed to run on ESP32-based devices with a highly modular approach. It provides a system of plugins (each derived from a common interface) and a task system that allows easy scheduling and concurrency.
Note: This project is not seeing any regular updates. The intent of this repo is to share the concepts and code within.
Note: Be sure to configure your authorization headers. Look for TODO_ in the codebase
Get the Docker Image
docker pull ajcasagrande/bird-iot:esp-idf-v4.0.2
Build a binary for the ttgo-display
project
./docker-make.sh env=ttgo-display
The Bird-IoT Platform is built around a central IotApp
singleton that:
- Manages device profiles (GPIO pin assignments, hardware specifics).
- Initializes core services (NVS, Wi-Fi, MQTT, safe mode, etc.).
- Registers plugins (each plugin is a self-contained module providing some feature or driver).
- Coordinates tasks (each plugin can schedule its own asynchronous task, e.g. for polling hardware or performing repeated actions).
Key Points:
- Safe Mode: The platform supports a “safe mode” that can limit or bypass certain features if too many failed boots occur.
- Profiles: A JSON or struct-based system describes board-specific pins (e.g., SDA, SCL, servo pins, etc.).
- Startup Flow:
- System initializes
- NVS is set up
- Boot counters are incremented
- Wi-Fi and MQTT connect
- Plugins are setup (configure) and then start (run)
- Tasks begin periodic or event-driven execution
IotPlugin
is the base class. Each plugin:
- Has a name (e.g.
"Console Plugin"
). - Implements any of these optional lifecycle hooks:
bool setup()
— configure, allocate resources, and do any required initialization.bool start()
— start actual execution (e.g. begin tasks, open sockets, etc.).void onWifiConnected()
/onWifiDisconnected()
void onMqttConnected()
/onMqttDisconnected()
bool handleMqttMessage(const char *topic, int id, const char *cmd, const JsonDocument& json)
bool handleCommand(const std::string &cmd)
— for console or remote command handling.void addFramesAndOverlays()
— for UI-based plugins that draw on a display (OLED, TFT, etc.).
Plugin Registration:
- Plugins are registered in
IotApp
viaApp.registerPlugin(...)
. - Once registered, they will be automatically
setup()
andstart()
in the platform’s initialization sequence. - A plugin can also register or spawn its own tasks for scheduling repeated or event-driven actions.
Most plugins rely on TaskClass
(or Task<T>
templated class) to schedule:
- Periodic tasks with intervals (e.g. 1 second).
- Single-shot tasks that run once after an initial delay.
- Paused/resume functionality.
- Pinning to a specific ESP32 core (
coreId
). - Exact timing vs. yield-based timing.
Key APIs in TaskClass
:
configure(interval, stackSize, priority, coreId, useExactTiming, allowDuringOTA, initialDelay)
start() / pause() / resume() / remove()
_executeIfAllowed()
— an internal guard to skip runs if paused or in an OTA update.
Any plugin can:
- Subclass
TaskClass
(or useTask<PluginType>
) to create a periodic job. - Start the task inside
setup()
orstart()
. - Implement the
execute()
routine that is called each time the task fires.
Below is a summary of all major plugins/features discovered in this project. Many are conditioned by #if CONFIG_ENABLE_...
macros in sdkconfig
or Kconfig defaults.
Note: Not all plugins must be enabled at once. Each plugin can be individually toggled via build config to keep firmware lean.
- Macro:
CONFIG_ENABLE_CONSOLE_PLUGIN
- Description: Provides a serial console interface (via UART). It integrates with linenoise for command history, completion, etc.
- Features:
- Supports live command input on serial
- Basic console commands (
help
,meminfo
, etc.) - Ties into the
CommandPlugin
for additional commands
- Macro:
CONFIG_ENABLE_COMMANDS_PLUGIN
- Description: Allows execution of text-based commands from multiple sources (serial console, websockets, MQTT messages, etc.).
- Features:
- Provides a unified
handleCommand(const std::string&)
. - Built-in commands:
help
,tasks
,heap
,ip
,vcc
,reset
,version
, and more. - Simplifies creating new commands in a centralized place.
- Provides a unified
- Macro:
CONFIG_ENABLE_BUTTON_PLUGIN
- Description: Manages button inputs (GPIO pins). Each button is an instance of
IotButton
. - Features:
- Schedules a background task to handle debouncing or event polling
- Allows hooking custom callbacks on button press
- Macro:
CONFIG_ENABLE_SECURE_OTA_PLUGIN
- Description: Handles firmware over-the-air updates, with optional:
- HTTPS downloads
- MD5 or SHA256 checks
- Rollback support and verification
- Workflow:
- On receiving an MQTT message with OTA details, it suspends non-critical tasks, downloads new firmware, and if successful, reboots into the new image.
- Macro:
CONFIG_ENABLE_CAMERA_SERVER_PLUGIN
- Description: Sets up a minimal HTTP server that can serve camera frames or respond to a basic route (
/
).
- Macro:
CONFIG_ENABLE_HTTPS_SERVER_PLUGIN
- Description: Embedded HTTPS server built on httpsserver library.
- Features:
- Creates a self-signed certificate
- Serves static or dynamic content over TLS
- Example handler for root
/
endpoint
- Macro:
CONFIG_ENABLE_BLE_BEACON
- Description: Configures an iBeacon on the ESP32 that broadcasts a custom UUID, major/minor, etc.
- Features:
- Use
btStart()
internally - Advertise indefinite iBeacon data
- Optionally releases BT Classic memory
- Use
- Macro:
CONFIG_ENABLE_BLE_KEYBOARD_PLUGIN
- Description: Emulates a BLE HID keyboard that can send key presses (including media keys).
- Features:
- Uses NimBLE or Bluedroid under the hood
sendKey()
,sendMediaKey()
for controlling remote devices from MQTT commands- Pairing/bonding optional
- Macro:
CONFIG_ENABLE_NIMBLE_PLUGIN
- Description: Enables the NimBLE stack for BLE operations (lightweight alternative to Classic).
- Features:
- Creates a basic iBeacon advertisement
- Hooks into the standard NimBLE host tasks
- Macro:
CONFIG_ENABLE_MDNS_PLUGIN
- Description: Registers an mDNS service so the device can be discovered locally via
deviceid.local
. - Features:
- Optionally includes additional mDNS service TXT records (version, environment, etc.)
- Macro:
CONFIG_ENABLE_I2C_OLED_DISPLAY_PLUGIN
- Description: Manages a SSD1306/SH1106 or similar small OLED display over I2C.
- Features:
- Uses
OLEDDisplayUi
for frames and overlays - Automatic progress bar for e.g. OTA
addFramesAndOverlays()
allows each plugin to draw a portion of the UI
- Uses
- Macro:
CONFIG_ENABLE_SPI_DISPLAY_PLUGIN
- Description: Uses spilcd driver for ST7789 or similar SPI-based LCD.
- Features:
- Screen saver example
- Optional backbuffer
- Rotation settings
- Macro:
CONFIG_ENABLE_TFT_DISPLAY
- Description: Manages a TFT_eSPI-driven display (e.g. ILI9341, ST7735, ST7789).
- Features:
- Basic UI overlays, progress bars, screen saver
- Adjust backlight brightness
- Rotation toggles
- Macro:
CONFIG_ENABLE_GARAGE_PLUGIN
- Description: Simple garage door opener plugin using a relay or GPIO toggle.
- Features:
- MQTT command to
toggle
ordouble_toggle
- Delays to simulate relay press
- MQTT command to
- Macro:
CONFIG_ENABLE_GPS_PLUGIN
- Description: Reads data from an attached GPS (e.g. UART NMEA).
- Features:
- Uses
TinyGPS++
- Maintains average speed, max speed, satellite info
- Can draw overlays on TFT display
- Uses
- Macro:
CONFIG_ENABLE_HOBBYWING_TASK_PLUGIN
- Description: Interacts with a Hobbywing ESC or car ESC over a UART or TTL signal.
- Features:
- Captures raw data frames from ESC
- Example logging of data (RPM, voltage, etc.)
- Macro:
CONFIG_ENABLE_IR_PLUGIN
- Description: Transmits 38kHz IR signals for devices (e.g. TVs or AC).
- Features:
send_nec()
andsend_lg_ac()
examples- Mark/space approach at 38kHz using raw GPIO toggling
- Macro:
CONFIG_ENABLE_RF_RAW_PLUGIN
- Description: Captures raw edges from an RF receiver pin, logs timing for further decode.
- Features:
- Attaches interrupt
- Collects microsecond-level timing for analysis
- Macro:
CONFIG_ENABLE_RF_SNIFFER_PLUGIN
- Description: Uses a 433/315MHz receiving library to decode pulses into recognizable codes (like “RC Switch” style codes).
- Features:
- Publishes discovered codes via MQTT
- Optional raw capturing
- Macro:
CONFIG_ENABLE_RF_TRANSMIT_PLUGIN
- Description: Transmits 433/315MHz codes (like RC Switch).
- Features:
- MQTT command to send a code with a given bit length, pulse length, protocol, sync factors
- Macro:
CONFIG_ENABLE_HUNTER_FAN_PLUGIN
- Description: Sends Hunter Fan-style remote codes via a certain timing pattern on a GPIO.
- Features:
- Supports a specific pulse logic for older Hunter fans
- Macro:
CONFIG_ENABLE_ACURITE_SENSOR_PLUGIN
- Description: Work in progress to decode Acurite temperature/humidity sensor signals in the 433MHz band.
- Macro:
CONFIG_ENABLE_SERVO_INPUT_PLUGIN
- Description: Uses RMT to read servo pulses from an RC receiver.
- Features:
- Multiple channels
- Each channel has a
duty()
or pulse width in microseconds - Notifies changes via a background task
- Macro:
CONFIG_ENABLE_SNTP_PLUGIN
- Description: Synchronizes system time via NTP servers.
- Features:
- Automatic updates after Wi-Fi is connected
- Timezone support, time sync callback
- Can show a time overlay on TFT or OLED
- Macro:
CONFIG_ENABLE_SPIFFS_PLUGIN
- Description: Initializes SPIFFS filesystem.
- Features:
- Formats on failure (if needed)
- Logs total/used space
- Macro:
CONFIG_ENABLE_SECURE_WEBSOCKET_PLUGIN
- Description: Creates an HTTPS server with websocket capability on port 443, using a certificate/key stored in the firmware.
- Features:
- 2-way TLS
- Real-time data over wss
- Macro:
CONFIG_ENABLE_WEBSOCKET_COMMAND_PLUGIN
- Description: Allows receiving console-like commands over a websocket (usually on some port).
- Features:
- Forwards lines to the
CommandPlugin::handleCommand()
- Forwards lines to the
- Macro:
CONFIG_ENABLE_WEBSOCKET_SERVER_PLUGIN
- Description: Similar to above, but might run on a simpler or different port (e.g.
81
or8081
). - Features:
- Echo or dispatch commands
- Maintains a map of connected clients
- Macro:
CONFIG_ENABLE_WEBSOCKET_CLIENT_PLUGIN
- Description: Connects to a remote wss server from the device as a client.
- Features:
- Periodic keepalive
- Data exchange in real-time
- Macro:
CONFIG_ENABLE_CONNECTION_STATS_PLUGIN
- Description: Tracks Wi-Fi and MQTT connect/disconnect events:
- Number of drops
- Time of last connect
- Optionally logs or publishes stats
- Macro:
CONFIG_ENABLE_RSSI_STATS_PLUGIN
- Description: Periodically polls Wi-Fi RSSI (signal strength).
- Features:
- Publishes min / max / average RSSI after a configurable interval
- Optional logs
- Macro:
CONFIG_ENABLE_WIFI_SCAN_PLUGIN
- Description: Scans for nearby APs either on each channel or a single channel, then publishes results over MQTT.
- Features:
- Passive scanning
- Displays SSID, RSSI, channel, auth mode
- Macro:
CONFIG_ENABLE_EXAMPLE_BASIC_PLUGIN
- Description: A minimal plugin example with basic
setup()
/start()
stubs.
- Macro:
CONFIG_ENABLE_EXAMPLE_TASK_PLUGIN
- Description: Demonstrates scheduling an internal
TaskClass
to run periodically.
- Macro:
CONFIG_ENABLE_EXAMPLE_SENSOR_PLUGIN
- Description: Illustrates a sensor plugin pattern with periodic polling and publishing (two tasks):
pollTask
publishTask
This platform is highly modular. Each plugin can be toggled on/off via build flags (CONFIG_ENABLE_*
).
Most plugins use their own TaskClass
for concurrency.
To add a new feature:
- Subclass
IotPlugin
. - Implement
setup()
,start()
, and any relevant hooks. - Optionally create a
Task<YourPlugin>
for periodic or event-based logic. - Register the plugin with
App.registerPlugin(new YourPlugin());
.
Complete IoT platform for the ESP32 using ESP-IDF and FreeRTOS.
- IR
- RF 433mhz / 315mhz
- Hunter Fan Remote
- Temp / Humidity
- LG Portable AC
- Garage Opener
- Door Sensor (Reed Switch) x2
mkdir -p ~/esp
cd ~/esp
git clone --recurse-submodules --branch v4.0.2 [email protected]:espressif/esp-idf.git
cd esp-idf
./install.sh
D0
appears to control an on board RED led on the NodeMCU boardsD4
controls the very bright blue light on the ESP-12E module
Plug one wire into GND
and the other wire into a GPIO pin. D0
and D4
are some known working GPIO pins, however not all GPIO pins will work. Typically the reed switches I have are normally open switches, but this can be confirmed by running the software and checking.
- Not all GPIO pins are good for i2c. Some known good ones are
D1
,D2
,D3
,D4
,D5
,D6
. - Auto detection is supported, just setup the
SDA_PIN
andSCL_PIN
Wire into the Normally Open Pins which is the middle pin and the pin not connected by a white line. First and second pins if looking from the front.
- Auto detect using i2c
- Connect GND, VCC to 5v (Vin), and the Digital Output pin to
D2
on the nodemcu.- From left to right it is:
GND, D2, __, Vin, ................. __, __, __, (optional antenna)
- Create a new basic plugin
make create-plugin name=<...> folder=<...>
- Create a new Task plugin
make create-task-plugin name=<...> folder=<...>
Note: name
should include the suffix of Plugin or whatever.
Note: folder
is the sub-folder within plugins folder the plugin is to be put.
./docker-make.sh
make udev-rules