Tractor intermittent wiper motor control with added DFRobot Intelligent Rain Detection Module

Hello people, I need help to improve a project I made a year or so ago that consisted of controlling an Auto-Return Wiper Motor for my tractor. This project currently have variable intermittent motor control via a Slide Potentiometer. I want to add automatic wiper control depending on the rain/snow detected on the windshield. I have found an Intelligent Rain Detection Module from DFRobot P/N SEN0545.

https://www.dfrobot.com/product-2611.html
This sensor can detect rain through the windshield from inside the tractor cab.
Does anyone have experience with this DFRobot Intelligent Rain Detection Module?

How could I add this sensor to my current sketch for it to work?
This is my current code:

// This simulation is to add intermittent control to an Auto Return wiper motor for a tractor.
// When the Slide Potentiometer is all the way to the left, wiper motor is in continuous mode.
// The further you move the slide Potentiometer to the right, the longer the Intermittent Delay is (20 Seconds Max)
// The blue LED is to simulate when the Auto Return Wiper Motor would powered by a 12V source

int sensorPin = A1; // Delay Slider Potentiometer
int motorPin = 2; // To Wiper Relay
int interval = 0;

void setup() {
  // declare the ledPin as an OUTPUT:
  pinMode(motorPin, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
}

void loop() {

  int sensorValue = analogRead(A1);
  if (sensorValue <= 4) { // Determine the minimum potentiometer position before intemittent wiper is activated
    interval = 0;
  }

  else interval = (2000 + (sensorValue * 18)); // 2000 is the time for a complete wipe, sensorValue determine the maximum intermittent in seconds

  static unsigned long lastTime = 0;
  if (millis() - lastTime >= interval)
  {
    lastTime = millis();
    //Serial.println(interval);
    //Serial.println(sensorValue);
    digitalWrite(motorPin, LOW);
    digitalWrite(LED_BUILTIN, HIGH);
    delay (400); // Pulse the motor to activate auto-return
    digitalWrite(motorPin, HIGH);
    digitalWrite(LED_BUILTIN, LOW);
  }

}

Here it is on wokwi.com

Could this work?
Any feedback would be appreciated...

Cheers,

No experience, but the documentation makes no mention of it working after dark.

Actually, they make it sound like it will work in the dark. From the FAQ in the user manual:

Q1: How much does the sunlight in night-time and day-time environments affect the rain
detection sensitivity and accuracy? What’s the purpose of optical calibration?
A1: There is no effect, because the module uses HALIOS®-SD patented technology that can reduce the influence of sunlight on the module; optical calibration can eliminate the optical path asymmetry caused by problems like components, module production and installation tolerance.

An interesting bit of kit, but I remain to be convinced. I was amused by the admonitions to keep it out of direct sunlight for long periods and away from moisture and vibration. But I suppose if you needed the capability, for ~$20 it'd be worth trying out.

I will order one and give it a try...
Digikey.com have them, so shipping is fast:
https://www.digikey.com/en/products/detail/dfrobot/SEN0545/18069212?s=N4IgTCBcDaIMoFEByAGArAFjSAugXyA

Cheers,

I'll say: drop it. Save the money.
Driving buses equipped with that function I'm not impressed at all. The wipers sometimes run without reason, sometimes don't run when they should.
The human, drivers, input adjusting the interval is the best way. Rain/snow intensicity varies and adjusting the interval manually is the most reliable way.

Here is the updated code generated by chatGPT:

#include <SoftwareSerial.h>

SoftwareSerial rainSensor(10, 11); // RX (Arduino) ← TX (Sensor) (TX not used)
int potPin = A1;  // Potentiometer pin
int motorPin = 2; // To Wiper Relay
int interval = 0; // Delay interval

void setup() {
  pinMode(motorPin, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  rainSensor.begin(115200);
}

void loop() {
  int sensorValue = analogRead(potPin);
  int rainLevel = 0;  // Default value for rain level (No rain)

  // **Check if potentiometer is at maximum**
  if (sensorValue >= 1020) {
    // If potentiometer is at maximum, use rain sensor
    rainLevel = readRainSensor(); // Get rain intensity
  } else {
    // If potentiometer is not at maximum, use potentiometer control for delay
    if (sensorValue <= 4) interval = 0; // Min pot position → Continuous wipe
    else interval = (2000 + (sensorValue * 18)); // Max ~20s delay
  }

  // **Set wiper delay based on rain level (only if potentiometer is at max)**
  if (rainLevel == 3) { 
    interval = 0; // Heavy Rain → Continuous Wiping
  } else if (rainLevel == 2) { 
    interval = 3000; // Moderate Rain → Short Delay
  } else if (rainLevel == 1) { 
    interval = 8000; // Light Rain → Medium Delay
  } // No rain → potentiometer delay

  // **Wiper Control Logic**
  static unsigned long lastTime = 0;
  if (millis() - lastTime >= interval) {
    lastTime = millis();
    digitalWrite(motorPin, LOW);
    digitalWrite(LED_BUILTIN, HIGH);
    delay(400); // Pulse to trigger auto-return
    digitalWrite(motorPin, HIGH);
    digitalWrite(LED_BUILTIN, LOW);
  }
}

// **Function to Read Rain Sensor Data**
int readRainSensor() {
  if (rainSensor.available()) {
    int rainData = rainSensor.read();
    if (rainData >= 0 && rainData <= 3) return rainData; // Return valid rain level
  }
  return 0; // Default: No Rain
}

How It Works:

  1. When the potentiometer is at maximum (fully right), the rain sensor will control the wiper delay:
  • Heavy rain (level 3) → Continuous wiping.
  • Moderate rain (level 2) → Short delay (3s).
  • Light rain (level 1) → Medium delay (8s).
  • No rain (level 0) → No wiping.
  1. When the potentiometer is not at maximum, it controls the delay for the intermittent wipe (ranging from continuous to a delay of up to 20 seconds based on the potentiometer's position).

I will give it a try and see how it does.
Interesting project to be determined...

Cheers

This project is to free my hands to strickly operate the showblowing tractor.
The setup I currently have use a slide potentiometer to control the delay for the intermittent wiper swipes. If I want it to be on automatic, I just put the potentiometer to maximum and it will do the delay automatically depending on precipitation. This is usefull when you are snowblowing against the wind and suddently, the windshield if full of snow, so I have to stop to quickly turn on the wiper motor...

It may work, or not but I will try anyhow...

Cheers

I know what You think of. Driving cars, buses, truacks, trains(!) for 50 years I would really like to have had such a function.
Go on experimenting but I warn You, it isn't easy.
Good luck!

I had a Mazda 6 that had automatic wiper control and it was working flawlessly.
I just ordered the sensor, it will be interesting to see if I can make this work.
I will share my findings once I have experimented with all this...

Cheers

Interesting sensor! I also have a wiper project I would like to upgrade with this sensor. Thanks for making me aware of it.

Unfortunately you will need to do more to communicate with that sensor than what chatGTP has given you.
This sensor requires that the messages to/from it are to be formatted in a specific way.
It uses a 5 byte protocol that would look something like this: ;1015

it breaks down like this:

The first byte is always a semi colon ";" is called the message Header and indicates the start of a message.

The second byte is called a message flag that indicates what type of data is contained in this message.

The third and fourth bytes are the actual message data.

The fifth byte is a crc-8 checksum used to insure that the message has been received correctly.

The data must be parsed out from the message.
A good tutorial to get started learning how to do that can be found here:

This sensor also requires performing a calibration routine after installation before it can provide correct data.

Also there is a library that can make dealing with the crc-8 checksum easier:
GitHub - RobTillaart/CRC: CRC library for Arduino

Thanks, I haven't received the sensor yet...
Here is the last code generated with chatGPT with calibration triggered with a button...

#include <SoftwareSerial.h>

SoftwareSerial rainSensor(10, 11); // RX, TX (TX not used)

const int sensorPin = A1;   // Potentiometer
const int motorPin = 2;     // Wiper relay control
const int buttonPin = 7;    // Calibration button

int interval = 0;

void setup() {
  pinMode(motorPin, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP); // Internal pull-up for button
  Serial.begin(9600);
  rainSensor.begin(9600);
}

void loop() {
  if (digitalRead(buttonPin) == LOW) { // If calibration button is pressed
    Serial.println("Starting calibration...");
    startCalibration();
    delay(5000); // Wait for rain exposure
    saveCalibration();
  }

  int sensorValue = analogRead(sensorPin);

  if (sensorValue >= 1020) { // Potentiometer at maximum → Auto mode
    interval = getRainBasedDelay();
  } else if (sensorValue <= 4) { // Potentiometer at minimum → Continuous mode
    interval = 0;
  } else { // Manual mode based on potentiometer
    interval = (2000 + (sensorValue * 18)); // 2s wipe + variable delay (max 20s)
  }

  static unsigned long lastTime = 0;
  if (millis() - lastTime >= interval) {
    lastTime = millis();
    digitalWrite(motorPin, LOW);
    digitalWrite(LED_BUILTIN, HIGH);
    delay(400); // Pulse motor to activate auto-return
    digitalWrite(motorPin, HIGH);
    digitalWrite(LED_BUILTIN, LOW);
  }
}

// Read rain intensity and return appropriate delay
int getRainBasedDelay() {
  byte request[] = {0x03, 0x00, 0x01, 0x00, 0x14, 0x3F}; // Command to read rain level
  rainSensor.write(request, sizeof(request));
  delay(100); // Give sensor time to respond

  if (rainSensor.available()) {
    byte response = rainSensor.read();
    Serial.print("Rain Level: ");
    Serial.println(response);

    switch (response) {
      case 0x00: return 20000; // No rain → 20s delay
      case 0x01: return 15000; // Light rain → 15s delay
      case 0x02: return 10000; // Moderate rain → 10s delay
      case 0x03: return 5000;  // Heavy rain → 5s delay
      default: return 20000;   // Default to max delay if unknown response
    }
  }
  return 20000; // Default to max delay if no response
}

// Activate calibration mode
void startCalibration() {
  byte cmd[] = {0x03, 0x10, 0x01, 0x00, 0x14, 0x4F};
  rainSensor.write(cmd, sizeof(cmd));
  Serial.println("Calibration mode activated. Expose sensor to rain.");
}

// Save calibration data
void saveCalibration() {
  byte cmd[] = {0x03, 0x11, 0x01, 0x00, 0x14, 0x50};
  rainSensor.write(cmd, sizeof(cmd));
  Serial.println("Calibration saved. Restart sensor.");
}

I will do more research and testing once I get the rain sensor...

Cheers

I won't bother to go into the many many things that are completely wrong with that.

After you have received your sensor and figured out that chatGTP isn't going to be able to do this for you.
Read the tutorial I posted and learn what it means to parse a message.
Then come back and we will try to help.

P.S. The calibration is done once after install and never needs to be done again. It saves the calibration itself and returns a message saying whether it was successful or not. It must be done when it is not raining.
You really need to read the User manual and Datasheet provided by DFRobot.

1 Like

OK, got the sensor and after chatting with chatGPT for a while, this is what I got:

#include <SoftwareSerial.h>

SoftwareSerial rainSensor(10, 11);  // RX, TX (TX not used)

int sensorPin = A0;            // Potentiometer for delay control
int motorPin = 2;              // Wiper relay control
int calibrationButtonPin = 7;  // Calibration button pin
int rainState = 0;             // Current rain state (0: no rain, 1: light rain, etc.)
bool calibrationMode = false;

void setup() {
  Serial.begin(115200);                         // Start serial communication for debug
  rainSensor.begin(115200);                     // Start communication with the rain sensor
  pinMode(motorPin, OUTPUT);                    // Set motor relay pin as output
  pinMode(calibrationButtonPin, INPUT_PULLUP);  // Set the calibration button pin as input with pullup
  digitalWrite(motorPin, HIGH);                 // Initially keep the motor off
}

void loop() {
  // Read the potentiometer to control the wiper delay dynamically
  int sensorValue = analogRead(sensorPin);
  int interval = 0;  // Interval controlled by rain state & potentiometer

  // Check for calibration button press
  if (digitalRead(calibrationButtonPin) == LOW) {
    initiateCalibration();
  }

  // Check for rain sensor data
  if (rainSensor.available()) {
    byte receivedByte = rainSensor.read();
    if (receivedByte == 0x3A) {  // Frame start header
      byte data[5];              // Array to hold data bytes
      int i = 0;
      data[i++] = receivedByte;  // Store the header byte
      while (rainSensor.available() && i < 5) {
        data[i++] = rainSensor.read();  // Read the rest of the data frame
      }

      // Ensure we have a full 5-byte frame
      if (i == 5) {
        // Print raw data for debugging
        Serial.print("Raw data: ");
        for (int j = 0; j < 5; j++) {
          Serial.print(data[j], HEX);
          Serial.print(" ");
        }
        Serial.println();

        // Check if the header is correct (0x3A) and handle the rain state
        if (data[0] == 0x3A) {
          byte rainLevel = data[2];  // Extract rain level (third byte)

          Serial.print("Received data: Header: ");
          Serial.print(data[0], HEX);
          Serial.print(" Rain State: ");
          Serial.print(rainLevel, HEX);

          // Assign rain state
          rainState = rainLevel;
          switch (rainLevel) {
            case 0x00:
              Serial.println("No rain");
              rainState = 0;
              break;
            case 0x01:
              Serial.println("Light rain");
              rainState = 1;
              break;
            case 0x02:
              Serial.println("Moderate rain");
              rainState = 2;
              break;
            case 0x03:
              Serial.println("Heavy rain");
              rainState = 3;
              break;
            case 0x06:  // New case to handle post-calibration state
              Serial.println("Calibration in progress... (Temporary state)");
              break;
            default:
              Serial.print("Unknown rain state received: 0x");
              Serial.println(rainLevel, HEX);
              break;
          }

        } else {
          Serial.println("Invalid header byte, ignoring data");
        }
      }
    }
  }

  // Determine wiper interval based on rain state and potentiometer
  if (rainState != 0) {  // Only adjust interval if rain is detected
    switch (rainState) {
      case 1:                                               // Light rain
        interval = map(sensorValue, 0, 1023, 5000, 20000);  // 5s to 20s
        break;
      case 2:                                               // Moderate rain
        interval = map(sensorValue, 0, 1023, 3000, 10000);  // 3s to 10s
        break;
      case 3:                                              // Heavy rain
        interval = map(sensorValue, 0, 1023, 1000, 5000);  // 1s to 5s
        break;
    }
  }

  // Control the wiper motor based on interval
  static unsigned long lastTime = 0;
  if (rainState != 0 && millis() - lastTime >= interval) {
    lastTime = millis();
    digitalWrite(motorPin, LOW);   // Activate wiper motor
    delay(400);                    // Pulse the motor
    digitalWrite(motorPin, HIGH);  // Deactivate wiper motor
  }
}

// Function to initiate sensor calibration
void initiateCalibration() {
  Serial.println("Starting calibration...");

  // Calibration command sequence
  byte calibrationCommand[5] = { 0x3A, 0x08, 0x00, 0x00, 0x33 };

  // Send each byte of the calibration command
  for (int i = 0; i < 5; i++) {
    rainSensor.write(calibrationCommand[i]);
  }

  // Wait for the calibration process to complete
  delay(3000);  // Adjust this delay based on the module's calibration time

  Serial.println("Calibration complete.");
}


I am using an Arduino Nano for this project...

Basically, the sensor detects 4 rain states: No rain, Light rain, Moderate rain and Heavy rain.
A potentiometer is used to modify the wiper motor intervals for each rain states like this:
No Rain: Wipers remain OFF.
Light Rain: Potentiometer adjusts delay between 5s to 20s.
Moderate Rain: Potentiometer adjusts delay between 3s to 10s.
Heavy Rain: Potentiometer adjusts delay between 1s to 5s.
(I will modify these value for real life scenarios)

This seems to work OK as I see accurate output on serial when testing on the bench with rain sensor on the other side of 5mm glass and spraying water into it.

I designed and 3D printed a case for the sensor so it is almost touching the glass. The case also has a small Tact Switch used for the calibration.


I am not sure, however, if the calibration is really being performed and saved.

Can anyone help with this?
Here are the links for the Manual and Datasheet:
https://raw.githubusercontent.com/May-DFRobot/DFRobot/master/SEN0545User%20Manual.pdf
https://raw.githubusercontent.com/May-DFRobot/DFRobot/master/SEN0545Datasheet.pdf

If anyone want the 3D file for the case, let me know and I can upload the stl file here...

I will update this post when testing in real life to see if it will also detect snow...

Any inputs would be appreciated
Cheers

... Pretty sure calibration is not being performed, looking into it now...
Serial output after doing calibration is:
Starting calibration...

Calibration complete.

Raw data: 3A 82 6 0 48

Received data: Header: 3A Rain State: 6Calibration in progress... (Temporary state)

Updated code with calibration working, now the sensor send this data:

:white_check_mark: When Calibration Starts

Starting calibration...
Reading calibration data...

:white_check_mark: When Calibration is Successful

Calibration value received: 3A 83 XX XX XX
Calibration successful.

:white_check_mark: If Calibration Fails

Calibration verification failed.

Here's the complete code:

#include <SoftwareSerial.h>

SoftwareSerial rainSensor(10, 11); // RX, TX (TX not used)

int sensorPin = A0; // Potentiometer for delay control
int motorPin = 2; // Wiper relay control
int calibrationButtonPin = 7; // Calibration button pin
int rainState = 0; // Current rain state (0: no rain, 1: light rain, etc.)
bool calibrationMode = false;

void setup() {
  Serial.begin(115200); // Start serial communication for debug
  rainSensor.begin(115200); // Start communication with the rain sensor
  pinMode(motorPin, OUTPUT); // Set motor relay pin as output
  pinMode(calibrationButtonPin, INPUT_PULLUP); // Set the calibration button pin as input with pullup
  digitalWrite(motorPin, HIGH); // Initially keep the motor off
}

void loop() {
  // Read the potentiometer to control the wiper delay
  int sensorValue = analogRead(sensorPin);
  int interval = 0; // No default interval, we'll set it dynamically based on rain state

  if (sensorValue <= 4) { // Minimum potentiometer position (no intermittent)
    interval = 0;
  } else {
    interval = (2000 + (sensorValue * 18)); // Adjust the delay with potentiometer (2000ms base + scaling)
  }

  // Check for calibration button press
  if (digitalRead(calibrationButtonPin) == LOW) {
    initiateCalibration();
  }

  // Check for rain sensor data
  if (rainSensor.available()) {
    byte receivedByte = rainSensor.read();
    if (receivedByte == 0x3A) { // Frame start header
      byte data[5]; // Array to hold data bytes
      int i = 0;
      data[i++] = receivedByte; // Store the header byte
      while (rainSensor.available() && i < 5) {
        data[i++] = rainSensor.read(); // Read the rest of the data frame
      }
      
      // Ensure we have a full 5-byte frame
      if (i == 5) {
        // Print raw data for debugging
        Serial.print("Raw data: ");
        for (int j = 0; j < 5; j++) {
          Serial.print(data[j], HEX);
          Serial.print(" ");
        }
        Serial.println();

        // Check if the header is correct (0x3A) and handle the rain state
        if (data[0] == 0x3A) {
          byte rainLevel = data[2]; // Extract rain level (third byte)

          Serial.print("Received data: ");
          Serial.print("Header: ");
          Serial.print(data[0], HEX);
          Serial.print(" Rain State: ");
          Serial.print(rainLevel, HEX);

          // Check the rain state and take action
          switch (rainLevel) {
            case 0x00:  // No rain
              Serial.println("No rain");
              rainState = 0; // No rain, no wiper action
              break;
            case 0x01:  // Light rain
              Serial.println("Light rain");
              rainState = 1; // Light rain, trigger wiper action at interval
              break;
            case 0x02:  // Moderate rain
              Serial.println("Moderate rain");
              rainState = 2; // Moderate rain, trigger wiper action at shorter interval
              break;
            case 0x03:  // Heavy rain
              Serial.println("Heavy rain");
              rainState = 3; // Heavy rain, trigger wiper action at shortest interval
              break;
            case 0x06:  // Calibration in progress
              Serial.println("Calibration in progress... (Temporary state)");
              break;
            default:
              Serial.println("Unknown rain state");
              break;
          }
        } else {
          Serial.println("Invalid header byte, ignoring data");
        }
      }
    }
  }

  // Control the wiper motor based on rain state
  if (rainState != 0) {  // Only activate wiper motor if rain is detected
    static unsigned long lastTime = 0;

    // Set the interval dynamically based on rain state and potentiometer
    switch (rainState) {
      case 1: // Light rain
        interval = interval; // Controlled by potentiometer
        break;
      case 2: // Moderate rain
        interval = interval / 2; // Faster than light rain
        break;
      case 3: // Heavy rain
        interval = interval / 4; // Fastest wipe rate
        break;
      default:
        interval = 0;
        break;
    }

    // Control wiper motor with interval
    if (millis() - lastTime >= interval) {
      lastTime = millis();
      digitalWrite(motorPin, LOW); // Activate wiper motor
      delay(400); // Pulse the motor
      digitalWrite(motorPin, HIGH); // Deactivate wiper motor
    }
  } else {
    digitalWrite(motorPin, HIGH); // Keep motor off when there's no rain
  }
}

// Function to initiate sensor calibration
void initiateCalibration() {
  Serial.println("Starting calibration...");
  // Send calibration command to sensor
  byte calibrationCommand[] = {0x3A, 0x83, 0x00, 0x00, 0x54};
  rainSensor.write(calibrationCommand, sizeof(calibrationCommand));
  delay(3000); // Allow time for calibration

  // Read back calibration confirmation
  Serial.println("Reading calibration data...");
  byte readCommand[] = {0x3A, 0x03, 0x00, 0x00, 0x81};
  rainSensor.write(readCommand, sizeof(readCommand));
  delay(1000);

  if (rainSensor.available()) {
    Serial.print("Calibration value received: ");
    while (rainSensor.available()) {
      Serial.print(rainSensor.read(), HEX);
      Serial.print(" ");
    }
    Serial.println();
  } else {
    Serial.println("Calibration verification failed.");
  }
  Serial.println("Calibration successful.");
}

This is to be used with an "Auto-return wiper motor" as the sketch sent only a 400ms pulse to engage a wiper swipe.

I now need to install inside tractor to see how it work and do some tweaking if needed...

Cheers,

For anyone wanted to replicate this project, please note that the BLACK wire coming from the module is actually + 3.3V and the RED is Ground as mentioned in their product page here:
https://www.dfrobot.com/product-2611.html

Completed DFRobot Intelligent Rain Detection Module with case and connector.



Did you test it out? I'm thinking about ordering one myself for the same wiper purpose.

This project is still in progress, it does work great to detect rain, no so good for snow. I have to increase the sensibility settings to make it work better at detecting snow, but I have a few other projects right now that are more important time-wise.
I am replacing the dashboard that consists of simple warning lamps for my John Deere 322 tractor with a proper dashboard with a Nextion Toushscreen display:

I will work on the wiper controller later this spring and post here how it is working...
Cheers

1 Like

Can I ask how you stuck it to the windshield? It says to use an optical medium, but I assume any reasonably clear glue will do fine.

I didn't use any optical medium, the sensor is open to air. I just used double side tape on the 3D case I made and cut the tape where the sensor is on the box. I designed the case so the sensor is flush / almost touching the glass to take under consideration the thickness of the double side tape. The sensor fit tight inside the case and I used hot-melt glue to hold it down on the back side. I like using hot-melt glue because if you need to remove what is glued, you just need to add a few drops of isopropyl alcohol and in come right off.

Let me know if you would like the 3D printer STL file for the case if needed...

Cheers,