Added ADS131M08 external component

This commit is contained in:
Chris Stuurman 2026-01-08 21:47:23 +02:00
parent d859cd620b
commit c19152fed3
15 changed files with 3961 additions and 3311 deletions

2598
archive/sthome-ut9.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,9 @@
wifi:
domain: .sthome.org
min_auth_mode: WPA2 # requires esphome v2025.11.0 or later
networks:
- ssid: !secret wifi_ssid1
password: !secret wifi_password1
# - ssid: !secret wifi_ssid2
# password: !secret wifi_password2
# - ssid: !secret wifi_ssid3
# password: !secret wifi_password3
# - ssid: !secret wifi_ssid4
# password: !secret wifi_password4
# - ssid: !secret wifi_ssid5
# password: !secret wifi_password5
manual_ip:
# For faster connection startup,
# NB! set a static IP address in main config file

View File

@ -0,0 +1,6 @@
# ESPHome config/source/custom Folder
This folder contains Contains source code files and related resources for custom components.
For more information, visit the [ESPHome documentation](https://esphome.io/).

View File

@ -0,0 +1,24 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi
from esphome.const import CONF_ID
DEPENDENCIES = ["spi"]
ads131m08_ns = cg.esphome_ns.namespace("ads131m08")
ADS131M08Hub = ads131m08_ns.class_("ADS131M08Hub", cg.Component, spi.SPIDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(ADS131M08Hub),
cv.Required("drdy_pin"): pins.internal_gpio_input_pin_schema,
cv.Required("vref"): cv.float_,
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
drdy = await cg.gpio_pin_expression(config["drdy_pin"])
reference_voltage = config["vref"]
cg.add(var.set_reference_voltage(reference_voltage))
cg.add(var.set_drdy_pin(drdy))

View File

@ -0,0 +1,197 @@
#include "ads131m08.h"
/*
namespace esphome {
namespace ads131m08 {
static const char *const TAG = "ads131m08";
ADS131M08::ADS131M08(esphome::gpio::GPIOPin *pin)
: drdy_pin_(pin)
{
}
void ADS131M08::set_drdy_pin(InternalGPIOPin *pin)
{
drdy_pin_ = pin;
}
void ADS131M08::set_sensor(int channel, sensor::Sensor *s)
{
channels_[channel] = s;
}
void ADS131M08::set_reference_voltage(float reference_voltage)
{
this->reference_voltage_ = reference_voltage;
}
float ADS131M08::get_setup_priority() const
{
return setup_priority::HARDWARE;
}
void ADS131M08::setup()
{
// Initialize SPI with correct settings for ADS131M08
// SPI mode 1: CPOL=0, CPHA=1
//this->spi_setup();
SPI.begin();
SPI.setDataMode(SPI_MODE1);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV8); // Adjust clock speed as needed
// Configure DRDY pin
drdy_pin_->setup();
drdy_pin_->pin_mode(esphome::gpio::FLAG_INPUT | esphome::gpio::FLAG_PULLUP);
// Attach interrupt on falling edge of DRDY
// esphome::gpio::attach_interrupt(drdy_pin_, esphome::gpio::INTERRUPT_FALLING_EDGE, [this]() {
// this->data_ready_ = true;
// });
this->drdy_pin_->attach_interrupt(&ADS131M08Component::handle_drdy, this, gpio::INTERRUPT_FALLING_EDGE);
// Initialize ADS131M08
initialize_ads131m08();
}
// interrupt handler
void ADS131M08::handle_drdy(ADS131M08Component *arg)
{
arg->data_ready_ = true;
}
void ADS131M08::loop()
{
if (data_ready_) {
data_ready_ = false;
read_all_channels();
}
}
void ADS131M08::initialize_ads131m08()
{
// Send RESET command
SPI.transfer(0x06);
delay(1);
// Send UNLOCK command
SPI.transfer(0x55);
SPI.transfer(0x55);
// Configure registers as needed (example: set gain, etc.)
// Write to MODE register for continuous conversion, disable internal reference (use external MAX6070AAUT12+T at 1.25V)
write_register(0x02, 0x0000); // MODE register, continuous mode, INTREF_EN = 0
// Wait for external reference to settle
delay(100);
// Start conversions
SPI.transfer(0x08); // START command
}
void ADS131M08::write_register(uint8_t reg, uint16_t value)
{
SPI.transfer(0x40 | reg); // WREG command
SPI.transfer(0x00); // Number of registers - 1 (0 for one register)
SPI.transfer((value >> 8) & 0xFF);
SPI.transfer(value & 0xFF);
}
void ADS131M08::read_all_channels()
{
// Send RDATA command or read data directly
SPI.transfer(0x12); // RDATA command
// Read 24 bytes (3 bytes per channel for 8 channels)
for (int i = 0; i < 24; i++) {
data_buffer_[i] = SPI.transfer(0x00);
}
// Convert to voltages for all channels
//Sensor *channels[8] = {channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8};
for (int ch = 0; ch < 8; ch++) {
if (this->sensors_[ch] != nullptr) {
int offset = ch * 3;
int32_t raw = (data_buffer_[offset] << 16) | (data_buffer_[offset + 1] << 8) | data_buffer_[offset + 2];
if (raw & 0x800000)
raw |= 0xFF000000; // Sign extend
float v = (raw / 8388608.0) * this->reference_voltage_; // 2^23 = 8388608, Vref = 1.25V (MAX6070AAUT12+T)
this->sensors_[ch]->publish_state(v);
}
}
}
float ADS131M08::read_data(uint8_t channel)
{
// This function assumes read_all_channels() has been called and data is in data_buffer_
int offset = channel * 3;
int32_t raw = (data_buffer_[offset] << 16) | (data_buffer_[offset + 1] << 8) | data_buffer_[offset + 2];
if (raw & 0x800000)
raw |= 0xFF000000; // Sign extend
float v = (raw / 8388608.0) * this->reference_voltage_; // 2^23 = 8388608, Vref = 1.25V (MAX6070AAUT12+T)
return v;
}
void ADS131M08::dump_config() {
ESP_LOGCONFIG(TAG, "ADS131M08:");
LOG_PIN(" CS Pin:", this->cs_);
ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_);
}
} // namespace ads131m08
} // namespace esphome
*/
namespace esphome {
namespace ads131m08 {
static const char *const TAG = "ads131m08";
void ADS131M08Hub::setup()
{
this->spi_setup();
this->drdy_pin_->setup();
this->drdy_pin_->attach_interrupt(&ADS131M08Hub::isr, this, gpio::INTERRUPT_FALLING_EDGE);
}
void IRAM_ATTR ADS131M08Hub::isr(ADS131M08Hub *arg)
{
arg->data_ready_ = true;
}
void ADS131M08Hub::loop()
{
if (this->data_ready_) {
this->data_ready_ = false;
this->read_data_();
}
}
void ADS131M08Hub::read_data_()
{
this->enable();
// ADS131M08 Frame: 1 Status Word + 8 Channel Words + 1 CRC Word (24-bit words)
uint8_t frame[30]; // 10 words * 3 bytes
this->read_array(frame, 30);
this->disable();
for (int i = 0; i < 8; i++) {
if (this->sensors_[i] != nullptr) {
// Convert 24-bit two's complement to float (Simplified)
int32_t raw = (frame[3+(i*3)] << 16) | (frame[4+(i*3)] << 8) | frame[5+(i*3)];
if (raw & 0x800000)
raw |= 0xFF000000; // Sign extend
this->sensors_[i]->publish_state(raw * (this->reference_voltage_ / 8388608.0)); // 2^23 = 8388608, Vref = 1.25V ( for MAX6070AAUT12+T )
}
}
}
void ADS131M08Hub::dump_config()
{
ESP_LOGCONFIG(TAG, "ADS131M08:");
LOG_PIN(" CS Pin:", this->cs_);
LOG_PIN(" DRDY Pin:", this->drdy_pin_);
ESP_LOGCONFIG(TAG, " Reference Voltage: %.2fV", this->reference_voltage_);
}
} // namespace ads131m08
} // namespace esphome

View File

@ -0,0 +1,35 @@
#pragma once
// using external voltage reference
#include "esphome/core/component.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace ads131m08 {
class ADS131M08Sensor : public sensor::Sensor, public Component
{
};
class ADS131M08Hub : public Component, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_8MHZ>
{
public:
void setup() override;
void loop() override;
void set_drdy_pin(InternalGPIOPin *pin) { drdy_pin_ = pin; }
void register_sensor(int channel, ADS131M08Sensor *s) { sensors_[channel] = s; }
void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; }
void dump_config() override;
protected:
float reference_voltage_;
InternalGPIOPin *drdy_pin_;
ADS131M08Sensor *sensors_[8] = {nullptr};
volatile bool data_ready_{false};
static void isr(ADS131M08Hub *arg);
void read_data_();
};
} // namespace ads131m08
} // namespace esphome

