mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +01:00
feat: add AS5600 component/sensor (#5174)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
26277e4ba2
commit
991880d53f
8 changed files with 755 additions and 0 deletions
|
@ -34,6 +34,8 @@ esphome/components/analog_threshold/* @ianchi
|
||||||
esphome/components/animation/* @syndlex
|
esphome/components/animation/* @syndlex
|
||||||
esphome/components/anova/* @buxtronix
|
esphome/components/anova/* @buxtronix
|
||||||
esphome/components/api/* @OttoWinter
|
esphome/components/api/* @OttoWinter
|
||||||
|
esphome/components/as5600/* @ammmze
|
||||||
|
esphome/components/as5600/sensor/* @ammmze
|
||||||
esphome/components/as7341/* @mrgnr
|
esphome/components/as7341/* @mrgnr
|
||||||
esphome/components/async_tcp/* @OttoWinter
|
esphome/components/async_tcp/* @OttoWinter
|
||||||
esphome/components/atc_mithermometer/* @ahpohl
|
esphome/components/atc_mithermometer/* @ahpohl
|
||||||
|
|
228
esphome/components/as5600/__init__.py
Normal file
228
esphome/components/as5600/__init__.py
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
from esphome import pins
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_DIR_PIN,
|
||||||
|
CONF_DIRECTION,
|
||||||
|
CONF_HYSTERESIS,
|
||||||
|
CONF_RANGE,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@ammmze"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
as5600_ns = cg.esphome_ns.namespace("as5600")
|
||||||
|
AS5600Component = as5600_ns.class_("AS5600Component", cg.Component, i2c.I2CDevice)
|
||||||
|
|
||||||
|
DIRECTION = {
|
||||||
|
"CLOCKWISE": 0,
|
||||||
|
"COUNTERCLOCKWISE": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
POWER_MODE = {
|
||||||
|
"NOMINAL": 0,
|
||||||
|
"LOW1": 1,
|
||||||
|
"LOW2": 2,
|
||||||
|
"LOW3": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
HYSTERESIS = {
|
||||||
|
"NONE": 0,
|
||||||
|
"LSB1": 1,
|
||||||
|
"LSB2": 2,
|
||||||
|
"LSB3": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
SLOW_FILTER = {
|
||||||
|
"16X": 0,
|
||||||
|
"8X": 1,
|
||||||
|
"4X": 2,
|
||||||
|
"2X": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
FAST_FILTER = {
|
||||||
|
"NONE": 0,
|
||||||
|
"LSB6": 1,
|
||||||
|
"LSB7": 2,
|
||||||
|
"LSB9": 3,
|
||||||
|
"LSB18": 4,
|
||||||
|
"LSB21": 5,
|
||||||
|
"LSB24": 6,
|
||||||
|
"LSB10": 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONF_ANGLE = "angle"
|
||||||
|
CONF_RAW_ANGLE = "raw_angle"
|
||||||
|
CONF_RAW_POSITION = "raw_position"
|
||||||
|
CONF_WATCHDOG = "watchdog"
|
||||||
|
CONF_POWER_MODE = "power_mode"
|
||||||
|
CONF_SLOW_FILTER = "slow_filter"
|
||||||
|
CONF_FAST_FILTER = "fast_filter"
|
||||||
|
CONF_START_POSITION = "start_position"
|
||||||
|
CONF_END_POSITION = "end_position"
|
||||||
|
|
||||||
|
|
||||||
|
RESOLUTION = 4096
|
||||||
|
MAX_POSITION = RESOLUTION - 1
|
||||||
|
ANGLE_TO_POSITION = RESOLUTION / 360
|
||||||
|
POSITION_TO_ANGLE = 360 / RESOLUTION
|
||||||
|
# validate min range of 18deg (per datasheet) ... though i seem to get valid values down to a range of 192steps (16.875deg)
|
||||||
|
MIN_RANGE = round(18 * ANGLE_TO_POSITION)
|
||||||
|
|
||||||
|
|
||||||
|
def angle(min=-360, max=360):
|
||||||
|
return cv.All(
|
||||||
|
cv.float_with_unit("angle", "(°|deg)"), cv.float_range(min=min, max=max)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def angle_to_position(value, min=-360, max=360):
|
||||||
|
try:
|
||||||
|
value = angle(min=min, max=max)(value)
|
||||||
|
return (RESOLUTION + round(value * ANGLE_TO_POSITION)) % RESOLUTION
|
||||||
|
except cv.Invalid as e:
|
||||||
|
raise cv.Invalid(f"When using angle, {e.error_message}")
|
||||||
|
|
||||||
|
|
||||||
|
def percent_to_position(value):
|
||||||
|
value = cv.possibly_negative_percentage(value)
|
||||||
|
return (RESOLUTION + round(value * RESOLUTION)) % RESOLUTION
|
||||||
|
|
||||||
|
|
||||||
|
def position(min=-MAX_POSITION, max=MAX_POSITION):
|
||||||
|
"""Validate that the config option is a position.
|
||||||
|
Accepts integers, degrees, or percentage (of 360 degrees).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def validator(value):
|
||||||
|
if isinstance(value, str) and value.endswith("%"):
|
||||||
|
value = percent_to_position(value)
|
||||||
|
|
||||||
|
if isinstance(value, str) and (value.endswith("°") or value.endswith("deg")):
|
||||||
|
return angle_to_position(
|
||||||
|
value,
|
||||||
|
min=round(min * POSITION_TO_ANGLE),
|
||||||
|
max=round(max * POSITION_TO_ANGLE),
|
||||||
|
)
|
||||||
|
|
||||||
|
return cv.int_range(min=min, max=max)(value)
|
||||||
|
|
||||||
|
return validator
|
||||||
|
|
||||||
|
|
||||||
|
def position_range():
|
||||||
|
"""Validate that value given is a valid range for the device.
|
||||||
|
A valid range is one of the following:
|
||||||
|
- a value of 0 (meaning full range)
|
||||||
|
- 18 thru 360 degrees
|
||||||
|
- negative 360 thru negative 18 degrees (notes: these are normalized to their positive values, accepting negatives is for convenience)
|
||||||
|
"""
|
||||||
|
zero_validator = position(min=0, max=0)
|
||||||
|
negative_validator = cv.Any(
|
||||||
|
position(min=-MAX_POSITION, max=-MIN_RANGE),
|
||||||
|
zero_validator,
|
||||||
|
)
|
||||||
|
positive_validator = cv.Any(
|
||||||
|
position(min=MIN_RANGE, max=MAX_POSITION),
|
||||||
|
zero_validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
def validator(value):
|
||||||
|
is_negative_str = isinstance(value, str) and value.startswith("-")
|
||||||
|
is_negative_num = isinstance(value, (float, int)) and value < 0
|
||||||
|
if is_negative_str or is_negative_num:
|
||||||
|
return negative_validator(value)
|
||||||
|
return positive_validator(value)
|
||||||
|
|
||||||
|
return validator
|
||||||
|
|
||||||
|
|
||||||
|
def has_valid_range_config():
|
||||||
|
"""Validate that that the config start + end position results in a valid
|
||||||
|
positional range, which must be >= 18degrees
|
||||||
|
"""
|
||||||
|
range_validator = position_range()
|
||||||
|
|
||||||
|
def validator(config):
|
||||||
|
# if we don't have an end position, then there is nothing to do
|
||||||
|
if CONF_END_POSITION not in config:
|
||||||
|
return config
|
||||||
|
|
||||||
|
# determine the range by taking the difference from the end and start
|
||||||
|
range = config[CONF_END_POSITION] - config[CONF_START_POSITION]
|
||||||
|
|
||||||
|
# but need to account for start position being greater than end position
|
||||||
|
# where the range rolls back around the 0 position
|
||||||
|
if config[CONF_END_POSITION] < config[CONF_START_POSITION]:
|
||||||
|
range = RESOLUTION + config[CONF_END_POSITION] - config[CONF_START_POSITION]
|
||||||
|
|
||||||
|
try:
|
||||||
|
range_validator(range)
|
||||||
|
return config
|
||||||
|
except cv.Invalid as e:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"The range between start and end position is invalid. It was was {range} but {e.error_message}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return validator
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(AS5600Component),
|
||||||
|
cv.Optional(CONF_DIR_PIN): pins.gpio_input_pin_schema,
|
||||||
|
cv.Optional(CONF_DIRECTION, default="CLOCKWISE"): cv.enum(
|
||||||
|
DIRECTION, upper=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_WATCHDOG, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_POWER_MODE, default="NOMINAL"): cv.enum(
|
||||||
|
POWER_MODE, upper=True, space=""
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HYSTERESIS, default="NONE"): cv.enum(
|
||||||
|
HYSTERESIS, upper=True, space=""
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_SLOW_FILTER, default="16X"): cv.enum(
|
||||||
|
SLOW_FILTER, upper=True, space=""
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_FAST_FILTER, default="NONE"): cv.enum(
|
||||||
|
FAST_FILTER, upper=True, space=""
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_START_POSITION, default=0): position(),
|
||||||
|
cv.Optional(CONF_END_POSITION): position(),
|
||||||
|
cv.Optional(CONF_RANGE): position_range(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(i2c.i2c_device_schema(0x36)),
|
||||||
|
# ensure end_position and range are mutually exclusive
|
||||||
|
cv.has_at_most_one_key(CONF_END_POSITION, CONF_RANGE),
|
||||||
|
has_valid_range_config(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
cg.add(var.set_direction(config[CONF_DIRECTION]))
|
||||||
|
cg.add(var.set_watchdog(config[CONF_WATCHDOG]))
|
||||||
|
cg.add(var.set_power_mode(config[CONF_POWER_MODE]))
|
||||||
|
cg.add(var.set_hysteresis(config[CONF_HYSTERESIS]))
|
||||||
|
cg.add(var.set_slow_filter(config[CONF_SLOW_FILTER]))
|
||||||
|
cg.add(var.set_fast_filter(config[CONF_FAST_FILTER]))
|
||||||
|
cg.add(var.set_start_position(config[CONF_START_POSITION]))
|
||||||
|
|
||||||
|
if dir_pin_config := config.get(CONF_DIR_PIN):
|
||||||
|
pin = await cg.gpio_pin_expression(dir_pin_config)
|
||||||
|
cg.add(var.set_dir_pin(pin))
|
||||||
|
|
||||||
|
if (end_position_config := config.get(CONF_END_POSITION, None)) is not None:
|
||||||
|
cg.add(var.set_end_position(end_position_config))
|
||||||
|
|
||||||
|
if (range_config := config.get(CONF_RANGE, None)) is not None:
|
||||||
|
cg.add(var.set_range(range_config))
|
138
esphome/components/as5600/as5600.cpp
Normal file
138
esphome/components/as5600/as5600.cpp
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
#include "as5600.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as5600 {
|
||||||
|
|
||||||
|
static const char *const TAG = "as5600";
|
||||||
|
|
||||||
|
// Configuration registers
|
||||||
|
static const uint8_t REGISTER_ZMCO = 0x00; // 8 bytes / R
|
||||||
|
static const uint8_t REGISTER_ZPOS = 0x01; // 16 bytes / RW
|
||||||
|
static const uint8_t REGISTER_MPOS = 0x03; // 16 bytes / RW
|
||||||
|
static const uint8_t REGISTER_MANG = 0x05; // 16 bytes / RW
|
||||||
|
static const uint8_t REGISTER_CONF = 0x07; // 16 bytes / RW
|
||||||
|
|
||||||
|
// Output registers
|
||||||
|
static const uint8_t REGISTER_ANGLE_RAW = 0x0C; // 16 bytes / R
|
||||||
|
static const uint8_t REGISTER_ANGLE = 0x0E; // 16 bytes / R
|
||||||
|
|
||||||
|
// Status registers
|
||||||
|
static const uint8_t REGISTER_STATUS = 0x0B; // 8 bytes / R
|
||||||
|
static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R
|
||||||
|
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
|
||||||
|
|
||||||
|
void AS5600Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up AS5600...");
|
||||||
|
|
||||||
|
if (!this->read_byte(REGISTER_STATUS).has_value()) {
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// configuration direction pin, if given
|
||||||
|
// the dir pin on the chip should be low for clockwise
|
||||||
|
// and high for counterclockwise. If the pin is left floating
|
||||||
|
// the reported positions will be erratic.
|
||||||
|
if (this->dir_pin_ != nullptr) {
|
||||||
|
this->dir_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||||
|
this->dir_pin_->digital_write(this->direction_ == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// build config register
|
||||||
|
// take the value, shift it left, and add mask to it to ensure we
|
||||||
|
// are only changing the bits appropriate for that setting in the
|
||||||
|
// off chance we somehow have bad value in there and it makes for
|
||||||
|
// a nice visual for the bit positions.
|
||||||
|
uint16_t config = 0;
|
||||||
|
// clang-format off
|
||||||
|
config |= (this->watchdog_ << 13) & 0b0010000000000000;
|
||||||
|
config |= (this->fast_filter_ << 10) & 0b0001110000000000;
|
||||||
|
config |= (this->slow_filter_ << 8) & 0b0000001100000000;
|
||||||
|
config |= (this->pwm_frequency_ << 6) & 0b0000000011000000;
|
||||||
|
config |= (this->output_mode_ << 4) & 0b0000000000110000;
|
||||||
|
config |= (this->hysteresis_ << 2) & 0b0000000000001100;
|
||||||
|
config |= (this->power_mode_ << 0) & 0b0000000000000011;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// write config to config register
|
||||||
|
if (!this->write_byte_16(REGISTER_CONF, config)) {
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure the start position
|
||||||
|
this->write_byte_16(REGISTER_ZPOS, this->start_position_);
|
||||||
|
|
||||||
|
// configure either end position or max angle
|
||||||
|
if (this->end_mode_ == END_MODE_POSITION) {
|
||||||
|
this->write_byte_16(REGISTER_MPOS, this->end_position_);
|
||||||
|
} else {
|
||||||
|
this->write_byte_16(REGISTER_MANG, this->end_position_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the raw max from end position or start + range
|
||||||
|
this->raw_max_ = this->end_mode_ == END_MODE_POSITION ? this->end_position_ & 4095
|
||||||
|
: (this->start_position_ + this->end_position_) & 4095;
|
||||||
|
|
||||||
|
// calculate allowed range of motion by taking the start from the end
|
||||||
|
// but only if the end is greater than the start. If the start is greater
|
||||||
|
// than the end position, then that means we take the start all the way to
|
||||||
|
// reset point (i.e. 0 deg raw) and then we that with the end position
|
||||||
|
uint16_t range = this->raw_max_ > this->start_position_ ? this->raw_max_ - this->start_position_
|
||||||
|
: (4095 - this->start_position_) + this->raw_max_;
|
||||||
|
|
||||||
|
// range scale is ratio of actual allowed range to the full range
|
||||||
|
this->range_scale_ = range / 4095.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AS5600Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "AS5600:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with AS5600 failed!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, " Watchdog: %d", this->watchdog_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Fast Filter: %d", this->fast_filter_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Slow Filter: %d", this->slow_filter_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Hysteresis: %d", this->hysteresis_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Start Position: %d", this->start_position_);
|
||||||
|
if (this->end_mode_ == END_MODE_POSITION) {
|
||||||
|
ESP_LOGCONFIG(TAG, " End Position: %d", this->end_position_);
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, " Range: %d", this->end_position_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AS5600Component::in_range(uint16_t raw_position) {
|
||||||
|
return this->raw_max_ > this->start_position_
|
||||||
|
? raw_position >= this->start_position_ && raw_position <= this->raw_max_
|
||||||
|
: raw_position >= this->start_position_ || raw_position <= this->raw_max_;
|
||||||
|
}
|
||||||
|
|
||||||
|
AS5600MagnetStatus AS5600Component::read_magnet_status() {
|
||||||
|
uint8_t status = this->reg(REGISTER_STATUS).get() >> 3 & 0b000111;
|
||||||
|
return static_cast<AS5600MagnetStatus>(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<uint16_t> AS5600Component::read_position() {
|
||||||
|
uint16_t pos = 0;
|
||||||
|
if (!this->read_byte_16(REGISTER_ANGLE, &pos)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<uint16_t> AS5600Component::read_raw_position() {
|
||||||
|
uint16_t pos = 0;
|
||||||
|
if (!this->read_byte_16(REGISTER_ANGLE_RAW, &pos)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace as5600
|
||||||
|
} // namespace esphome
|
105
esphome/components/as5600/as5600.h
Normal file
105
esphome/components/as5600/as5600.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as5600 {
|
||||||
|
|
||||||
|
static const uint16_t POSITION_COUNT = 4096;
|
||||||
|
static const float RAW_TO_DEGREES = 360.0 / POSITION_COUNT;
|
||||||
|
static const float DEGREES_TO_RAW = POSITION_COUNT / 360.0;
|
||||||
|
|
||||||
|
enum EndPositionMode : uint8_t {
|
||||||
|
// In this mode, the end position is calculated by taking the start position
|
||||||
|
// and adding the range/positions. For example, you could say start at 90deg,
|
||||||
|
// and have a range of 180deg and effectively the sensor will report values
|
||||||
|
// from the physical 90deg thru 270deg.
|
||||||
|
END_MODE_RANGE,
|
||||||
|
// In this mode, the end position is explicitly set, and changing the start
|
||||||
|
// position will NOT change the end position.
|
||||||
|
END_MODE_POSITION,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum OutRangeMode : uint8_t {
|
||||||
|
// In this mode, the AS5600 chip itself actually reports these values, but
|
||||||
|
// effectively it splits the out-of-range values in half, and when positioned
|
||||||
|
// over the half closest to the min/start position, it will report 0 and when
|
||||||
|
// positioned over the half closes to the max/end position, it will report the
|
||||||
|
// max/end value.
|
||||||
|
OUT_RANGE_MODE_MIN_MAX,
|
||||||
|
// In this mode, when the magnet is positioned outside the configured
|
||||||
|
// range, the sensor will report NAN, which translates to "Unknown"
|
||||||
|
// in Home Assistant.
|
||||||
|
OUT_RANGE_MODE_NAN,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AS5600MagnetStatus : uint8_t {
|
||||||
|
MAGNET_GONE = 2, // 0b010 / magnet not detected
|
||||||
|
MAGNET_OK = 4, // 0b100 / magnet just right
|
||||||
|
MAGNET_STRONG = 5, // 0b101 / magnet too strong
|
||||||
|
MAGNET_WEAK = 6, // 0b110 / magnet too weak
|
||||||
|
};
|
||||||
|
|
||||||
|
class AS5600Component : public Component, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
/// Set up the internal sensor array.
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
/// HARDWARE_LATE setup priority
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
// configuration setters
|
||||||
|
void set_dir_pin(InternalGPIOPin *pin) { this->dir_pin_ = pin; }
|
||||||
|
void set_direction(uint8_t direction) { this->direction_ = direction; }
|
||||||
|
void set_fast_filter(uint8_t fast_filter) { this->fast_filter_ = fast_filter; }
|
||||||
|
void set_hysteresis(uint8_t hysteresis) { this->hysteresis_ = hysteresis; }
|
||||||
|
void set_power_mode(uint8_t power_mode) { this->power_mode_ = power_mode; }
|
||||||
|
void set_slow_filter(uint8_t slow_filter) { this->slow_filter_ = slow_filter; }
|
||||||
|
void set_watchdog(bool watchdog) { this->watchdog_ = watchdog; }
|
||||||
|
bool get_watchdog() { return this->watchdog_; }
|
||||||
|
void set_start_position(uint16_t start_position) { this->start_position_ = start_position % POSITION_COUNT; }
|
||||||
|
void set_end_position(uint16_t end_position) {
|
||||||
|
this->end_position_ = end_position % POSITION_COUNT;
|
||||||
|
this->end_mode_ = END_MODE_POSITION;
|
||||||
|
}
|
||||||
|
void set_range(uint16_t range) {
|
||||||
|
this->end_position_ = range % POSITION_COUNT;
|
||||||
|
this->end_mode_ = END_MODE_RANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the scale value for the configured range.
|
||||||
|
// For example, if configured to start at 0deg and end at 180deg, the
|
||||||
|
// range is 50% of the native/raw range, so the range scale would be 0.5.
|
||||||
|
// If configured to use the full 360deg, the range scale would be 1.0.
|
||||||
|
float get_range_scale() { return this->range_scale_; }
|
||||||
|
|
||||||
|
// Indicates whether the given *raw* position is within the configured range
|
||||||
|
bool in_range(uint16_t raw_position);
|
||||||
|
|
||||||
|
AS5600MagnetStatus read_magnet_status();
|
||||||
|
optional<uint16_t> read_position();
|
||||||
|
optional<uint16_t> read_raw_position();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
InternalGPIOPin *dir_pin_{nullptr};
|
||||||
|
uint8_t direction_;
|
||||||
|
uint8_t fast_filter_;
|
||||||
|
uint8_t hysteresis_;
|
||||||
|
uint8_t power_mode_;
|
||||||
|
uint8_t slow_filter_;
|
||||||
|
uint8_t pwm_frequency_{0};
|
||||||
|
uint8_t output_mode_{0};
|
||||||
|
bool watchdog_;
|
||||||
|
uint16_t start_position_;
|
||||||
|
uint16_t end_position_{0};
|
||||||
|
uint16_t raw_max_;
|
||||||
|
EndPositionMode end_mode_{END_MODE_RANGE};
|
||||||
|
float range_scale_{1.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace as5600
|
||||||
|
} // namespace esphome
|
119
esphome/components/as5600/sensor/__init__.py
Normal file
119
esphome/components/as5600/sensor/__init__.py
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
ICON_MAGNET,
|
||||||
|
ICON_ROTATE_RIGHT,
|
||||||
|
CONF_GAIN,
|
||||||
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
CONF_MAGNITUDE,
|
||||||
|
CONF_STATUS,
|
||||||
|
CONF_POSITION,
|
||||||
|
)
|
||||||
|
from .. import as5600_ns, AS5600Component
|
||||||
|
|
||||||
|
CODEOWNERS = ["@ammmze"]
|
||||||
|
DEPENDENCIES = ["as5600"]
|
||||||
|
|
||||||
|
AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingComponent)
|
||||||
|
|
||||||
|
CONF_ANGLE = "angle"
|
||||||
|
CONF_RAW_ANGLE = "raw_angle"
|
||||||
|
CONF_RAW_POSITION = "raw_position"
|
||||||
|
CONF_WATCHDOG = "watchdog"
|
||||||
|
CONF_POWER_MODE = "power_mode"
|
||||||
|
CONF_SLOW_FILTER = "slow_filter"
|
||||||
|
CONF_FAST_FILTER = "fast_filter"
|
||||||
|
CONF_PWM_FREQUENCY = "pwm_frequency"
|
||||||
|
CONF_BURN_COUNT = "burn_count"
|
||||||
|
CONF_START_POSITION = "start_position"
|
||||||
|
CONF_END_POSITION = "end_position"
|
||||||
|
CONF_OUT_OF_RANGE_MODE = "out_of_range_mode"
|
||||||
|
|
||||||
|
OutOfRangeMode = as5600_ns.enum("OutRangeMode")
|
||||||
|
OUT_OF_RANGE_MODES = {
|
||||||
|
"MIN_MAX": OutOfRangeMode.OUT_RANGE_MODE_MIN_MAX,
|
||||||
|
"NAN": OutOfRangeMode.OUT_RANGE_MODE_NAN,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CONF_AS5600_ID = "as5600_id"
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
AS5600Sensor,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
icon=ICON_ROTATE_RIGHT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_AS5600_ID): cv.use_id(AS5600Component),
|
||||||
|
cv.Optional(CONF_OUT_OF_RANGE_MODE): cv.enum(
|
||||||
|
OUT_OF_RANGE_MODES, upper=True, space="_"
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_RAW_POSITION): sensor.sensor_schema(
|
||||||
|
accuracy_decimals=0,
|
||||||
|
icon=ICON_ROTATE_RIGHT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_GAIN): sensor.sensor_schema(
|
||||||
|
accuracy_decimals=0,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_MAGNITUDE): sensor.sensor_schema(
|
||||||
|
accuracy_decimals=0,
|
||||||
|
icon=ICON_MAGNET,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_STATUS): sensor.sensor_schema(
|
||||||
|
accuracy_decimals=0,
|
||||||
|
icon=ICON_MAGNET,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_parented(var, config[CONF_AS5600_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await sensor.register_sensor(var, config)
|
||||||
|
|
||||||
|
if out_of_range_mode_config := config.get(CONF_OUT_OF_RANGE_MODE):
|
||||||
|
cg.add(var.set_out_of_range_mode(out_of_range_mode_config))
|
||||||
|
|
||||||
|
if angle_config := config.get(CONF_ANGLE):
|
||||||
|
sens = await sensor.new_sensor(angle_config)
|
||||||
|
cg.add(var.set_angle_sensor(sens))
|
||||||
|
|
||||||
|
if raw_angle_config := config.get(CONF_RAW_ANGLE):
|
||||||
|
sens = await sensor.new_sensor(raw_angle_config)
|
||||||
|
cg.add(var.set_raw_angle_sensor(sens))
|
||||||
|
|
||||||
|
if position_config := config.get(CONF_POSITION):
|
||||||
|
sens = await sensor.new_sensor(position_config)
|
||||||
|
cg.add(var.set_position_sensor(sens))
|
||||||
|
|
||||||
|
if raw_position_config := config.get(CONF_RAW_POSITION):
|
||||||
|
sens = await sensor.new_sensor(raw_position_config)
|
||||||
|
cg.add(var.set_raw_position_sensor(sens))
|
||||||
|
|
||||||
|
if gain_config := config.get(CONF_GAIN):
|
||||||
|
sens = await sensor.new_sensor(gain_config)
|
||||||
|
cg.add(var.set_gain_sensor(sens))
|
||||||
|
|
||||||
|
if magnitude_config := config.get(CONF_MAGNITUDE):
|
||||||
|
sens = await sensor.new_sensor(magnitude_config)
|
||||||
|
cg.add(var.set_magnitude_sensor(sens))
|
||||||
|
|
||||||
|
if status_config := config.get(CONF_STATUS):
|
||||||
|
sens = await sensor.new_sensor(status_config)
|
||||||
|
cg.add(var.set_status_sensor(sens))
|
98
esphome/components/as5600/sensor/as5600_sensor.cpp
Normal file
98
esphome/components/as5600/sensor/as5600_sensor.cpp
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#include "as5600_sensor.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as5600 {
|
||||||
|
|
||||||
|
static const char *const TAG = "as5600.sensor";
|
||||||
|
|
||||||
|
// Configuration registers
|
||||||
|
static const uint8_t REGISTER_ZMCO = 0x00; // 8 bytes / R
|
||||||
|
static const uint8_t REGISTER_ZPOS = 0x01; // 16 bytes / RW
|
||||||
|
static const uint8_t REGISTER_MPOS = 0x03; // 16 bytes / RW
|
||||||
|
static const uint8_t REGISTER_MANG = 0x05; // 16 bytes / RW
|
||||||
|
static const uint8_t REGISTER_CONF = 0x07; // 16 bytes / RW
|
||||||
|
|
||||||
|
// Output registers
|
||||||
|
static const uint8_t REGISTER_ANGLE_RAW = 0x0C; // 16 bytes / R
|
||||||
|
static const uint8_t REGISTER_ANGLE = 0x0E; // 16 bytes / R
|
||||||
|
|
||||||
|
// Status registers
|
||||||
|
static const uint8_t REGISTER_STATUS = 0x0B; // 8 bytes / R
|
||||||
|
static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R
|
||||||
|
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
|
||||||
|
|
||||||
|
float AS5600Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void AS5600Sensor::dump_config() {
|
||||||
|
LOG_SENSOR("", "AS5600 Sensor", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Out of Range Mode: %u", this->out_of_range_mode_);
|
||||||
|
if (this->angle_sensor_ != nullptr) {
|
||||||
|
LOG_SENSOR(" ", "Angle Sensor", this->angle_sensor_);
|
||||||
|
}
|
||||||
|
if (this->raw_angle_sensor_ != nullptr) {
|
||||||
|
LOG_SENSOR(" ", "Raw Angle Sensor", this->raw_angle_sensor_);
|
||||||
|
}
|
||||||
|
if (this->position_sensor_ != nullptr) {
|
||||||
|
LOG_SENSOR(" ", "Position Sensor", this->position_sensor_);
|
||||||
|
}
|
||||||
|
if (this->raw_position_sensor_ != nullptr) {
|
||||||
|
LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_);
|
||||||
|
}
|
||||||
|
if (this->gain_sensor_ != nullptr) {
|
||||||
|
LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_);
|
||||||
|
}
|
||||||
|
if (this->magnitude_sensor_ != nullptr) {
|
||||||
|
LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_);
|
||||||
|
}
|
||||||
|
if (this->status_sensor_ != nullptr) {
|
||||||
|
LOG_SENSOR(" ", "Status Sensor", this->status_sensor_);
|
||||||
|
}
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AS5600Sensor::update() {
|
||||||
|
if (this->gain_sensor_ != nullptr) {
|
||||||
|
this->gain_sensor_->publish_state(this->parent_->reg(REGISTER_AGC).get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->magnitude_sensor_ != nullptr) {
|
||||||
|
uint16_t value = 0;
|
||||||
|
this->parent_->read_byte_16(REGISTER_MAGNITUDE, &value);
|
||||||
|
this->magnitude_sensor_->publish_state(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 = magnet not detected
|
||||||
|
// 4 = magnet just right
|
||||||
|
// 5 = magnet too strong
|
||||||
|
// 6 = magnet too weak
|
||||||
|
if (this->status_sensor_ != nullptr) {
|
||||||
|
this->status_sensor_->publish_state(this->parent_->read_magnet_status());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pos = this->parent_->read_position();
|
||||||
|
if (!pos.has_value()) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto raw = this->parent_->read_raw_position();
|
||||||
|
if (!raw.has_value()) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->out_of_range_mode_ == OUT_RANGE_MODE_NAN) {
|
||||||
|
this->publish_state(this->parent_->in_range(raw.value()) ? pos.value() : NAN);
|
||||||
|
} else {
|
||||||
|
this->publish_state(pos.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->raw_position_sensor_ != nullptr) {
|
||||||
|
this->raw_position_sensor_->publish_state(raw.value());
|
||||||
|
}
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace as5600
|
||||||
|
} // namespace esphome
|
43
esphome/components/as5600/sensor/as5600_sensor.h
Normal file
43
esphome/components/as5600/sensor/as5600_sensor.h
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/as5600/as5600.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace as5600 {
|
||||||
|
|
||||||
|
class AS5600Sensor : public PollingComponent, public Parented<AS5600Component>, public sensor::Sensor {
|
||||||
|
public:
|
||||||
|
void update() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
void set_angle_sensor(sensor::Sensor *angle_sensor) { this->angle_sensor_ = angle_sensor; }
|
||||||
|
void set_raw_angle_sensor(sensor::Sensor *raw_angle_sensor) { this->raw_angle_sensor_ = raw_angle_sensor; }
|
||||||
|
void set_position_sensor(sensor::Sensor *position_sensor) { this->position_sensor_ = position_sensor; }
|
||||||
|
void set_raw_position_sensor(sensor::Sensor *raw_position_sensor) {
|
||||||
|
this->raw_position_sensor_ = raw_position_sensor;
|
||||||
|
}
|
||||||
|
void set_gain_sensor(sensor::Sensor *gain_sensor) { this->gain_sensor_ = gain_sensor; }
|
||||||
|
void set_magnitude_sensor(sensor::Sensor *magnitude_sensor) { this->magnitude_sensor_ = magnitude_sensor; }
|
||||||
|
void set_status_sensor(sensor::Sensor *status_sensor) { this->status_sensor_ = status_sensor; }
|
||||||
|
void set_out_of_range_mode(OutRangeMode oor_mode) { this->out_of_range_mode_ = oor_mode; }
|
||||||
|
OutRangeMode get_out_of_range_mode() { return this->out_of_range_mode_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sensor::Sensor *angle_sensor_{nullptr};
|
||||||
|
sensor::Sensor *raw_angle_sensor_{nullptr};
|
||||||
|
sensor::Sensor *position_sensor_{nullptr};
|
||||||
|
sensor::Sensor *raw_position_sensor_{nullptr};
|
||||||
|
sensor::Sensor *gain_sensor_{nullptr};
|
||||||
|
sensor::Sensor *magnitude_sensor_{nullptr};
|
||||||
|
sensor::Sensor *status_sensor_{nullptr};
|
||||||
|
OutRangeMode out_of_range_mode_{OUT_RANGE_MODE_MIN_MAX};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace as5600
|
||||||
|
} // namespace esphome
|
|
@ -322,6 +322,18 @@ ads1115:
|
||||||
address: 0x48
|
address: 0x48
|
||||||
i2c_id: i2c_bus
|
i2c_id: i2c_bus
|
||||||
|
|
||||||
|
as5600:
|
||||||
|
i2c_id: i2c_bus
|
||||||
|
dir_pin: GPIO27
|
||||||
|
direction: clockwise
|
||||||
|
start_position: 90deg
|
||||||
|
range: 180deg
|
||||||
|
watchdog: true
|
||||||
|
power_mode: low1
|
||||||
|
hysteresis: lsb1
|
||||||
|
slow_filter: 8x
|
||||||
|
fast_filter: lsb6
|
||||||
|
|
||||||
dallas:
|
dallas:
|
||||||
pin:
|
pin:
|
||||||
allow_other_uses: true
|
allow_other_uses: true
|
||||||
|
@ -555,6 +567,16 @@ sensor:
|
||||||
state_topic: hi/me
|
state_topic: hi/me
|
||||||
retain: false
|
retain: false
|
||||||
availability:
|
availability:
|
||||||
|
- platform: as5600
|
||||||
|
name: AS5600 Position
|
||||||
|
raw_position:
|
||||||
|
name: AS5600 Raw Position
|
||||||
|
gain:
|
||||||
|
name: AS5600 Gain
|
||||||
|
magnitude:
|
||||||
|
name: AS5600 Magnitude
|
||||||
|
status:
|
||||||
|
name: AS5600 Status
|
||||||
- platform: as7341
|
- platform: as7341
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
gain: X8
|
gain: X8
|
||||||
|
|
Loading…
Reference in a new issue