This document establishes debugging standards and best practices for the AquaPi project.
Use appropriate log levels for different types of messages:
| Level | Macro | When to Use | Example |
|---|---|---|---|
| DEBUG | ESP_LOGD() |
General debugging information, detailed trace | ESP_LOGD("ezo_ph", "Reading pH value: %.2f", value); |
| INFO | ESP_LOGI() |
Important state changes, confirmations | ESP_LOGI("ezo_ph", "pH sensor initialized successfully"); |
| WARNING | ESP_LOGW() |
Issues that don't stop execution | ESP_LOGW("ezo_ph", "pH sensor not detected at address 99"); |
| ERROR | ESP_LOGE() |
Errors affecting functionality | ESP_LOGE("i2c", "I2C bus communication failed"); |
| VERBOSE | ESP_LOGV() |
Extremely detailed trace (enable in config) | ESP_LOGV("ezo_ph", "Sending command: %s", cmd); |
Always follow this format for consistency:
ESP_LOG[LEVEL]("component_name", "Message with context and data");Best Practices:
- Use descriptive component names (e.g., "ezo_ph", "i2c_detection", "debug")
- Include relevant data: addresses, values, states
- Make messages actionable (help users understand what to do)
- Use format specifiers correctly:
%d(int),%f(float),%s(string),0x%02X(hex)
Examples:
// Good - includes context and data
ESP_LOGD("ezo_ph", "pH reading: %.2f at address 0x%02X", value, address);
ESP_LOGI("i2c_detection", "Found %d I2C devices on bus", count);
ESP_LOGW("ezo_ec", "EC sensor not responding at address 100");
// Bad - lacks context
ESP_LOGD("sensor", "Got value");
ESP_LOGW("main", "Error");Important: Use ESPHome's native I2C API instead of the Arduino Wire library. ESPHome's I2C component and Arduino's Wire library cannot coexist on the same bus - they conflict for hardware control.
Correct approach - ESPHome I2C API:
// Check if I2C device is present using ESPHome's API
uint8_t address = 0x63; // 99 decimal = 0x63 hex
esphome::i2c::ErrorCode err = id(bus_a)->write(address, nullptr, 0);
if (err == esphome::i2c::ERROR_OK) {
// Sensor is present
ESP_LOGI("ezo_ph", "pH sensor detected at address 99 (0x63)");
id(ph_ezo).send_custom("R");
} else {
ESP_LOGW("ezo_ph", "pH sensor not detected at address 99 (0x63)");
}Helper function pattern (recommended):
// Define a reusable helper function in lambda
auto check_i2c_device = [](uint8_t address) -> bool {
esphome::i2c::ErrorCode err = id(bus_a)->write(address, nullptr, 0);
return (err == esphome::i2c::ERROR_OK);
};
// Use it multiple times
if (check_i2c_device(0x63)) {
ESP_LOGI("ezo_ph", "pH sensor detected");
}Note: Writing nullptr (0 bytes) is a standard I2C probe technique that checks if a device ACKs its address without sending any data.
| Sensor | Decimal Address | Hex Address | Default Variable |
|---|---|---|---|
| EZO DO | 97 | 0x61 | addDO |
| EZO ORP | 98 | 0x62 | addORP |
| EZO pH | 99 | 0x63 | addPH |
| EZO EC | 100 | 0x64 | addEC |
| EZO RTD | 102 | 0x66 | addRTD |
| EZO PMP (White) | 103 | 0x67 | addPMP_white |
| EZO PMP (Yellow) | 104 | 0x68 | addPMP_yellow |
| EZO CO2 | 105 | 0x69 | addCO2 |
| EZO PMP (Blue) | 106 | 0x6A | addPMP_blue |
| EZO PMP (Red) | 108 | 0x6C | addPMP_red |
| EZO PMP (Green) | 109 | 0x6D | addPMP_green |
| EZO PMP (Orange) | 110 | 0x6E | addPMP_orange |
| EZO HUM | 111 | 0x6F | addHUM |
The project now includes dedicated I2C detection components in common/i2c_detection.yaml:
- Individual Sensor Detection - Binary sensors for each EZO device
- I2C Device Counter - Total number of connected devices
- I2C Bus Status - Overall bus health
- Address List - All detected addresses
- Manual Scan Button - Force a bus scan
Always validate data before use:
// Check if sensor is connected using ESPHome I2C API
esphome::i2c::ErrorCode err = id(bus_a)->write(0x63, nullptr, 0);
if (err != esphome::i2c::ERROR_OK) {
ESP_LOGW("ezo_ph", "pH sensor not detected, skipping read");
return NAN; // Return NAN for numeric sensors
}
// Check for null/invalid values
std::string str = id(raw_value_ec).state;
if (str.empty()) {
ESP_LOGW("ezo_ec", "No data from EC sensor");
return NAN;
}
// Validate parsed data
std::vector<std::string> v = parse_csv(str);
if (v.size() != 4) {
ESP_LOGW("ezo_ec", "Unexpected data format: expected 4 values, got %d", v.size());
return NAN;
}The system should continue operating even when sensors are missing:
lambda: |-
// Attempt to read sensor
esphome::i2c::ErrorCode err = id(bus_a)->write(0x63, nullptr, 0);
if (err == esphome::i2c::ERROR_OK) {
id(ph_ezo).send_custom("R");
return true; // Success
} else {
// Sensor missing, log but don't crash
ESP_LOGW("ezo_ph", "pH sensor not available");
return false; // Failure, but system continues
}-
Debug Module (
common/debug.yaml):- Heap memory monitoring
- Loop time tracking
- Memory usage percentage
- I2C health score
- System health status
- ESPHome version
- Reset reason tracking
-
I2C Detection Module (
common/i2c_detection.yaml):- Individual sensor detection binary sensors
- Total device counter
- I2C bus status
- List of detected addresses
- Manual scan button
-
Debug Buttons:
- Memory Report - Detailed memory statistics
- I2C Report - Complete I2C bus scan with device identification
Watch for memory leaks or high usage:
# In automation or script
- if:
condition:
lambda: 'return id(memory_usage_percent).state > 80.0;'
then:
- logger.log:
level: WARN
message: "High memory usage detected!"Monitor I2C bus reliability:
# Check I2C health before critical operations
- if:
condition:
lambda: 'return id(i2c_health_score).state < 50.0;'
then:
- logger.log:
level: ERROR
message: "I2C bus degraded - check connections"Use the debug buttons in Home Assistant or the web interface:
- Press "Debug - I2C Report" to see all connected devices
- Press "Debug - Memory Report" to check system resources
- Press "I2C Scan Now" to refresh sensor detection
Symptoms:
- "I2C Devices Connected" shows 0
- All EZO sensor detection shows "off"
Solutions:
- Check physical connections (SDA=GPIO21, SCL=GPIO22)
- Verify EZO circuits are powered (green LED)
- Run "I2C Scan Now" button
- Check logs for I2C errors:
ESP_LOGE("i2c", ...) - Verify I2C pullup resistors (usually built-in on EZO circuits)
Symptoms:
- Other sensors work, but one doesn't appear
- Sensor binary sensor shows "off"
Solutions:
- Verify the sensor's I2C address (might have been changed)
- Check if sensor LED is on (power issue)
- Try the sensor alone on the bus (address conflict?)
- Use "Debug - I2C Report" to see actual addresses
- Check for loose wiring on that specific sensor
Symptoms:
- "Memory Usage Percent" > 80%
- System becomes unstable or crashes
- "System Health Status" shows "WARNING" or "CRITICAL"
Solutions:
- Press "Debug - Memory Report" to see details
- Reduce update intervals in sensor configs
- Disable unused sensors or components
- Check for memory leaks in custom lambda code
- Consider reducing log verbosity
Symptoms:
- Sensors intermittently stop responding
- "I2C Health Score" is low (<50%)
- Seeing "NACK" or timeout errors in logs
Solutions:
- Check wire quality and length (keep under 1 meter for high reliability)
- Add external pullup resistors (2.2kΩ-4.7kΩ) to SDA and SCL if bus is long
- Reduce update frequency to give devices more time
- Check for ground loops or power supply noise
- Try reducing I2C bus speed (add to device_base.yaml):
i2c: sda: ${sdaPin} scl: ${sclPin} scan: true id: bus_a frequency: 50kHz # Default is 100kHz
Symptoms:
- Sensor detection shows "on" but no readings
- Values show as "Unknown" or "NAN"
Solutions:
- Check sensor calibration
- Verify sensor is in appropriate medium (water for aquatic sensors)
- Check command syntax in logs
- Try manual read using sensor's read button
- Send "Status" command to check sensor state
- Review sensor-specific troubleshooting in Atlas Scientific documentation
Symptoms:
- Log shows messages like
[E][ezo.sensor:088]: read erroror[E][ezo-pmp:111]: read error - These errors appear every 60 seconds (at sensor update interval)
- Sensor is configured but not physically connected
Understanding the Error:
These errors occur when ESPHome tries to read from a sensor at its configured update_interval, but the sensor is not present on the I2C bus. The cryptic error code (e.g., :088:, :111:) is an internal ESPHome component line number, not helpful for troubleshooting.
Solutions:
-
If the sensor is not installed and you don't plan to use it:
Disable automatic updates by setting the update interval to "never" in your substitutions:
substitutions: update_ph: "never" # Disable pH sensor updates update_ec: "never" # Disable EC sensor updates update_orp: "never" # Disable ORP sensor updates update_do: "never" # Disable DO sensor updates update_pmp_white: "never" # Disable White pump updates update_pmp_green: "never" # Disable Green pump updates update_pmp_red: "never" # Disable Red pump updates
You can also use very long intervals to effectively disable updates:
substitutions: update_ph: "365days" # Only update once per year (effectively disabled)
-
If you plan to install the sensor later:
Set the update interval to "never" temporarily, then change it back when the sensor is installed.
-
Check the helpful detection warnings:
The system now logs user-friendly warnings for missing sensors every 60 seconds:
[W][ezo_detection:xxx] No pH (0x63) sensor detected! If not in use, set 'update_ph: "never"' in substitutions to disable updates and avoid errors.These messages tell you exactly which sensor is missing and how to disable it.
-
Use the I2C Detection sensors:
Check the "EZO XX Sensor Detected" binary sensors in Home Assistant to see which sensors are found. Only enable update intervals for detected sensors.
Technical Details:
EZO sensors (pH, EC, ORP, DO, CO2, RTD, HUM) and EZO pumps now use a button-based update pattern with detection checks:
- The button checks if the sensor is present before attempting to read
- If the sensor is missing, a friendly warning is logged (e.g., "No pH sensor detected at address: 99!!")
- If the sensor is present, the read proceeds normally
This prevents the cryptic "read error" messages when using button-triggered updates. However, if the sensor's update_interval is set (not "never"), ESPHome will also try automatic updates which will generate errors if the sensor is missing.
Recommended Configuration:
For sensors that may not always be connected:
substitutions:
update_ph: "60s" # Sensor update interval (can set to "never")
update_button_ph: "60s" # Button-triggered update interval (has detection check)- Set
update_ph: "never"to disable automatic sensor reads - Keep
update_button_ph: "60s"to enable button-triggered reads with detection checking - This gives you clean logs while still allowing on-demand reads when the sensor is connected
✅ DO:
- Use ESPHome's native I2C API (
id(bus_a)->write()) for sensor detection - Log with appropriate levels (DEBUG, INFO, WARN, ERROR)
- Include context in all log messages
- Check for null/NAN before processing data
- Monitor memory and I2C health regularly
- Test with sensors disconnected
- Provide graceful fallbacks for missing sensors
❌ DON'T:
- Use Arduino Wire library (conflicts with ESPHome's I2C component)
- Log at ERROR level for expected conditions (like missing optional sensors)
- Assume sensors are always present
- Ignore NAN or null values
- Skip validation of parsed data
- Remove error checking to "simplify" code
- ESPHome I2C Bus Documentation: https://esphome.io/components/i2c.html
- Atlas Scientific EZO Documentation:
Atlas EZO docs/directory in repository root - ESPHome Logging: https://esphome.io/components/logger.html
- ESPHome Debug Component: https://esphome.io/components/debug.html
For issues or questions:
- Check the logs first (enable VERBOSE if needed)
- Run diagnostic buttons (Memory Report, I2C Report)
- Check this debugging guide
- Review GitHub issues: https://github.com/TheRealFalseReality/aquapi/issues
- Consult Atlas Scientific support for sensor-specific issues