View File

@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import CONF_ID, CONF_CHANNEL
from . import ADS131M08Hub, ads131m08_ns
ADS131M08Sensor = ads131m08_ns.class_("ADS131M08Sensor", sensor.Sensor, cg.Component)
CONFIG_SCHEMA = sensor.sensor_schema().extend({
cv.GenerateID(): cv.declare_id(ADS131M08Sensor),
cv.Required("ads_hub_id"): cv.use_id(ADS131M08Hub),
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
}).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
hub = await cg.get_variable(config["ads_hub_id"])
cg.add(hub.register_sensor(config[CONF_CHANNEL], var))

9
device.yaml Normal file
View File

@ -0,0 +1,9 @@
# this is just a simple test device setup that's used to compile and test the examples
esphome:
name: custom_components_test
esp32:
board: esp32dev
framework:
type: esp-idf

View File

@ -1,15 +1,31 @@
esphome:
packages:
- !include common/wifi.yaml
# - !include common/canbus.yaml
# - !include common/felicityinverter.yaml
substitutions:
name: sthome-ut1
friendly_name: sthome-ut1
friendly_name: "sthome-ut1"
esphome:
name: "${name}"
friendly_name: "${friendly_name}"
esp32:
board: esp32dev
board: nodemcu-32s
variant: esp32
framework:
type: arduino
type: arduino #esp-idf #
debug:
update_interval: 10s
# Enable logging
logger:
level: INFO
level: DEBUG
logs:
sensor: INFO
text_sensor: INFO
# Enable Home Assistant API
api:
@ -21,36 +37,20 @@ ota:
password: "37f546590fcc15e1323d273540eb623a"
wifi:
#ssid: !secret wifi_ssid
#password: !secret wifi_password
# we will use local dns server for local dns resolution
domain: ".sthome.org"
networks:
- ssid: !secret wifi_ssid1
password: !secret wifi_password1
- ssid: !secret wifi_ssid2
password: !secret wifi_password2
- ssid: !secret wifi_ssid3
password: !secret wifi_password3
- ssid: !secret wifi_ssid4
password: !secret wifi_password4
- ssid: !secret wifi_ssid5
password: !secret wifi_password5
manual_ip:
# For faster connection startup, set a static IP address
# Set this to the IP of the ESP
static_ip: 10.0.2.1
gateway: 10.0.0.2
subnet: 255.255.240.0
dns1: 10.0.0.1
dns2: 10.0.0.2
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "sthome-ut1 Fallback Hotspot"
ssid: "${name} Fallback Hotspot"
password: "7SglIlgdkpAD"
captive_portal:
#preferences:
# flash_write_interval: 30s
@ -62,11 +62,29 @@ sun:
time:
- platform: homeassistant
text_sensor:
- platform: debug
device:
name: "Device Info"
reset_reason:
name: "Reset Reason"
# human readable update text sensor from sensor:uptime
- platform: template
name: Uptime
id: uptime_human
icon: mdi:clock-start
update_interval: 10s
#define OUTPUT_R1 16
#define OUTPUT_R2 17
#define OUTPUT_R3 18
#define OUTPUT_R4 19
switch:
- platform: restart
name: "${name} Restart"
id: "restart_switch"
- platform: gpio
pin:
number: GPIO16
@ -176,3 +194,31 @@ sensor:
if (id(alarm_signal).state > 1.5) {
id(relay1).turn_on();
}
# human readable uptime sensor output to the text sensor above
- platform: uptime
name: Uptime in Days
id: uptime_sensor_days
update_interval: 10s
on_raw_value:
then:
- text_sensor.template.publish:
id: uptime_human
state: !lambda |-
int seconds = round(id(uptime_sensor_days).raw_state);
int days = seconds / (24 * 3600);
seconds = seconds % (24 * 3600);
int hours = seconds / 3600;
seconds = seconds % 3600;
int minutes = seconds / 60;
seconds = seconds % 60;
auto days_str = std::to_string(days);
auto hours_str = std::to_string(hours);
auto minutes_str = std::to_string(minutes);
auto seconds_str = std::to_string(seconds);
return (
(days ? days_str + "d " : "") +
(hours ? hours_str + "h " : "") +
(minutes ? minutes_str + "m " : "") +
(seconds_str + "s")
).c_str();

