Automatic Headlight Help

Hello all,

I am new to this forum and quite new to programming. I have come across a stumbling block and hope to gain some experience here so I can learn how to solve the problem.

I have watched many tutorials online and read lots about Arduino but I haven't managed to find a solution to the problem I have (although I'm sure it's because I'm not looking in the right places!)

I have come up with the following code which is working well and has been for the last few months. There are just a few changes I'd like to impliment to make it a more sophisticated setup.

I'll start with the slight niggle I have first;

When I pass under a footbridge or small flyover in slightly dim light conditions then the headlights come on for about half a second. I am struggling to average out the reading from the LDR sufficiently to prevent this happening without causing the lights to stay on once triggered or not come on at all.

Additional functionality I would like to add is;

  1. Turn lights on when the alarm is disarmed if it is dark.
  2. Leave lights on for 60 seconds when the vehicle is switched off if it is dark.

This is the code I am currently using that works well apart from aforementioned issues.

const int numReadings = 35;
int readings[numReadings];       
int readIndex = 0;              
int total = 0;                  
int average = 0;   
int CalcRead = map(A1,0,1023,0,100);         
const int lightSensePin = A1; //LDR in Dashboard
const int lightRelayPin = 12; //Relay to control lights pull LOW
const int OnThreshold = 225;
const int OffThreshold = 550;
const int ignPin = 2; //5v HIGH when ignition is ON
const int ledPin =13;//For testing outside car
int ignState = 0;
void setup() {
  Serial.begin(9600);
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
  readings[thisReading] = 0;
  }
  pinMode(lightSensePin, INPUT);
  pinMode(lightRelayPin,OUTPUT);
  pinMode(ignPin, INPUT);
  pinMode(ledPin, OUTPUT);
  }

void loop() {
 ignState = digitalRead(ignPin);
   total = total - readings[readIndex];
  readings[readIndex] = analogRead(lightSensePin);
  total = total + readings[readIndex];
  readIndex = readIndex + 1;
  if (readIndex >= numReadings) {
  readIndex = 0;
  }

  average = total / numReadings;
  Serial.println(average);
  delay(1);
if (ignState == HIGH){
  if (average <OnThreshold){
    digitalWrite(lightRelayPin, LOW);}
    else if (average >OffThreshold){
      digitalWrite(lightRelayPin,HIGH);
    }}
    else
    {digitalWrite(lightRelayPin,HIGH);}}

Thank you for looking and hopefully there is a solution out there!

Many Thanks, George

I forgot to mention that I was trying to get my head around state change detection for the 60 second Headlights ON delay after turning the ignition OFF.

Would state change detection to start a timer be the best way of implementing this?

If so, how would I go about it?

I am struggling to average out the reading from the LDR

Another approach is to capture the time when the 'dark' threshold is first detected. Come back a minute later and check if it is till 'dark'. Yes? Ok, I guess it really is dark, turn on the lights. This alone would interfere with the ability to turn the lights on immediately as in when you jump in the car at night, start it up and would like to drive off right away without waiting for your system to make the 'is it really dark?' decision. This could be overcome by allowing the 'is it really dark?' function to run only if 'x' amount of time has passed since the car was started. Else, turn the lights on now if the dark threshold is detected.- Scotty

Thanks for the reply Scotty,

This is a solution I had thought about but with a slightly different approach.

I thought about asking - has the light level been below the threshold value for more than, say, 2 seconds? then a quick fluctuation at motorway speed such as a bridge wouldn't trigger it but they would come on if, for example, you went into a tunnel.

Timing, however, is the part of programming that I am struggling to get my head around.

This afternoon I have tried to get the delay portion of the code to work. (I have also changed some of the LDR reading calculations to see if that improves things.)

There is now a new problem - the headlights work as before, but, with the new code I have added, there is some unwanted behaviour.

When the ignition is turned OFF and the lights are OFF (ambient light levels HIGH) the lights stay off. (as it should be)

