#include // Define the pins for RS485 communication #define RO 2 #define DI 3 #define RE 8 #define DE 7 #define RESPONSE_FRAME_SIZE 21 char sensorDataTextBuffer[200]; struct SoilSensorData { bool isSensorTimeout {false}; bool isValid {false}; float temperature {-1.0}; float humidity {-1.0}; unsigned int conductivity {-1}; float ph {-1.0}; unsigned int nitrogen {-1}; unsigned int phosphorus {-1}; unsigned int potassium {-1}; unsigned int salinity {-1}; }; class SoilSensor { public: SoilSensor() { modbus = new SoftwareSerial(RO, DI); } void initialise() { Serial.begin(9600); // Initialize serial communication for debugging modbus->begin(9600); // Initialize software serial communication at 9600 baud rate pinMode(RE, OUTPUT); // Set RE pin as output pinMode(DE, OUTPUT); // Set DE pi } void sendDataRequest() { digitalWrite(DE, HIGH); digitalWrite(RE, HIGH); delay(10); // Send the request frame to the soil sensor modbus->write(soilSensorRequest, sizeof(soilSensorRequest)); } SoilSensorData read() { SoilSensorData soilSensorData; // End the transmission mode and set to receive mode for RS485 digitalWrite(DE, LOW); digitalWrite(RE, LOW); delay(10); //Wait for the response from the sensor or timeout after 1 second unsigned long startTime = millis(); while (modbus->available() < RESPONSE_FRAME_SIZE && millis() - startTime < 1000) { delay(1); } if (modbus->available() >= RESPONSE_FRAME_SIZE) // If valid response received { // Read the response from the sensor byte index = 0; while (modbus->available() && index < RESPONSE_FRAME_SIZE) { soilSensorResponse[index] = modbus->read(); index++; } soilSensorData = computeData(); } else { soilSensorData.isSensorTimeout = true; } return soilSensorData; } ~SoilSensor() { if (modbus != nullptr) delete modbus; delete[] soilSensorResponse; } private: const byte soilSensorRequest[8] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x08, 0x44, 0x0C}; SoftwareSerial* modbus = nullptr; byte soilSensorResponse[RESPONSE_FRAME_SIZE]; int convertBytesToDecimal(unsigned int offset) { return soilSensorResponse[offset] * 256 + soilSensorResponse[offset + 1]; } SoilSensorData computeData() const { SoilSensorData soilSensorData; if (soilSensorResponse[0] != 1 || soilSensorResponse[1] != 3 || soilSensorResponse[2] != 16) { soilSensorData.isValid = false; return soilSensorData; } soilSensorData.isValid = true; soilSensorData.temperature = convertBytesToDecimal(3) / 10.0; //degrees celcius soilSensorData.humidity = convertBytesToDecimal(5) / 10.0; // percent soilSensorData.conductivity = convertBytesToDecimal(7); // microSiemens per centimetre soilSensorData.ph = convertBytesToDecimal(9) / 100.0; soilSensorData.nitrogen = convertBytesToDecimal(11); // miligram per Kilogram soilSensorData.phosphorus = convertBytesToDecimal(13); // miligram per Kilogram soilSensorData.potassium = convertBytesToDecimal(15); // miligram per Kilogram soilSensorData.salinity = convertBytesToDecimal(17); // miligram per Kilogram return soilSensorData; } }; SoilSensor soilSensor; void writeSensorDataToString(SoilSensorData & sensorData) { if (sensorData.isSensorTimeout) { sprintf(sensorDataTextBuffer, "Incomplete data or sensor time out"); } else { if (sensorData.isValid) { char* tempStr = malloc(6 * sizeof(char)); char* humidityStr = malloc(6 * sizeof(char)); char* phStr = malloc(6 * sizeof(char)); dtostrf(sensorData.temperature, 4, 1, tempStr); dtostrf(sensorData.humidity, 4, 1, humidityStr); dtostrf(sensorData.ph, 4, 2, phStr); auto conductivity = sensorData.conductivity; auto n = sensorData.nitrogen; auto p = sensorData.phosphorus; auto k = sensorData.potassium; auto salinity = sensorData.salinity; sprintf(sensorDataTextBuffer, "Temperature: %s; Humidity: %s; Conductivity: %u; PH: %s; N: %u; P: %u; K: %u; Salinity: %u", tempStr, humidityStr, conductivity, phStr, n, p, k, salinity); if (tempStr != NULL) free(tempStr); if (humidityStr != NULL) free(humidityStr); if (phStr != NULL) free(phStr); } else { sprintf(sensorDataTextBuffer, "Data read from sensor is invalid"); } } } void setup() { soilSensor.initialise(); } void loop() { soilSensor.sendDataRequest(); SoilSensorData sensorData = soilSensor.read(); writeSensorDataToString(sensorData); Serial.println(sensorDataTextBuffer); delay(2000); // Wait for a second before the next loop iteration }