Converted tfmini custom component into an external component

This commit is contained in:
John Dillenburg
2025-03-01 13:14:31 -06:00
parent abfc6939dd
commit 177179d8a4
11 changed files with 416 additions and 178 deletions

View File

@@ -7,6 +7,7 @@ class ADXL345Sensor : public PollingComponent {
public:
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);
Sensor *off_vertical = new Sensor();
Sensor *jitter = new Sensor();
ADXL345Sensor() : PollingComponent(100) { }
@@ -23,8 +24,16 @@ class ADXL345Sensor : public PollingComponent {
void update() override {
sensors_event_t event;
accel.getEvent(&event);
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;
off_vertical->publish_state(max(abs(pitch_amount), abs(roll_amount)));
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;
// double pitch_amount = atan2(event.acceleration.y,
// sqrt(pow(event.acceleration.x, 2) + pow(event.acceleration.z, 2))) * 180 / PI;
// double roll_amount = atan2(-1 * event.acceleration.x,
// sqrt(pow(event.acceleration.y, 2) + pow(event.acceleration.z, 2))) * 180 / PI;
off_vertical -> publish_state(max(abs(pitch_amount), abs(roll_amount)));
jitter -> publish_state(abs(event.acceleration.x) + abs(event.acceleration.y) +
abs(event.acceleration.z));
}
};

View File

@@ -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.

View File

@@ -0,0 +1,58 @@
# TFMini-S Time of Flight Distance Sensor for ESPHome
This is an external component for ESPHome that supports the TFMini-S time of flight distance sensor.
## Installation
You can install this component in two ways:
### 1. Using ESPHome external components
```yaml
external_components:
- source: github://your-username/esphome-tfmini@main
components: [ tfmini ]
```
### 2. Manual installation
Clone this repository into your ESPHome `custom_components` directory:
```bash
cd <your config directory>/custom_components
git clone https://github.com/your-username/esphome-tfmini.git
```
## Configuration
Add the following to your ESPHome configuration file:
```yaml
# Example configuration
uart:
tx_pin: GPIO17
rx_pin: GPIO16
baud_rate: 115200
sensor:
- platform: tfmini
name: "Distance Sensor"
update_interval: 1s
```
## Features
- Measures distance in meters
- Automatic configuration of the TFMini-S sensor
- Signal strength validation
- Temperature monitoring (internal, not exposed yet)
## Notes
- The sensor is configured to use the standard 9-byte output format
- Refresh rate is set to 100Hz
- Data is provided in meters with 2 decimal precision
## License
This component is licensed under the MIT License.

View File

