The self-watering garden is a proof-of-concept IoT system for small gardens and irrigation zones. The goal is simple: water plants based on soil conditions instead of a fixed timer. It is the case study behind the IoT System Architecture and the OttO edge runtime.
Why This Matters
A small garden system is large enough to expose real IoT architecture problems: noisy sensors, local control, manual overrides, telemetry, and unreliable networks. That makes it a useful case study without requiring industrial-scale hardware.
That makes it a useful architecture example. A garden project sounds small, but it has the same moving parts as many larger IoT systems: sensors, actuators, wireless communication, an edge gateway, APIs, a user interface, and decisions that affect the physical world.

Why Build It
Most small irrigation systems are schedule based. A controller opens a zone at a configured time, runs for a configured duration, and repeats that schedule regardless of the actual soil condition.
That is easy to understand, but it ignores useful context:
- Recent rain may make watering unnecessary.
- Hot dry days may require more water.
- Different plants need different moisture ranges.
- A raised bed, container, and in-ground zone can dry out at different rates.
A sensor-driven system can make a better local decision: water when the soil is too dry, stop when the target moisture range has been reached, and leave the zone alone when it does not need water.
Project Status
This project should be read as a proof of concept and architecture case study, not as a finished commercial irrigation product.
The important part is the system boundary. The project explores how a collection station, control station, edge gateway, and dashboard fit together. It is less about one exact sensor or board and more about how the pieces communicate without becoming tightly coupled.
System Components
The system has four primary runtime components.
Collection Station
A collection station is the device-side telemetry node. It reads soil moisture and other environmental data, then publishes those readings to the gateway.
The original project used ESP-class hardware for this role. The exact board can change as long as the station can read the sensor, connect to the local network, and publish data using the expected message contract.

Soil moisture sensing deserves special attention because cheap sensors can be noisy or unreliable. These notes cover that layer in more detail:
Control Station
A control station is the actuator side of the system. It receives a command and switches something physical, such as a sprinkler valve, pump, relay, light, or heater.

The control station should stay deliberately small. It should know how to execute a command safely, report its state, and fail predictably. It should not need to know the full garden policy or dashboard workflow.
OttO Edge Gateway
The gateway, called OttO in this project, is the coordinating layer. It receives telemetry from collection stations, keeps recent state, exposes APIs, streams dashboard updates, and publishes control commands when the application logic decides a zone should change state.
This is the most important software boundary in the project. Sensors do not talk directly to dashboards. Dashboards do not talk directly to valves. The gateway sits between them and gives each side a stable interface.
See OttO: A Go-Based IoT Edge Gateway Architecture for the gateway architecture.
Dashboard
The dashboard is the human-facing application. It should let a user observe current readings, inspect zone state, adjust thresholds, and manually override a zone when needed.
The dashboard should use the gateway’s REST and WebSocket APIs. It should not subscribe directly to raw device topics or depend on internal Go data structures. That keeps the UI replaceable and keeps device messaging hidden behind the gateway boundary.
Data and Control Flow
A normal watering decision follows this loop:
- A collection station samples soil moisture.
- The station publishes the reading over MQTT.
- OttO receives the message and updates its local state.
- Application logic compares the reading against the zone threshold.
- If watering is needed, OttO publishes a command to the control station.
- The control station opens the valve or starts the pump.
- New moisture readings continue to arrive while watering is active.
- OttO sends a stop command when the target moisture range is reached.
- The dashboard shows the current readings and zone state.
This loop is intentionally event driven. The collection station reports what it sees. The gateway decides what that means. The control station performs the physical action.
MQTT, REST, and WebSockets
The project uses different protocols for different boundaries.
MQTT is the device messaging boundary. Collection stations and control stations can publish or subscribe to small topic-based messages without knowing about the dashboard or REST API. See Adding MQTT to the IoT Gateway.
REST is the request/response boundary for clients that need structured access to state and configuration. It is useful for dashboards, tests, admin tools, and backend services. See Adding the REST API to IoT Gateway.
WebSockets are useful for live dashboard updates. The browser can stay connected to OttO and receive changes as new telemetry arrives instead of polling repeatedly.
These boundaries keep the system easier to reason about. Devices speak MQTT. Applications use HTTP and WebSockets. The gateway translates between those worlds.
Cloud Boundary
The cloud is optional in this proof of concept.
A later system could send selected data to a hosted backend for durable storage, remote access, alerts, analytics, or fleet management. Those are useful capabilities, but they are not required for the local control loop.
That distinction is important. Watering should continue to work on the local network even if an internet connection is down. Cloud services can extend the system, but they should not be required for basic irrigation control.
Common Pitfalls
Letting Sensor Noise Drive Commands
Soil moisture readings can be noisy. A single low reading should not necessarily turn on a pump. The control logic should use thresholds, hysteresis, smoothing, or repeated readings so it does not rapidly turn equipment on and off.
Putting Policy on the Device
Collection stations should collect data. Control stations should switch equipment. If every device owns its own policy, the system becomes hard to inspect and update. Keeping policy in the gateway makes behavior more visible and testable.
Treating the Dashboard as the System
A dashboard is an interface, not the architecture. The watering loop should keep working without a browser open. The dashboard should observe and configure the system, not be the only place where control decisions exist.
Depending on the Cloud for Local Control
Remote access and historical reporting are useful, but basic watering should not require a hosted service. The local gateway should be capable of making the core decision close to the devices.
Where This Fits
The self-watering garden ties together the rest of the IoT articles:
- Building an IoT Device Manager in Go explains the device abstraction behind replaceable sensors and actuators.
- IoT System Architecture: Device to Cloud describes the broader device-to-cloud model.
- OttO: A Go-Based IoT Edge Gateway Architecture describes the edge gateway role.
- Adding MQTT to the IoT Gateway covers the device messaging path.
- Adding the REST API to IoT Gateway covers the client-facing API boundary.