View File

@ -47,7 +47,7 @@ esphome:
on_boot:
- priority: 600 # This is where most sensors are set up (higher number means higher priority)
then:
- ds1307.read_time: # read the RTC time
#- ds1307.read_time: # read the RTC time
- lambda: |-
id(can_msgctr) = 0;
id(update_heating_display).execute(false);
@ -69,7 +69,7 @@ logger:
level: very_verbose
initial_level: very_verbose
logs:
canbus: INFO
canbus: very_verbose
touchscreen: INFO
xpt2046: INFO
script: INFO
@ -98,24 +98,25 @@ wifi:
captive_portal:
time:
- platform: ds1307
#- platform: ds1307
# repeated synchronization is not necessary unless the external RTC
# is much more accurate than the internal clock
update_interval: never
# update_interval: never
- platform: homeassistant
id: time_source
#update_interval: 360min # Change sync interval from default 5min to 6 hours
on_time_sync:
then:
- ds1307.write_time:
# - if: # Publish the time the device was last restarted, but only once.
# condition:
# lambda: 'return id(device_last_restart).state == "";'
# then:
# - text_sensor.template.publish:
# id: device_last_restart
# state: !lambda 'return id(time_source).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
#- ds1307.write_time:
- if: # Publish the time the device was last restarted, but only once.
condition:
lambda: 'return id(device_last_restart).state == "";'
then:
- text_sensor.template.publish:
id: device_last_restart
state: !lambda 'return id(time_source).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
# - script.execute: time_update
# - script.execute: init_calendar
@ -211,7 +212,7 @@ interval:
- interval: 30s
then:
- script.execute: send_info_request
- interval: 500ms
- interval: 15s # 500ms
then:
- script.execute: send_quick_update_request
@ -1190,7 +1191,7 @@ sensor:
# return currenttime.timestamp - time_obj.timestamp;
# update_interval: 60s
#text_sensor:
text_sensor:
## - platform: template
## id: module_time
## name: "Module time"
@ -1218,12 +1219,12 @@ sensor:
# id: uptime_human
# icon: mdi:clock-start
#
# - platform: template
# name: 'Last Restart'
# id: device_last_restart
# icon: mdi:clock
# entity_category: diagnostic
#
- platform: template
name: 'Last Restart'
id: device_last_restart
icon: mdi:clock
entity_category: diagnostic
script:
- id: send_quick_update_request

