Smart Plant Watering System

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.
- sensorID (
- 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.
- pumpID (
- Schedule Entry: Define a struct WateringSchedule with:
- time (
std::string
): HH:MM format. - sensorID (
std::string
): target sensor. - duration (
int
): watering duration in seconds.
- time (
- 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.
- Device Registration:
addSensor
andaddPump
validate uniqueness withstd::any_of
, then instantiate viastd::make_unique
. - Scheduling:
scheduleWatering
ensures the sensor exists before appending a schedule entry. - 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 (usingsleep_for
), then toggled off. Every reading is logged. - Removal and Cancellation:
removeSensor
andcancelWatering
usestd::remove_if
to purge entries and associated schedules. - 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]