It’s funny how once the project works, after a brief break it’s immediately on to the next project with no longer thinking about the previous one. This project never got the final and most important update: the code and the success videos.
Let’s do success videos first. The full episode:
And JUST the finale (though if you’ve made it this far, watching the video above and doing the like-and-subscriby thing gives me dopamine that helps me make more of these):
Let’s be clear, I have not shared code (outside of code-snaippets on niche home automation forms) since I was in college, when the Internet had promise. With that, let’s walk through how this works.
I used C++ (I think technically different than Arduino C ) in Visual Studio Code using PlatformIO. Let’s be clear, I feel really cool typing the above sentence because three months ago that would have meant nothing to me. Why did I use that approach? I’m trying to recall right now… going to check what I wrote and see if I can come up with a reasonable explanation…
All right, after a quick read through, I am pretty sure I chose VS code over the Arduino IDE a) to feel cool for using VS code, b) to expand my skill set, and c) because I needed multiple different chips on different comports talking to each other and I was having some difficulty doing that with the Arduino IDE.
So with no further ado, I present what I wrote that should be mortifying to any real programmer or software developer especially, but it worked for me.
/*
v5: instead of broadcasting to all, i iterate over it and it works fine now
*/
#include <Arduino.h>
#include <esp_now.h>
#include <WiFi.h>
#include <algorithm>
#include <BBQ10Keyboard.h>
#include <iostream>
#include <string>
#define CHANNEL 0
#define PRINTSCANRESULTS 0
#define NUMSLAVES 7
esp_now_peer_info_t slaves[NUMSLAVES] = {};
BBQ10Keyboard keyboard;
int SlaveCnt = 0;
//led onboard
const int ledPin = 2; // the number of the LED pin
// Set up tactile buttons
struct Button {
const int pin;
bool currentState;
bool prevState;
};
//I am not using these but they are here for redundancy
String burnerMac[6] = {"94:3C:C6:33:99:24",
"08:B6:1F:34:A5:50",
"08:B6:1F:3B:53:1C",
"C8:F0:9E:74:E1:A0",
"08:B6:1F:3D:22:70",
"C8:F0:9E:50:7D:40"};
/*
1: Slave:C8:F0:9E:74:E1:A0 [C8:F0:9E:74:E1:A1] (-48) burner[3]
2: Slave:08:B6:1F:34:A5:50 [08:B6:1F:34:A5:51] (-51) burner[1]
5: Slave:C8:F0:9E:50:7D:40 [C8:F0:9E:50:7D:41] (-55) burner[5]
6: Slave:08:B6:1F:3B:53:1C [08:B6:1F:3B:53:1D] (-56) burner[2]
7: Slave:94:3C:C6:33:99:24 [94:3C:C6:33:99:25] (-61) burner[0]
8: Slave:08:B6:1F:3D:22:70 [08:B6:1F:3D:22:71] (-65) burner[4]
*/
const int numButtons = 6;
Button buttons[numButtons] = {
{4, false, false},
{16, false, false},
{17, false, false},
{5, false, false},
{18, false, false},
{19, false, false}
};
//timers for ScanSlave and ManageSlave
unsigned long scanTimer = 600000;
unsigned long lastTimer = 0;
//below is the basic transmission payload that I determine
//This worked but
typedef struct cmd_struct {
uint8_t burnerNumbers[6];
bool fire[6];
uint8_t igniterStartDelays[6]; //IMPORTANT: uint8_t is 1 byte, int is 4 bytes. Max value is 255, so I will have to multiply it by 10 on the client side
uint8_t igniterEndDelays[6];
uint8_t solenoidEndDelays[6];
} cmd_struct;
typedef struct payload_struct {
uint8_t burnerNumbers[6];
bool fire[6];
uint8_t igniterStartDelays[6]; //IMPORTANT: uint8_t is 1 byte, int is 4 bytes. Max value is 255, so I will have to multiply it by 10 on the client side
uint8_t igniterEndDelays[6];
uint8_t solenoidEndDelays[6];
} payload_struct;
payload_struct* payload;
// Init ESP Now with fallback
void InitESPNow() {
WiFi.disconnect();
if (esp_now_init() == ESP_OK) {
Serial.println("ESPNow Init Success");
}
else {
Serial.println("ESPNow Init Failed");
// Retry InitESPNow, add a counte and then restart?
// InitESPNow();
// or Simply Restart
ESP.restart();
}
}
// Scan for slaves in AP mode
void ScanForSlave() {
int8_t scanResults = WiFi.scanNetworks(); //returns an int8_t of compatible devices (e.g. in AP mode)
//reset slaves
memset(slaves, 0, sizeof(slaves));
SlaveCnt = 0;
Serial.println("");
if (scanResults == 0) {
Serial.println("No WiFi devices in AP Mode found");
} else {
Serial.print("Found "); Serial.print(scanResults); Serial.println(" devices ");
for (int i = 0; i < scanResults; ++i) {
// Print SSID and RSSI for each device found
String SSID = WiFi.SSID(i);
int32_t RSSI = WiFi.RSSI(i);
String BSSIDstr = WiFi.BSSIDstr(i);
if (PRINTSCANRESULTS) { //initialized to 0
Serial.print(i + 1); Serial.print(": "); Serial.print(SSID); Serial.print(" ["); Serial.print(BSSIDstr); Serial.print("]"); Serial.print(" ("); Serial.print(RSSI); Serial.print(")"); Serial.println("");
}
delay(10);
// Check if the current device starts with `Slave`
if (SSID.indexOf("Slave") == 0) {
// SSID of interest
Serial.print(i + 1); Serial.print(": "); Serial.print(SSID); Serial.print(" ["); Serial.print(BSSIDstr); Serial.print("]"); Serial.print(" ("); Serial.print(RSSI); Serial.print(")"); Serial.println("");
// Get BSSID => Mac Address of the Slave
int mac[6];
if ( 6 == sscanf(BSSIDstr.c_str(), "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5] ) ) {
for (int ii = 0; ii < 6; ++ii ) {
slaves[SlaveCnt].peer_addr[ii] = (uint8_t) mac[ii];
} // it appears the variable slaves[i].peer_addr = an array of 6 hexidecimal numbers
}
slaves[SlaveCnt].channel = CHANNEL; // pick a channel
slaves[SlaveCnt].encrypt = 0; // no encryption
SlaveCnt++;
}
}
}
if (SlaveCnt > 0) {
Serial.print(SlaveCnt); Serial.println(" Slave(s) found, processing..");
} else {
Serial.println("No Slave Found, trying again.");
}
// clean up ram
WiFi.scanDelete();
}
// Check if the slave is already paired with the master.
// If not, pair the slave with master
void manageSlave() {
if (SlaveCnt > 0) {
for (int i = 0; i < SlaveCnt; i++) {
Serial.print("Processing: ");
for (int ii = 0; ii < 6; ++ii ) { //purely for outputting mac addresses of slaves; Slave[i] is int, Slave[i].peeraddr is mac in an array[6]
Serial.print((uint8_t) slaves[i].peer_addr[ii], HEX);
if (ii != 5) Serial.print(":");
}
Serial.print(" Status: ");
// check if the peer exists
bool exists = esp_now_is_peer_exist(slaves[i].peer_addr);
if (exists) {
// Slave already paired.
Serial.println("Already Paired");
} else {
// Slave not paired, attempt pair
esp_err_t addStatus = esp_now_add_peer(&slaves[i]);
if (addStatus == ESP_OK) {
// Pair success
Serial.println("Pair success");
//THIS IS WHERE I SHOULD WRITE IT TO EEPROM, and on the SLAVE SIDE I SHOULD DO THE SAME
} else if (addStatus == ESP_ERR_ESPNOW_NOT_INIT) {
// How did we get so far!!
Serial.println("ESPNOW Not Init");
} else if (addStatus == ESP_ERR_ESPNOW_ARG) {
Serial.println("Add Peer - Invalid Argument");
} else if (addStatus == ESP_ERR_ESPNOW_FULL) {
Serial.println("Peer list full");
} else if (addStatus == ESP_ERR_ESPNOW_NO_MEM) {
Serial.println("Out of memory");
} else if (addStatus == ESP_ERR_ESPNOW_EXIST) {
Serial.println("Peer Exists");
} else {
Serial.println("Not sure what happened");
}
//delay(100);
}
}
} else {
// No slave found to process
Serial.println("No Slave found to process");
}
}
void sendData(const uint8_t* burnerNumbers, const bool* fire, const uint8_t* igniterStartDelays, const uint8_t* igniterEndDelays,const uint8_t* solenoidEndDelays) {
cmd_struct cmd;
std::copy(burnerNumbers, burnerNumbers+6, cmd.burnerNumbers);
std::copy(fire, fire+6, cmd.fire);
std::copy(igniterStartDelays, igniterStartDelays+6, cmd.igniterStartDelays);
std::copy(igniterEndDelays, igniterEndDelays+6, cmd.igniterEndDelays);
std::copy(solenoidEndDelays, solenoidEndDelays+6, cmd.solenoidEndDelays);
//esp_err_t result = esp_now_send(peer_addr, &test, sizeof(test));
for(int i=0; i<SlaveCnt; i++){ //QUESTION: is slaves[i] same as cmd.burnerNumbers[i]
esp_err_t result = esp_now_send(slaves[i].peer_addr, (uint8_t *) &cmd, sizeof(cmd_struct)); //peer_addr replaced with 0 for all
Serial.print("Send Status: ");
if (result == ESP_OK) {
Serial.println("Success");
} else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
Serial.println("ESPNOW not Init.");
} else if (result == ESP_ERR_ESPNOW_ARG) {
Serial.println("Invalid Argument");
} else if (result == ESP_ERR_ESPNOW_INTERNAL) {
Serial.println("Internal Error");
} else if (result == ESP_ERR_ESPNOW_NO_MEM) {
Serial.println("ESP_ERR_ESPNOW_NO_MEM");
} else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
Serial.println("Peer not found.");
} else {
Serial.println("Not sure what happened");
}
}
}
// callback when data is sent from Master to Slave
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
/*char macStr[18];
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.print("Last Packet Sent to: "); Serial.println(macStr);
Serial.print("Last Packet Send Status: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
*/
}
bool wasClicked(int x){
if(!buttons[x].currentState && buttons[x].prevState){
return true;
}
else
return false;
}
// callback when data is recv from Master
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
char macStr[18];
payload = (payload_struct*) data;
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
// DEBUG
Serial.print("Last Packet Recv from: "); Serial.println(macStr);
}
void setup() {
Serial.begin(115200);
delay(1000);
WiFi.mode(WIFI_STA);
Serial.println("ESPNow ***MASTER***");
Serial.print("STA MAC: "); Serial.println(WiFi.macAddress());
Wire.begin();
keyboard.begin();
keyboard.setBacklight(0.5f);
InitESPNow();
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
// DECLARE IO PINS
pinMode(ledPin, OUTPUT);
for(int i=0; i<6; i++)
pinMode(buttons[i].pin, INPUT_PULLDOWN);
//Initial scan
ScanForSlave(); // I need to not do this repeatedly and add a timer
if (SlaveCnt > 0) // check if slave channel is defined
manageSlave(); // THIS TAKES ABOUT 6 SECONDS AND SHOULD NOT BE CALLED IN THE LOOOP UNLESS NECESSARY
Serial.println("Setup complete");
}
void loop() {
const int keyCount = keyboard.keyCount();
uint8_t burnerNumbers[6] = {0, 1, 2, 3, 4, 5};
bool fire[6] = {0, 0, 0, 0, 0, 0};
uint8_t igniterStartDelays[6] = {0, 0, 0, 0, 0, 0}; //IMPORTANT: uint8_t is 1 byte, int is 4 bytes. Max value is 255, so I will have to multiply it by 10 on the client side
uint8_t igniterEndDelays[6] = {1, 1, 1, 1, 1, 1};
uint8_t solenoidEndDelays[6] = {2, 2, 2, 2, 2, 2,};
for(int i=0; i<6; i++){
buttons[i].currentState = digitalRead(buttons[i].pin);
if (buttons[i].currentState == LOW) digitalWrite(ledPin, LOW);
else digitalWrite(ledPin, HIGH);
}
if(millis()-lastTimer > scanTimer){
lastTimer = millis();
ScanForSlave(); // I need to not do this repeatedly and add a timer
if (SlaveCnt > 0) // check if slave channel is defined
manageSlave(); // THIS TAKES ABOUT 6 SECONDS AND SHOULD NOT BE CALLED IN THE LOOOP UNLESS NECESSARY
}
//check who got clicked
for(uint8_t i=0; i<6; i++){
if(wasClicked(i)){
fire[i] = 1;
igniterStartDelays[i] = 50; // MAKE SURE TO MULTIPLY THESE x10 in the SLAVE CODE
igniterEndDelays[i] = 4;
solenoidEndDelays[i] = 5;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
Serial.printf("Button %d was clicked\n", i);
}
}
for(int i=0; i<6; i++)
buttons[i].prevState = buttons[i].currentState;
if (keyCount != 0){
uint8_t burnerNumbers[6] = {0, 1, 2, 3, 4, 5};
bool fire[6] = {0, 0, 0, 0, 0, 0};
uint8_t igniterStartDelays[6] = {0, 0, 0, 0, 0, 0}; //IMPORTANT: uint8_t is 1 byte, int is 4 bytes. Max value is 255, so I will have to multiply it by 10 on the client side
uint8_t igniterEndDelays[6] = {1, 1, 1, 1, 1, 1};
uint8_t solenoidEndDelays[6] = {2, 2, 2, 2, 2, 2,};
const BBQ10Keyboard::KeyEvent key = keyboard.keyEvent();
if (key.state == BBQ10Keyboard::StatePress){
Serial.print(key.key); Serial.println(" pressed");
switch(key.key){
case 'w': //burner 0, small poof
fire[0] = 1;
igniterStartDelays[0] = 0;
igniterEndDelays[0] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[0] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 's': //burner 0, bigger poof
fire[0] = 1;
igniterStartDelays[0] = 10; //eg 200
igniterEndDelays[0] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[0] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'z': //burner 0, biggest poof
fire[0] = 1;
igniterStartDelays[0] = 0; //eg 200
igniterEndDelays[0] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[0] = 0;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
/////////////////////////////////////////////////////////////////////////////
case 'e': //burner 1, small poof
fire[1] = 1;
igniterStartDelays[1] = 0;
igniterEndDelays[1] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[1] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'd': //burner 1, bigger poof
fire[1] = 1;
igniterStartDelays[1] = 10; //eg 200
igniterEndDelays[1] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[1] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'x': //burner 1, biggest poof
fire[1] = 1;
igniterStartDelays[1] = 0; //eg 200
igniterEndDelays[1] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[1] = 0;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
/////////////////////////////////////////////////////////////////////////////////////
case 'r': //burner 2, small poof
fire[2] = 1;
igniterStartDelays[2] = 0;
igniterEndDelays[2] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[2] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'f': //burner 2, bigger poof
fire[2] = 1;
igniterStartDelays[2] = 10; //eg 200
igniterEndDelays[2] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[2] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'c': //burner 2, biggest poof
fire[2] = 1;
igniterStartDelays[2] = 0; //eg 200
igniterEndDelays[2] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[2] = 0;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
Serial.println("w pressed");
break;
/////////////////////////////////////////////////////////////////////////////////////
case 't': //burner 3, small poof
fire[3] = 1;
igniterStartDelays[3] = 0;
igniterEndDelays[3] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[3] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'g': //burner 3, bigger poof
fire[3] = 1;
igniterStartDelays[3] = 10; //eg 200
igniterEndDelays[3] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[3] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'v': //burner 3, biggest poof
fire[3] = 1;
igniterStartDelays[3] = 0; //eg 200
igniterEndDelays[3] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[3] = 0;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
//////////////////////////////////////////////////////////////////////////
case 'y': //burner 4, small poof
fire[4] = 1;
igniterStartDelays[4] = 0;
igniterEndDelays[4] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[4] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'h': //burner 4, bigger poof
fire[4] = 1;
igniterStartDelays[4] = 10; //eg 200
igniterEndDelays[4] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[4] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'b': //burner 4, biggest poof
fire[4] = 1;
igniterStartDelays[4] = 0; //eg 200
igniterEndDelays[4] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[4] = 0;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
///////////////////////////////////////////////////////////////////////////
case 'u': //burner 5, small poof
fire[5] = 1;
igniterStartDelays[5] = 0;
igniterEndDelays[5] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[5] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'j': //burner 5, bigger poof
fire[5] = 1;
igniterStartDelays[5] = 10; //eg 200
igniterEndDelays[5] = 10; //remember these ge 10x when the client finally receives them
solenoidEndDelays[5] = 10;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'n': //burner 5, biggest poof
fire[5] = 1;
igniterStartDelays[5] = 0; //eg 200
igniterEndDelays[5] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[5] = 0;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
//////////////////////// DUAL BURNERS !!!!!////////////////////////////
case 'i': //burner 1 and 2 safe poof
fire[0] = 1;
igniterStartDelays[0] = 0; //eg 200
igniterEndDelays[0] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[0] = 0;
fire[1] = 1;
igniterStartDelays[1] = 0; //eg 200
igniterEndDelays[1] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[1] = 0;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'k': //burner 2 and 3 safe poof
fire[2] = 1;
igniterStartDelays[2] = 0; //eg 200
igniterEndDelays[2] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[2] = 0;
fire[3] = 1;
igniterStartDelays[3] = 0; //eg 200
igniterEndDelays[3] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[3] = 0;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
case 'm': //burner 2 and 3 safe poof
fire[4] = 1;
igniterStartDelays[4] = 0; //eg 200
igniterEndDelays[4] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[4] = 0;
fire[5] = 1;
igniterStartDelays[5] = 0; //eg 200
igniterEndDelays[5] = 20; //remember these ge 10x when the client finally receives them
solenoidEndDelays[5] = 0;
sendData(burnerNumbers, fire, igniterStartDelays, igniterEndDelays, solenoidEndDelays);
break;
}
}
}
}
This uses the ESPNow library to set up a master/helper (I think master is still defensible to keep around but support using alternate words, in this case, “helper,” is reasonable) protocol between one master ESP32 and several helper ESP32’s. Don’t get me wrong, I am sure there are many things about this code that can be optimized. For instance, in the library distribution, the mastership stops to scan for SSID’s of potential helper chips every minute. As an interrupt. As you might imagine, that is problematic when it happens precisely during the moment when the solenoid valve is open and flame is erupting from a canister (there are other fact patterns that can have an equally terrifying result), but this one was pretty easily remedied by changing the discovery times).
I don’t want to get really into the code, because I’m the last person you should be explaining good programming practices to anybody, but I found it really helpful hard coding the Mac address of the different helper chips; cutting down on misfires from coding bugs was pretty important, and it’s not like I’m swapping in and out new flame ejecting devices on a regular basis.
The helper code:
/*
homegreeter2000 slafe v5
*/
/*Home Greeter receiver- big problem, fr reasons unclear, it's missing logic to turn shit off*/
#include <Arduino.h>
#include <esp_now.h>
#include <WiFi.h>
#include <iostream>
#include <string>
#define CHANNEL 0
#define LEDPIN 2
#define SOLENOIDPIN 4
#define IGNITERPIN 5
#define FIREPIN 33
//when below is true, the main loop will execute the firing logic
bool runFire = false;
// THESE SHOULD BE ADJUSTED FOR OPTIMAL COMBUSTION
/*unsigned long solenoidDelay = 0;
unsigned long igniterDelay = 250; //1000 = 1 second
unsigned long solenoidRunTime = 500;
unsigned long igniterRunTime = 250;*/
//globals for how long to run the fire routine
int igniterStartDelay;
int igniterEndDelay;
int solenoidEndDelay;
//flags for the loop
bool igniterIsOn = false;
bool solenoidIsOn = false;
bool fireStarted = false;
unsigned long startTime;
//below is the basic transmission payload that I determine
typedef struct payload_struct {
uint8_t burnerNumbers[6];
bool fire[6];
uint8_t igniterStartDelays[6]; //IMPORTANT: uint8_t is 1 byte, int is 4 bytes. Max value is 255, so I will have to multiply it by 10 on the client side
uint8_t igniterEndDelays[6];
uint8_t solenoidEndDelays[6];
} payload_struct;
payload_struct* payload;
//array of mac address Strings associated with burners
String burnerMac[6] = {"94:3C:C6:33:99:24",
"08:B6:1F:34:A5:50",
"08:B6:1F:3B:53:1C",
"C8:F0:9E:74:E1:A0",
"08:B6:1F:3D:22:70",
"C8:F0:9E:50:7D:40"};
// Init ESP Now with fallback
void InitESPNow() {
WiFi.disconnect();
if (esp_now_init() == ESP_OK) {
Serial.println("ESPNow Init Success");
}
else {
Serial.println("ESPNow Init Failed");
// Retry InitESPNow, add a counte and then restart?
// InitESPNow();
// or Simply Restart
ESP.restart();
}
}
// config AP SSID
void configDeviceAP() {
String Prefix = "Slave:";
//String Mac = WiFi.macAddress(); // i made this global for easy access
String Mac = WiFi.macAddress();
String SSID = Prefix + Mac;
String Password = "123456789";
bool result = WiFi.softAP(SSID.c_str(), Password.c_str(), CHANNEL, 0);
if (!result) {
Serial.println("AP Config failed.");
} else {
Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
}
}
// callback when data is recv from Master
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
char macStr[18];
payload = (payload_struct*) data;
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
// DEBUG
//Serial.print("Last Packet Recv from: "); Serial.println(macStr);
//solenoidStart always = 0
//NEW CODE FOR NEW PAYLOAD 5/3/2023
for(int i=0; i<6; i++){
Serial.print("payload->fire["); Serial.print(i); Serial.print("]: "); Serial.println(payload->fire[i]);
Serial.print("WiFi.macAddress()==burnerMac["); Serial.print(i); Serial.print("]: "); Serial.println(WiFi.macAddress()==burnerMac[i]);
if(payload->fire[i] == true && WiFi.macAddress()==burnerMac[i]){
//burner i is instructed to fire, check if burnerMac[i] is this device's mac
//put timers in global variables igniterStartDelay, igniterEndDelay, solenoidEndDelay
igniterStartDelay = payload->igniterStartDelays[i]*10;
igniterEndDelay = payload->igniterEndDelays[i]*10;
solenoidEndDelay = payload->solenoidEndDelays[i]*10;
Serial.print ("Last Packet Recv:\n");
Serial.print("Bool Fire: "); Serial.println(payload->fire[i]);
Serial.print("Int burnerNumber: "); Serial.println(payload->burnerNumbers[i]);
runFire = true;
}
}
Serial.println("");
}
void setup() {
/*DynamicJsonDocument configJson(1024);*/
pinMode(IGNITERPIN, OUTPUT);
digitalWrite(IGNITERPIN, LOW);
pinMode(LEDPIN, OUTPUT);
pinMode(SOLENOIDPIN, OUTPUT);
pinMode(FIREPIN, OUTPUT);
digitalWrite(SOLENOIDPIN, LOW);
Serial.begin(115200);
delay(1000);
Serial.println("ESPNow ***SLAVE***");
//Set device in AP mode to begin with
WiFi.mode(WIFI_AP);
// configure device AP mode
configDeviceAP();
// This is the mac address of the Slave in AP Mode
Serial.print("AP MAC: "); Serial.println(WiFi.softAPmacAddress());
Serial.print("Actual MAC: "); Serial.println(WiFi.macAddress());
// Init ESPNow with a fallback logic
InitESPNow();
esp_now_register_recv_cb(OnDataRecv);
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info.
/*pinMode(LEDPIN, OUTPUT);
pinMode(SOLENOIDPIN, OUTPUT);
pinMode(IGNITERPIN, OUTPUT);
pinMode(FIREPIN, OUTPUT);
esp_now_register_recv_cb(OnDataRecv);
//setup relay controllers here
digitalWrite(SOLENOIDPIN, LOW);
digitalWrite(IGNITERPIN, LOW);*/
Serial.println("Setup complete");
}
void loop() {
if(runFire){
//noInterrupts();
Serial.println("Fire Started");
digitalWrite(FIREPIN, HIGH); //D33
digitalWrite(LEDPIN, HIGH); //D2
digitalWrite(SOLENOIDPIN, HIGH); //D4
Serial.println("Solenoid On");
delay(igniterStartDelay);
digitalWrite(IGNITERPIN, HIGH); //D5
Serial.println("Igniter On");
delay(igniterEndDelay);
digitalWrite(IGNITERPIN, LOW);
Serial.println("Igniter Off");
delay(solenoidEndDelay);
digitalWrite(SOLENOIDPIN, LOW);
Serial.println("Solenoid Off");
runFire = false; //reset all the flags to off
digitalWrite(FIREPIN, LOW); //D33
digitalWrite(LEDPIN, LOW); //D2
Serial.println("Fire Done");
}
}
I think it’s important to share that pretty much all of the master/helper code I used was from the good people at http://randomnerdtutorials.com, and they were pretty responsive when I ran into an issue with having more than four helpers.
Dear reader, having made it to this point, you have my sincere thanks.