|

Smart Plant Watering System

Spread the love

Develop a C++ console application to monitor soil moisture and automate watering for a home garden comprised of multiple plants. The system should support registering plant sensors, scheduling watering cycles, and logging moisture data. Required features:

  • Plant Sensor Representation: Implement a PlantSensor class containing:
    • sensorID (std::string): unique identifier (e.g., “PS001”).
    • plantName (std::string): species or common name.
    • moistureLevel (double): percentage value from 0.0 to 100.0.
    • Methods:
      • readMoisture(): simulates reading a new moisture value and logs the result.
      • display(): prints sensorID, plantName, and current moisture.
  • Water Pump Representation: Implement a WaterPump class containing:
    • pumpID (std::string): unique identifier (e.g., “WP100”).
    • isActive (bool): current state of the pump (on/off).
    • Method toggle(bool state): activates or deactivates the pump and logs action.
    • Method display(): prints pumpID and status.
  • Schedule Entry: Define a struct WateringSchedule with:
    • time (std::string): HH:MM format.
    • sensorID (std::string): target sensor.
    • duration (int): watering duration in seconds.
  • GardenManager: A class to coordinate sensors, pumps, and schedules:
    • addSensor(sensorID, plantName): register a new plant sensor.
    • removeSensor(sensorID): unregister sensor and remove related schedules.
    • addPump(pumpID): register a water pump device.
    • removePump(pumpID): remove pump.
    • scheduleWatering(time, sensorID, duration): add a WateringSchedule.
    • cancelWatering(time, sensorID): remove matching schedule.
    • runSchedule(currentTime): for each schedule matching currentTime, read moisture; if below threshold (30%), activate pump for specified duration.
    • logData(sensorID, moistureLevel): store timestamped moisture readings.
    • displaySensors(), displayPumps(), displaySchedules(), displayLogs(): list all registered objects and logs.
  • Behavioral Requirements:
    • Prevent scheduling for unknown sensors.
    • Use std::vector<std::unique_ptr> and std::vector<std::unique_ptr> for devices.
    • Store schedules in std::vector and logs in std::vectorstd::string.
    • Simulated time-driven loop in main() should demonstrate registering devices, creating schedules, and executing watering cycles.

Solution Implementation

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <string>
#include <ctime>
#include <thread>
#include <chrono>

// Simulates a plant moisture sensor
class PlantSensor {
public:
    std::string sensorID;
    std::string plantName;
    double moistureLevel;

    PlantSensor(const std::string& id, const std::string& name)
        : sensorID(id), plantName(name), moistureLevel(100.0) {}

    // Simulate moisture reading (random drop)
    void readMoisture() {
        // Simulate new reading
        moistureLevel = std::max(0.0, moistureLevel - (rand() % 20));
        std::cout << "Sensor " << sensorID << " reads " << moistureLevel << "%\n";
    }

    void display() const {
        std::cout << sensorID << " (" << plantName
                  << ") - Moisture: " << moistureLevel << "%\n";
    }
};

// Simulates a water pump
class WaterPump {
public:
    std::string pumpID;
    bool isActive;

    WaterPump(const std::string& id)
        : pumpID(id), isActive(false) {}

    // Toggle pump state
    void toggle(bool state) {
        isActive = state;
        std::cout << "Pump " << pumpID
                  << (state ? " activated\n" : " deactivated\n");
    }

    void display() const {
        std::cout << pumpID << " - " << (isActive ? "ON" : "OFF") << "\n";
    }
};

// Schedule entry for watering
struct WateringSchedule {
    std::string time;       // HH:MM
    std::string sensorID;   // target sensor
    int duration;           // seconds
};

// Manages sensors, pumps, schedules, and logs
class GardenManager {
    std::vector<std::unique_ptr<PlantSensor>> sensors;
    std::vector<std::unique_ptr<WaterPump>> pumps;
    std::vector<WateringSchedule> schedules;
    std::vector<std::string> logs;

public:
    // Register a new sensor
    void addSensor(const std::string& id, const std::string& name) {
        if (std::any_of(sensors.begin(), sensors.end(),
            [&](const auto& s){ return s->sensorID == id; })) {
            std::cerr << "Sensor exists: " << id << "\n";
            return;
        }
        sensors.push_back(std::make_unique<PlantSensor>(id, name));
        std::cout << "Added sensor " << id << " for " << name << "\n";
    }

    // Remove sensor and its schedules
    void removeSensor(const std::string& id) {
        schedules.erase(
            std::remove_if(schedules.begin(), schedules.end(),
                [&](const auto& sch){ return sch.sensorID == id; }),
            schedules.end()
        );
        sensors.erase(
            std::remove_if(sensors.begin(), sensors.end(),
                [&](const auto& s){ return s->sensorID == id; }),
            sensors.end()
        );
        std::cout << "Removed sensor and schedules: " << id << "\n";
    }

    // Register a new pump
    void addPump(const std::string& id) {
        if (std::any_of(pumps.begin(), pumps.end(),
            [&](const auto& p){ return p->pumpID == id; })) {
            std::cerr << "Pump exists: " << id << "\n";
            return;
        }
        pumps.push_back(std::make_unique<WaterPump>(id));
        std::cout << "Added pump " << id << "\n";
    }

    // Remove a pump
    void removePump(const std::string& id) {
        pumps.erase(
            std::remove_if(pumps.begin(), pumps.end(),
                [&](const auto& p){ return p->pumpID == id; }),
            pumps.end()
        );
        std::cout << "Removed pump: " << id << "\n";
    }

    // Schedule a watering event
    void scheduleWatering(const std::string& time,
                          const std::string& sid,
                          int duration) {
        if (!std::any_of(sensors.begin(), sensors.end(),
            [&](const auto& s){ return s->sensorID == sid; })) {
            std::cerr << "Unknown sensor: " << sid << "\n";
            return;
        }
        schedules.push_back({time, sid, duration});
        std::cout << "Scheduled watering for " << sid
                  << " at " << time << " for " << duration << "s\n";
    }

    // Cancel a watering event
    void cancelWatering(const std::string& time, const std::string& sid) {
        auto it = std::remove_if(schedules.begin(), schedules.end(),
            [&](const auto& sch){ return sch.time == time && sch.sensorID == sid; });
        if (it == schedules.end()) {
            std::cerr << "No schedule to cancel for " << sid << "\n";
            return;
        }
        schedules.erase(it, schedules.end());
        std::cout << "Canceled watering for " << sid << " at " << time << "\n";
    }

    // Execute schedules at currentTime
    void runSchedule(const std::string& currentTime) {
        for (auto& sch : schedules) {
            if (sch.time == currentTime) {
                // Find sensor, read moisture, decide watering
                for (auto& s : sensors) {
                    if (s->sensorID == sch.sensorID) {
                        s->readMoisture();
                        if (s->moistureLevel < 30.0) {
                            // Use first pump for simplicity
                            if (!pumps.empty()) {
                                pumps[0]->toggle(true);
                                std::this_thread::sleep_for(std::chrono::seconds(sch.duration));
                                pumps[0]->toggle(false);
                            }
                        }
                        // Log reading
                        logs.push_back(currentTime + " | " + sch.sensorID
                                        + " moisture: " + std::to_string(s->moistureLevel));
                    }
                }
            }
        }
    }

    // Display all sensors, pumps, schedules, and logs
    void displaySensors() const {
        std::cout << "\nSensors:\n";
        for (const auto& s : sensors) s->display();
    }
    void displayPumps() const {
        std::cout << "\nPumps:\n";
        for (const auto& p : pumps) p->display();
    }
    void displaySchedules() const {
        std::cout << "\nSchedules:\n";
        for (const auto& sch : schedules)
            std::cout << sch.time << " | " << sch.sensorID
                      << " for " << sch.duration << "s\n";
    }
    void displayLogs() const {
        std::cout << "\nLogs:\n";
        for (const auto& entry : logs) std::cout << entry << "\n";
    }
};

Example Simulation :
// Example simulation in main()
int main() {
    srand(time(nullptr));
    GardenManager gm;
    // Add devices and pumps
    gm.addSensor("PS001", "Tomato");
    gm.addSensor("PS002", "Basil");
    gm.addPump("WP100");

    // Schedule watering events
    gm.scheduleWatering("06:00", "PS001", 5);
    gm.scheduleWatering("06:00", "PS002", 3);
    gm.scheduleWatering("18:00", "PS001", 5);

    // Simulate day cycle
    std::vector<std::string> times = {"06:00", "18:00"};
    for (const auto& t : times) {
        std::cout << "\n-- Time: " << t << " --\n";
        gm.runSchedule(t);
        gm.displaySensors();
        gm.displayPumps();
    }
    gm.displayLogs();
    return 0;
}

Detailed Explanation of Logic

The GardenManager orchestrates plant sensors and water pumps using std::unique_ptr containers for ownership. Schedules live in a simple std::vector<WateringSchedule>, and logs are strings recorded after each cycle.

  1. Device Registration: addSensor and addPump validate uniqueness with std::any_of, then instantiate via std::make_unique.
  2. Scheduling: scheduleWatering ensures the sensor exists before appending a schedule entry.
  3. Execution Loop: In runSchedule, for each matching time, sensors simulate moisture drops, and if below 30%, the first pump is toggled on for the entry’s duration (using sleep_for), then toggled off. Every reading is logged.
  4. Removal and Cancellation: removeSensor and cancelWatering use std::remove_if to purge entries and associated schedules.
  5. Display: Separate methods iterate over each container, calling display() or printing fields directly, providing clear, formatted output.

If you have any questions , feel free to contact us at [email protected]

Similar Posts