//
// TRASMISSIONE
//
#include <PubSubClient.h>
#include <WiFi.h>
#include <string.h>
#include <time.h>
#include <DHTesp.h>
#define pin_beacon_received 12
// ********* DHT 22 *************
#define DHT_PIN 15
DHTesp dht; // obgect of the DHT sernsor
char stringHumidity[10];
bool value_published = false;
// ******************************
// *********** MQTT *************
#define MQTT_TOPIC "/polimi/iot/2023/10534945"
#define MQTT_CLIENT_ID "IoT_BruniPegurri_1" // oppure IoT_BruniPegurri_1
#define MQTT_CLIENT_ID_WO_number "IoT_BruniPegurri_"
#define MQTT_BROKER "broker.hivemq.com"
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
typedef enum{
MQTT_START, // waiting beacon message
MQTT_REG_REQ, // send registration request
MQTT_WAIT_ACK, // waiting receive ACK
MQTT_NORMAL_OP // extract & publish humidity value
}MQTT_e;
MQTT_e MQTT_handler;
String MQTT_payload;
bool new_mqtt_mex = false;
bool reconnecting = false;
// ******************************
// ********** TIMER *************
volatile int counter = 0; // counter increase every 1ms -> used to sincronize
hw_timer_t *mytimer = NULL;
// ******************************
// *********** CFP **************
int frameDuration;
int collisionAccessPartDuration;
int collisionFreePartDuration;
int slotDuration;
int slot = -5; // default
int beaconInterval;
// ******************************
// ****** CSMA-CA variable ******
bool registered = false; // false = not yet registered at PANC
int reg_slot = 0; // random slot in wich send reg
// ******************************
bool sleeping = false;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void IRAM_ATTR timer_func(){ // trigger if timer expires every 1ms
counter++;
}
void MQTTcallback(char* topic, byte* payload, unsigned int length) {
if(reconnecting == false && sleeping == false && value_published == false){
new_mqtt_mex = true;
MQTT_payload = ""; // global string where we save the payload
for (int i=0;i<length;i++) {
MQTT_payload += (char)payload[i];
}
}
reconnecting = false;
}
void reconnect(){
while (!mqttClient.connected()) {
Serial.println("Connecting to MQTT broker...");
if (mqttClient.connect(MQTT_CLIENT_ID)) {
Serial.println("MQTT connected");
mqttClient.subscribe(MQTT_TOPIC);
} else {
Serial.print("MQTT failed, rc=");
Serial.println(mqttClient.state());
delay(5000);
}
}
}
void extract_beacon(String beacon){
String message = beacon;
String frameDuration_str = "";
String collisionAccessPartDuration_str = "";
String collisionFreePartDuration_str = "";
String slotDuration_str = "";
String beaconInterval_str = "";
char id_str[20];
memset(id_str,'\0',sizeof(id_str));
int count_comma = 0;
int start_new_field = 0;
for(int i=0; i<message.length(); i++){
if(message[i] == ','){
count_comma++;
i++; // -> delete comma
start_new_field = i;
}
if(count_comma == 0){
frameDuration_str += message[i];
}
else if(count_comma == 1){
collisionAccessPartDuration_str += message[i];
}
else if(count_comma == 2){
collisionFreePartDuration_str += message[i];
}
else if(count_comma == 3){
slotDuration_str += message[i];
}
else if(count_comma == 4){
beaconInterval_str += message[i];
}
else if(count_comma >=5){
id_str[i-start_new_field] = message[i];
}
if(strcmp(id_str, MQTT_CLIENT_ID) == 0){
slot = count_comma -5 ; // position - offset
break; // jump out for loop
}
else{
slot = -5;
}
}
frameDuration = atoi(frameDuration_str.c_str());
collisionAccessPartDuration = atoi(collisionAccessPartDuration_str.c_str());
collisionFreePartDuration = atoi(collisionFreePartDuration_str.c_str());
slotDuration = atoi(slotDuration_str.c_str());
beaconInterval = atoi(beaconInterval_str.c_str());
}
void setup(){
Serial.begin(115200);
// *********** I/O setup *******************
dht.setup(DHT_PIN, DHTesp::DHT22);
pinMode(pin_beacon_received,OUTPUT); // used for debugging
// *****************************************
// ********** WIFI setup *******************
WiFi.mode(WIFI_STA);
WiFi.begin("Wokwi-GUEST",""); // SSID, PSW
Serial.print("Connecting to WiFi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(50);
}
Serial.println("WiFi connected");
// *****************************************
// ********** MQTT setup *******************
mqttClient.setServer(MQTT_BROKER, 1883);
mqttClient.setCallback(MQTTcallback);
while (!mqttClient.connected()) {
Serial.println("Connecting to MQTT broker...");
if (mqttClient.connect(MQTT_CLIENT_ID)) {
Serial.println("MQTT connected");
mqttClient.subscribe(MQTT_TOPIC);
} else {
Serial.print("MQTT failed, rc=");
Serial.println(mqttClient.state());
delay(5000);
}
}
// *****************************************
// ******** counter TIMER setup ************
mytimer = timerBegin(1,80,true); // using timer 1, (clk_rate/time_div)=(80MHz/80), true = timer increment
timerAttachInterrupt(mytimer, &timer_func, true);
timerAlarmWrite(mytimer, 1000, true); // true= periodic or one_shot timer
timerAlarmEnable(mytimer); // start timer
// *****************************************
MQTT_handler = MQTT_START;
Serial.println("*** TX-ESP READY ***");
}
void loop(){
switch(MQTT_handler)
{
case MQTT_START: // waiting for the arrival of the beacon message
{
if(new_mqtt_mex){
new_mqtt_mex = false;
char type[5];
String message = "";
memset(type,'\0',sizeof(type));
if((char)MQTT_payload[3] == ',' && MQTT_payload.length() < 500){ // <500 to avoid extra long messages (to fixed)
for (int i=0;i<MQTT_payload.length();i++) {
if(i<3){
type[i] = (char)MQTT_payload[i];
}
if(i>=4){
message += (char)MQTT_payload[i];
}
}
}
if(strcmp(type, "BCN") == 0){ // we received a beacon message
digitalWrite(pin_beacon_received, HIGH); // to check fisical ESP32 receive beacon
counter = 0; // start counter to not exceed our slot in CFP
extract_beacon(message); // function that extract beacon information
Serial.print("frameDuration: "); Serial.println(frameDuration);
Serial.print("collisionAccessPartDuration: "); Serial.println(collisionAccessPartDuration);
Serial.print("collisionFreePartDuration: "); Serial.println(collisionFreePartDuration);
Serial.print("slotDuration: "); Serial.println(slotDuration);
Serial.print("beaconInterval: "); Serial.println(beaconInterval);
Serial.print("My Slot Position: "); Serial.println(slot);
if(slot < 0 ){ // slot = -5 by defalult
registered = false;
digitalWrite(pin_beacon_received, LOW);
MQTT_handler = MQTT_REG_REQ;
}
else{
registered = true; // per sicurezza (ci possiamo essere persi l ACK nel CAP precedente)
// sleep until our slot in CFP
sleep((collisionAccessPartDuration + slotDuration * slot)/1000);
digitalWrite(pin_beacon_received, LOW);
MQTT_handler = MQTT_NORMAL_OP;
}
}
}
break;
}
case MQTT_REG_REQ: // send a registration request in a random slot
{
if (mqttClient.connected() && WiFi.isConnected() == true){
char message_to_send[100];
snprintf(message_to_send, sizeof(message_to_send), "REGREQ,%s", MQTT_CLIENT_ID);
// random wait before sending registration (publishing)
reg_slot = rand() %(collisionAccessPartDuration/slotDuration); // random number between [ 0, (CAP/T_slot)-1]
Serial.print("registration_slot: "); Serial.println(reg_slot);
if(slotDuration * reg_slot != 0)
sleep((slotDuration * reg_slot)/1000); // wait until random time slot in CAP
mqttClient.publish(MQTT_TOPIC, message_to_send);
Serial.println("Publishing REQUEST");
MQTT_handler = MQTT_WAIT_ACK;
}
break;
}
case MQTT_WAIT_ACK: // waiting ACK only in CAP time
{
//////////////////////////////////////////////////////////////////////
// counter NON si ferma con la sleep()
//////////////////////////////////////////////////////////////////////
if((collisionAccessPartDuration - counter) > 0){ // we are already in CAP
if(new_mqtt_mex){
new_mqtt_mex = false;
char ack[5];
char ackID[30];
memset(ack,'\0',sizeof(ack));
memset(ackID,'\0',sizeof(ackID));
if((char)MQTT_payload[3] == ','){
for (int i=0;i<MQTT_payload.length();i++) {
if(i<3){
ack[i] = (char)MQTT_payload[i];
}
if(i>=4){
ackID[i-4] = (char)MQTT_payload[i];
}
}
}
if(strcmp(ack, "ACK") == 0 && strcmp(ackID, MQTT_CLIENT_ID) == 0){ // we received our ACK
Serial.print("ackID: ");Serial.println(ackID);
Serial.println("----- SUCCESSFULLY REGISTERED -----");
registered = true;
// sleep until new beacon where extract our slot
sleep((beaconInterval - counter)/1000);
MQTT_handler = MQTT_START;
}
}
}
else{ // CAP time is finish and NO ACK was received
Serial.println("NO ACK received");
Serial.println("----- CAP FINISHED -----");
// sleep until new beacon arrive
sleep((beaconInterval - counter)/1000);
MQTT_handler = MQTT_START;
}
break;
}
case MQTT_NORMAL_OP:
{
if((counter - collisionAccessPartDuration - slotDuration * slot) >= slotDuration){
value_published = false;
Serial.println("----- CFP SLOT FINISHED -----");
// sleep until new beacon
sleep((beaconInterval - counter -100 )/1000); // -100ms to be sure to catch beacon
MQTT_handler = MQTT_START;
}
else{ // we are in our CFP slot
if (mqttClient.connected() && WiFi.isConnected() == true && value_published == false) {
TempAndHumidity data = dht.getTempAndHumidity(); // extract humidity value
sprintf(stringHumidity, "%d", (int)data.humidity);
char message_to_send[100];
srand(time(NULL)); // Seed the random number generator with the current time
if ((rand() % 2) == 0) {
snprintf(message_to_send, sizeof(message_to_send), "%s,%s,%s%c", MQTT_CLIENT_ID, stringHumidity, MQTT_CLIENT_ID_WO_number, '3');
} else {
snprintf(message_to_send, sizeof(message_to_send), "%s,%s,%s%c", MQTT_CLIENT_ID, stringHumidity, MQTT_CLIENT_ID_WO_number, '4');
}
mqttClient.publish(MQTT_TOPIC, message_to_send);
Serial.println("Publishing...");
value_published = true;
}
}
break;
}
}
if (WiFi.isConnected() ) {
if(mqttClient.connected()){
mqttClient.loop();
}
else if (!mqttClient.connected() ) { // handle MQTT reconnection
reconnecting = true;
reconnect();
}
}
else { // handle WIFI reconnection
Serial.println("Reconnecting to Wi-Fi...");
WiFi.reconnect();
}
}