The 'Ambient Light Monitoring System is designed in such a way that once a specific mode is toggled on, the sensors in the environment will monitor and maintain the lux value (measure of intensity of ambient light) set by the user. 
 
Lets have a look at all the components that we will need for this project:
1. ESP32 NodeMCU modules 
3. WS2812 LED Strips 
6. Jumpers 
8. Resistors 
9. Switches 
 
 
For a detailed step by step assembly process, please refer to the video below:

 

Base Station Code explanation: 
 
The base station code can be broken down into three major constituents, which are: 
1. The communication between the base station and end devices using Lora 
Modules. 
2. The potentiometer code which allows the user to increase/decrease the 
brightness of each room to the desired lux reading. 
3. Code for the switch which toggles the control mode on end devices. 
 
 
End Device Code: 
 
Like the base station, the code for end devices can be broken down into constituents, 
which are: 
Modules. 
2. The code for the LED strip brightness, which will vary based on the user defined 
input at the base station during manual mode. 
3. Code to control the LED strip brightness during 'maintain Lux value mode'. 
 
 
Code for base station:
 
#include <Arduino.h> 
 
#include <FastLED.h> 
 
#include <SPI.h> 
 
#include <LoRa.h> 
 
 
 
#define debounceInterval 200 
 
 
 
const int csPin = 5; 
 
const int resetPin = 4; 
 
const int irqPin = 2; 
 
 
 
byte localAddress = 0xAA; 
 
byte destinationAddress = 0xBB; 
 
byte address; 
 
unsigned long lastMillis = 0; 
 
unsigned long lastInterruptMillis = 0; 
 
int val; 
 
long lastSendTime = 0; 
 
int interval = random(500) + 100; 
 
String command; 
 
boolean buttonPrevState = false; 
 
boolean buttonPressed = false; 
 
 
 
void receiveMessage(int packetSize); 
 
void IRAM_ATTR toggle(); 
 
void sendMessage(String outgoingbyte destinationAddress); 
 
 
 
void setup() 
 
{ 
 
  Serial.begin(115200); 
 
  pinMode(14, INPUT); 
 
  pinMode(25, INPUT_PULLUP); 
 
 
 
 
  attachInterrupt(25, toggle, FALLING); 
 
  Serial.println("Start LoRa duplex"); 
 
 
 
  LoRa.setPins(csPin, resetPin, irqPin); 
 
 
 
  if (!LoRa.begin(433E6)) 
 
  { 
 
    Serial.println("LoRa init failed. Check your connections."); 
 
    while (true) 
 
    { 
 
    } 
 
  } 
 
  LoRa.setSpreadingFactor(12); 
 
} 
 
void loop() 
 
{ 
 
  val = analogRead(14); 
 
  val = map(val, 040950100); 
 
  sendMessage(String(val), 0xBB); 
 
 
 
  if (buttonPressed == true) 
 
  { 
 
    if (millis() - lastInterruptMillis > debounceInterval) 
 
    { 
 
      Serial.println("Button Pressed"); 
 
      sendMessage("ModeChange"0xBB); 
 
      delay(100); 
 
      buttonPrevState = !buttonPrevState; 
 
      buttonPressed = false; 
 
      lastInterruptMillis = millis(); 
 
    } 
 
  } 
 
  else 
 
  { 
 
    buttonPressed = false; 
 
  } 
 
  receiveMessage(LoRa.parsePacket()); 
 
} 
 
 
 
void IRAM_ATTR toggle() 
 
{ 
 
  if (digitalRead(25) == 0) 
 
  { 
 
 
 
 
    buttonPressed = !buttonPressed; 
 
  } 
 
} 
 
 
 
void sendMessage(String outgoingbyte destinationAddress) 
 
{ 
 
  LoRa.beginPacket(); 
 
  LoRa.write(destinationAddress); 
 
  LoRa.write(localAddress); 
 
  LoRa.write(outgoing.length()); 
 
  LoRa.print(outgoing); 
 
  LoRa.endPacket(); 
 
} 
 
void receiveMessage(int packetSize) 
 
{ 
 
  if (packetSize == 0) 
 
    return; 
 
 
 
  int recipient = LoRa.read(); 
 
  byte sender = LoRa.read(); 
 
  byte incomingLength = LoRa.read(); 
 
 
 
  String incoming = ""; 
 
 
 
  while (LoRa.available()) 
 
  { 
 
    incoming += (char)LoRa.read(); 
 
  } 
 
  if (incomingLength != incoming.length()) 
 
  { 
 
    Serial.println("Error: Message length does not match length"); 
 
    return; 
 
  } 
 
 
 
  if (recipient != localAddress) 
 
  { 
 
    return; 
 
  } 
 
} 
 
 
Code for End Devices:
 
#include <Arduino.h> 
 
#include <FastLED.h> 
 
#include <Wire.h> 
 
#include <ClosedCube_OPT3001.h> 
 
#include <SPI.h> 
 