When the ignition is turned OFF and the lights are ON (ambient light levels LOW) the lights now stay on indefinitely. I want this to happen but they need to switch themselves OFF after 60 seconds (code set to 30s currently for testing)

Please can someone advise how I can change the code to make them turn OFF after the set delay period?

Thanks, George

Would help if I uploaded the code!

const int numReadings = 35;
int readings[numReadings];       
int readIndex = 0;              
int total = 0;                  
int average = 0;   
int CalcRead = map(A1,0,1023,0,100);         
const int lightSensePin = A1; //LDR in Dashboard
const int lightRelayPin = 12; //Relay to control lights pull LOW
const int OnThreshold = 240;
const int OffThreshold = 600;
const int ignPin = 2; //5v HIGH when ignition is ON
int ignState = 0;
unsigned long ignOffTime = millis();
const long lightDelay = 30000;
void setup() {
  Serial.begin(9600);
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
  readings[thisReading] = 0;
  }
  pinMode(lightSensePin, INPUT);
  pinMode(lightRelayPin,OUTPUT);
  pinMode(ignPin, INPUT);
  }

void loop() {
   
 ignState = digitalRead(ignPin);
   total = total - readings[readIndex];
  readings[readIndex] = analogRead(lightSensePin);
  total = total + readings[readIndex];
  readIndex = readIndex + 1;
  if (readIndex >= numReadings) {
  readIndex = 0;
  }

  average = total / numReadings;
  Serial.println(average);
  delay(1);
if (ignState == HIGH){
  if (average <OnThreshold){
    digitalWrite(lightRelayPin, LOW);}
    else if (average >OffThreshold){
      digitalWrite(lightRelayPin,HIGH);}
    else
    {digitalWrite(lightRelayPin,HIGH);}}




//timer code added
unsigned long currentMillis = millis();
if (ignState == LOW && lightRelayPin == LOW){
  (ignOffTime = currentMillis);
  if (currentMillis - ignOffTime <= lightDelay)
  {digitalWrite (lightRelayPin,LOW);}
  else if (currentMillis - ignOffTime >= lightDelay)
  {digitalWrite (lightRelayPin, HIGH);}
  else {digitalWrite(lightRelayPin, HIGH);}}
}
if (ignState == HIGH){
  if (average <OnThreshold){
    digitalWrite(lightRelayPin, LOW);}
    else if (average >OffThreshold){
      digitalWrite(lightRelayPin,HIGH);}
    else
    {digitalWrite(lightRelayPin,HIGH);}}




//timer code added

Your code would be a LOT easier to read if you put EVERY { and EVERY } on a line BY ITSELF.

You turn the relay pin off if the light level is low, and on if it is above the threshold and on if it is equal the threshold. Both of those conditions are true if the light level is not below the threshold, so you don't need the else if statement.

  (ignOffTime = currentMillis);

(What) (are) (the) (parentheses) (for) (?)

That the ignition switch IS off does NOT mean that it was just turned off. You need to look at the state change detection example and determine when the switch BECOMES turned off. THAT is the time you want to record.

Thanks for your reply Paul,

I'm quite new to coding so didn't know about separating the { & }. I will change that.

As for the relay. Pulling it LOW turns the lights ON and holding the pin HIGH turns the lights OFF.

The ON threshold is lower than the OFF threshold hence the else if statement. This is to prevent the lights flashing on and off when passing through dappled light (such as under trees.)

Regarding state change detection. I need to detect the falling edge I guess? Would this be implemented with a != statement?

It's timing that I am struggling to understand and I'm not all too clear on state change detection either.

Thanks again, George

Regarding state change detection. I need to detect the falling edge I guess?

First, you detect that a change has occurred - the current value is not the same as the previous value (yes, using !=). Then, you can do something if the current state is one thing (pressed, for example) and something else if the current state is the other thing (released, for example). Either of those things can be nothing.

