Skip to content

Add particle count and dallas probe#28

Merged
Bunton33 merged 10 commits intomainfrom
Add-particle-count-and-dallas-probe
Jan 22, 2026
Merged

Add particle count and dallas probe#28
Bunton33 merged 10 commits intomainfrom
Add-particle-count-and-dallas-probe

Conversation

@Bunton33
Copy link
Contributor

@Bunton33 Bunton33 commented Jan 21, 2026

Version: 26.1.21.1

What does this implement/fix?

Adds

  • PM particle count, ranges, and alarms
  • Dallas probe
  • Deep sleep

Types of changes

  • Bugfix (fixed change that fixes an issue)
  • New feature (thanks!)
  • Breaking change (repair/feature that breaks existing functionality)
  • Dependency Update - Does not publish
  • Other - Does not publish
  • Website of github readme file update - Does not publish
  • Github workflows - Does not publish

Checklist / Checklijst:

  • The code change has been tested and works locally
  • The code change has not yet been tested

If user-visible functionality or configuration variables are added/modified:

  • Added/updated documentation for the web page

Summary by CodeRabbit

Release Notes

New Features

  • Added particle number concentration monitoring for multiple PM sizes
  • Introduced battery voltage and charge level tracking
  • Enhanced sleep management with OTA mode controls and wake prevention toggle
  • Expanded alarm detection to include particle count thresholds
  • Added configurable sleep duration with device wake prevention logic

Bug Fixes

  • Fixed firmware selection logic for correct update source detection

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

Walkthrough

Version bump and major firmware rework for ESPHome device. Introduces deep sleep and alarm detection logic with PM number concentration measurement support. Adds new SEN5x custom component for reading particle concentrations. Firmware source selection logic corrected across device configurations.

Changes

Cohort / File(s) Summary
Core ESPHome Configuration
Integrations/ESPHome/Core.yaml
Version bumped to 26.1.21.1. Redesigned boot sequence with delayed deep sleep initialization, OTA mode guard, and temperature/humidity alarm logic. Added SEN5x number concentration sensor integration, battery monitoring (MAX17048), and PM count aggregation. New reportAllValues script to publish computed PM totals. Renamed alarm-related template sensors for consistency. Expanded deep sleep controller with sleep duration configuration and conditional wake prevention based on OTA/alarm states.
Firmware Selection Fixes
Integrations/ESPHome/TEMP_PRO-1_ETH.yaml, Integrations/ESPHome/TEMP_PRO-1_W.yaml
Changed firmware selector condition from state string comparison to current_option() method call. Ensures OTA firmware source URL selection triggers on actual selected option rather than stored state value.
SEN5x Number Component — Header & Initialization
Integrations/ESPHome/custom_components/sen5x_number/__init__.py, Integrations/ESPHome/custom_components/sen5x_number/sen5x_number.h
New ESPHome custom component for SEN5x particle number concentration sensor. Header defines SEN5xNumberConcentration class with I2C communication support, CRC validation, and sensor setters for five PM size ranges (0.5–10 µm) plus typical particle size. Init file adds CODEOWNERS metadata.
SEN5x Number Component — C++ Implementation
Integrations/ESPHome/custom_components/sen5x_number/sen5x_number.cpp
Implements SEN5x I2C command (0x0413) to read 45-byte mass and number concentration response. Parses PM number counts per size, validates CRC-8 for each field, converts to standard units (#/m³), and publishes to configured sensors. Includes error handling and diagnostic logging.
SEN5x Number Component — Python Config
Integrations/ESPHome/custom_components/sen5x_number/sensor.py
Python integration layer defining component schema, unit constants, and code generation. Allows YAML configuration of PM concentration sensors and ties them to C++ setter methods during ESPHome compilation.

Sequence Diagram(s)

sequenceDiagram
    participant Device as ESPHome Device
    participant HA as Home Assistant
    participant Sensors as SEN5x & PM Sensors
    participant Sleep as Deep Sleep Controller
    
    Device->>Device: Boot Sequence
    Device->>Sensors: Initialize SEN5x (0x0413)
    Sensors-->>Device: Return PM number concentrations
    Device->>Device: Calculate Total PM Count
    alt OTA Mode Active or Prevent Sleep
        Device->>Device: Log Warning
        Device->>Sleep: Prevent Deep Sleep
    else Check Alarm Conditions
        Device->>Device: Evaluate Temperature/Humidity/PM Range
        alt Alarm Triggered
            Device->>HA: Update Sensor Values
            Device->>Device: Publish Alarm State
            Device->>Sleep: Prevent Deep Sleep
        else No Alarm
            Device->>Sleep: Schedule Deep Sleep After Delay
        end
    end
    HA-->>Device: Client Connected (OTA or Manual)
    Device->>Sleep: Cancel/Defer Sleep if OTA Active
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 The sensor hops with numbered counts,
PM particles dance on morning's fronts,
Deep sleep awaits when alarms are clear,
But wakes when danger whispers near!
CRC checks keep data true,
A firmware fix—the config's new!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add particle count and dallas probe' directly corresponds to two of the main features added in this PR: particle count functionality and Dallas temperature probe support. However, it notably omits other significant changes like deep sleep functionality, which according to the PR objectives is also a major feature added.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@Integrations/ESPHome/Core.yaml`:
- Around line 691-704: The entity configuration for the template sensor "Sleep
Duration" (id deep_sleep_sleep_duration) uses an invalid uppercase
entity_category value; change entity_category: "CONFIG" to the lowercase string
entity_category: "config" so ESPHome validation accepts it.

In `@Integrations/ESPHome/custom_components/sen5x_number/sen5x_number.cpp`:
- Around line 89-148: read_data_() currently always returns true even when
parse_value() fails CRC for every sensor field; add a bool flag (e.g.,
any_published = false) at the start of the parsing block and set it to true
whenever you call publish_state on any of the members (pm_0_5_number_,
pm_1_0_number_, pm_2_5_number_, pm_4_0_number_, pm_10_0_number_,
typical_particle_size_); after processing all fields return any_published
instead of true so the method returns false when no values passed CRC and
nothing was published, leaving parse_value and publish_state calls unchanged.

In `@Integrations/ESPHome/custom_components/sen5x_number/sensor.py`:
- Around line 34-67: Remove the inappropriate device_class from the
number-concentration sensor schemas: in sensor.py update the
sensor.sensor_schema calls for CONF_PM_0_5_NUMBER, CONF_PM_1_0_NUMBER,
CONF_PM_2_5_NUMBER, CONF_PM_4_0_NUMBER and CONF_PM_10_0_NUMBER to omit
device_class=DEVICE_CLASS_PM25 (leave unit_of_measurement, icon,
accuracy_decimals and state_class intact); this ensures the number sensors use
no device_class as required by Home Assistant specs.
🧹 Nitpick comments (1)
Integrations/ESPHome/Core.yaml (1)

815-831: Avoid masking NaNs in total PM count calculation.
std::max can turn NaN inputs into 0, so totals may publish even when source values are invalid. Consider guarding and skipping publish when any input is NaN.

🔧 Suggested change
@@
           // Ranges: 0.3-0.5, 0.5-1, 1-2.5, 2.5-4, 4-10
+          if (std::isnan(id(pm_0_5_number).state) ||
+              std::isnan(id(pm_1_0_number).state) ||
+              std::isnan(id(pm_2_5_number).state) ||
+              std::isnan(id(pm_4_0_number).state) ||
+              std::isnan(id(pm_10_0_number).state)) {
+            ESP_LOGW("Apollo", "Skipping Total PM Count publish due to NaN inputs");
+            return;
+          }
           float pm_0_3_to_0_5 = id(pm_0_5_number).state;

Comment on lines +691 to +704
- platform: template
name: "Sleep Duration"
id: deep_sleep_sleep_duration
min_value: 0.01
max_value: 24
step: 0.01
mode: box
update_interval: never
optimistic: true
restore_value: true
initial_value: 1
icon: "mdi:arrow-collapse-right"
entity_category: CONFIG
unit_of_measurement: "h"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

ESPHome entity_category config validation values

💡 Result:

Valid values for ESPHome's entity_category are the Home Assistant EntityCategory options: config or diagnostic (you can also leave it unset / None). You can also set it to an empty string ("") in ESPHome to remove the default category. [1][2]

Sources:
[1] Home Assistant developer docs — Entity (entity_category = CONFIG or DIAGNOSTIC / None).
[2] ESPHome component docs — entity_category option (examples, and note that set to "" removes default).


🏁 Script executed:

#!/bin/bash
# Search for entity_category usage patterns in common ESPHome configurations
# and check if there's any code that validates this field
rg "entity_category" --type yaml --type python -i -B 2 -A 2 | head -100

Repository: ApolloAutomation/TEMP_PROJECT-1

Length of output: 105


🌐 Web query:

ESPHome entity_category lowercase config case sensitive validation

💡 Result:

Short answer: Use lowercase strings (e.g. "diagnostic", "config") for entity_category in ESPHome. The docs and examples show entity_category as a string with lowercase values (or the empty string ""), and conventions/validation expect those exact values. [1][2][3]

Notes:

  • Examples in the ESPHome docs use lowercase entity_category values (e.g. "diagnostic"). [3]
  • The docs describe entity_category as an optional string and show "" to clear it. [1][2]
  • Home Assistant enforces which entity types are valid for certain categories; mismatches can produce errors when entities are added. If you see errors, ensure you use the correct category for the component type. [4]

Sources:

  1. ESPHome component docs (entity_category entries). [1]
  2. ESPHome sensor/text_sensor/select docs (entity_category usage). [2]
  3. ESPHome template example using entity_category: "diagnostic". [3]
  4. Home Assistant community discussion about entity_category/type mismatches. [4]

Change entity_category to use lowercase config.

ESPHome only accepts lowercase values for entity_category ("config" or "diagnostic"). The uppercase variant "CONFIG" is invalid and will fail config validation.

🔧 Suggested change
-    entity_category: CONFIG
+    entity_category: config
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- platform: template
name: "Sleep Duration"
id: deep_sleep_sleep_duration
min_value: 0.01
max_value: 24
step: 0.01
mode: box
update_interval: never
optimistic: true
restore_value: true
initial_value: 1
icon: "mdi:arrow-collapse-right"
entity_category: CONFIG
unit_of_measurement: "h"
- platform: template
name: "Sleep Duration"
id: deep_sleep_sleep_duration
min_value: 0.01
max_value: 24
step: 0.01
mode: box
update_interval: never
optimistic: true
restore_value: true
initial_value: 1
icon: "mdi:arrow-collapse-right"
entity_category: config
unit_of_measurement: "h"
🤖 Prompt for AI Agents
In `@Integrations/ESPHome/Core.yaml` around lines 691 - 704, The entity
configuration for the template sensor "Sleep Duration" (id
deep_sleep_sleep_duration) uses an invalid uppercase entity_category value;
change entity_category: "CONFIG" to the lowercase string entity_category:
"config" so ESPHome validation accepts it.

Comment on lines +89 to +148
auto parse_value = [&](uint8_t offset) -> float {
uint8_t msb = response[offset];
uint8_t lsb = response[offset + 1];
uint8_t crc = response[offset + 2];

if (crc != this->sht_crc_(msb, lsb)) {
ESP_LOGW(TAG, "CRC check failed for byte offset %d", offset);
return NAN;
}

uint16_t raw = (static_cast<uint16_t>(msb) << 8) | lsb;
return static_cast<float>(raw);
};

// Extract number concentrations (converting from #/cm³ to #/m³)
// Note: 1 #/cm³ = 1,000,000 #/m³
if (this->pm_0_5_number_ != nullptr) {
float value = parse_value(12) / 10.0f; // Scaled by 10 per datasheet
if (!std::isnan(value)) {
this->pm_0_5_number_->publish_state(value * 1e6f); // Convert to #/m³
}
}

if (this->pm_1_0_number_ != nullptr) {
float value = parse_value(15) / 10.0f;
if (!std::isnan(value)) {
this->pm_1_0_number_->publish_state(value * 1e6f);
}
}

if (this->pm_2_5_number_ != nullptr) {
float value = parse_value(18) / 10.0f;
if (!std::isnan(value)) {
this->pm_2_5_number_->publish_state(value * 1e6f);
}
}

if (this->pm_4_0_number_ != nullptr) {
float value = parse_value(21) / 10.0f;
if (!std::isnan(value)) {
this->pm_4_0_number_->publish_state(value * 1e6f);
}
}

if (this->pm_10_0_number_ != nullptr) {
float value = parse_value(24) / 10.0f;
if (!std::isnan(value)) {
this->pm_10_0_number_->publish_state(value * 1e6f);
}
}

// Typical particle size in µm
if (this->typical_particle_size_ != nullptr) {
float value = parse_value(27) / 1000.0f; // Scaled by 1000 per datasheet
if (!std::isnan(value)) {
this->typical_particle_size_->publish_state(value);
}
}

return true;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Return failure when no values pass CRC.
read_data_() currently returns true even if all values fail CRC, so update() clears the warning and silently drops data. Track whether any value was published and return false if none.

🔧 Suggested change
@@
-  auto parse_value = [&](uint8_t offset) -> float {
+  bool any_valid = false;
+  auto parse_value = [&](uint8_t offset) -> float {
@@
     if (!std::isnan(value)) {
       this->pm_0_5_number_->publish_state(value * 1e6f); // Convert to `#/m³`
+      any_valid = true;
     }
   }
