ClearGrow Anomaly Detection Models
This directory contains TensorFlow Lite models for the TinyML anomaly detection feature.
Current Status
ML Feature Status: DISABLED (CONFIG_CLEARGROW_ENABLE_ML=n)
Model File: anomaly_detector.tflite is present (placeholder model, 30.6 KB INT8 quantized)
The ML feature is disabled by default in sdkconfig.defaults. A placeholder model file is provided in the repository for build testing and initial integration. This model is untrained and will produce random results - it is suitable only for testing the TFLite inference pipeline, not for production anomaly detection.
Model Requirements
If you want to enable the ML anomaly detection feature, you must provide a trained model:
Model Specifications
| Property | Value | Notes |
|---|---|---|
| Input Shape | (1, 60, 6) | Batch=1, 60 time steps, 6 features |
| Output Shape | (1, 60, 6) | Autoencoder reconstruction |
| Input Features | 6 | temp, humidity, VPD, CO2, leaf_temp, PPFD |
| Operations | FullyConnected, ReLU, Reshape, Mul, Add, Mean | Registered in tflite_runner.cc |
| Quantization | INT8 (recommended) or FP32 | INT8 faster on ESP32-S3 |
| Tensor Arena | <256KB | Allocated in PSRAM |
| Format | TFLite v3 | FlatBuffers schema version |
Input Features (in order)
- Temperature (°C) - Air temperature
- Humidity (%) - Relative humidity
- VPD (kPa) - Vapor Pressure Deficit
- CO2 (ppm) - Carbon dioxide concentration
- Leaf Temperature (°C) - Infrared leaf surface temp
- PPFD (µmol/m²/s) - Photosynthetic Photon Flux Density
Normalization
The model expects normalized inputs (z-score):
normalized_value = (raw_value - mean) / std
Current normalization parameters (hardcoded in anomaly_detector.c):
| Feature | Mean | Std Dev |
|---|---|---|
| Temperature | 24.0°C | 5.0°C |
| Humidity | 60.0% | 15.0% |
| VPD | 1.0 kPa | 0.3 kPa |
| CO2 | 800 ppm | 300 ppm |
| Leaf Temp | 22.0°C | 3.0°C |
| PPFD | 400 µmol/m²/s | 200 µmol/m²/s |
Important: If you retrain the model with different normalization, update these values in components/tflite_runner/src/anomaly_detector.c lines 30-46.
Placeholder Model (Included)
A placeholder model (anomaly_detector.tflite, 30.6 KB) is included in the repository. This was generated using generate_placeholder_model.py and is suitable for:
- Build testing with
CONFIG_CLEARGROW_ENABLE_ML=y - Integration testing of the TFLite inference pipeline
- Development without real sensor data
WARNING: The placeholder model is untrained and will produce random/meaningless results. Do not use in production.
Regenerating the Placeholder Model
If you need to regenerate the placeholder model:
Prerequisites
- Python 3.7+
- TensorFlow 2.x
# Create virtual environment (recommended)
python3 -m venv venv
source venv/bin/activate
pip install tensorflow
Generate Model
cd /root/cleargrow/controller/models
python3 generate_placeholder_model.py
This creates anomaly_detector.tflite with the following characteristics:
- Architecture: Simple autoencoder (Flatten → Dense(32) → Dense(360) → Reshape)
- Size: ~30 KB (INT8 quantized)
- Input/Output: (1, 60, 6) float32
- Operations: FullyConnected, ReLU, Reshape (all registered in tflite_runner.cc)
- Training: None (random weights)
Using Docker (if TensorFlow not available locally)
cd /root/cleargrow/controller/models
docker run --rm -v $(pwd):/models python:3.10 bash -c "
pip install tensorflow && \
cd /models && \
python3 generate_placeholder_model.py
"
Note: The placeholder model is already included in the repository, so regeneration is only needed if you've modified generate_placeholder_model.py.
Training a Real Model
For production deployment, train on actual sensor data:
1. Collect Training Data
Collect at least 7-30 days of sensor data from normally operating grow rooms:
- All 6 sensor types
- 1Hz sampling rate
- Only use periods with no known anomalies/issues
- Include variation in day/night cycles, growth stages
Export from data logger or use /api/v1/export/history REST endpoint.
2. Train Autoencoder
import tensorflow as tf
import numpy as np
import pandas as pd
# Load your sensor data
data = pd.read_csv('sensor_history.csv')
# Extract features (temp, humidity, vpd, co2, leaf_temp, ppfd)
features = data[['temperature', 'humidity', 'vpd', 'co2', 'leaf_temp', 'ppfd']].values
# Normalize (save mean/std for deployment)
mean = features.mean(axis=0)
std = features.std(axis=0)
normalized = (features - mean) / std
# Create sequences of 60 time steps
def create_sequences(data, window=60):
sequences = []
for i in range(len(data) - window + 1):
sequences.append(data[i:i+window])
return np.array(sequences)
X_train = create_sequences(normalized)
# Build autoencoder
model = tf.keras.Sequential([
tf.keras.layers.InputLayer(input_shape=(60, 6)),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(32, activation='relu'),
tf.keras.layers.Dense(360),
tf.keras.layers.Reshape((60, 6))
])
model.compile(optimizer='adam', loss='mse')
model.fit(X_train, X_train, epochs=50, batch_size=32, validation_split=0.2)
3. Convert to TFLite
# INT8 quantization
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
def representative_dataset():
for i in range(100):
yield [X_train[i:i+1].astype(np.float32)]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.float32
converter.inference_output_type = tf.float32
tflite_model = converter.convert()
with open('anomaly_detector.tflite', 'wb') as f:
f.write(tflite_model)
4. Update Normalization Parameters
Edit /root/cleargrow/controller/components/tflite_runner/src/anomaly_detector.c:
static const float feature_means[INPUT_FEATURES] = {
mean[0], /* temperature */
mean[1], /* humidity */
mean[2], /* VPD */
mean[3], /* CO2 */
mean[4], /* leaf_temp */
mean[5] /* PPFD */
};
static const float feature_stds[INPUT_FEATURES] = {
std[0], std[1], std[2], std[3], std[4], std[5]
};
5. Tune Threshold
The anomaly threshold (default 0.7) may need tuning:
// anomaly_detector.c line 21
#define ANOMALY_THRESHOLD 0.7f // Adjust based on validation data
Use ROC curve analysis on validation data with known anomalies to find optimal threshold.
Enabling the Feature
Once you have a trained model:
- Place
anomaly_detector.tflitein this directory - Enable in
sdkconfig.defaults:Or via menuconfig:CONFIG_CLEARGROW_ENABLE_ML=yidf.py menuconfig # → ClearGrow Configuration → Enable TinyML anomaly detection - Rebuild and flash:
idf.py build flash monitor - Monitor initialization:
I (1234) tflite: Initializing TensorFlow Lite Micro... I (1240) tflite: Model schema version: 3 I (1245) tflite: Tensor arena: 256 KB in PSRAM I (1250) tflite: Arena used: 45678 bytes (17.8%) I (1255) anomaly: TFLite anomaly detection task started
Monitoring
When ML is enabled, watch for anomaly detection logs:
W (12345) anomaly: Anomaly detected! Score: 1.234
W (12346) anomaly: Anomaly detected with score: 1.234
Anomaly events are posted to the event system but are not currently handled by automation (see Gap CTRL-ML-003 in assessment).
Memory Usage
Check tensor arena usage in logs:
I (1250) tflite: Arena used: 45678 bytes (17.8%)
If approaching 100%, increase TENSOR_ARENA_SIZE in tflite_runner.cc line 20.
Troubleshooting
Build fails: "No such file: anomaly_detector.tflite"
- ML is enabled but model file missing
- Solution: Disable ML in sdkconfig or generate placeholder model
Inference fails: "AllocateTensors() failed"
- Model too large for 256KB arena
- Solution: Simplify model or increase TENSOR_ARENA_SIZE
Low accuracy / too many false positives
- Normalization parameters don't match training data
- Threshold too low
- Model undertrained
- Solution: Retrain with more data, tune threshold
Inference timeout errors
- Model too complex for 5s timeout
- Solution: Simplify model or increase INFERENCE_TIMEOUT_MS in tflite_runner.cc
References
- Assessment:
/root/cleargrow/docs/project/assessments/findings/controller/tinyml_inference.md - TFLite Micro Docs: https://www.tensorflow.org/lite/microcontrollers
- ESP TFLite Micro: https://components.espressif.com/components/espressif/esp-tflite-micro
- Autoencoder Guide: https://www.tensorflow.org/tutorials/generative/autoencoder
Related Gaps
- CTRL-ML-001 (Critical): Model file missing - ADDRESSED by disabling feature
- CTRL-ML-002 (Low): CONFIG not documented - RESOLVED by this README + Kconfig
- CTRL-ML-003 (High): No automation handler for anomaly events - TODO
- CTRL-ML-004 (Medium): No UI for ML feature - TODO
- CTRL-ML-005 (Low): Hardcoded normalization params - DOCUMENTED