Skip to main content

Architecture Overview

Aqua-IoT implements a three-tier IoT architecture separating sensor data collection, message-based communication, and data persistence/visualization. This design provides modularity, scalability, and fault tolerance.

Tier 1: Sensor Layer

The sensor layer consists of an Arduino microcontroller with five physical sensors monitoring six environmental parameters.

Arduino Firmware Architecture

The main firmware loop (Arduino/sensores.ino) executes a 2-second polling cycle:
1

Initialization

The setup() function initializes all sensors and serial communication:
void setup() {
  Serial.begin(9600);  // 9600 baud serial
  dht.begin();         // Initialize DHT11
  sensors.begin();     // Initialize DS18B20
  gravityTds.begin();  // Initialize TDS sensor
  pinMode(echo, INPUT);
  pinMode(trigger, OUTPUT);
}
2

Sensor Reading

Each iteration of loop() reads all six sensors sequentially:
void loop() {
  delay(2000);  // 2-second interval
  
  // DHT11: Temperature and humidity
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  
  // DS18B20: Water temperature
  sensors.requestTemperatures();
  float waterTemp = sensors.getTempCByIndex(0);
  
  // TDS: Total dissolved solids
  gravityTds.setTemperature(25);
  gravityTds.update();
  float tdsValue = gravityTds.getTdsValue();
  
  // HC-SR04: Water level
  calculo();  // Updates global 'distancia'
  
  // LDR: Light intensity
  int sensorVal = analogRead(sensorPin);
  int lux = sensorRawToPhys(sensorVal);
}
3

Data Serialization

Sensor values are printed to serial in a specific format that the Raspberry Pi expects:
Serial.print(F("Umidade: "));
Serial.print(h);
Serial.print(F("%  Temperatura: "));
Serial.print(t);
Serial.print(F("°C "));
// ... continues for all sensors
The serial output format is critical. The Python script on Raspberry Pi uses wrap(string, 5) to split the output into 5-character chunks for each sensor. Changes to the Arduino output format require corresponding updates in mqtt-arduino.py.

Sensor Details

Type: Digital temperature and humidity sensorSpecifications:
  • Temperature range: 0-50°C (±2°C accuracy)
  • Humidity range: 20-90% RH (±5% accuracy)
  • Sampling rate: 1 Hz (once per second)
  • Interface: Single-wire digital
Pin Configuration:
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
Measurements:
  • Air temperature (plants zone)
  • Relative humidity (plants zone)

Tier 2: Edge Gateway

The Raspberry Pi serves as an edge gateway, bridging the Arduino’s serial communication with the cloud-based Django application using MQTT as an intermediary.

MQTT Broker (Mosquitto)

Purpose: Decouples data producers (Arduino) from consumers (Django API), enabling asynchronous, reliable message delivery. Configuration:
# mqtt-arduino.py and mqtt-django.py
broker = 'localhost'
port = 18083
client_id = 'aqua'  # Unique client identifier
The system uses Mosquitto MQTT broker running locally on the Raspberry Pi. For distributed deployments, update the broker address to point to a remote MQTT server.

MQTT Topic Hierarchy

Aqua-IoT uses six dedicated topics under the sensores/ namespace:
sensores/
├── temperatura-plantas   # Air temperature from DHT11
├── umidade              # Humidity from DHT11
├── ldr                  # Light intensity from LDR
├── temperatura-agua     # Water temperature from DS18B20
├── tds                  # Total dissolved solids
└── nivel                # Water level from HC-SR04
Publisher: mqtt-arduino.py (reads Arduino serial, publishes to topics) Subscriber: mqtt-django.py (subscribes to topics, posts to Django API)

Data Flow: Arduino to MQTT

The mqtt-arduino.py script acts as a serial-to-MQTT bridge:
1

Serial Connection

Establishes USB serial connection with Arduino:
import serial

# Connect to Arduino on USB port
ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
time.sleep(2)  # Wait for Arduino reset
2

MQTT Client Initialization

Creates MQTT client and connects to local broker:
import paho.mqtt.client as mqtt

client = mqtt.Client(client_id='aqua')
client.on_connect = on_connect
client.connect(broker='localhost')
3

Serial Reading and Parsing

Reads Arduino serial output and splits into sensor values:
from textwrap import wrap

line = ser.readline()
if line:
    string = line.decode()
    # Split into 5-character chunks
    temp, hum, ldr, tds, temp_agua, nivel = wrap(string, 5)
This parsing approach assumes each sensor value is exactly 5 characters. Ensure Arduino serial output is formatted consistently, or modify the parsing logic for variable-length values.
4

Publishing to MQTT

Publishes each sensor value to its corresponding topic:
client.publish("sensores/temperatura", temp)
client.publish("sensores/umidade", hum)
client.publish("sensores/ldr", ldr)
client.publish("sensores/tds", tds)
client.publish("sensores/temperatura-agua", temp_agua)
client.publish("sensores/nivel", nivel)

Data Flow: MQTT to Django

The mqtt-django.py script subscribes to MQTT topics and forwards data to the Django REST API:
1

Topic Subscription

Connects to MQTT broker and subscribes to all sensor topics:
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("Conetado ao broker")
        client.subscribe("sensores/temperatura-plantas")
        client.subscribe("sensores/umidade")
        client.subscribe("sensores/ldr")
        client.subscribe("sensores/tds")
        client.subscribe("sensores/temperatura-agua")
        client.subscribe("sensores/nivel")
2

Message Handling

Receives MQTT messages and routes to appropriate handler:
def on_message(client, userdata, message):
    if message.topic == "sensores/temperatura-plantas":
        # Handle temperature message
    elif message.topic == "sensores/umidade":
        # Handle humidity message
    # ... etc for all topics
3

API Payload Construction

Creates JSON payload with sensor metadata:
# Example: Temperature sensor
nome = "Temperatura"
tipo = "Plantas"
grupo = "Grupo P"
temperatura = str(message.payload.decode("utf-8"))
unidade_medida = "graus"

temperatura_plantas = {
    'nome': nome,
    'tipo': tipo,
    'grupo': grupo,
    'temperatura': temperatura,
    'unidade_medida': unidade_medida
}
4

HTTP POST to Django

Sends data to Django REST API with token authentication:
import requests

headers = {
    'Authorization': 'Token 2d75140c068049278f9cb7d39b1a20f05aecdc56'
}
url = "http://127.0.0.1:8000/api/temperatura-plantas/"

response = requests.post(url, headers=headers, json=temperatura_plantas)
print(response)  # Should be <Response [201]>
The authentication token is hardcoded in the script. For production, store tokens in environment variables or a secure configuration file. Generate a unique token for each deployment.

Tier 3: Cloud Application Layer

The Django application provides data persistence, REST API endpoints, and a web dashboard for visualization.

Django Project Structure

Django/
├── manage.py
├── Pipfile                 # Dependencies: django, djangorestframework, psycopg2
├── painel/                 # Project configuration
│   ├── settings.py         # Database, REST framework config
│   ├── urls.py             # URL routing
│   └── wsgi.py             # WSGI entry point
├── sensores/               # Main application
│   ├── models.py           # Database models
│   ├── views.py            # View functions and API viewsets
│   ├── serializers.py      # DRF serializers
│   ├── urls.py             # App-specific URLs
│   └── migrations/         # Database migrations
├── templates/              # HTML templates
└── static/                 # CSS, JavaScript, images

Database Models

Aqua-IoT uses Django ORM with six sensor models inheriting from an abstract base class:
class Sensor(models.Model):
    nome = models.CharField(max_length=50)
    tipo = models.CharField(max_length=50)      # "Plantas" or "Acuario"
    grupo = models.CharField(max_length=50)     # "Grupo P" or "Grupo A"
    data_criacao = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        abstract = True
class TemperaturaPlantas(Sensor):
    temperatura = models.FloatField(max_length=200, default='00')
    unidade_medida = models.CharField(max_length=50, default='graus')
    
    def __str__(self):
        return str(self.temperatura) + str(self.unidade_medida)
API Endpoint: /api/temperatura-plantas/ MQTT Topic: sensores/temperatura-plantas

REST API Architecture

Django REST Framework provides the API layer with token-based authentication: Settings Configuration:
# Django/painel/settings.py
INSTALLED_APPS = [
    'rest_framework',
    'rest_framework.authtoken',
    'sensores',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
}
ViewSet Example:
class TemperaturaAquarioViewset(viewsets.ViewSet):
    permission_classes = (IsAuthenticated,)
    
    def create(self, request):
        serializer = TemperaturaAquarioSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        the_response = TemperaturaAquarioSerializer(serializer.save())
        return Response(the_response.data, status=status.HTTP_201_CREATED)
API Endpoints:
  • POST /api/temperatura-plantas/ - Create temperature reading (plants)
  • POST /api/umidade/ - Create humidity reading
  • POST /api/ldr/ - Create light intensity reading
  • POST /api/tds/ - Create TDS reading
  • POST /api/temperatura-aquario/ - Create temperature reading (aquarium)
  • POST /api/nivel/ - Create water level reading
All endpoints require token authentication via Authorization: Token <token> header. Currently only POST (create) operations are exposed. Extend viewsets with list() and retrieve() methods to add GET endpoints.

Web Dashboard

The dashboard view aggregates all sensor data and passes it to the template:
def home(request):
    if request.user.is_authenticated:
        temperaturap = TemperaturaPlantas.objects.all()
        nivel = NivelAgua.objects.all()
        umidade = Umidade.objects.all()
        tds = Tds.objects.all()
        temperaturaa = TemperaturaAquario.objects.all()
        ldr = Ldr.objects.all()
        
        context = {
            'temperaturas': temperaturap,
            'nivels': nivel,
            'umidades': umidade,
            'tdss': tds,
            'temperaturaas': temperaturaa,
            'ldrs': ldr,
        }
        return render(request, "index.html", context)
    else:
        return redirect(userLogin)
Authentication: Users must log in to access the dashboard. The system uses Django’s built-in authentication with custom login/logout views.

Database Configuration

Aqua-IoT uses PostgreSQL for data persistence:
# Django/painel/settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'aqua',
        'USER': 'postgres',
        'PASSWORD': 'pos2023',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}
Time Zone: Configured for São Paulo, Brazil (America/Sao_Paulo) to ensure correct timestamp localization.

Data Flow Summary

1

Sensor Measurement

Arduino reads six sensors every 2 seconds
2

Serial Transmission

Arduino outputs formatted data via USB serial (9600 baud)
3

Serial to MQTT

Raspberry Pi mqtt-arduino.py reads serial and publishes to 6 MQTT topics
4

MQTT Broker

Mosquitto broker receives messages and queues for subscribers
5

MQTT to HTTP

Raspberry Pi mqtt-django.py subscribes to topics and constructs JSON payloads
6

API Ingestion

Django REST API receives POST requests, validates, and saves to database
7

Data Storage

PostgreSQL stores sensor readings with automatic timestamps
8

Dashboard Query

Django views query database and render HTML templates
9

User Visualization

Web browser displays real-time and historical sensor data

Scalability Considerations

Multiple Sensor Nodes

Add more Arduino boards by:
  • Using unique MQTT client IDs
  • Creating distinct topic namespaces (sensores/node1/, sensores/node2/)
  • Running multiple mqtt-arduino.py instances with different serial ports

Remote MQTT Broker

Deploy Mosquitto on a dedicated server:
  • Update broker address in Python scripts
  • Enable MQTT authentication and encryption (TLS)
  • Configure firewall rules for port 1883 (or 8883 for TLS)

Cloud Django Deployment

Host Django on cloud platforms:
  • Use production WSGI server (Gunicorn/uWSGI)
  • Serve static files via CDN or Nginx
  • Configure ALLOWED_HOSTS and disable DEBUG
  • Use environment variables for secrets

Time-Series Database

For high-frequency data:
  • Migrate to InfluxDB or TimescaleDB
  • Implement data aggregation and downsampling
  • Add retention policies to manage storage

Security Best Practices

The default configuration is intended for development and local testing. For production deployments, implement these security measures:
  1. Change Default Credentials: Update PostgreSQL password and Django SECRET_KEY
  2. Enable MQTT Authentication: Configure Mosquitto with username/password or TLS certificates
  3. Use HTTPS: Deploy Django behind Nginx with Let’s Encrypt SSL certificates
  4. Token Rotation: Generate unique API tokens per client and rotate regularly
  5. Network Segmentation: Isolate IoT devices on separate VLAN
  6. Input Validation: Add bounds checking on sensor values to detect malfunctions
  7. Rate Limiting: Implement API rate limiting to prevent abuse

Performance Optimization

  • Database Indexing: Add indexes on data_criacao field for faster time-range queries
  • Query Optimization: Use select_related() and prefetch_related() to reduce database queries
  • Caching: Implement Redis caching for frequently accessed dashboard data
  • Sensor Polling: Adjust Arduino delay for less frequent updates if 2-second interval is excessive
  • Data Aggregation: Pre-calculate hourly/daily averages for historical charts

Troubleshooting Architecture Issues

Test each component independently:
# 1. Verify Arduino serial output
screen /dev/ttyACM0 9600

# 2. Test MQTT publishing
mosquitto_sub -t "sensores/#" -v

# 3. Check Django API
curl -X POST http://localhost:8000/api/temperatura-plantas/ \
  -H "Authorization: Token YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"nome":"Test","tipo":"Plantas","grupo":"Grupo P","temperatura":25.5,"unidade_medida":"graus"}'

# 4. Verify database storage
python manage.py shell
>>> from sensores.models import TemperaturaPlantas
>>> TemperaturaPlantas.objects.count()

Further Reading

This architecture provides a foundation for IoT monitoring systems. Adapt the design to your specific requirements by adding alert systems, machine learning for anomaly detection, or mobile applications for remote access.

Build docs developers (and LLMs) love