Suppose that you are a ticket taker. Everybody has a ticket. Some are blue. Some are red. You can easily tell when one person hands you a red ticket, and the next person also hands you a red ticket. The current ticket color is the same as the previous ticket, so no change happened.

If the next person hands you a blue ticket, the current ticket color is NOT the same as the previous ticket color, so a change has occurred.

You might have all the people with blue tickets go left, and all the people with red tickets go right. When the state is the same, you can tell a person "follow that person". When the state changes, you can't say that any more. What you need to say (go left or go right) depends on the current ticket color.

Sorry I changed a few of the states to work with my ESP
Compiles Error free,
for both ESP and Nano, though I don't have a nano to actually test.

Have a read thru and see if it confuses you more.!
But it should give another view.

You can also Smooth the bumps in the readings by keeping a running total,
then either add or subtract half the current reading depending on direction.
I did some crazy stuff trying to smooth a tilt sensor many(MANY) moons ago.
I wrote a little VB to demonstrate different methods and the reaction times.

I guess I should read your original post to see what you were actually asking :o

#define LIGHTSON  LOW
#define LIGHTSOFF HIGH
#define READINTERVAL 5
const int numReadings = 50;

#ifdef ESP8266
  #define HEADER F("ESP-")
  #define IGNITIONACTIVE LOW
  const int ignPin = 0;         //0v LOW when ignition is ON
  const int lightSensePin = A0; //LDR in Dashboard
  const int lightRelayPin = 16; //Relay to control lights pull LOW is ON
  const int OnThreshold = 50;
  const int OffThreshold = 600;
#else
  #define HEADER F("OTHER-")
  #define IGNITIONACTIVE HIGH
  const int ignPin = 2;         //5v HIGH when ignition is ON
  const int lightSensePin = A1; //LDR in Dashboard
  const int lightRelayPin = 12; //Relay to control lights pull LOW is ON
  const int OnThreshold = 240;
  const int OffThreshold = 600;
#endif

int readings[numReadings];       
int readIndex = 0;              
int total = 0;                  
int average = 0;   

//int CalcRead = map(A1,0,1023,0,100);



int ignState = 0;
unsigned long ignLastOnTime = 0;
unsigned long lightsOnTime = 0;
unsigned long lastread = 0;
const long lightDelay = 30000;

unsigned long lastdaylighttime = 0;
int runningaverage = 0;
int smoothed = 0;
int difference = 0;
bool needlights = false;
bool lightsareon = false;
bool AlwaysUseLights = false;

//=====================================
String pad(String x2,byte p);
void TurnLightsOn(void);
void TurnLightsOff(void);
bool IsIgnitionOn(void);
bool isitDark(void);
bool isitLight(void);
int  CalcRead(int r);
//=====================================
void TurnLightsOn(void){digitalWrite(lightRelayPin, LIGHTSON); lightsareon=true;}
void TurnLightsOff(void){digitalWrite(lightRelayPin, LIGHTSOFF); lightsareon=false;}
bool IsIgnitionOn(void){return (digitalRead(ignPin) == IGNITIONACTIVE);}
bool isitDark(void){return (smoothed < OnThreshold);}
bool isitLight(void){return (average > OffThreshold);}
int  CalcRead(int r){return map(r,0,1023,0,100);}
String pad(String x2,byte p){
  for (int i = x2.length(); i < p; i++) {x2 = String(0) + x2;}
  return x2;
}
//=====================================

void setup() {
  Serial.begin(115200);
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {readings[thisReading] = 0;}
  pinMode(lightSensePin, INPUT);
  pinMode(lightRelayPin,OUTPUT);
  pinMode(ignPin, INPUT);
  readIndex = 0;
  total = analogRead(lightSensePin);
  lightsOnTime = millis();
  ignLastOnTime = millis();
  TurnLightsOff();
}


