This document provides a high-level introduction to the water purification control system implemented in the Arduino-based codebase. The system monitors water quality and manages the transfer of water between two tanks using automated pump control. This overview covers the system's purpose, key components, operational flow, and how the software architecture maps to physical hardware.
For detailed information about specific subsystems, refer to:
- Hardware pin assignments and electrical connections: Hardware Architecture
- Software implementation details: Hardware Components
- Software architecture and design patterns: Software Architecture
- Configuration parameters and calibration: Configuration Reference
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 1-345
The water purification control system automates the operation of a dual-tank water management system with the following objectives:
- Water Quality Monitoring: Continuously measures pH to ensure water quality remains within safe parameters (7.5-9.0 pH)
- Automated Pump Control: Manages water transfer from Tank 1 (source) to Tank 2 (storage) based on level sensors
- Safety Enforcement: Implements turbid water override that disables pumping when pH is out of acceptable range
- Real-time Monitoring: Provides local LCD display and cloud telemetry via Ubidots IoT platform
- Flow Measurement: Tracks water flow rate and frequency for system performance monitoring
The system executes as a single Arduino sketch on an Arduino Mega, interfacing with multiple sensors, a relay-controlled pump, an LCD display, and an external WiFi telemetry gateway.
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 198-248
The following table maps physical system components to their corresponding code entities in the implementation:
| Componente | Code Entity | Type | Purpose |
|---|---|---|---|
| pH Sensor | A1 pin, ph variable |
Analog Input | Water quality measurement (7.5-9.0 range) |
| Tank 1 Upper Float | FLOTADOR_ALTO (D7) |
Digital Input | Tank 1 high water level detection |
| Tank 1 Lower Float | FLOTADOR_BAJO (D6) |
Digital Input | Tank 1 low water level detection |
| Tank 2 Lower Float | Nivel_T2 (A0), lvltanque |
Analog Input | Continuous Tank 2 fill level (0-100%) |
| Flow Sensor YF-S201 | sensorPin (D3), ISRCountPulse() |
Interrupt Input | Water flow frequency measurement |
| Motor Relay | MOTOR_RELAY (D4), M variable |
Digital Output | Pump ON/OFF control |
| Status LED | ledverde1 (D5) |
Digital Output | Visual operation indicator |
| LCD Display | u8g2 object, draw() function |
SPI Interface | Local real-time data visualization |
| WiFi Telemetry | Serial1, Ubidots protocol |
UART Interface | Cloud data transmission |
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 18-22
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 27-28
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 42-47
flowchart LR
subgraph PL["Physical Layer"]
T1["Tank 1 (Source Water)"]
T2["Tank 2 (Purifier Storage)"]
n1["Water Pump"]
end
subgraph SL["Sensor Layer (Pin Assignaments)"]
FLS1["FLOTADOR_BAJO- Pin D6"]
FLS2["FLOTADOR_ALTO - Pin D7"]
pHSensor["pH Sensor Pin A1 buffer_arr[10]"]
FlowSensor["Nivel_T2 Pin A0 CANTIDAD variable"]
NexT_DS["Flow Sensor Pin D3 ISRCountPulse()"]
end
subgraph AL["Actuator Layer"]
Relay["MOTOR_RELAY - Pin D4"]
Pump["ledverde1 Pin D5"]
end
subgraph PC["Processing Core"]
DataAcq["digitalRead()[165-166] FLOTADOR_A/B"]
TankStatus["pH Median Filter [136-162] avgval calculation"]
Validation["pH Validation [198] 7.5 < ph < 9"]
ControlLogic["Turbid Override [242-248] T=000, M=000"]
OutputMgmt["Tank Level States [200-240] 6 decision branches"]
Debug["digitalWrite(MOTOR_RELAY) [202-239] M variable"]
n2["sprintf(command)[261-270] POST format"]
n3["draw() Function [305-345] u8g2 rendering"]
n4["analogRead(nivel_T2) [167-172] porcentajet1"]
n5["GetFrequency() [175-178] frequency, flow_Lmin"]
n6["loop() Function[121-303]"]
end
subgraph CL["Communication Layer"]
WiFiMod["Serial1 (Telemetry) 115200 baud"]
MQTT["Serial (USB) 115200 baud"]
HTTP["u8g2 Object U8G2_ST920_128X64 SPI Pins 10,8"]
end
subgraph EXT["External Systems / Cloud"]
SmartDashboard["WiFi Gateway Command Parser"]
IoTCloud["Ubidots Cloud DEVICE_LABEL: IOT_Control"]
end
T1 --> FLS1 & FLS2 & pHSensor
FLS1 --> DataAcq
FLS2 --> DataAcq
pHSensor --> TankStatus
DataAcq --> OutputMgmt
TankStatus --> n2 & Validation & n3
Validation -- Invalid --> ControlLogic
Validation -- Valid --> OutputMgmt
ControlLogic --> Debug
OutputMgmt --> Debug & n2
Debug --> n3 & n2 & Relay
Relay --> n1
n1 --> T2
n1 -.-> NexT_DS
T2 -.-> FlowSensor
FlowSensor --> n4
NexT_DS --> n5
n5 --> n3 & n2
n4 --> n2 & n3 & OutputMgmt
n2 --> WiFiMod
n3 --> HTTP
MQTT -.-> n6
IoTCloud <-- HTTP POST --> SmartDashboard
SmartDashboard <-- flow_control flag --> WiFiMod
n1@{ shape: rect}
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
n5@{ shape: rect}
n6@{ shape: rect}
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 121-303
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 18-22
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 42-47
The system operates in a continuous cycle within the loop() function. The following diagram illustrates the execution sequence with specific function calls and code references:
flowchart LR
loopEntryLine121[loop Entry Line 121]
digitalWriteledverde1,HIGH123[digitalWrite ledverde1, HIGH Line 123]
delay3000124[delay 3000 Line 124]
digitalWriteledver1LOW125[digitalWrite ledver1, LOW Line 125]
flow_controltrue132{flow_control = true Line 132}
subgraph Sensor Acquisition Block
direction TB
forloop:10samples136-140analogReadA1[for loop: 10 samples Lines 136-140 analogRead A1]
Bubblesortbuffer_arr141-152[Bubble sort buffer_arr Lines 141-152]
CalculatepH154-159-5.7*volt+calibration_value[Calculate pH Lines 154-159 -5.7*volt + calibration_value]
digitalReadFLOTADOR_ALTO/BAJO165-166[digitalRead FLOTADOR_ALTO/BAJO Lines 165-166]
analogReadNivel_T2167porcentajet1CANTIDAD/1023*100[analogRead Nivel_T2 Line 167 porcentajet1 = CANTIDAD/1023*100]
GetFrecuency1752.5sinterruptcount[GetFrecuency Line 175 2.5s interrupt count]
flow_Lminfrequency/factorK176[flow_Lminfrequency/factorK176]
end
u8g2.firstPagedo-whileloop190-194callsdraw[u8g2.first Page do-while loop Lines 190-194 calls draw]
ph750&&ph918{ph > 750 > && < ph Line 918}
subgraph Tank Level Decision Tree
direction TB
FLOTADOR_A0FLOTADOR_B0200{FLOTADOR_A = 0 FLOTADOR_B = 0 Line 200}
FLOTADOR_A0FLOTADOR_B1207CANTIDAD110{FLOTADOR_A = 0 FLOTADOR_B = 1 CANTIDAD < 110 Line 207}
FLOTADOR_A0FLOTADOR_B1214CANTIDAD500{FLOTADOR_A = 0 FLOTADOR_B = 1 CANTIDAD > 500 Line 214}
FLOTADOR_A1FLOTADOR_B0221{FLOTADOR_A = 1 FLOTADOR_B = 0 Line 221}
FLOTADOR_A1FLOTADOR_B1228CANTIDAD500{FLOTADOR_A = 1 FLOTADOR_B = 1 CANTIDAD > 500 Line 228}
FLOTADOR_A1FLOTADOR_B1235CANTIDAD110{FLOTADOR_A = 1 FLOTADOR_B = 1 CANTIDAD < 110 Line 235}
digitalWriteMOTOR_RELAY,1T0/1,M0[digitalWrite MOTOR_RELAY, 1 T = 0/1, M = 0]
digitalWriteMOTOR_RELAY,0T0/1,M1[digitalWrite MOTOR_RELAY, 0 T = 0/1, M = 1]
digitalWriteMOTOR_RELAY,1T0,M0[digitalWrite MOTOR_RELAY, 1 T = 0, M = 0]
end
TurbidWaterMode242-248digitalWriteMOTOR_RELAY,1T000,M000[Turbid Water Mode Lines 242-248 digitalWrite MOTOR_RELAY, 1 T = 000,M = 000]
dtostrfconvertions252-257floattostr_sensor1-6[dtostrf convertions Lines 252-257 float to str_sensor 1-6]
sprintfcommandchain261-270init#...USER_AGENT...end#final[sprintfcommand chain Lines 261-270 init#...USER_AGENT...end#final]
Serial.printlncommand274[Serial.println command Line 274]
Serial1.printlncommand277[Serial1.println command Line 277]
freecommand280[freecommand Line 280]
Serial1.availableloop289-294readtelemetry_unit[Serial1.available loop Lines 289-294 read telemetry_unit]
Endofloop303[End of loop Line 303]
%%Direcionamientos
loopEntryLine121 --> digitalWriteledverde1,HIGH123
digitalWriteledverde1,HIGH123 --> delay3000124
delay3000124 --> digitalWriteledver1LOW125
digitalWriteledver1LOW125 --> flow_controltrue132
flow_controltrue132 --false--> Serial1.availableloop289-294readtelemetry_unit
flow_controltrue132 --true--> forloop:10samples136-140analogReadA1
forloop:10samples136-140analogReadA1 --> Bubblesortbuffer_arr141-152
Bubblesortbuffer_arr141-152 --> CalculatepH154-159-5.7*volt+calibration_value
CalculatepH154-159-5.7*volt+calibration_value --> digitalReadFLOTADOR_ALTO/BAJO165-166
digitalReadFLOTADOR_ALTO/BAJO165-166 --> analogReadNivel_T2167porcentajet1CANTIDAD/1023*100
analogReadNivel_T2167porcentajet1CANTIDAD/1023*100 --> GetFrecuency1752.5sinterruptcount
GetFrecuency1752.5sinterruptcount --> flow_Lminfrequency/factorK176
flow_Lminfrequency/factorK176 --> u8g2.firstPagedo-whileloop190-194callsdraw
u8g2.firstPagedo-whileloop190-194callsdraw --> ph750&&ph918
ph750&&ph918 --false--> TurbidWaterMode242-248digitalWriteMOTOR_RELAY,1T000,M000
TurbidWaterMode242-248digitalWriteMOTOR_RELAY,1T000,M000 --> dtostrfconvertions252-257floattostr_sensor1-6
ph750&&ph918 --true--> FLOTADOR_A0FLOTADOR_B0200
FLOTADOR_A0FLOTADOR_B0200 --match--> digitalWriteMOTOR_RELAY,1T0,M0
digitalWriteMOTOR_RELAY,1T0,M0 --> dtostrfconvertions252-257floattostr_sensor1-6
FLOTADOR_A0FLOTADOR_B0200 --no match--> FLOTADOR_A0FLOTADOR_B1207CANTIDAD110
FLOTADOR_A0FLOTADOR_B1207CANTIDAD110 --match--> digitalWriteMOTOR_RELAY,0T0/1,M1
digitalWriteMOTOR_RELAY,0T0/1,M1 --> dtostrfconvertions252-257floattostr_sensor1-6
FLOTADOR_A0FLOTADOR_B1207CANTIDAD110 --no match--> FLOTADOR_A0FLOTADOR_B1214CANTIDAD500
FLOTADOR_A0FLOTADOR_B1214CANTIDAD500 --match--> digitalWriteMOTOR_RELAY,1T0/1,M0
digitalWriteMOTOR_RELAY,1T0/1,M0 --> dtostrfconvertions252-257floattostr_sensor1-6
FLOTADOR_A0FLOTADOR_B1214CANTIDAD500 --no match--> FLOTADOR_A1FLOTADOR_B0221
FLOTADOR_A1FLOTADOR_B0221 --match--> digitalWriteMOTOR_RELAY,1T0/1,M0
FLOTADOR_A1FLOTADOR_B0221 --no match--> FLOTADOR_A1FLOTADOR_B1228CANTIDAD500
FLOTADOR_A1FLOTADOR_B1228CANTIDAD500 --match--> digitalWriteMOTOR_RELAY,1T0/1,M0
FLOTADOR_A1FLOTADOR_B1228CANTIDAD500 --no match--> FLOTADOR_A1FLOTADOR_B1235CANTIDAD110
FLOTADOR_A1FLOTADOR_B1235CANTIDAD110 --match--> digitalWriteMOTOR_RELAY,0T0/1,M1
dtostrfconvertions252-257floattostr_sensor1-6 --> sprintfcommandchain261-270init#...USER_AGENT...end#final
sprintfcommandchain261-270init#...USER_AGENT...end#final --> Serial.printlncommand274
Serial.printlncommand274 --> Serial1.printlncommand277
Serial1.printlncommand277 --> freecommand280
freecommand280 --> Serial1.availableloop289-294readtelemetry_unit
Serial1.availableloop289-294readtelemetry_unit --> Endofloop303
Endofloop303 --> loopEntryLine121
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 121-303
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 61-70
The codebase is organized into several functional subsystems, each implemented as code blocks within the main sketch:
| Subsystem | Code Location | Key Functions/Variables | Purpose |
|---|---|---|---|
| pH Sensing | 136-162 |
buffer_arr[10], avgval, calibration_value |
10-sample median-filtered pH measurement |
| Float Switches | 165-166 |
FLOTADOR_ALTO, FLOTADOR_BAJO, digitalRead() |
Binary Tank 1 level detection |
| Analog Level | 167-172 |
Nivel_T2, CANTIDAD, porcentajet1 |
Binary Tank 1 level detection |
| Flow Sensor | 61-70 175-178 |
GetFrequency(), ISRCountPulse(), pulseConter |
Interrupt-driven flow rate calculation |
The motor control logic (198-248) implements a safety-first decision tree:
- Primary Gate: pH validation (7.5 < pH < 9.0) - invalid pH forces turbid water mode
- Tank State Evaluation: Six distinct float switch combinations determine pump operation
- Tank 2 Threshold Logic: Analog level sensor (
CANTIDAD) uses thresholds of 110 (empty) and 500 (full) - Output Variables:
M(motor state: 0=OFF, 1=ON),T(Tank 1 status)
| Subsystem | Code Location | Key Entities | Purpose |
|---|---|---|---|
| Motor Relay | 202-243 |
digitalWrite(MOTOR_RELAY), M variable |
Controls pump via relay on pin D4 |
| LCD Display | 190-194 305-345 |
u8g2 object, draw() function |
Renders 128x64 display with 4 data rows |
| Telemetry | 261-294 |
Serial1, sprintf(command), Ubidots protocol |
Binary Tank 1 level detection |
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 136-294
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 305-345
The system transmits telemetry to Ubidots IoT platform using a custom text-based protocol over Serial1:
| Variable | Code Symbol | Ubidots Label | Description |
|---|---|---|---|
| 1 | ph |
ph |
Water pH value (float) |
| 2 | T |
Tanque1 |
Tank 1 status (0, 1, or 000 for turbid) |
| 3 | lvltanque |
Tanque2 |
Tank 2 level percentage (0-100) |
| 4 | frecuencia |
frecuencia |
Flow frequency in Hz |
| 5 | M |
Motor |
Motor state (0=OFF, 1=ON, 000=turbid) |
| 6 | flujocaudal |
caudal |
Flow rate in L/min |
The flow_control flag lines (77, 132, 293) implements a simple handshake:
- Set to
falseafter command transmission - Reset to
truewhenSerial1.available() > 0(gateway responds) - Prevents command overflow when gateway is processing previous request
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 76-89
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 261-294
| Pin | Type | Purpose | Code Constant |
|---|---|---|---|
| D3 | Interrupt Input | Flow sensor pulse | sensorPin |
| D4 | Digital Output | Motor relay control | MOTOR_RELAY |
| D5 | Digital Output | Status LED | ledverde1 |
| D6 | Digital Input | Tank 1 lower float | FLOTADOR_BAJO |
| D7 | Digital Input | Tank 1 upper float | FLOTADOR_ALTO |
| D8 | Digital Output | LCD reset | u8g2 reset parameter |
| D10 | Digital Output | LCD chip select | u8g2 CS parameter |
| A0 | Analog Input | Tank 2 level sensor | Nivel_T2 |
| A1 | Analog Input | pH sensor | pH measurement |
| Interface | Baud Rate | Purpose | Code Reference |
|---|---|---|---|
| Serial (USB) | 115200 | Debug output | 104 |
| Serial1 (UART1) | 115200 | Telemetry gateway | 105 |
| SPI | Hardware | LCD display | 15 U8G2_ST7920 |
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 18-22
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 42
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 104-105
The pH validation check (198) acts as a primary safety gate. When pH is outside the 7.5-9.0 range:
- Motor is forced OFF regardless of tank levels
- Variables
TandMare set to000(turbid water indicator) - All subsequent motor control logic is bypassed
This ensures that only clean water (within acceptable pH range) is pumped to Tank 2.
The pH sensor uses a 10-sample median filter (136-162):
- Collects 10 analog readings with 30ms delay between samples
- Performs bubble sort on
buffer_arr[10] - Averages middle 6 values (indices 2-7), discarding outliers
- Converts to voltage and applies calibration formula
This approach eliminates sensor noise and provides stable pH readings.
The GetFrequency() function (61-70) uses hardware interrupts for accurate flow measurement:
- Resets
pulseConterto 0 - Enables interrupts for 2.5 seconds (
measureInterval) ISRCountPulse()increments counter on each sensor pulse- Calculates frequency:
pulseConter * 1000 / measureInterval
The interrupt service routine (56-59) is lightweight, ensuring no pulses are missed during high flow rates.
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 56-70
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 136-162
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 198-248
To begin working with this system:
- Hardware Setup: Review pin assignments and connect sensors/actuators as documented in Hardware Architecture
- Library Installation: Install U8g2 graphics library via Arduino Library Manager (see Installation and Setup Guide)
- Configuration: Set Ubidots credentials in TOKEN and DEVICE_LABEL constants (see Ubidots Configuration)
- Calibration: Adjust calibration_value for pH sensor and factorK for flow sensor model (see Sensor Calibration)
- Deployment: Upload sketch to Arduino Mega and monitor Serial output for debug information
Sources: purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 33
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 47
purificador_agua_via_ubidots/purificador_agua_via_ubidots.ino 81-82