// VCNL4010 publishing to MQTT server (Adafruit MQTT version) // Jeff Ritchie 5 3 26 for fab academy 2026 // // Initial prompt/context for this document: // Create an ESP32-C3 Arduino program that reads a VCNL4010 proximity and // ambient light sensor, publishes the readings to an MQTT server, and makes // the data available for another interface, such as a web page, to visualize. // ---------- Libraries ---------- // // WiFi.h: // Lets the ESP32-C3 connect to a wireless network. #include // Adafruit_MQTT and Adafruit_MQTT_Client: // Provide MQTT client tools for connecting to the broker and publishing // messages to topics. #include "Adafruit_MQTT.h" #include "Adafruit_MQTT_Client.h" // Wire.h: // Provides I2C communication. The VCNL4010 sensor talks to the ESP32-C3 over // the I2C bus. #include // Adafruit_VCNL4010: // Provides simple functions for reading ambient light and proximity values // from the VCNL4010 sensor. #include "Adafruit_VCNL4010.h" // secrets.h stores local WiFi credentials that should not be published. #include "secrets.h" // ---------- WiFi Configuration ---------- // // The wireless network name and password are defined in secrets.h. // The ESP32-C3 must connect to WiFi before it can reach the MQTT broker. const char* ssid = WIFI_SSID; const char* password = WIFI_PASSWORD; // ---------- MQTT Configuration ---------- // // These settings tell the ESP32-C3 where the MQTT broker is and how to log in. // Port 1883 is the standard unencrypted MQTT port used by the Arduino sketch. // A browser visualization usually connects to the same broker through a // WebSocket port instead. #define MQTT_SERVER "mqtt.fabcloud.org" #define MQTT_PORT 1883 #define MQTT_USER "fabacademy" #define MQTT_PASS "fabacademy" // ---------- Device Identification and MQTT Topics ---------- // // device_id is included inside each JSON payload so a subscriber can tell // which physical board sent the data. const char* device_id = "esp32c3_01"; // TOPIC_SENSOR: // The main data topic. The loop publishes VCNL4010 sensor readings here. #define TOPIC_SENSOR "fabacademy/ritchie/esp32c3_01/vcnl4010" // TOPIC_STATUS: // A separate status topic. This sketch publishes "online" after connecting to // MQTT, which helps confirm that the board reached the broker. #define TOPIC_STATUS "fabacademy/ritchie/esp32c3_01/status" // ---------- Object Instantiations ---------- // // WiFiClient: // The network connection object used underneath MQTT. WiFiClient client; // Adafruit_MQTT_Client: // The MQTT client object. It uses the WiFi client and the broker settings // defined above. Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, MQTT_PORT, MQTT_USER, MQTT_PASS); // MQTT publish objects: // These wrap the topic names and make it easier to publish messages. Adafruit_MQTT_Publish sensorFeed = Adafruit_MQTT_Publish(&mqtt, TOPIC_SENSOR); Adafruit_MQTT_Publish statusFeed = Adafruit_MQTT_Publish(&mqtt, TOPIC_STATUS); // VCNL4010 sensor object: // This is used to initialize the sensor and read its two data values. Adafruit_VCNL4010 vcnl; // ---------- Timing Variables ---------- // // lastPublish stores the time of the previous MQTT message. // publishInterval controls how often the sketch sends data. // 1000 milliseconds = 1 second. unsigned long lastPublish = 0; const unsigned long publishInterval = 1000; // ---------- WiFi ---------- // // Connects the ESP32-C3 to the WiFi network. // This function blocks inside the while loop until the board is connected. void setup_wifi() { Serial.print("Connecting to WiFi: "); Serial.println(ssid); // Start the WiFi connection attempt using the credentials above. WiFi.begin(ssid, password); // Keep checking WiFi.status() until the connection succeeds. // A dot is printed every half second so the Serial Monitor shows progress. while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } // Once connected, print the board's local IP address for debugging. Serial.println("\nWiFi connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } // ---------- MQTT ---------- // // Connects or reconnects to the MQTT broker. // The loop calls this repeatedly, but it immediately returns when MQTT is // already connected. void MQTT_connect() { int8_t ret; // If already connected, no work is needed. if (mqtt.connected()) { return; } Serial.print("Connecting to MQTT... "); // Try to connect to MQTT up to five times. // mqtt.connect() returns 0 when the connection succeeds. uint8_t retries = 5; while ((ret = mqtt.connect()) != 0) { Serial.println(mqtt.connectErrorString(ret)); Serial.println("Retrying MQTT connection in 5 seconds..."); // Clean up the failed connection before trying again. mqtt.disconnect(); delay(5000); // If all retries fail, stop the program here. // This makes the failure obvious instead of continuing silently. if (--retries == 0) { while (1); // halt } } Serial.println("MQTT connected"); // Publish "online" status after the MQTT connection succeeds. // This library does not retain the message by default, so subscribers see it // only when they are listening at the time it is sent. statusFeed.publish("online"); } // ---------- Setup ---------- // // setup() runs once when the ESP32-C3 starts or resets. // It prepares Serial, I2C, the VCNL4010 sensor, and WiFi. void setup() { // Start Serial Monitor output for debugging messages. Serial.begin(115200); delay(1000); Serial.println("ESP32-C3 VCNL4010 MQTT publisher (Adafruit MQTT)"); // Start the I2C bus. The VCNL4010 uses I2C to communicate with the board. Wire.begin(); // Try to find and initialize the VCNL4010 sensor. // If the sensor is not found, print an error and stop here. if (!vcnl.begin()) { Serial.println("VCNL4010 sensor not found"); while (1) delay(2000); } Serial.println("Found VCNL4010"); // Connect to WiFi after the sensor is ready. setup_wifi(); } // ---------- Loop ---------- // // loop() runs continuously after setup() finishes. // It keeps MQTT connected and publishes sensor readings once per second. void loop() { // Make sure the board is connected to the MQTT broker before publishing. MQTT_connect(); // millis() is the number of milliseconds since the board started. // Using millis() instead of delay() allows the loop to keep running. unsigned long now = millis(); // Publish only when publishInterval has passed since the last message. if (now - lastPublish >= publishInterval) { lastPublish = now; // Read the two VCNL4010 sensor values. // ambient is the ambient light reading. // proximity increases or decreases based on nearby reflected infrared light. uint16_t ambient = vcnl.readAmbient(); uint16_t proximity = vcnl.readProximity(); // Build a JSON message as text. // Example: // { // "device_id":"esp32c3_01", // "sensor":"vcnl4010", // "ambient":123, // "proximity":456 // } // // The browser visualization subscribes to TOPIC_SENSOR and looks for the // ambient and proximity fields in this payload. String payload = "{"; payload += "\"device_id\":\""; payload += device_id; payload += "\","; payload += "\"sensor\":\"vcnl4010\","; payload += "\"ambient\":"; payload += ambient; payload += ","; payload += "\"proximity\":"; payload += proximity; payload += "}"; // Print the JSON payload to the Serial Monitor for debugging. Serial.print("Publishing: "); Serial.println(payload); // Publish the JSON payload to the MQTT sensor topic. // If publish() returns false, print an error to the Serial Monitor. if (!sensorFeed.publish(payload.c_str())) { Serial.println("Publish failed"); } } }