void loop() {  
  //Serial.print('.'); // uncomment to see how many dots fill a screen !
  if ((millis() - lastread) < READINTERVAL){return;} // too much reading is bad for the mind.!
  lastread = millis();
  total = total - readings[readIndex];
  readings[readIndex] = analogRead(lightSensePin);
  total = total + readings[readIndex];
   
  readIndex = readIndex + 1;
  if (readIndex < numReadings) {return;}

// So you have done 35 readings, whats to do?
  readIndex = 0;
  ignState = IsIgnitionOn();        // Test Ignition
  average = total / numReadings;

  runningaverage = (runningaverage + average);
  runningaverage = runningaverage/2;

  if(smoothed > runningaverage)     {smoothed = smoothed - ((smoothed - runningaverage)/2);}
  else if(smoothed < runningaverage){smoothed = smoothed + ((runningaverage - smoothed) /2);}

  if(IsIgnitionOn()){
    if (AlwaysUseLights) {lightsOnTime = millis(); needlights = true;}
    if (isitDark())   {
      if ((millis() - lastdaylighttime) > 750){
        lightsOnTime = millis(); 
        needlights = true;
       }
    } else {
      lastdaylighttime = millis();
      if ((millis() - lightsOnTime) > 3000){needlights = false;}
    }
    ignLastOnTime = millis();
  } else {
    //if you want to keep the delay for the full time and not turn off if it gets light.
    // remove next line 
    if (!isitDark())   {needlights = false;} 
    if((millis() - ignLastOnTime) > lightDelay){needlights = false;}
  }

  Serial.print((String) HEADER + F("Time:") + String(millis(),DEC) + F(", "));
  Serial.print((String) F("Ign:")           + String(IsIgnitionOn()) + F(", "));
  Serial.print((String) F("Dark:")          + String(isitDark()) + F(", "));
  Serial.print((String) F("Need:")          + String(needlights) + F(", "));
  Serial.print((String) F("Lamp:")          + String(lightsareon) + F(", "));
  Serial.print((String) F("Total:")         + pad(String(total,DEC),6) + F(", "));
  Serial.print((String) F("Average:")       + pad(String(average,DEC),3) + F(", "));
  Serial.print((String) F("Smoothed:")      + pad(String(smoothed,DEC),3) + F(", "));
  Serial.print((String) F("Avg2:")          + pad(String(runningaverage,DEC),3) + F(", ")); 
  Serial.print((String) F("Avg3:")          + pad(String(((runningaverage+average)/2),DEC),3) + F(", ")); 

  // Finally Turn the lights On or Off depending on current state and required state.
  if(lightsareon){
    Serial.print((String) F("Burn:")  + pad(String((millis() - ignLastOnTime)/100L,DEC),3) + F(", "));
    if (!needlights){TurnLightsOff();}
  } else {
    if (needlights) {TurnLightsOn();}
  }
  Serial.println();
}

Ahh . "Number Weighting"
Was that the term I was looking for,?
Can't be, I've never marked an exam paper,

... Or was it..??
Smoothing.?
That feels nicer.

It's more like a low-pass filter. Short blips are ignored but a persistent trend isn't.

There's lots of ways to make low pass filters. A moving-average filter is usually the best for this kind of stuff where you want it to still react quickly and you don't care too much about the actual frequencies in use.

The Scientist and Engineer's Guide to Digital Signal Processing, Chapter 15 part 5 shows the quick and efficient way to do it. The rest of the book is a great read too, for the next time you have a problem removing or detecting an input frequency.

Hiddenvision thabks for your reply. The code literally blows my mind despite my best efforts to understand it.

With the way it has been written now, is there any need for the buffer thresholds (off higher than on?) As the averaging will be much better ?

Cheers, George

No, it appears the code never calls isiitLight() so the higher threshold isn't used. If it does what you want then that can be removed.

Sorry for the late reply, been off elsewhere.

To be honest I forget what the code does now.
But I had added in stuff that was never called.
To trap for an upper limit is OK to do,
I think because I was only shorting the Analog pin with my finger I just used the single value for ease.

it was more of a debug thing.
Just comment out the lines not needed and add in more that are.!

Hv.