Control Custom Builds | Operate Projects From Your PolyCast5
Why Custom Builds?
By using just a single other ESP32 microcontroller, you can easily set up your PolyCast5 to send custom commands to it to control literally anything! For example, how about a cool 3D-printed robotic arm?
This works by using PolyCast5's built-in ESP32-C5 brain to utilize ESP-NOW communication. With this low-power, instantaneous communication, you can easily set up any other ESP32-based device to receive PolyCast5 commands and then execute anything on its behalf. Off-the-shelf ESP32s are also super cheap and easy to set up in Arduino IDE, so don't shy away from making something cool!
Setting Up Arduino
To start, you will need Arduino IDE so that you can write and upload code (don't worry, it's easy). The installation can be found here if you don't already have it.
After that, you will need to install the ESP32 boards manager for Arduino. This is needed so that you can build for ESP32 targets. To do this, in Arduino IDE go to File > Preferences > Additional boards manager URL > and paste:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
Then, you can go to the boards manager tab on the left and install "ESP32 by Espressif Systems".
With that, you're good to go!
Examples
To help get you started, below are some basic example programs that can serve as a good starting point for whatever cool things you may want to make! To help, I've also commented out almost every line so you can walk through the code yourself.
But before diving in, whenever you add an ESP32 to your PolyCast5 you will be prompted to enter the receiver MAC address. This is the MAC address of whatever ESP32 you are going to upload this receiver code to, and is needed so the PolyCast5 knows where to send the data. You can grab it in the tutorial below:
The first is a basic no-encryption ESP-NOW receiver program that simply extracts and prints whatever command was sent to it. To build upon this, you can add some basic if statements or anything else to do specific things when a given command is received.
#include <WiFi.h>
#include <esp_now.h>
#define POLYCAST5_MAGIC "PC5: " // Data prefix to filter for PolyCast5 data specifically
volatile unsigned int cmd_received; // Command being received over ESP-NOW from your PolyCast5
// Callback that triggers when data is received
void receive_cb(const esp_now_recv_info_t *info, const uint8_t *data, int len)
{
// Optionally print the sender MAC (PolyCast5)
// Serial.print("From ");
// for (int i = 0; i < 6; i++) {
// Serial.printf("%02X", info->src_addr[i]);
// if (i < 5) Serial.print(":");
// }
// Serial.print(" | ");
// The received data will be in the form "PC5: %u" for filtering
// We need to extract the %u (unsigned int) command
char buf[ESP_NOW_MAX_DATA_LEN]; // Create a buffer to store the data string
// If the received data length is less than the max buffer size, make len the new buffer size
size_t copy_len = len < ESP_NOW_MAX_DATA_LEN - 1 ? len : ESP_NOW_MAX_DATA_LEN - 1;
memcpy(buf, data, copy_len); // Copy len bytes of the data into the buffer
buf[copy_len] = '\0'; // Null-terminate the string
// Try to parse "PC5: %u" out of the string
unsigned int tmp;
if (sscanf(buf, POLYCAST5_MAGIC "%u", &tmp) == 1) { // If success -> data is valid
cmd_received = tmp; // Move the parsed value into global variable
Serial.print("Got: ");
Serial.println(cmd_received); // The command received
}
else { // Data is not valid
Serial.print("Unexpected data: ");
Serial.println(buf);
}
}
void setup() {
// Enable serial terminal
Serial.begin(115200);
// Setup Wi-Fi mode as station for ESP-NOW
WiFi.mode(WIFI_STA);
WiFi.disconnect(); // Disconnect from any APs
// Initialize ESP-NOW itself
if (esp_now_init() != ESP_OK) {
Serial.println("ERROR: ESP-NOW init failed"); // Some debugging
}
// Register the receive callback
esp_now_register_recv_cb(receive_cb);
// Confirm in the terminal
Serial.println("ESP-NOW receiver ready (no encryption)");
}
void loop() {
// Nothing to do here yet (happens in callback)
delay(100);
// You would add extra code here to do something based on the cmd_received variable,
// like control an LED or anything else!
}Be sure to enable 'USB-CDC On Boot' if you're not using a board with a USB-to-UART bridge.
But what if you need something a bit more secure? Not to worry, since PolyCast5 also has an encryption mode that can be selected when adding a new ESP32. The equivalent code example using encryption can be found below!
Note: You will need to fill in your own PolyCast5's MAC address and local master key, which is displayed whenever adding an encrypted ESP32.
#include <WiFi.h>
#include <esp_now.h>
#define POLYCAST5_MAGIC "PC5: " // Data prefix to filter for PolyCast5 data specifically
#define LMK_SIZE 16 // Local Master Key size for ESP-NOW encryption in bytes
#define MAC_SIZE 6 // PolyCast5 MAC address size in bytes
// 16-byte local master key for encryption
// !CHANGE THIS TO THE GENERATED KEY SHOWN ON POLYCAST5
static const uint8_t local_master_key[LMK_SIZE] = {
0xEB, 0xFA, 0xE9, 0xBE, 0xCA, 0xC4, 0x65, 0xB0,
0xC7, 0x5A, 0xE3, 0x59, 0xD3, 0x8B, 0x4D, 0x2B
};
// MAC address of the sender
// !CHANGE THIS TO THE DEVICE MAC SHOWN ON POLYCAST5
static const uint8_t polycast5_mac[MAC_SIZE] = {0xD0, 0xCF, 0x13, 0xE0, 0xA7, 0x2C};
volatile unsigned int cmd_received; // Command being received over ESP-NOW from your PolyCast5
// Callback that triggers when data is received
void receive_cb(const esp_now_recv_info_t *info, const uint8_t *data, int len)
{
// Optionally print the sender MAC (PolyCast5)
// Serial.print("From ");
// for (int i = 0; i < MAC_SIZE; i++) {
// Serial.printf("%02X", info->src_addr[i]);
// if (i < MAC_SIZE - 1) Serial.print(":");
// }
// Serial.print(" | ");
// The received data will be in the form "PC5: %u" for security purposes
// We need to extract the %u (unsigned int) command
char buf[ESP_NOW_MAX_DATA_LEN]; // Create a buffer to store the data string
// If the received data length is less than the max buffer size, make len the new buffer size
size_t copy_len = len < ESP_NOW_MAX_DATA_LEN - 1 ? len : ESP_NOW_MAX_DATA_LEN - 1;
memcpy(buf, data, copy_len); // Copy len bytes of the data into the buffer
buf[copy_len] = '\0'; // Null-terminate the string
// Try to parse "PC5: %u" out of the string
unsigned int tmp;
if (sscanf(buf, POLYCAST5_MAGIC "%u", &tmp) == 1) { // If success -> data is valid and the command is now stored in cmd_received
cmd_received = tmp;
Serial.print("Got: ");
Serial.println(cmd_received); // The command received
}
else { // Data is not valid
Serial.print("Unexpected data: ");
Serial.println(buf);
}
}
void setup() {
// Enable serial terminal
Serial.begin(115200);
// Setup Wi-Fi mode as station for ESP-NOW
WiFi.mode(WIFI_STA);
WiFi.disconnect(); // Disconnect from any APs
// Initialize ESP-NOW itself
if (esp_now_init() != ESP_OK) {
Serial.println("ERROR: ESP-NOW init failed"); // Some debugging
}
// Setup the peer with encryption enabled and the custom LMK (local master key)
esp_now_peer_info_t peerInfo = {0};
memcpy(peerInfo.peer_addr, polycast5_mac, MAC_SIZE); // Copy over the MAC
peerInfo.channel = 1; // Must match PolyCast5 Wi-Fi channel
peerInfo.encrypt = true; // Enable encryption
memcpy(peerInfo.lmk, local_master_key, LMK_SIZE); // Copy over your shared LMK
// Add the peer
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("ERROR: esp_now_add_peer failed"); // Some debugging
}
// Register the receive callback
esp_now_register_recv_cb(receive_cb);
// Confirm in the terminal
Serial.println("ESP-NOW receiver ready (encrypted)");
}
void loop() {
// Nothing to do here yet (happens in callback)
delay(100);
// You would add extra code here to do something based on the cmd_received variable,
// like control an LED or anything else!
}Epic! Now any data you transmit will be encrypted with AES-128-CCM.
I want to point out this makes it basically impossible to hack with the exception of physical access (dump to get the LMK) and replay attacks. A monotonic counter to negate this can be easily added but isn't included in the example sketch for simplicity. Additionally, enabling flash encryption and secure boot will fix the physical access problem.
After this basic communication is set up, you can use it to control literally anything by utilizing the power of the receiving ESP32 microcontroller. All ESP32s have broken out pins that allow you to connect various peripherals and devices.
If you're looking for some inspiration, pretty much every project I've ever built uses an ESP32 and therefore can be directly controlled by PolyCast5. Check some out below.
There is even an article on building ESP32s from scratch if you want to go even further.
But what if you want to control something that doesn't use ESP32? Not to worry! You can simply connect the receiving ESP32 to whatever it is through its built-in UART/SPI/etc. Since they cost only a couple of dollars and can be programmed in Arduino, it's no sweat!
You can also always use PolyCast5's built-in mile-long LoRa technology to communicate with anything via PolyPlug GPIOs should that be more suitable. PolyPlugs also have AES-128-CCM out-of-the-box with replay protection. This is the same concept as using an ESP32 but with a higher-power wireless technology (range is 1km instead of 100m).
Happy casting!