View File

@ -1,3 +1,6 @@
packages:
- !include common/wifi.yaml
substitutions:
name: sthome-ut6
friendly_name: "sthome-ut6"
@ -22,6 +25,7 @@ esp8266:
# Enable logging
logger:
level: INFO
# Enable Home Assistant API
api:
@ -33,29 +37,8 @@ ota:
password: "5956a60f6cf40cf4b6b172e23f236572"
wifi:
#ssid: !secret wifi_ssid
#password: !secret wifi_password
# we will use local dns server for local dns resolution
domain: ".sthome.org"
networks:
- ssid: !secret wifi_ssid1
password: !secret wifi_password1
- ssid: !secret wifi_ssid2
password: !secret wifi_password2
- ssid: !secret wifi_ssid3
password: !secret wifi_password3
- ssid: !secret wifi_ssid4
password: !secret wifi_password4
- ssid: !secret wifi_ssid5
password: !secret wifi_password5
manual_ip:
# For faster connection startup, set a static IP address
# Set this to the IP of the ESP
static_ip: 10.0.2.6
gateway: 10.0.0.2
subnet: 255.255.240.0
dns1: 10.0.0.1
dns2: 10.0.0.2
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
@ -109,6 +92,10 @@ time:
# - script.execute: heartbeat
#
debug:
update_interval: 60s
switch:
# Switch to restart the ESP
- platform: restart
@ -189,6 +176,12 @@ sensor:
text_sensor:
- platform: debug
# device:
# name: "Device Info"
reset_reason:
name: "Reset Reason"
# Expose WiFi information as sensors
- platform: wifi_info
ip_address:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff