Skip to main content

Overview

The Invernaderos API uses a hierarchical MQTT topic structure for organizing sensor data, actuator commands, and system events. Topics support multi-tenant isolation and follow MQTT best practices for scalability.

Topic Hierarchy

The system uses the following topic pattern:
GREENHOUSE/{tenantId}/{resource}/{action}

Root Topics

GREENHOUSE
topic
Legacy Format - Maps to DEFAULT tenant for backward compatibility
GREENHOUSE
GREENHOUSE/{tenantId}
topic
Multi-Tenant Format - Isolates data by tenant
GREENHOUSE/SARA
GREENHOUSE/001
GREENHOUSE/NARANJOS
SYSTEM/RESPONSE
topic
Response Topic - Echo/verification messages
SYSTEM/RESPONSE

Tenant-Based Topics

The API supports multi-tenant isolation through topic-based routing. Each tenant’s data is segregated using a unique tenant identifier in the topic path.

Topic Extraction

The system extracts the tenant ID from the topic using the following logic:
val tenantId = when {
    topic.startsWith("GREENHOUSE/") -> 
        topic.substringAfter("GREENHOUSE/")
             .takeWhile { it != '/' }
    topic == "GREENHOUSE" -> "DEFAULT"
    else -> "UNKNOWN"
}

Examples

TopicExtracted Tenant IDDescription
GREENHOUSEDEFAULTLegacy format for backward compatibility
GREENHOUSE/SARASARAVivero Sara greenhouse system
GREENHOUSE/001001Generic tenant ID
GREENHOUSE/NARANJOSNARANJOSLos Naranjos farm
The DEFAULT tenant is automatically created during database migration (V7) to support existing systems using the legacy GREENHOUSE topic.

Sensor Topics

Sensor data is published to tenant-specific topics following this pattern:
GREENHOUSE/{tenantId}

Message Format

{
  "TEMPERATURA INVERNADERO 01": 25.5,
  "HUMEDAD INVERNADERO 01": 60.2,
  "TEMPERATURA INVERNADERO 02": 26.3,
  "HUMEDAD INVERNADERO 02": 58.7,
  "INVERNADERO_01_SECTOR_01": 1.0,
  "INVERNADERO_01_SECTOR_02": 0.0,
  "INVERNADERO_01_EXTRACTOR": 1.0,
  "timestamp": "2025-11-16T10:30:00Z"
}

Subscription Pattern

To subscribe to all greenhouse data across tenants, use the wildcard pattern:
mosquitto_sub -t "GREENHOUSE/+"
To subscribe to a specific tenant:
mosquitto_sub -t "GREENHOUSE/SARA"

Actuator Topics

Actuator commands follow a more specific topic hierarchy:
greenhouse/{greenhouseId}/actuators/{actuatorId}/command

Command Format

{
  "command": "turn_on",
  "value": 1,
  "timestamp": "2025-11-16T10:30:00Z"
}
Actuator commands should use QoS 1 (at least once) to ensure reliable delivery.

Alert Topics

System alerts are published to tenant-specific alert topics:
greenhouse/{greenhouseId}/alerts/{alertType}

Alert Types

THRESHOLD_EXCEEDED
alert
Sensor reading exceeded configured threshold
SENSOR_OFFLINE
alert
Sensor has not reported within expected interval
ACTUATOR_FAILURE
alert
Actuator failed to respond to command
SYSTEM_ERROR
alert
System-level error requiring attention

Alert Format

{
  "message": "Temperature exceeded threshold: 35.2°C > 30.0°C",
  "severity": "WARNING",
  "timestamp": "2025-11-16T10:30:00Z"
}
Alerts should use QoS 2 (exactly once) to prevent duplicate alert notifications.

System Event Topics

System-wide events are published to the following topics:
system/events/#

Event Types

  • system/events/startup - API started
  • system/events/shutdown - API shutting down
  • system/events/connection - MQTT connection status changes
  • system/events/error - System-level errors

Topic-Based Routing

The system uses Spring Integration’s message routing to direct MQTT messages to appropriate handlers:
@ServiceActivator(inputChannel = "mqttInputChannel")
fun mqttMessageHandler(): MessageHandler {
    return MessageHandler { message ->
        val topic = message.headers[MqttHeaders.RECEIVED_TOPIC] as? String
        when {
            topic?.startsWith("GREENHOUSE") == true -> 
                greenhouseDataListener.handleGreenhouseData(message)
            topic?.contains("/sensors/") == true -> 
                sensorDataListener.handleSensorData(message)
            topic?.contains("/actuators/") == true -> 
                actuatorStatusListener.handleActuatorStatus(message)
            else -> 
                logger.warn("Unknown topic: $topic")
        }
    }
}

Routing Table

Topic PatternHandlerQoSDescription
GREENHOUSEGreenhouseDataListener0Legacy greenhouse data
GREENHOUSE/+GreenhouseDataListener0Multi-tenant greenhouse data
greenhouse/+/sensors/#SensorDataListener0Individual sensor readings
greenhouse/+/actuators/#ActuatorStatusListener1Actuator status updates
system/events/#SystemEventListener0System events

Wildcard Subscriptions

MQTT supports two types of wildcards:

Single-Level Wildcard (+)

Matches exactly one topic level:
# Matches: GREENHOUSE/SARA, GREENHOUSE/001, GREENHOUSE/NARANJOS
# Does NOT match: GREENHOUSE, GREENHOUSE/SARA/sensors
mosquitto_sub -t "GREENHOUSE/+"

Multi-Level Wildcard (#)

Matches zero or more topic levels (must be at the end):
# Matches: system/events/startup, system/events/error, system/events/connection/lost
mosquitto_sub -t "system/events/#"
Avoid subscribing to # (all topics) in production as it will receive every message published to the broker.

Response Topic (Echo)

The API automatically echoes received messages to the SYSTEM/RESPONSE topic for verification:
GREENHOUSE/SARA  →  API Processes  →  SYSTEM/RESPONSE
This allows hardware engineers to:
  1. Publish sensor data to GREENHOUSE/{tenantId}
  2. Subscribe to SYSTEM/RESPONSE
  3. Verify the API received and processed the data correctly
# Publish sensor data
mosquitto_pub -t "GREENHOUSE/SARA" \
  -m '{"TEMPERATURA INVERNADERO 01":25.5}'

Best Practices

Topic Naming

  • Use descriptive, hierarchical topic names
  • Avoid spaces and special characters
  • Use forward slashes (/) to separate levels
  • Keep topic depth reasonable (3-5 levels max)

QoS Selection

  • QoS 0: Sensor data (frequent, loss acceptable)
  • QoS 1: Actuator commands (must be received)
  • QoS 2: Critical alerts (no duplicates)

Multi-Tenancy

  • Always include tenant ID in topics for isolation
  • Use the DEFAULT tenant for legacy compatibility
  • Validate tenant IDs in message handlers

Security

  • Implement ACL rules to restrict topic access per tenant
  • Use authentication for all MQTT clients
  • Avoid publishing sensitive data in topic names
The MQTT broker (EMQX) supports Access Control Lists (ACLs) to restrict which clients can publish/subscribe to specific topics. Configure ACLs to enforce tenant isolation.

Build docs developers (and LLMs) love