@@ -0,0 +1,17 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
DEPENDENCIES = ['uart']
AUTO_LOAD = ['sensor']
tfmini_ns = cg.esphome_ns.namespace('tfmini')
TFMiniComponent = tfmini_ns.class_('TFMiniComponent', cg.Component)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(TFMiniComponent)
}).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,76 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import (
CONF_ID,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_METER,
UNIT_CENTIMETER,
UNIT_CELSIUS,
ICON_RULER,
ENTITY_CATEGORY_DIAGNOSTIC,
)
DEPENDENCIES = ['uart']
tfmini_ns = cg.esphome_ns.namespace('tfmini')
TFMiniSensor = tfmini_ns.class_('TFMiniSensor', sensor.Sensor, cg.Component,
uart.UARTDevice)
CONF_STRENGTH = "strength"
CONF_TEMPERATURE = "temperature"
CONF_DISTANCE_UNIT = "distance_unit"
CONFIG_SCHEMA = (
sensor.sensor_schema(
TFMiniSensor,
unit_of_measurement=UNIT_CENTIMETER, # Default to cm instead of meters
accuracy_decimals=0, # Integer precision for cm
device_class=DEVICE_CLASS_DISTANCE,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_RULER,
)
.extend({
cv.Optional(CONF_DISTANCE_UNIT, default="cm"): cv.enum({
"cm": "CENTIMETERS",
"m": "METERS",
}),
cv.Optional(CONF_STRENGTH): sensor.sensor_schema(
unit_of_measurement="",
accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
})
.extend(uart.UART_DEVICE_SCHEMA)
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
cg.add(var.set_distance_unit(config[CONF_DISTANCE_UNIT]))
if config[CONF_DISTANCE_UNIT] == "m":
cg.add(var.set_unit_of_measurement(UNIT_METER))
cg.add(var.set_accuracy_decimals(2))
if CONF_STRENGTH in config:
sens = await sensor.new_sensor(config[CONF_STRENGTH])
cg.add(var.set_strength_sensor(sens))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature_sensor(sens))

View File

@@ -0,0 +1,112 @@
#include "tfmini.h"
#include <esphome/core/log.h>
namespace esphome {
namespace tfmini {
static const char *const TAG = "tfmini";
static const uint8_t HEADER = 0x59; // frame header of data package
void TFMiniSensor::setup() {
this->set_timeout(50, [this]() { this->setup_internal_(); });
}
void TFMiniSensor::setup_internal_() {
// Configure standard 9-byte output format (cm)
const uint8_t standard_mode[5] = {0x5A, 0x05, 0x05, 0x01, 0x65};
this->write_array(standard_mode, 5);
delay(100);
// Set refresh rate to 100Hz (1000/0x0A = 100)
const uint8_t rate100[6] = {0x5A, 0x06, 0x03, 0x0A, 0x00, 0x6D};
this->write_array(rate100, 6);
delay(100);
// Save the settings
const uint8_t save_setting[4] = {0x5A, 0x04, 0x11, 0x6F};
this->write_array(save_setting, 4);
// Wait for the sensor to come back online
delay(1000);
this->flush();
ESP_LOGD(TAG, "TFMini setup completed");
}
void TFMiniSensor::loop() {
while (this->available() >= 9) {
if (this->read() != HEADER) {
continue;
}
if (this->read() != HEADER) {
continue;
}
uint8_t data[7];
if (!this->read_array(data, 7)) {
continue;
}
// Verify checksum
uint8_t checksum = HEADER + HEADER;
for (size_t i = 0; i < 6; i++) {
checksum += data[i];
}
if (checksum != data[6]) {
ESP_LOGW(TAG, "TFMini checksum error");
continue;
}
// Extract distance data (first two bytes)
uint16_t distance_cm = data[0] | (data[1] << 8);
// Extract signal strength (next two bytes)
uint16_t strength = data[2] | (data[3] << 8);
// Extract temperature (next two bytes)
float temperature = (data[4] | (data[5] << 8)) / 8.0f - 256.0f;
// Only publish valid readings
if (strength < 100) {
ESP_LOGW(TAG, "TFMini signal strength too low: %u", strength);
continue;
}
// Convert to requested unit
float distance_value;
if (this->distance_unit_ == METERS) {
distance_value = distance_cm / 100.0f;
ESP_LOGD(TAG, "Distance: %.2f m, Strength: %u, Temperature: %.1f °C",
distance_value, strength, temperature);
} else {
distance_value = distance_cm;
ESP_LOGD(TAG, "Distance: %d cm, Strength: %u, Temperature: %.1f °C",
distance_cm, strength, temperature);
}
// Publish all values
this->publish_state(distance_value);
if (this->strength_sensor_ != nullptr) {
this->strength_sensor_->publish_state(strength);
}
if (this->temperature_sensor_ != nullptr) {
this->temperature_sensor_->publish_state(temperature);
}
return;
}
}
void TFMiniSensor::dump_config() {
ESP_LOGCONFIG(TAG, "TFMini:");
LOG_SENSOR(" ", "Distance", this);
ESP_LOGCONFIG(TAG, " Distance Unit: %s", this->distance_unit_ == METERS ? "meters" : "centimeters");
LOG_SENSOR(" ", "Signal Strength", this->strength_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
this->check_uart_settings(115200);
}
} // namespace tfmini
} // namespace esphome

View File

@@ -0,0 +1,41 @@
#pragma once
#include <esphome/core/component.h>
#include <esphome/components/sensor/sensor.h>
#include <esphome/components/uart/uart.h>
namespace esphome {
namespace tfmini {
enum DistanceUnit {
CENTIMETERS,
METERS
};
class TFMiniSensor : public sensor::Sensor, public Component, public uart::UARTDevice {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
void set_strength_sensor(sensor::Sensor *strength_sensor) { strength_sensor_ = strength_sensor; }
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_distance_unit(const std::string &distance_unit) {
if (distance_unit == "METERS") {
this->distance_unit_ = METERS;
} else {
this->distance_unit_ = CENTIMETERS;
}
}
protected:
void setup_internal_();
sensor::Sensor *strength_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
DistanceUnit distance_unit_{CENTIMETERS};
};
} // namespace tfmini
} // namespace esphome

View File

@@ -1,13 +1,15 @@
esphome:
name: garage-door-controller
friendly_name: Garage door controller
includes:
- TFmini.h
packages:
beacon: !include { file: packages/beacon.yaml }
wifi: !include { file: packages/wifi.yaml, vars: { ssid: "garage-door-controller" }}
external_components:
- source: external_components/esphome-tfmini
components: [ tfmini ]
substitutions:
led_count: "60"
@@ -15,6 +17,7 @@ esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
@@ -50,9 +53,10 @@ switch:
restore_mode: ALWAYS_OFF
on_turn_on:
then:
- delay: 100ms
- delay: 30000ms
- switch.turn_off: relay
# configurable distance thresholds
number:
- platform: template
@@ -129,29 +133,25 @@ binary_sensor:
#******************** Sensor *********************
sensor:
- platform: custom
lambda: |-
auto distance_sensor = new TFminiSensor(id(uart_bus));
App.register_component(distance_sensor);
return {distance_sensor};
sensors:
id: distance_sensor_raw
internal: True
name: "Distance sensor raw"
unit_of_measurement: "cm"
on_value:
then:
- sensor.template.publish:
id: distance_sensor
state: !lambda 'return x;'
- sensor.template.publish:
id: distance_sensor_leds
state: !lambda 'return x;'
- lambda: |-
id(sensor_counter) = id(sensor_counter) + 1;
# - sensor.template.publish:
# id: reading_sensor_counter
# state: !lambda 'return id(reading_counter).state + 1;'
- platform: tfmini
id: distance_sensor_raw
internal: True
distance_unit: cm
name: "Distance sensor raw"
unit_of_measurement: "cm"
on_value:
then:
- sensor.template.publish:
id: distance_sensor
state: !lambda 'return x;'
- sensor.template.publish:
id: distance_sensor_leds
state: !lambda 'return x;'
- lambda: |-
id(sensor_counter) = id(sensor_counter) + 1;
# - sensor.template.publish:
# id: reading_sensor_counter
# state: !lambda 'return id(reading_counter).state + 1;'
- platform: template
name: "Distance sensor"
id: distance_sensor
@@ -291,6 +291,13 @@ light:
// Measure update rate
id(effect_counter) = id(effect_counter) + 1;
// Set safe_zone flag based on distance
if (distance >= min_safe && distance <= max_safe) {
id(safe_zone) = true;
} else {
id(safe_zone) = false;
}
// blink if less than min_safe
if (distance < min_safe) {
if ((millis() % 1000) < 500) {
@@ -305,7 +312,6 @@ light:
// solid red in safe zone
if (distance < max_safe) {
it.all() = Color(255,0,0);
id(safe_zone) = true;
return;
}

View File

@@ -14,5 +14,3 @@ time:
bluetooth_proxy:
esp32_ble_tracker:

View File

@@ -1,6 +1,5 @@
esphome:
includes:
- leapmmw_sensor.h
comment: Sen0395
# Disable uart logging
logger:
@@ -8,7 +7,7 @@ logger:
level: DEBUG
uart:
id: SEN095_UART_BUS
id: SEN0395_UART_BUS
rx_pin: ${uart_rx_pin}
tx_pin: ${uart_tx_pin}
baud_rate: 115200
@@ -17,6 +16,7 @@ uart:
parity: NONE
dfrobot_sen0395:
- uart_id: SEN0395_UART_BUS
binary_sensor:
- platform: gpio
@@ -26,139 +26,50 @@ binary_sensor:
pin:
number: ${presence_pin}
mode: INPUT_PULLDOWN
# when motion is detected, the radar sensor is on so publish radar state to HA
on_press:
then:
lambda: !lambda |-
if (!id(mmwave_sensor).state) {
id(mmwave_sensor).publish_state(true);
}
sensor:
- platform: custom
lambda: |-
auto s = new leapmmw(id(SEN095_UART_BUS));
App.register_component(s);
return {};
sensors:
switch:
- platform: template
name: "${device_name_pretty} mmwave_sensor"
id: mmwave_sensor # do not change
entity_category: config
optimistic: true
turn_on_action:
- uart.write: "setUartOutput 1 0"
- delay: 1s
- uart.write: "saveConfig"
- delay: 4s
- uart.write: "sensorStart"
turn_off_action:
- uart.write: "sensorStop"
- delay: 2s
- platform: template
name: "${device_name_pretty} led"
id: led # do not change
entity_category: config
optimistic: true
turn_on_action:
- switch.turn_off: mmwave_sensor
- delay: 2s
- uart.write: "setLedMode 1 0"
- delay: 3s
- lambda: |-
leapmmw(id(SEN095_UART_BUS)).getmmwConf("getLedMode 1");
- delay: 2s
- switch.turn_on: mmwave_sensor
turn_off_action:
- switch.turn_off: mmwave_sensor
- delay: 2s
- uart.write: "setLedMode 1 1"
- delay: 3s
- lambda: |-
leapmmw(id(SEN095_UART_BUS)).getmmwConf("getLedMode 1");
- delay: 2s
- switch.turn_on: mmwave_sensor
number:
- platform: template
name: "${device_name_pretty} distance"
id: distance # do not change
entity_category: config
min_value: 0.15
max_value: 9.45
step: 0.15
unit_of_measurement: M
mode: box
lambda: |-
leapmmw(id(SEN095_UART_BUS)).getmmwConf("getRange");
return {};
set_action:
- switch.turn_off: mmwave_sensor
- delay: 2s
- uart.write: !lambda
std::string range = "setRange 0 " + str_sprintf("%.2f", x);
return std::vector<unsigned char>(range.begin(), range.end());
- delay: 3s
- switch.turn_on: mmwave_sensor
# - platform: template
# name: "${device_name_pretty} distance"
# id: distance # do not change
# entity_category: config
# min_value: 0.15
# max_value: 9.45
# step: 0.15
# unit_of_measurement: M
# mode: box
# lambda: 'return {};'
# set_action:
# - dfrobot_sen0395.settings:
# detection_segments:
# - !lambda |-
# return 0;
# - !lambda |-
# return id(distance).state;
- platform: template
name: "${device_name_pretty} latency"
id: latency # do not change
entity_category: config
min_value: 1
max_value: 600
lambda: |-
leapmmw(id(SEN095_UART_BUS)).getmmwConf("getLatency");
return {};
step: 1
unit_of_measurement: s
mode: box
set_action:
- switch.turn_off: mmwave_sensor
- delay: 2s
- uart.write: !lambda
std::string setL = "setLatency 0.1 " + str_sprintf("%.0f", x);
return std::vector<unsigned char>(setL.begin(), setL.end());
- delay: 3s
- switch.turn_on: mmwave_sensor
# - platform: template
# name: "${device_name_pretty} latency"
# id: latency # do not change
# entity_category: config
# min_value: 1
# max_value: 600
# step: 1
# unit_of_measurement: s
# mode: box
# lambda: 'return {};'
# set_action:
# - dfrobot_sen0395.settings:
# output_latency:
# delay_after_detect: 0s
# delay_after_disappear: !lambda 'return id(latency).state * 1.0f;'
- platform: template
name: "${device_name_pretty} sensitivity"
id: sensitivity # do not change
entity_category: config
min_value: 0
max_value: 9
lambda: |-
leapmmw(id(SEN095_UART_BUS)).getmmwConf("getSensitivity");
return {};
step: 1
set_action:
- switch.turn_off: mmwave_sensor
- delay: 2s
- uart.write: !lambda
std::string mss = "setSensitivity " + to_string((int)x);
return std::vector<unsigned char>(mss.begin(), mss.end());
- delay: 3s
- switch.turn_on: mmwave_sensor
button:
- platform: restart
name: Restart_ESP_${device_name}
entity_category: diagnostic
on_press:
- uart.write:
id: SEN095_UART_BUS
data: "resetSystem 0"
- platform: template
name: factory_reset_mmwMCU_${device_name}
id: factory_reset_mmwMCU
entity_category: diagnostic
on_press:
- switch.turn_off: mmwave_sensor
- delay: 2s
- uart.write: "resetCfg"
- delay: 3s
# - platform: template
# name: "${device_name_pretty} sensitivity"
# id: sensitivity # do not change
# entity_category: config
# min_value: 0
# max_value: 9
# step: 1
# lambda: 'return {};'
# set_action:
# - dfrobot_sen0395.settings:
# sensitivity: !lambda 'return (int32_t)id(sensitivity).state;'

View File

@@ -4,14 +4,3 @@ wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "${ssid}"
password: !secret ap_and_web_server_password
web_server:
auth:
username: johndillenburg
password: !secret ap_and_web_server_password
captive_portal: