Wasstronics Office Status Light
A wireless status light that keeps people from walking in on your meetings, built with an Arduino Nano ESP32, an Arduino Nesso N1, and Arduino Cloud keeping them in sync.
Devices & Components
1
Arduino Nesso N1
1
Ring with 24 RGB WS2812 LEDs and integrated driver
1
Arduino® Nano ESP32
Software & Tools
Arduino IDE
Project description
Code
thingProperties.h
cpp
Header file for Nesso. Add to project
1#include <ArduinoIoTCloud.h> 2#include <Arduino_ConnectionHandler.h> 3#include <arduino_secrets.h> 4 5const char SSID[] = SECRET_SSID; 6const char PASS[] = SECRET_PASS; 7const char DEVICE_LOGIN_NAME[] = SECRET_DEVICE_LOGIN_NAME; 8const char DEVICE_KEY[] = SECRET_DEVICE_KEY; 9 10void onDoorStatusChange(); 11 12int doorStatus; 13 14void initProperties() { 15 ArduinoCloud.setBoardId(DEVICE_LOGIN_NAME); 16 ArduinoCloud.setSecretDeviceKey(DEVICE_KEY); 17 ArduinoCloud.addProperty(doorStatus, READWRITE, ON_CHANGE, onDoorStatusChange); 18} 19 20WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);
main.ino
cpp
For Nesso N1 - Through Arduino IDE
1#include "thingProperties.h" 2#include <Arduino_Nesso_N1.h> 3 4NessoDisplay display; 5NessoTouch touch; 6NessoBattery battery; 7 8const int DISPLAY_WIDTH = 240; 9const int DISPLAY_HEIGHT = 135; 10 11const uint16_t COLOR_DEEP_GREEN = 0x07E0; // swap once you've picked from the swatch test 12 13bool wasTouched = false; 14unsigned long lastTouchMillis = 0; 15const unsigned long TOUCH_DEBOUNCE_MS = 300; 16 17unsigned long lastBatteryCheck = 0; 18const unsigned long BATTERY_CHECK_INTERVAL_MS = 5000; 19uint16_t batteryPercent = 0; 20 21// True until the very first real doorStatus value has arrived from the Cloud 22bool waitingForFirstSync = true; 23 24void setup() { 25 Serial.begin(9600); 26 delay(1500); 27 28 display.begin(); 29 display.setRotation(1); 30 touch.begin(); 31 32 battery.begin(); 33 battery.enableCharge(); 34 35 drawLoadingScreen(); 36 37 initProperties(); 38 ArduinoCloud.begin(ArduinoIoTPreferredConnection); 39 setDebugMessageLevel(2); 40 ArduinoCloud.printDebugInfo(); 41 42 waitForFirstSync(); 43 44 batteryPercent = battery.getChargeLevel(); 45} 46 47void loop() { 48 ArduinoCloud.update(); 49 handleTouch(); 50 handleBatteryRefresh(); 51 delay(20); 52} 53 54void drawLoadingScreen() { 55 display.fillScreen(TFT_BLACK); 56 display.setTextColor(TFT_WHITE); 57 display.setTextSize(2); 58 display.drawString("Loading.", 40, 50); 59 display.drawString("Please wait...", 40, 75); 60} 61 62void waitForFirstSync() { 63 unsigned long startAttempt = millis(); 64 const unsigned long CONNECT_TIMEOUT_MS = 30000; 65 66 Serial.println("Waiting for first doorStatus sync from Cloud..."); 67 68 // onDoorStatusChange() flips waitingForFirstSync to false the moment 69 // the real value arrives, we just sit here polling the connection until then 70 while (waitingForFirstSync && (millis() - startAttempt < CONNECT_TIMEOUT_MS)) { 71 ArduinoCloud.update(); 72 delay(100); 73 } 74 75 if (!waitingForFirstSync) { 76 Serial.print("Synced after "); 77 Serial.print(millis() - startAttempt); 78 Serial.println("ms"); 79 80 display.fillScreen(TFT_BLACK); 81 display.setTextColor(TFT_WHITE); 82 display.setTextSize(3); 83 display.drawString("Connected!", 30, 55); 84 delay(1000); 85 86 drawState(); // now show the actual synced color 87 } else { 88 Serial.println("Timed out waiting for sync, showing current value anyway."); 89 display.fillScreen(TFT_BLACK); 90 display.setTextColor(TFT_RED); 91 display.setTextSize(2); 92 display.drawString("Still connecting...", 20, 60); 93 delay(1500); 94 drawState(); // show whatever we've got, even if it might be default/stale 95 } 96} 97 98void handleTouch() { 99 int16_t x, y; 100 bool touched = touch.isTouched() && touch.read(x, y); 101 102 if (touched && !wasTouched && (millis() - lastTouchMillis > TOUCH_DEBOUNCE_MS)) { 103 lastTouchMillis = millis(); 104 doorStatus = (doorStatus + 1) % 6; 105 onDoorStatusChange(); 106 } 107 108 wasTouched = touched; 109} 110 111void handleBatteryRefresh() { 112 unsigned long now = millis(); 113 if (now - lastBatteryCheck >= BATTERY_CHECK_INTERVAL_MS) { 114 lastBatteryCheck = now; 115 uint16_t newPercent = battery.getChargeLevel(); 116 if (newPercent != batteryPercent) { 117 batteryPercent = newPercent; 118 drawBatteryOverlay(); 119 } 120 } 121} 122 123// Runs whenever doorStatus changes: initial Cloud sync, touch, dashboard, or the Nano 124void onDoorStatusChange() { 125 if (waitingForFirstSync) { 126 // this is the very first value arriving after boot, don't draw yet, 127 // waitForFirstSync() will handle showing "Connected!" then the real state 128 waitingForFirstSync = false; 129 return; 130 } 131 drawState(); 132} 133 134void drawState() { 135 switch (doorStatus) { 136 case 0: display.fillScreen(TFT_RED); break; 137 case 1: display.fillScreen(TFT_YELLOW); break; 138 case 2: display.fillScreen(COLOR_DEEP_GREEN); break; 139 case 3: drawOffScreen(); break; 140 case 4: display.fillScreen(TFT_WHITE); break; 141 case 5: drawFadeScreen(); break; 142 default: display.fillScreen(TFT_BLACK); break; 143 } 144 drawBatteryOverlay(); 145} 146 147void drawOffScreen() { 148 display.fillScreen(TFT_BLACK); 149 display.setTextColor(TFT_WHITE); 150 display.setTextSize(4); 151 display.drawString("OFF", 80, 50); 152} 153 154void drawFadeScreen() { 155 display.fillScreen(TFT_BLACK); 156 display.setTextSize(4); 157 158 display.setTextColor(TFT_RED); 159 display.drawString("F", 40, 50); 160 161 display.setTextColor(TFT_YELLOW); 162 display.drawString("A", 85, 50); 163 164 display.setTextColor(COLOR_DEEP_GREEN); 165 display.drawString("D", 130, 50); 166 167 display.setTextColor(TFT_CYAN); 168 display.drawString("E", 175, 50); 169} 170 171void drawBatteryOverlay() { 172 char batteryLabel[6]; 173 sprintf(batteryLabel, "%d%%", batteryPercent); 174 175 display.fillRect(DISPLAY_WIDTH - 44, 2, 42, 16, TFT_BLACK); 176 display.setTextColor(TFT_WHITE); 177 display.setTextSize(1); 178 display.drawString(batteryLabel, DISPLAY_WIDTH - 40, 5); 179}
arduino_secrets.h
cpp
Add to nesso project. Add WiFi and Nesso details
1#define SECRET_SSID "[YOUR WIFI SSID]" 2#define SECRET_PASS "[YOUR WIFI PASSWORD]" 3#define SECRET_DEVICE_LOGIN_NAME "[YOUR SDLN]" 4#define SECRET_DEVICE_KEY "[YOUR SDK]"
Nano Main
cpp
This is the main code for nano. On Arduino Cloud
1#include "thingProperties.h" // auto-generated by Arduino Cloud 2#include <Adafruit_NeoPixel.h> 3 4#define LED_PIN 5 // real GPIO behind the board's "D2" label 5#define NUM_LEDS 24 6#define BRIGHTNESS 80 7 8Adafruit_NeoPixel ring(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800); 9 10// Custom colors, tweak these two lines if a color still isn't reading right on the ring 11const uint32_t COLOR_YELLOW = ring.Color(255, 170, 0); // pulled back from pure (255,255,0), which reads too green on this ring 12const uint32_t COLOR_DEEP_GREEN = ring.Color(0, 230, 116); // vivid green from earlier tuning 13 14// Tracks the fade animation's position between loop() passes 15uint16_t fadeHue = 0; 16unsigned long lastFadeStep = 0; 17const unsigned long FADE_STEP_INTERVAL_MS = 20; // lower = faster fade 18 19void setup() { 20 Serial.begin(9600); 21 delay(1500); 22 23 ring.begin(); 24 ring.setBrightness(BRIGHTNESS); 25 ring.show(); 26 27 initProperties(); 28 ArduinoCloud.begin(ArduinoIoTPreferredConnection); 29 30 setDebugMessageLevel(2); 31 ArduinoCloud.printDebugInfo(); 32} 33 34void loop() { 35 ArduinoCloud.update(); 36 37 if (doorStatus == 5) { 38 unsigned long now = millis(); 39 if (now - lastFadeStep >= FADE_STEP_INTERVAL_MS) { 40 lastFadeStep = now; 41 fadeHue += 256; 42 for (int i = 0; i < NUM_LEDS; i++) { 43 uint32_t pixelColor = ring.gamma32(ring.ColorHSV(fadeHue + (i * 3000))); 44 ring.setPixelColor(i, pixelColor); 45 } 46 ring.show(); 47 } 48 } 49} 50 51// Runs automatically whenever doorStatus changes 52void onDoorStatusChange() { 53 switch (doorStatus) { 54 case 0: 55 fillRingColor(ring.Color(255, 0, 0)); // red: busy, do not enter 56 break; 57 case 1: 58 fillRingColor(COLOR_YELLOW); // yellow: can enter, be quiet 59 break; 60 case 2: 61 fillRingColor(COLOR_DEEP_GREEN); // green: okay to enter 62 break; 63 case 3: 64 fillRingColor(ring.Color(0, 0, 0)); // off 65 break; 66 case 4: 67 fillRingColor(ring.Color(255, 255, 255)); // white 68 break; 69 case 5: 70 // fade mode: nothing to do here, loop() takes over the animation 71 break; 72 default: 73 fillRingColor(ring.Color(0, 0, 0)); 74 break; 75 } 76} 77 78void fillRingColor(uint32_t color) { 79 for (int i = 0; i < NUM_LEDS; i++) { 80 ring.setPixelColor(i, color); 81 } 82 ring.show(); 83}
Downloadable files
P002_V01_B01_AB_OfficeLightBody_16082026
Main Body stl file for Office Light
P002_V01_B01_AB_OfficeLightBody_16082026.stl
P002_V01_B02_AB_OfficeLightLid_15082026
Lid stl file, Translucent White PETG
P002_V01_B02_AB_OfficeLightLid_15082026.stl
Comments
Only logged in users can leave comments