Overview
The Water Quality Meters feature enables registration, configuration, and real-time monitoring of IoT water quality sensors. Meters collect data from multiple sensors and transmit it via WebSocket connections for real-time analysis.
IoT Integration Real-time sensor data collection via WebSocket
Multi-Sensor Support pH, TDS, temperature, conductivity, turbidity
Weather Data Integrated weather API for environmental context
Sensor Types
Each water quality meter can collect data from five primary sensors:
Measures the acidity or alkalinity of water
Range: 0-14
Optimal: 6.5-8.5 for drinking water
Type: ph in API
TDS (Total Dissolved Solids)
Measures dissolved minerals, salts, and metals
Unit: ppm (parts per million)
Optimal: < 500 ppm for drinking water
Type: tds in API
Measures water temperature
Unit: Celsius (°C)
Type: temperature in API
Impact: Affects other sensor readings
Measures water’s ability to conduct electricity
Unit: μS/cm (microsiemens per centimeter)
Type: conductivity in API
Related: Correlates with TDS
Measures water clarity and suspended particles
Unit: NTU (Nephelometric Turbidity Units)
Optimal: < 5 NTU for drinking water
Type: turbidity in API
Meter Registration
Create a Meter
Register a new water quality meter in a workspace:
POST /api/meters/{workspace_id}/
Content-Type : application/json
Authorization : Bearer {access_token}
{
"name" : "Lake Monitor Station 1" ,
"location" : {
"name_location" : "Central Lake" ,
"lat" : 40.7128 ,
"lon" : -74.0060
}
}
Response:
{
"message" : "Meter created successfully" ,
"meter" : {
"id" : "meter_abc123" ,
"name" : "Lake Monitor Station 1" ,
"location" : {
"name_location" : "Central Lake" ,
"lat" : 40.7128 ,
"lon" : -74.0060
},
"state" : "disconnected" ,
"rol" : "owner"
}
}
Data Model
class Location ( BaseModel ):
name_location: str | None = None
lat: float # Latitude
lon: float # Longitude
class WQMeterCreate ( BaseModel ):
name: str
location: Location
class WQMeter ( WQMeterCreate ):
state: MeterConnectionState = MeterConnectionState. DISCONNECTED
See: ~/workspace/source/app/features/meters/domain/model.py:22
Token-Based Connection
Meters use secure token-based authentication to establish WebSocket connections.
Pairing Process
Request Connection Token
Generate a time-limited token for the meter to connect: POST /api/meters/{workspace_id}/pair/{meter_id}/
Authorization : Bearer {access_token}
Response: {
"message" : "Connection received" ,
"token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Token is valid for 30 days (2,592,000 seconds) and contains workspace, owner, and meter identifiers.
Validate Token
Before establishing WebSocket connection, validate the token: POST /api/meters/{workspace_id}/pair/{meter_id}/validate/
Content-Type : application/json
Authorization : Bearer {access_token}
{
"token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Connect via WebSocket
Use the validated token to establish WebSocket connection for real-time data transmission.
Token Payload
class MeterPayload ( BaseModel ):
id_workspace: str
owner: str
id_meter: str
exp: float # Expiration timestamp
A meter can only have one active connection at a time. Attempting to pair an already active meter returns a 409 Conflict error.
Real-Time Data Collection
WebSocket Integration
Meters transmit sensor data in real-time via WebSocket connections:
class SRColorValue ( BaseModel ):
r: int
g: int
b: int
class RecordBody ( BaseModel ):
color: SRColorValue
conductivity: float
ph: float
temperature: float
tds: float
turbidity: float
class RecordResponse ( BaseModel ):
color: Record[SRColorValue]
conductivity: Record[ float ]
ph: Record[ float ]
temperature: Record[ float ]
tds: Record[ float ]
turbidity: Record[ float ]
Source: ~/workspace/source/app/share/socketio/domain/model.py:14
Connection States
class MeterConnectionState ( str , Enum ):
CONNECTED = "connected"
DISCONNECTED = "disconnected"
Sensor Data Structure
class SensorRecord ( BaseModel , Generic[T]):
id : str
datetime: str # ISO 8601 timestamp
value: T # Sensor-specific value type
class Sensor ( BaseModel ):
type : str # Sensor type (ph, tds, etc.)
list : list[SensorRecord] # Historical readings
Query Sensor Records
Get All Sensor Data
Retrieve sensor records with filtering:
GET /api/meters/records/{workspace_id}/{meter_id}/?
start_date=2024-01-01&
end_date=2024-01-31&
sensor_type=ph&
limit=100&
index=cursor_token
Authorization : Bearer {access_token}
Query Parameters:
start_date (optional): ISO 8601 date string
end_date (optional): ISO 8601 date string
sensor_type (optional): Filter by sensor (ph, tds, temperature, conductivity, turbidity)
limit: Number of records (default: 10)
index: Pagination cursor
Get Specific Sensor Records
Query records for a single sensor type:
GET /api/meters/records/{workspace_id}/{meter_id}/{sensor_name}/?limit=50
Authorization : Bearer {access_token}
Example Response:
{
"message" : "Records retrieved successfully" ,
"records" : [
{
"id" : "record_123" ,
"datetime" : "2024-01-15T10:30:00Z" ,
"value" : 7.2
},
{
"id" : "record_124" ,
"datetime" : "2024-01-15T10:31:00Z" ,
"value" : 7.3
}
]
}
Weather API Integration
Meters can access weather data based on their geographic location, providing environmental context for water quality readings.
Get Current Weather
GET /api/meters/{workspace_id}/weather/{meter_id}/
Authorization : Bearer {access_token}
Response:
{
"success" : true ,
"message" : "Current weather data" ,
"data" : {
"temperature" : 22.5 ,
"humidity" : 65 ,
"conditions" : "Partly Cloudy" ,
"location" : {
"lat" : 40.7128 ,
"lon" : -74.0060
}
}
}
Get Historical Weather
Retrieve weather data for the past N days:
GET /api/meters/{workspace_id}/weather/{meter_id}/?last_days=7
Authorization : Bearer {access_token}
Weather data is fetched based on the meter’s configured location (latitude/longitude). Ensure meters have accurate location data for relevant weather information.
See implementation: ~/workspace/source/app/features/meters/presentation/routes.py:227
Meter Management
List All Meters in Workspace
GET /api/meters/{workspace_id}/
Authorization : Bearer {access_token}
Get Meter Details
GET /api/meters/{workspace_id}/{meter_id}/
Authorization : Bearer {access_token}
Update Meter Configuration
PUT /api/meters/{workspace_id}/{meter_id}/
Content-Type : application/json
Authorization : Bearer {access_token}
{
"name" : "Updated Meter Name" ,
"location" : {
"name_location" : "New Location" ,
"lat" : 41.8781 ,
"lon" : -87.6298
}
}
Delete a Meter
Deleting a meter removes all associated sensor records and historical data. This action cannot be undone.
DELETE /api/meters/{workspace_id}/{meter_id}/
Authorization : Bearer {access_token}
Sensor Status
class SensorStatus ( str , Enum ):
ACTIVE = "active"
DISABLED = "disabled"
Meters can be temporarily disabled without deleting historical data.
Best Practices
Accurate Locations Set precise GPS coordinates for accurate weather data correlation
Secure Tokens Store connection tokens securely on IoT devices
Connection Monitoring Monitor connection state and implement reconnection logic
Data Validation Validate sensor readings on the device before transmission
Example: Complete Meter Setup
import requests
# 1. Create workspace
workspace_response = requests.post(
"https://api.example.com/api/workspaces/" ,
headers = { "Authorization" : f "Bearer { token } " },
json = {
"name" : "River Monitoring Project" ,
"type" : "private"
}
)
workspace_id = workspace_response.json()[ "data" ][ "id" ]
# 2. Register meter
meter_response = requests.post(
f "https://api.example.com/api/meters/ { workspace_id } /" ,
headers = { "Authorization" : f "Bearer { token } " },
json = {
"name" : "Upstream Station" ,
"location" : {
"name_location" : "River Mile 42" ,
"lat" : 40.7128 ,
"lon" : - 74.0060
}
}
)
meter_id = meter_response.json()[ "meter" ][ "id" ]
# 3. Get connection token
pair_response = requests.post(
f "https://api.example.com/api/meters/ { workspace_id } /pair/ { meter_id } /" ,
headers = { "Authorization" : f "Bearer { token } " }
)
connection_token = pair_response.json()[ "token" ]
# 4. Use connection_token to establish WebSocket connection
# (Implementation depends on IoT device firmware)