#include <LoRa.h> 
 
 
 
#define debounceInterval 200 
 
#define LED_PIN 15 
 
#define NUM_LEDS 64 
 
#define OPT3001_ADDRESS 0x44 
 
 
 
const int csPin = 5; 
 
const int resetPin = 4; 
 
const int irqPin = 2; 
 
byte localAddress = 0xBB; 
 
byte destinationAddress = 0xAA; 
 
long lastSendTime = 0; 
 
int interval = random(500) + 100; 
 
 
 
byte address; 
 
String command; 
 
 
 
boolean buttonPrevState = false; 
 
boolean buttonPressed = false; 
 
unsigned long lastMillis = 0; 
 
unsigned long lastInterruptMillis = 0; 
 
int val; 
 
 
 
CRGB leds[NUM_LEDS]; 
 
ClosedCube_OPT3001 opt3001; 
 
 
 
void configureSensor(); 
 
void printResult(String textOPT3001 result); 
 
void printError(String textOPT3001_ErrorCode error); 
 
void receiveMessage(int packetSize); 
 
 
 
void setup() 
 
{ 
 
  Serial.begin(115200); 
 
  pinMode(34, INPUT); 
 
  pinMode(12, OUTPUT); 
 
 
 
 
  pinMode(13, OUTPUT); 
 
  pinMode(15, OUTPUT); 
 
  digitalWrite(13, HIGH); 
 
  digitalWrite(12, LOW); 
 
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS); 
 
 
 
  opt3001.begin(OPT3001_ADDRESS); 
 
  Serial.print("OPT3001 Manufacturer ID"); 
 
  Serial.println(opt3001.readManufacturerID()); 
 
  Serial.print("OPT3001 Device ID"); 
 
  Serial.println(opt3001.readDeviceID()); 
 
 
 
  configureSensor(); 
 
  printResult("High-Limit"opt3001.readHighLimit()); 
 
  printResult("Low-Limit"opt3001.readLowLimit()); 
 
  Serial.println("----"); 
 
 
 
  Serial.println("Start LoRa duplex"); 
 
 
 
  LoRa.setPins(csPin, resetPin, irqPin); 
 
 
 
  if (!LoRa.begin(433E6)) 
 
  { 
 
    Serial.println("LoRa init failed. Check your connections."); 
 
    while (true) 
 
    { 
 
    } 
 
  } 
 
  LoRa.setSpreadingFactor(12); 
 
} 
 
void loop() 
 
{ 
 
  for (int j = 0; j < NUM_LEDS; j++) 
 
  { 
 
    leds[j] = CRGB(255255255); 
 
  } 
 
  FastLED.setBrightness(val); 
 
  FastLED.show(); 
 
 
 
  if (buttonPressed == true) 
 
  { 
 
    if (millis() - lastInterruptMillis > debounceInterval) 
 
    { 
 
 
 
 
      OPT3001 result = opt3001.readResult(); 
 
      int setBrightnessValue = result.lux; 
 
      Serial.print("To be set brightness = "); 
 
      Serial.println(setBrightnessValue); 
 
      delay(1000); 
 
      buttonPrevState = !buttonPrevState; 
 
      buttonPressed = false; 
 
      lastInterruptMillis = millis(); 
 
      while (buttonPrevState == true && buttonPressed == false) 
 
      { 
 
        OPT3001 result = opt3001.readResult(); 
 
        int toBeSetBrightness = result.lux; 
 
 
 
        printResult("OPT3001", result); 
 
        if (toBeSetBrightness > setBrightnessValue) 
 
        { 
 
          FastLED.setBrightness(val--); 
 
          FastLED.show(); 
 
          Serial.println("Decreasing Brightness"); 
 
          Serial.println(val); 
 
          delay(200); 
 
        } 
 
        else if (toBeSetBrightness < setBrightnessValue) 
 
        { 
 
          FastLED.setBrightness(val++); 
 
          FastLED.show(); 
 
          Serial.println("Increasing Brightness"); 
 
          Serial.println(val); 
 
          delay(200); 
 
        } 
 
        else 
 
        { 
 
          Serial.println("in this else"); 
 
        } 
 
      } 
 
    } 
 
    else 
 
    { 
 
      buttonPressed = false; 
 
    } 
 
  } 
 
  receiveMessage(LoRa.parsePacket()); 
 
} 
 
 
 
 
 
 
void configureSensor() 
 
{ 
 
  OPT3001_Config newConfig; 
 
 
 
  newConfig.RangeNumber = B1100; 
 
  newConfig.ConvertionTime = B0; 
 
  newConfig.Latch = B1; 
 
  newConfig.ModeOfConversionOperation = B11; 
 
 
 
  OPT3001_ErrorCode errorConfig = opt3001.writeConfig(newConfig); 
 
  if (errorConfig != NO_ERROR) 
 
    printError("OPT3001 configuration", errorConfig); 
 
  else 
 
  { 
 
    OPT3001_Config sensorConfig = opt3001.readConfig(); 
 
    Serial.println("OPT3001 Current Config:"); 
 
    Serial.println("------------------------------"); 
 
 
 
    Serial.print("Conversion ready (R):"); 
 
    Serial.println(sensorConfig.ConversionReady, HEX); 
 
 
 
    Serial.print("Conversion time (R/W):"); 
 
    Serial.println(sensorConfig.ConvertionTime, HEX); 
 
 
 
    Serial.print("Fault count field (R/W):"); 
 
    Serial.println(sensorConfig.FaultCount, HEX); 
 
 
 
    Serial.print("Flag high field (R-only):"); 
 
    Serial.println(sensorConfig.FlagHigh, HEX); 
 
 
 
    Serial.print("Flag low field (R-only):"); 
 
    Serial.println(sensorConfig.FlagLow, HEX); 
 
 
 
    Serial.print("Latch field (R/W):"); 
 
    Serial.println(sensorConfig.Latch, HEX); 
 
 
 
    Serial.print("Mask exponent field (R/W):"); 
 
    Serial.println(sensorConfig.MaskExponent, HEX); 
 
 
 
    Serial.print("Mode of conversion operation (R/W):"); 
 
    Serial.println(sensorConfig.ModeOfConversionOperation, HEX); 
 
 
 
 
 
 
    Serial.print("Polarity field (R/W):"); 
 
    Serial.println(sensorConfig.Polarity, HEX); 
 
 
 
    Serial.print("Overflow flag (R-only):"); 
 
    Serial.println(sensorConfig.OverflowFlag, HEX); 
 
 
 
    Serial.print("Range number (R/W):"); 
 
    Serial.println(sensorConfig.RangeNumber, HEX); 
 
 
 
    Serial.println("------------------------------"); 
 
  } 
 
} 
 
 
 
void printResult(String textOPT3001 result) 
 
{ 
 
  if (result.error == NO_ERROR) 
 
  { 
 
    Serial.print(text); 
 
    Serial.print(": "); 
 
    Serial.print(result.lux); 
 
    Serial.println(" lux"); 
 
  } 
 
  else 
 
  { 
 
    printError(text, result.error); 
 
  } 
 
} 
 
 
 
void printError(String textOPT3001_ErrorCode error) 
 
{ 
 
  Serial.print(text); 
 
  Serial.print(": [ERROR] Code #"); 
 
  Serial.println(error); 
 
} 
 
 
 
void sendMessage(String outgoingbyte destinationAddress) 
 
{ 
 
  LoRa.beginPacket(); 
 
  LoRa.write(destinationAddress); 
 
  LoRa.write(localAddress); 
 
  LoRa.write(outgoing.length()); 
 
  LoRa.print(outgoing); 
 
  LoRa.endPacket(); 
 
 
 
 
} 
 
void receiveMessage(int packetSize) 
 
{ 
 
  if (packetSize == 0) 
 
    return; 
 
 
 
  int recipient = LoRa.read(); 
 
  byte sender = LoRa.read(); 
 
  byte incomingLength = LoRa.read(); 
 
 
 
  String incoming = ""; 
 
 
 
  while (LoRa.available()) 
 
  { 
 
    incoming += (char)LoRa.read(); 
 
  } 
 
  if (incomingLength != incoming.length()) 
 
  { 
 
    Serial.println("Error: Message length does not match length"); 
 
    return; 
 
  } 
 
 
 
  if (recipient != localAddress) 
 
  { 
 
    return; 
 
  } 
 
 
  if (sender == destinationAddress) 
 
  { 
 
    if (incoming == "ModeChange") 
 
    { 
 
      buttonPressed = !buttonPressed; 
 
    } 
 
    else 
 
    { 
 
      String data = incoming; 
 
      data += '\n'; 
 
      char buff[100]; 
 
      data.toCharArray(buff, 100); 
 
      val = data.toInt(); 
 
      Serial.print("Received Value from POT is: "); 
 
      Serial.println(val); 
 
 
    } 
 
  } 
 
} 
 
 
Working:
Base station: The base station reads the potentiometer's analog input value and constantly transmits to the end device using the LoRa module. 
The base station also has a switch which toggles the end device between 'manual mode' and 'maintain lux value mode'. 
Whenever this switch is pressed, a message is transmitted through the L0Ra module indicating to the end device to switch the control mode.
End device: The end device takes potentiometer readings received by LoRa module and controls
the brightness of the LED strip (when in manual mode). Whenever the end device receives a mode toggling message, 
the end device increases or decreases the brightness of the LED strip to maintain the lux value of the environment when the switch was pressed.
 During this time, the value transmitted by the potentiometer does not affect the LED strip's brightness.
 
Hope you implement this project idea and let us know how it went. 
Feel free to come up with other cool ideas and share them with us. 
You can buy all the components required from our store Robocraze.
 
 
Comment below if you get stuck or have any further questions.