From 5e3b681308a3b44e9308f69be0fe0bf6b3e88195 Mon Sep 17 00:00:00 2001 From: John Dillenburg Date: Sun, 2 Mar 2025 20:07:36 -0600 Subject: [PATCH] Added ADXL345 external component --- README.md | 1 + components/adxl345/LICENSE | 21 ++++++ components/adxl345/README.md | 129 +++++++++++++++++++++++++++++++++ components/adxl345/__init__.py | 93 ++++++++++++++++++++++++ components/adxl345/adxl345.cpp | 109 ++++++++++++++++++++++++++++ components/adxl345/adxl345.h | 40 ++++++++++ components/adxl345/sensor.py | 4 + components/tfmini/__init__.py | 2 + garage-door-controller.yaml | 2 +- 9 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 components/adxl345/LICENSE create mode 100644 components/adxl345/README.md create mode 100644 components/adxl345/__init__.py create mode 100644 components/adxl345/adxl345.cpp create mode 100644 components/adxl345/adxl345.h create mode 100644 components/adxl345/sensor.py diff --git a/README.md b/README.md index 2466322..c991d56 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Welcome to my ESPhome configuration directory. This repository contains the ESPh | Component | Description | |-----------|-------------| | [TFmini](components/tfmini) | External component for the TFmini-S time of flight distance sensor with distance, signal strength, and temperature readings | +| [Adxl345](components/adxl345) | External component for the ADXL345 accelerometer. Measures acceleration along x, y and z axes. | ## Devices diff --git a/components/adxl345/LICENSE b/components/adxl345/LICENSE new file mode 100644 index 0000000..f7e6893 --- /dev/null +++ b/components/adxl345/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Your Name + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/components/adxl345/README.md b/components/adxl345/README.md new file mode 100644 index 0000000..a3f72cb --- /dev/null +++ b/components/adxl345/README.md @@ -0,0 +1,129 @@ +# ADXL345 Component for ESPHome + +This is a custom component for ESPHome that interfaces with the ADXL345 3-axis accelerometer. + +## Features + +- Supports all standard I2C configuration options including multiple I2C buses +- Configurable measurement range (2G, 4G, 8G, 16G) +- Provides raw acceleration values for X, Y, and Z axes +- Calculates off-vertical angle (useful for bed/chair position tracking) +- Calculates jitter (total movement across all axes) +- Full Home Assistant integration with proper device classes and units + +## Installation + +1. Create a `components` directory in your ESPHome configuration directory if it doesn't exist +2. Create an `adxl345` directory inside the `components` directory +3. Copy the following files into the `adxl345` directory: + - `__init__.py` + - `adxl345.h` + - `adxl345.cpp` + - `sensor.py` + +## Dependencies + +This component requires the following libraries: +- Wire +- SPI +- Adafruit BusIO +- Adafruit Unified Sensor +- Adafruit ADXL345 + +Add these to your ESPHome configuration: + +```yaml +libraries: + - "Wire" + - "SPI" + - "Adafruit BusIO" + - "Adafruit Unified Sensor" + - "Adafruit ADXL345" +``` + +## Usage + +### Basic Configuration + +```yaml +# Define component +adxl345: + - id: my_adxl345 + address: 0x53 + update_interval: 100ms + +# Define sensors from component +sensor: + - platform: adxl345 + id: my_adxl345 + accel_x: + name: "Acceleration X" + accel_y: + name: "Acceleration Y" + accel_z: + name: "Acceleration Z" +``` + +### Advanced Configuration + +```yaml +# Define I2C bus +i2c: + sda: GPIO21 + scl: GPIO22 + frequency: 400kHz + +# Define component with all options +adxl345: + - id: my_adxl345 + address: 0x53 + update_interval: 100ms + range: 4G # Options: 2G, 4G, 8G, 16G + +# Define sensors with filtering +sensor: + - platform: adxl345 + id: my_adxl345 + accel_x: + name: "Acceleration X" + filters: + - median: + window_size: 5 + send_every: 5 + send_first_at: 1 + accel_y: + name: "Acceleration Y" + accel_z: + name: "Acceleration Z" + off_vertical: + name: "Tilt Angle" + filters: + - sliding_window_moving_average: + window_size: 10 + send_every: 5 + jitter: + name: "Movement Detection" + filters: + - threshold: + above: 10.0 + then: ON + below: 2.0 + then: OFF +``` + +## Available Sensors + +| Sensor | Description | Unit | +|--------|-------------|------| +| `accel_x` | Raw X-axis acceleration | m/s² | +| `accel_y` | Raw Y-axis acceleration | m/s² | +| `accel_z` | Raw Z-axis acceleration | m/s² | +| `off_vertical` | Maximum angle from vertical (derived from pitch and roll) | degrees | +| `jitter` | Sum of absolute accelerations across all axes (movement detection) | m/s² | + +## Example Use Cases + +- Bed/chair incline monitoring +- Movement detection +- Vibration monitoring +- Orientation sensing \ No newline at end of file diff --git a/components/adxl345/__init__.py b/components/adxl345/__init__.py new file mode 100644 index 0000000..c64792a --- /dev/null +++ b/components/adxl345/__init__.py @@ -0,0 +1,93 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID, ICON_RULER, STATE_CLASS_MEASUREMENT +from esphome.components import i2c, sensor + +DEPENDENCIES = ['i2c'] +AUTO_LOAD = ['sensor'] +MULTI_CONF = True +CODEOWNERS = ["@jdillenburg"] + +adxl345_ns = cg.esphome_ns.namespace('adxl345') +ADXL345Component = adxl345_ns.class_('ADXL345Component', cg.PollingComponent, i2c.I2CDevice) + +CONF_RANGE = "range" +RANGE_2G = 0 +RANGE_4G = 1 +RANGE_8G = 2 +RANGE_16G = 3 + +CONF_OFF_VERTICAL = "off_vertical" +CONF_JITTER = "jitter" +CONF_ACCEL_X = "accel_x" +CONF_ACCEL_Y = "accel_y" +CONF_ACCEL_Z = "accel_z" + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(ADXL345Component), + cv.Optional(CONF_RANGE, default="2G"): cv.enum({ + "2G": RANGE_2G, + "4G": RANGE_4G, + "8G": RANGE_8G, + "16G": RANGE_16G, + }, upper=True), + cv.Optional(CONF_OFF_VERTICAL): sensor.sensor_schema( + unit_of_measurement="°", + icon=ICON_RULER, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_JITTER): sensor.sensor_schema( + unit_of_measurement="m/s²", + icon="mdi:vibrate", + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACCEL_X): sensor.sensor_schema( + unit_of_measurement="m/s²", + icon="mdi:axis-x-arrow", + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACCEL_Y): sensor.sensor_schema( + unit_of_measurement="m/s²", + icon="mdi:axis-y-arrow", + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACCEL_Z): sensor.sensor_schema( + unit_of_measurement="m/s²", + icon="mdi:axis-z-arrow", + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, + ), +}).extend(cv.polling_component_schema("100ms")).extend(i2c.i2c_device_schema(0x53)) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + # Configure the range + cg.add(var.set_range(config[CONF_RANGE])) + + # Register sensors if configured + if CONF_OFF_VERTICAL in config: + sens = await sensor.new_sensor(config[CONF_OFF_VERTICAL]) + cg.add(var.set_off_vertical_sensor(sens)) + + if CONF_JITTER in config: + sens = await sensor.new_sensor(config[CONF_JITTER]) + cg.add(var.set_jitter_sensor(sens)) + + if CONF_ACCEL_X in config: + sens = await sensor.new_sensor(config[CONF_ACCEL_X]) + cg.add(var.set_accel_x_sensor(sens)) + + if CONF_ACCEL_Y in config: + sens = await sensor.new_sensor(config[CONF_ACCEL_Y]) + cg.add(var.set_accel_y_sensor(sens)) + + if CONF_ACCEL_Z in config: + sens = await sensor.new_sensor(config[CONF_ACCEL_Z]) + cg.add(var.set_accel_z_sensor(sens)) \ No newline at end of file diff --git a/components/adxl345/adxl345.cpp b/components/adxl345/adxl345.cpp new file mode 100644 index 0000000..c9612ae --- /dev/null +++ b/components/adxl345/adxl345.cpp @@ -0,0 +1,109 @@ +#include "adxl345.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace adxl345 { + +static const char *const TAG = "adxl345"; + +void ADXL345Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up ADXL345..."); + + if (!accel_.begin(this->address_)) { + ESP_LOGE(TAG, "Could not find ADXL345 sensor at address 0x%02X!", this->address_); + this->mark_failed(); + return; + } + + // Map our range enum values to Adafruit library constants + range_t adafruit_range; + switch (this->range_) { + case 0: // RANGE_2G + adafruit_range = ADXL345_RANGE_2_G; + break; + case 1: // RANGE_4G + adafruit_range = ADXL345_RANGE_4_G; + break; + case 2: // RANGE_8G + adafruit_range = ADXL345_RANGE_8_G; + break; + case 3: // RANGE_16G + adafruit_range = ADXL345_RANGE_16_G; + break; + default: + adafruit_range = ADXL345_RANGE_2_G; + break; + } + + accel_.setRange(adafruit_range); + ESP_LOGD(TAG, "ADXL345 setup complete"); +} + +void ADXL345Component::dump_config() { + ESP_LOGCONFIG(TAG, "ADXL345:"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + + const char* range_str; + switch (this->range_) { + case 0: range_str = "2G"; break; + case 1: range_str = "4G"; break; + case 2: range_str = "8G"; break; + case 3: range_str = "16G"; break; + default: range_str = "Unknown"; break; + } + ESP_LOGCONFIG(TAG, " Range: %s", range_str); + + if (this->off_vertical_ != nullptr) { + LOG_SENSOR(" ", "Off Vertical", this->off_vertical_); + } + if (this->jitter_ != nullptr) { + LOG_SENSOR(" ", "Jitter", this->jitter_); + } + if (this->accel_x_ != nullptr) { + LOG_SENSOR(" ", "Acceleration X", this->accel_x_); + } + if (this->accel_y_ != nullptr) { + LOG_SENSOR(" ", "Acceleration Y", this->accel_y_); + } + if (this->accel_z_ != nullptr) { + LOG_SENSOR(" ", "Acceleration Z", this->accel_z_); + } +} + +void ADXL345Component::update() { + sensors_event_t event; + accel_.getEvent(&event); + + // Publish raw accelerometer values if sensors are configured + if (this->accel_x_ != nullptr) { + this->accel_x_->publish_state(event.acceleration.x); + } + + if (this->accel_y_ != nullptr) { + this->accel_y_->publish_state(event.acceleration.y); + } + + if (this->accel_z_ != nullptr) { + this->accel_z_->publish_state(event.acceleration.z); + } + + // Calculate and publish off_vertical if sensor is configured + if (this->off_vertical_ != nullptr) { + double pitch_amount = atan(event.acceleration.y / + sqrt(pow(event.acceleration.x, 2) + pow(event.acceleration.z, 2))) * 180 / PI; + double roll_amount = atan(-1 * event.acceleration.x / + sqrt(pow(event.acceleration.y, 2) + pow(event.acceleration.z, 2))) * 180 / PI; + + this->off_vertical_->publish_state(max(abs(pitch_amount), abs(roll_amount))); + } + + // Calculate and publish jitter if sensor is configured + if (this->jitter_ != nullptr) { + float jitter_value = abs(event.acceleration.x) + abs(event.acceleration.y) + abs(event.acceleration.z); + this->jitter_->publish_state(jitter_value); + } +} + +} // namespace adxl345 +} // namespace esphome \ No newline at end of file diff --git a/components/adxl345/adxl345.h b/components/adxl345/adxl345.h new file mode 100644 index 0000000..eadc93f --- /dev/null +++ b/components/adxl345/adxl345.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include +#include +#include + +namespace esphome { +namespace adxl345 { + +class ADXL345Component : public PollingComponent, public i2c::I2CDevice { + public: + ADXL345Component() : PollingComponent(100) {} + + void setup() override; + void update() override; + void dump_config() override; + + void set_off_vertical_sensor(sensor::Sensor *off_vertical) { off_vertical_ = off_vertical; } + void set_jitter_sensor(sensor::Sensor *jitter) { jitter_ = jitter; } + void set_accel_x_sensor(sensor::Sensor *accel_x) { accel_x_ = accel_x; } + void set_accel_y_sensor(sensor::Sensor *accel_y) { accel_y_ = accel_y; } + void set_accel_z_sensor(sensor::Sensor *accel_z) { accel_z_ = accel_z; } + + void set_range(uint8_t range) { range_ = range; } + + protected: + Adafruit_ADXL345_Unified accel_{12345}; + sensor::Sensor *off_vertical_{nullptr}; + sensor::Sensor *jitter_{nullptr}; + sensor::Sensor *accel_x_{nullptr}; + sensor::Sensor *accel_y_{nullptr}; + sensor::Sensor *accel_z_{nullptr}; + uint8_t range_{0}; // Default to 2G range (0) +}; + +} // namespace adxl345 +} // namespace esphome \ No newline at end of file diff --git a/components/adxl345/sensor.py b/components/adxl345/sensor.py new file mode 100644 index 0000000..093b8de --- /dev/null +++ b/components/adxl345/sensor.py @@ -0,0 +1,4 @@ +# Empty sensor.py file +# All functionality has been moved to __init__.py + +from . import CONF_OFF_VERTICAL, CONF_JITTER, CONF_ACCEL_X, CONF_ACCEL_Y, CONF_ACCEL_Z \ No newline at end of file diff --git a/components/tfmini/__init__.py b/components/tfmini/__init__.py index 1525f88..2d9ab9b 100644 --- a/components/tfmini/__init__.py +++ b/components/tfmini/__init__.py @@ -4,6 +4,8 @@ from esphome.const import CONF_ID DEPENDENCIES = ['uart'] AUTO_LOAD = ['sensor'] +CODEOWNERS = ["@jdillenburg"] + tfmini_ns = cg.esphome_ns.namespace('tfmini') TFMiniComponent = tfmini_ns.class_('TFMiniComponent', cg.Component) diff --git a/garage-door-controller.yaml b/garage-door-controller.yaml index bb47843..e3a38f1 100644 --- a/garage-door-controller.yaml +++ b/garage-door-controller.yaml @@ -7,7 +7,7 @@ packages: wifi: !include { file: packages/wifi.yaml, vars: { ssid: "garage-door-controller" }} external_components: - - source: github://jdillenburg/esphome@master + - source: github://jdillenburg/esphome@main components: [ tfmini ] substitutions: