// ====================================================================
// Task 3: MQTT + JSON (Pico W, Wokwi)
// Receives control commands via MQTT, publishes sensor data
// ====================================================================
//
//--------------------------------------------------------------------------
// Note: Please Wait for atleast 20 sec to update led status in Nano.
// Since the WOKWI simulation is bit slow.
//------------------------------------------------------------------------
/*
Commands for Arduino Nano IOT 33
================================================
IOT TASK 3 - REMOTE CONTROL
================================================
COMMAND | DESCRIPTION
-----------------|------------------------------
ON | Turn PICO LED permanently ON
OFF | Turn PICO LED OFF
BLINK | Enable LED blinking mode
NOBLINK | Disable blinking (Stay ON)
INTERVAL <ms> | Set blink speed (50-2000ms)
POT | Toggle Potentiometer control
PICO MQTT | Toggle PICO MQTT Communication
STATUS | Show current PICO Interval and POT values
STATS | Show Local & Remote message statistics
HELP | Show this menu
-----------------|------------------------------
Example: INTERVAL 500
================================================
*/
//
#include <WiFi.h>
//#include <esp_wifi.h>
#include <PubSubClient.h>
#include "SimpleJson.h"
#include "led_control.h"
// -- WiFi (Wokwi) ---------------------------------------------------
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASS = "";
// -- MQTT ------------------------------------------------------------
const char* MQTT_BROKER = "141.69.95.10";
const int MQTT_PORT = 1883;
const char* TOPIC_DATA = "iem2026/vira/task3/pico/data";
const char* TOPIC_CMD = "iem2026/vira/task3/pico/cmd";
// -- Robustness ------------------------------------------------------
const char* SHARED_TOKEN = "iem2026";
const char* EXPECTED_SOURCE = "nano";
const unsigned long WATCHDOG_TIMEOUT = 20000;
const int INTERVAL_MIN = 50;
const int INTERVAL_MAX = 2000;
int last_seq_no = 0;
unsigned long last_watchdog_time;
int blink_interval = 1000;
bool poti_control_mqtt_cmd = false;
int pico_mqtt_send_seq_no=0;
unsigned long pico_mqtt_publish_last_time;
const unsigned long publish_interval_ms = 5000;
bool pico_publish_mqtt_msg=true;
String ack_msg="";
//unsigned long mqtt_connection_check_old_time;
// -- Hardware --------------------------------------------------------
//const int LED_PIN = 28;
//const int BUTTON_PIN = 2;
//const int POT_PIN = 26;
//const int HEARTBEAT_PIN = LED_BUILTIN;
// -- State -----------------------------------------------------------
//bool blinkEnabled = true;
//bool ledState = false;
int overrideInterval = -1; // -1 = poti controls
unsigned long lastValidCmd = 0;
bool inSafeState = false;
int expectedSeqNr = -1;
unsigned long seqOutgoing = 0;
bool message_received = false;
bool cmd_received = false;
// Statistics
unsigned long msgAccepted = 0, msgRejectedJson = 0;
unsigned long msgRejectedAuth = 0, msgSeqGaps = 0;
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
SimpleJson json_out, json_in;
void setupWiFi() {
// TODO: Connect to WiFi (Wokwi-GUEST, same as Task 1/2)
WiFi.begin(WIFI_SSID,WIFI_PASS);
Serial1.println("\nWiFi Connecting");
//WiFi.setSleep(false);
//esp_wifi_set_max_tx_power(40);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial1.print(".");
}
Serial1.println("\nWiFi connected");
}
void enterSafeState() {
// TODO: Activate safe state
// - Set overrideInterval = -1 (back to poti)
// - Print warning message
button_event = false;
led_state = LED_BLINK;
inSafeState = true;
overrideInterval = -1;
pico_publish_mqtt_msg=true;
Serial1.println("");
Serial1.println("Message Timeout");
Serial1.println("No Message Received From Nano");
Serial1.println("Entering Safe State");
Serial1.println("");
}
void leaveSafeState() {
// TODO: Leave safe state when valid message arrives
inSafeState = false;
overrideInterval = 0;
Serial1.println("");
Serial1.println("Leaving Safe State");
Serial1.println("");
}
bool validateMessage() {
//----------Checking Token---------------------------------------
if (!json_in.hasKey("token"))
{
Serial1.println("Missing Token");
return false;
}
const char* token = json_in.getString("token");
if (strcmp(token, SHARED_TOKEN) != 0)
{
Serial1.println("Invalid Token");
return false;
}
// ----------------------------Checking for Valid source------------------------------
if (!json_in.hasKey("source"))
{
Serial1.println("Missing source");
return false;
}
const char* source = json_in.getString("source");
if (strcmp(source, EXPECTED_SOURCE) != 0)
{
Serial1.println("Invalid Source");
return false;
}
//-----------checking Sequence No-------------------------
if (!json_in.hasKey("seq"))
{
Serial1.println("Missing Sequence Number");
return false;
}
//-----------all check passed-----------------------------------
//Serial1.print("Message OK "); //Serial1.println(msg);
// TODO: Check token (must match SHARED_TOKEN)
// TODO: Check source (must match EXPECTED_SOURCE)
return true;
}
bool checkSequence() {
// TODO: Check sequence number
// - Detect gaps (warn but accept)
// - Detect replays (discard)
// - Detect restart (seq == 0 -> resync)
int seq_no = json_in.getInt("seq");
if (seq_no != last_seq_no + 1)
{
Serial1.println("Warning: Data Packet Lost!");
last_seq_no = seq_no;
return false;
}
else
{
last_seq_no = seq_no;
return true;
}
}
void processCommand() {
ack_msg = "";
// TODO: Process received commands:
// - blinkEnabled (bool)
// - ledOn (bool) -> LED permanently on
// - interval (int) -> override with range check
// - useLocalPot (bool) -> reset override
if (json_in.hasKey("set_interval"))
{
//Serial1.print("----------------------------");
blink_interval = json_in.getInt("set_interval");
ack_msg += "| Interval: " + String(blink_interval)+"ms |";
//Serial1.println(blink_interval);
//Serial1.println(json_in.getInt("set_interval"));
}
if (json_in.hasKey("set_poti_control"))
{
poti_control_mqtt_cmd = json_in.getBool("set_poti_control");
ack_msg += "| Pot Control:" + String(poti_control_mqtt_cmd? "true":"false") + " |";
}
if (json_in.hasKey("set_led_state_blink"))
{
if (json_in.getBool("set_led_state_blink")==true)
{
led_state = LED_BLINK;
ack_msg += "| LED_BLINKING |";
}
else if (json_in.getBool("set_led_state_blink")==false)
{
led_state = LED_NOBLINK;
ack_msg += "| LED_NOBLINK |";
}
}
if (json_in.hasKey("set_led_state"))
{
bool state = json_in.getBool("set_led_state");
led_state = state ? LED_ON : LED_OFF;
ack_msg += state ? " | LED: ON |" : " | LED: OFF |";
}
if(json_in.hasKey("pico_mqtt_status"))
{
if(json_in.getBool("pico_mqtt_status")==true)
{
pico_publish_mqtt_msg=true;
ack_msg += "| Turned on mqtt messages from pico |";
}
else if (json_in.getBool("pico_mqtt_status")==false)
{
pico_publish_mqtt_msg=false;
ack_msg += "| Turned off mqtt messages from pico |";
}
}
if (ack_msg != "")
{
cmd_received=true;
}
}
void mqttCallback(char* TOPIC_CMD, byte* payload, unsigned int length) {
// TODO: Receive and process message:
// 1. Copy payload into char array (null-terminate!)
// 2. Parse JSON (jsonIn.parse)
// 3. Call validateMessage()
// 4. Call checkSequence()
// 5. Reset watchdog timer
// 6. Call processCommand()
//Serial1.print("Message arrived on topic: ");
//Serial1.println(TOPIC_CMD);
char msg [length + 1];
memcpy(msg, payload, length);
msg[length] = '\0';
if (json_in.parse(msg))
{
if (! (last_seq_no == json_in.getInt("seq")) )
{
if (validateMessage())
{
last_watchdog_time = millis();
//led_state = LED_OFF;//parse commands here
checkSequence()? 0:msgSeqGaps++ ;
processCommand();
msgAccepted ++;
}
else
{
Serial1.println("Message Autheuntication Failed: Discarding....");
Serial1.print("Message: "); Serial1.println(msg);
msgRejectedAuth++;
}
}
else
{
Serial1.println("Repeated Message: Discarding....");
Serial1.print("Message: "); Serial1.println(msg);
msgRejectedJson++;
}
}
else
{
Serial1.println("JSON Parse Error!");
Serial1.print("Message: "); Serial1.println(msg);
msgRejectedJson++;
}
//Serial1.print("Topic: "); Serial1.println(TOPIC_DATA);
//Serial1.print("Message: "); Serial1.println(msg);
}
void mqttReconnect() {
while (!mqtt.connected())
{
Serial1.println("Attempting MQTT connection...");
{
if (mqtt.connect("my-pico-001"))
{
Serial1.println("Connected to mqtt server");
mqtt.subscribe(TOPIC_CMD);
}
else {
Serial1.print("failed, rc=");
Serial1.print(mqtt.state());
Serial1.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(2000);
}
}
}
// TODO: Connect to MQTT + subscribe to TOPIC_CMD
}
void publishSensorData(int potValue, int blinkInterval) {
// TODO: Build JSON with SimpleJson:
// token, source, seq, potValue, blinkEnabled,
// interval, ledState, safeState, uptime
// Then mqtt.publish(TOPIC_DATA, buf)
json_out.clear();
json_out.setString("token", "iem2026");
json_out.setString("source", "pico");
json_out.setInt("seq", pico_mqtt_send_seq_no++);
json_out.setInt("interval", blinkInterval);
json_out.setInt("poti", potValue);
json_out.setString("led_state", state_names[led_state]);
json_out.setInt("msg_acc", (int)msgAccepted);
json_out.setInt("msg_rej_json", (int)msgRejectedJson);
json_out.setInt("msg_rej_auth", (int)msgRejectedAuth);
json_out.setInt("msg_gaps", (int)msgSeqGaps);
if(cmd_received)
{
json_out.setString("ack", ack_msg.c_str());
cmd_received = false;
ack_msg = "";
}
mqtt.publish(TOPIC_DATA, json_out.toString().c_str());
}
void setup() {
init_led_controller();
Serial1.begin(115200);
//Serial1.println("Hello, Raspberry Pi Pico W!");
setupWiFi();
mqtt.setServer(MQTT_BROKER, MQTT_PORT);
mqtt.setCallback(mqttCallback);
led_state = LED_BLINK;
pico_mqtt_publish_last_time = millis();
//mqtt.subscribe(TOPIC_CMD);
//mqtt.connect("my-pico-001");
//if (!mqtt.connected()) {
// mqttReconnect();
//}
//mqtt_connection_check_old_time = millis();
// TODO: setupWiFi()
// TODO: mqtt.setServer + mqtt.setCallback
}
void loop() {
unsigned long now = millis();
heart_beat(now);
handle_led_fsm(now);
read_pot(now);
check_serial_cmd();
//led_state = LED_BLINK;
//-------------------------WATCHDOG--------------------------------------------
if ((now - last_watchdog_time) > WATCHDOG_TIMEOUT)
{
if (inSafeState != true)
{
enterSafeState();
}
}
else
{
if (inSafeState == true)
leaveSafeState();
}
//-------------------if in safe state------------------------------------------------------
if (inSafeState == true)
{
handle_button_event();
led_state=check_serial_cmd();
blink_delay = poti_blink_delay;
}
// --------if not in safe state decide the interval control by pot cmd received or interval ms received from nano-----------------------------------
else
{
if (poti_control_mqtt_cmd == true)
{
blink_delay = poti_blink_delay;
}
else
{
blink_delay = blink_interval;
}
}
//-------------Publish Pico status---------------------------------------
if ((now - pico_mqtt_publish_last_time) > publish_interval_ms || cmd_received)
{
pico_mqtt_publish_last_time = now;
if (pico_publish_mqtt_msg==true)
{
publishSensorData(poti_blink_delay,blink_delay);
}
}
// TODO: Check MQTT connection + mqtt.loop()
// TODO: Check watchdog (enterSafeState if needed)
// TODO: Read button (blink toggle, same as Task 1)
// TODO: Read poti + determine interval
// TODO: Blink with variable interval (same as Task 1)
// TODO: Publish sensor data (every 500ms)
// MUST be called every loop — processes incoming messages
if (!mqtt.connected())
{
mqttReconnect();
}
mqtt.loop();
//mqtt.publish("iem/task3/notpico/data", "hello viraj here");
//Serial1.println("inloop");
//delay(2000);
//led_state = LED_ON;
}