@@
     if (!std::isnan(value)) {
       this->pm_1_0_number_->publish_state(value * 1e6f);
+      any_valid = true;
     }
   }
@@
     if (!std::isnan(value)) {
       this->pm_2_5_number_->publish_state(value * 1e6f);
+      any_valid = true;
     }
   }
@@
     if (!std::isnan(value)) {
       this->pm_4_0_number_->publish_state(value * 1e6f);
+      any_valid = true;
     }
   }
@@
     if (!std::isnan(value)) {
       this->pm_10_0_number_->publish_state(value * 1e6f);
+      any_valid = true;
     }
   }
@@
     if (!std::isnan(value)) {
       this->typical_particle_size_->publish_state(value);
+      any_valid = true;
     }
   }
 
-  return true;
+  return any_valid;
🤖 Prompt for AI Agents
In `@Integrations/ESPHome/custom_components/sen5x_number/sen5x_number.cpp` around
lines 89 - 148, read_data_() currently always returns true even when
parse_value() fails CRC for every sensor field; add a bool flag (e.g.,
any_published = false) at the start of the parsing block and set it to true
whenever you call publish_state on any of the members (pm_0_5_number_,
pm_1_0_number_, pm_2_5_number_, pm_4_0_number_, pm_10_0_number_,
typical_particle_size_); after processing all fields return any_published
instead of true so the method returns false when no values passed CRC and
nothing was published, leaving parse_value and publish_state calls unchanged.

Comment on lines +34 to +67
cv.Optional(CONF_PM_0_5_NUMBER): sensor.sensor_schema(
unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
icon=ICON_CHEMICAL_WEAPON,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM25,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PM_1_0_NUMBER): sensor.sensor_schema(
unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
icon=ICON_CHEMICAL_WEAPON,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM25,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PM_2_5_NUMBER): sensor.sensor_schema(
unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
icon=ICON_CHEMICAL_WEAPON,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM25,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PM_4_0_NUMBER): sensor.sensor_schema(
unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
icon=ICON_CHEMICAL_WEAPON,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM25,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_PM_10_0_NUMBER): sensor.sensor_schema(
unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
icon=ICON_CHEMICAL_WEAPON,
accuracy_decimals=0,
device_class=DEVICE_CLASS_PM25,
state_class=STATE_CLASS_MEASUREMENT,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cd Integrations/ESPHome/custom_components/sen5x_number && head -40 sensor.py

Repository: ApolloAutomation/TEMP_PROJECT-1

Length of output: 206


🏁 Script executed:

rg "DEVICE_CLASS|UNIT_NUMBER_PER_CUBIC_METER" Integrations/ESPHome/custom_components/sen5x_number/ -A 2 -B 2

Repository: ApolloAutomation/TEMP_PROJECT-1

Length of output: 4865


🌐 Web query:

Home Assistant sensor device_class PM25 number concentration #/m³``

💡 Result:

Home Assistant’s built‑in PM device_class (pm25 / SensorDeviceClass.PM25 or NumberDeviceClass.PM25) expects mass concentration in µg/m³ — it does not support number concentration in #/m³. Use a generic sensor (no device_class) or expose the reading with a unit you set (e.g. "#/m³") and keep device_class unset so HA won’t validate/overwrite the unit. (developers.home-assistant.io)

Citations:


Remove device_class=DEVICE_CLASS_PM25 from number-concentration sensors.

DEVICE_CLASS_PM25 is defined in Home Assistant for mass concentration (µg/m³) only. Using it with #/m³ (number concentration) violates Home Assistant's sensor specifications and will be rejected or misrepresented. Omit device_class for these five sensors.

🔧 Suggested change
             cv.Optional(CONF_PM_0_5_NUMBER): sensor.sensor_schema(
                 unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
                 icon=ICON_CHEMICAL_WEAPON,
                 accuracy_decimals=0,
-                device_class=DEVICE_CLASS_PM25,
                 state_class=STATE_CLASS_MEASUREMENT,
             ),
             cv.Optional(CONF_PM_1_0_NUMBER): sensor.sensor_schema(
                 unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
                 icon=ICON_CHEMICAL_WEAPON,
                 accuracy_decimals=0,
-                device_class=DEVICE_CLASS_PM25,
                 state_class=STATE_CLASS_MEASUREMENT,
             ),
             cv.Optional(CONF_PM_2_5_NUMBER): sensor.sensor_schema(
                 unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
                 icon=ICON_CHEMICAL_WEAPON,
                 accuracy_decimals=0,
-                device_class=DEVICE_CLASS_PM25,
                 state_class=STATE_CLASS_MEASUREMENT,
             ),
             cv.Optional(CONF_PM_4_0_NUMBER): sensor.sensor_schema(
                 unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
                 icon=ICON_CHEMICAL_WEAPON,
                 accuracy_decimals=0,
-                device_class=DEVICE_CLASS_PM25,
                 state_class=STATE_CLASS_MEASUREMENT,
             ),
             cv.Optional(CONF_PM_10_0_NUMBER): sensor.sensor_schema(
                 unit_of_measurement=UNIT_NUMBER_PER_CUBIC_METER,
                 icon=ICON_CHEMICAL_WEAPON,
                 accuracy_decimals=0,
-                device_class=DEVICE_CLASS_PM25,
                 state_class=STATE_CLASS_MEASUREMENT,
             ),
🤖 Prompt for AI Agents
In `@Integrations/ESPHome/custom_components/sen5x_number/sensor.py` around lines
34 - 67, Remove the inappropriate device_class from the number-concentration
sensor schemas: in sensor.py update the sensor.sensor_schema calls for
CONF_PM_0_5_NUMBER, CONF_PM_1_0_NUMBER, CONF_PM_2_5_NUMBER, CONF_PM_4_0_NUMBER
and CONF_PM_10_0_NUMBER to omit device_class=DEVICE_CLASS_PM25 (leave
unit_of_measurement, icon, accuracy_decimals and state_class intact); this
ensures the number sensors use no device_class as required by Home Assistant
specs.

@Bunton33 Bunton33 merged commit 88ee7fd into main Jan 22, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants