Merge branch 'dev' into nvds-fix-rtttl-octave

This commit is contained in:
NP v/d Spek 2024-06-13 18:42:34 +02:00 committed by GitHub
commit b84eb566f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
71 changed files with 1335 additions and 877 deletions

View File

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
with:

View File

@ -40,7 +40,7 @@ jobs:
arch: [amd64, armv7, aarch64]
build_type: ["ha-addon", "docker", "lint"]
steps:
- uses: actions/checkout@v4.1.6
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
with:

View File

@ -34,7 +34,7 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
@ -66,7 +66,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -87,7 +87,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -108,7 +108,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -129,7 +129,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -150,7 +150,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -199,7 +199,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -229,7 +229,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -254,7 +254,7 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Find all YAML test files
id: set-matrix
run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
@ -271,7 +271,7 @@ jobs:
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -303,7 +303,7 @@ jobs:
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -358,7 +358,7 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -387,6 +387,13 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/gcc.json"
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
- name: Run 'pio run --list-targets -e esp32-idf-tidy'
if: matrix.name == 'Run script/clang-tidy for ESP32 IDF'
run: |
. venv/bin/activate
mkdir -p .temp
pio run --list-targets -e esp32-idf-tidy
- name: Run clang-tidy
run: |
. venv/bin/activate
@ -410,7 +417,7 @@ jobs:
count: ${{ steps.list-components.outputs.count }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
with:
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
fetch-depth: 500
@ -454,11 +461,11 @@ jobs:
matrix:
file: ${{ fromJson(needs.list-components.outputs.components) }}
steps:
- name: Install libsodium
run: sudo apt-get install libsodium-dev
- name: Install dependencies
run: sudo apt-get install libsodium-dev libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -484,7 +491,7 @@ jobs:
matrix: ${{ steps.split.outputs.components }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Split components into 20 groups
id: split
run: |
@ -508,11 +515,11 @@ jobs:
- name: List components
run: echo ${{ matrix.components }}
- name: Install libsodium
run: sudo apt-get install libsodium-dev
- name: Install dependencies
run: sudo apt-get install libsodium-dev libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:

View File

@ -19,7 +19,7 @@ jobs:
tag: ${{ steps.tag.outputs.tag }}
branch_build: ${{ steps.tag.outputs.branch_build }}
steps:
- uses: actions/checkout@v4.1.6
- uses: actions/checkout@v4.1.7
- name: Get tag
id: tag
# yamllint disable rule:line-length
@ -51,7 +51,7 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4.1.6
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
with:
@ -83,7 +83,7 @@ jobs:
- linux/arm/v7
- linux/arm64
steps:
- uses: actions/checkout@v4.1.6
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
with:
@ -174,7 +174,7 @@ jobs:
- ghcr
- dockerhub
steps:
- uses: actions/checkout@v4.1.6
- uses: actions/checkout@v4.1.7
- name: Download digests
uses: actions/download-artifact@v4.1.7

View File

@ -13,10 +13,10 @@ jobs:
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Checkout Home Assistant
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
with:
repository: home-assistant/core
path: lib/home-assistant

View File

@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.6
uses: actions/checkout@v4.1.7
- name: Run yamllint
uses: frenck/action-yamllint@v1.5.0
with:

View File

@ -94,6 +94,7 @@ esphome/components/current_based/* @djwmarcx
esphome/components/dac7678/* @NickB1
esphome/components/daikin_arc/* @MagicBear
esphome/components/daikin_brc/* @hagak
esphome/components/dallas_temp/* @ssieb
esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core
esphome/components/datetime/* @jesserockz @rfdarter
@ -144,6 +145,7 @@ esphome/components/gdk101/* @Szewcson
esphome/components/globals/* @esphome/core
esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core
esphome/components/gpio/one_wire/* @ssieb
esphome/components/gps/* @coogle
esphome/components/graph/* @synco
esphome/components/graphical_display_menu/* @MrMDavidson
@ -270,6 +272,7 @@ esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra
esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931
@ -317,6 +320,7 @@ esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
esphome/components/scd4x/* @martgras @sjtrny
esphome/components/script/* @esphome/core
esphome/components/sdl/* @clydebarrow
esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath
esphome/components/seeed_mr24hpc1/* @limengdu

View File

@ -488,6 +488,15 @@ def command_run(args, config):
if exit_code != 0:
return exit_code
_LOGGER.info("Successfully compiled program.")
if CORE.is_host:
from esphome.platformio_api import get_idedata
idedata = get_idedata(config)
if idedata is None:
return 1
program_path = idedata.raw["prog_path"]
return run_external_process(program_path)
port = choose_upload_log_host(
default=args.device,
check_default=None,

View File

@ -1,25 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.const import CONF_ID, CONF_PIN
MULTI_CONF = True
AUTO_LOAD = ["sensor"]
dallas_ns = cg.esphome_ns.namespace("dallas")
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DallasComponent),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
}
).extend(cv.polling_component_schema("60s"))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
CONFIG_SCHEMA = cv.invalid(
'The "dallas" component has been replaced by the "one_wire" component.\nhttps://esphome.io/components/one_wire'
)

View File

@ -1,287 +0,0 @@
#include "dallas_component.h"
#include "esphome/core/log.h"
namespace esphome {
namespace dallas {
static const char *const TAG = "dallas.sensor";
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
static const uint8_t DALLAS_MODEL_DS1822 = 0x22;
static const uint8_t DALLAS_MODEL_DS18B20 = 0x28;
static const uint8_t DALLAS_MODEL_DS1825 = 0x3B;
static const uint8_t DALLAS_MODEL_DS28EA00 = 0x42;
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
switch (this->resolution_) {
case 9:
return 94;
case 10:
return 188;
case 11:
return 375;
default:
return 750;
}
}
void DallasComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
pin_->setup();
// clear bus with 480µs high, otherwise initial reset in search_vec() fails
pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(480);
one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory)
std::vector<uint64_t> raw_sensors;
raw_sensors = this->one_wire_->search_vec();
for (auto &address : raw_sensors) {
auto *address8 = reinterpret_cast<uint8_t *>(&address);
if (crc8(address8, 7) != address8[7]) {
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
continue;
}
if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
address8[0] != DALLAS_MODEL_DS18B20 && address8[0] != DALLAS_MODEL_DS1825 &&
address8[0] != DALLAS_MODEL_DS28EA00) {
ESP_LOGW(TAG, "Unknown device type 0x%02X.", address8[0]);
continue;
}
this->found_sensors_.push_back(address);
}
for (auto *sensor : this->sensors_) {
if (sensor->get_index().has_value()) {
if (*sensor->get_index() >= this->found_sensors_.size()) {
this->status_set_error("Sensor configured by index but not found");
continue;
}
sensor->set_address(this->found_sensors_[*sensor->get_index()]);
}
if (!sensor->setup_sensor()) {
this->status_set_error();
}
}
}
void DallasComponent::dump_config() {
ESP_LOGCONFIG(TAG, "DallasComponent:");
LOG_PIN(" Pin: ", this->pin_);
LOG_UPDATE_INTERVAL(this);
if (this->found_sensors_.empty()) {
ESP_LOGW(TAG, " Found no sensors!");
} else {
ESP_LOGD(TAG, " Found sensors:");
for (auto &address : this->found_sensors_) {
ESP_LOGD(TAG, " 0x%s", format_hex(address).c_str());
}
}
for (auto *sensor : this->sensors_) {
LOG_SENSOR(" ", "Device", sensor);
if (sensor->get_index().has_value()) {
ESP_LOGCONFIG(TAG, " Index %u", *sensor->get_index());
if (*sensor->get_index() >= this->found_sensors_.size()) {
ESP_LOGE(TAG, "Couldn't find sensor by index - not connected. Proceeding without it.");
continue;
}
}
ESP_LOGCONFIG(TAG, " Address: %s", sensor->get_address_name().c_str());
ESP_LOGCONFIG(TAG, " Resolution: %u", sensor->get_resolution());
}
}
void DallasComponent::register_sensor(DallasTemperatureSensor *sensor) { this->sensors_.push_back(sensor); }
void DallasComponent::update() {
this->status_clear_warning();
bool result;
{
InterruptLock lock;
result = this->one_wire_->reset();
}
if (!result) {
if (!this->found_sensors_.empty()) {
// Only log error if at the start sensors were found (and thus are disconnected during uptime)
ESP_LOGE(TAG, "Requesting conversion failed");
this->status_set_warning();
}
for (auto *sensor : this->sensors_) {
sensor->publish_state(NAN);
}
return;
}
{
InterruptLock lock;
this->one_wire_->skip();
this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
}
for (auto *sensor : this->sensors_) {
if (sensor->get_address() == 0) {
ESP_LOGV(TAG, "'%s' - Indexed sensor not found at startup, skipping update", sensor->get_name().c_str());
sensor->publish_state(NAN);
continue;
}
this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
bool res = sensor->read_scratch_pad();
if (!res) {
ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
sensor->publish_state(NAN);
this->status_set_warning();
return;
}
if (!sensor->check_scratch_pad()) {
sensor->publish_state(NAN);
this->status_set_warning();
return;
}
float tempc = sensor->get_temp_c();
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", sensor->get_name().c_str(), tempc);
sensor->publish_state(tempc);
});
}
}
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
optional<uint8_t> DallasTemperatureSensor::get_index() const { return this->index_; }
void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; }
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
uint64_t DallasTemperatureSensor::get_address() { return this->address_; }
const std::string &DallasTemperatureSensor::get_address_name() {
if (this->address_name_.empty()) {
this->address_name_ = std::string("0x") + format_hex(this->address_);
}
return this->address_name_;
}
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
auto *wire = this->parent_->one_wire_;
{
InterruptLock lock;
if (!wire->reset()) {
return false;
}
wire->select(this->address_);
wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD);
for (unsigned char &i : this->scratch_pad_) {
i = wire->read8();
}
}
return true;
}
bool DallasTemperatureSensor::setup_sensor() {
bool r = this->read_scratch_pad();
if (!r) {
ESP_LOGE(TAG, "Reading scratchpad failed: reset");
return false;
}
if (!this->check_scratch_pad())
return false;
if (this->scratch_pad_[4] == this->resolution_)
return false;
if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
// DS18S20 doesn't support resolution.
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
return false;
}
switch (this->resolution_) {
case 12:
this->scratch_pad_[4] = 0x7F;
break;
case 11:
this->scratch_pad_[4] = 0x5F;
break;
case 10:
this->scratch_pad_[4] = 0x3F;
break;
case 9:
default:
this->scratch_pad_[4] = 0x1F;
break;
}
auto *wire = this->parent_->one_wire_;
{
InterruptLock lock;
if (wire->reset()) {
wire->select(this->address_);
wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
wire->write8(this->scratch_pad_[2]); // high alarm temp
wire->write8(this->scratch_pad_[3]); // low alarm temp
wire->write8(this->scratch_pad_[4]); // resolution
wire->reset();
// write value to EEPROM
wire->select(this->address_);
wire->write8(0x48);
}
}
delay(20); // allow it to finish operation
wire->reset();
return true;
}
bool DallasTemperatureSensor::check_scratch_pad() {
bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
bool config_validity = false;
switch (this->get_address8()[0]) {
case DALLAS_MODEL_DS18B20:
config_validity = ((this->scratch_pad_[4] & 0x9F) == 0x1F);
break;
default:
config_validity = ((this->scratch_pad_[4] & 0x10) == 0x10);
}
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
crc8(this->scratch_pad_, 8));
#endif
if (!chksum_validity) {
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
} else if (!config_validity) {
ESP_LOGW(TAG, "'%s' - Scratch pad config register invalid!", this->get_name().c_str());
}
return chksum_validity && config_validity;
}
float DallasTemperatureSensor::get_temp_c() {
int16_t temp = (int16_t(this->scratch_pad_[1]) << 11) | (int16_t(this->scratch_pad_[0]) << 3);
if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
int diff = (this->scratch_pad_[7] - this->scratch_pad_[6]) << 7;
temp = ((temp & 0xFFF0) << 3) - 16 + (diff / this->scratch_pad_[7]);
}
return temp / 128.0f;
}
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
} // namespace dallas
} // namespace esphome

View File

@ -1,79 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esp_one_wire.h"
#include <vector>
namespace esphome {
namespace dallas {
class DallasTemperatureSensor;
class DallasComponent : public PollingComponent {
public:
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
void register_sensor(DallasTemperatureSensor *sensor);
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void update() override;
protected:
friend DallasTemperatureSensor;
InternalGPIOPin *pin_;
ESPOneWire *one_wire_;
std::vector<DallasTemperatureSensor *> sensors_;
std::vector<uint64_t> found_sensors_;
};
/// Internal class that helps us create multiple sensors for one Dallas hub.
class DallasTemperatureSensor : public sensor::Sensor {
public:
void set_parent(DallasComponent *parent) { parent_ = parent; }
/// Helper to get a pointer to the address as uint8_t.
uint8_t *get_address8();
uint64_t get_address();
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
const std::string &get_address_name();
/// Set the 64-bit unsigned address for this sensor.
void set_address(uint64_t address);
/// Get the index of this sensor. (0 if using address.)
optional<uint8_t> get_index() const;
/// Set the index of this sensor. If using index, address will be set after setup.
void set_index(uint8_t index);
/// Get the set resolution for this sensor.
uint8_t get_resolution() const;
/// Set the resolution for this sensor.
void set_resolution(uint8_t resolution);
/// Get the number of milliseconds we have to wait for the conversion phase.
uint16_t millis_to_wait_for_conversion() const;
bool setup_sensor();
bool read_scratch_pad();
bool check_scratch_pad();
float get_temp_c();
std::string unique_id() override;
protected:
DallasComponent *parent_;
uint64_t address_;
optional<uint8_t> index_;
uint8_t resolution_;
std::string address_name_;
uint8_t scratch_pad_[9] = {
0,
};
};
} // namespace dallas
} // namespace esphome

View File

@ -1,252 +0,0 @@
#include "esp_one_wire.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace dallas {
static const char *const TAG = "dallas.one_wire";
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
const int ONE_WIRE_ROM_SEARCH = 0xF0;
ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
bool HOT IRAM_ATTR ESPOneWire::reset() {
// See reset here:
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
// Wait for communication to clear (delay G)
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
uint8_t retries = 125;
do {
if (--retries == 0)
return false;
delayMicroseconds(2);
} while (!pin_.digital_read());
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
delayMicroseconds(480);
// Release the bus, delay I
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(70);
// sample bus, 0=device(s) present, 1=no device present
bool r = !pin_.digital_read();
// delay J
delayMicroseconds(410);
return r;
}
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
// drive bus low
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// from datasheet:
// write 0 low time: t_low0: min=60µs, max=120µs
// write 1 low time: t_low1: min=1µs, max=15µs
// time slot: t_slot: min=60µs, max=120µs
// recovery time: t_rec: min=1µs
// ds18b20 appears to read the bus after roughly 14µs
uint32_t delay0 = bit ? 6 : 60;
uint32_t delay1 = bit ? 54 : 5;
// delay A/C
delayMicroseconds(delay0);
// release bus
pin_.digital_write(true);
// delay B/D
delayMicroseconds(delay1);
}
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
// drive bus low
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// note: for reading we'll need very accurate timing, as the
// timing for the digital_read() is tight; according to the datasheet,
// we should read at the end of 16µs starting from the bus low
// typically, the ds18b20 pulls the line high after 11µs for a logical 1
// and 29µs for a logical 0
uint32_t start = micros();
// datasheet says >1µs
delayMicroseconds(3);
// release bus, delay E
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// Unfortunately some frameworks have different characteristics than others
// esp32 arduino appears to pull the bus low only after the digital_write(false),
// whereas on esp-idf it already happens during the pin_mode(OUTPUT)
// manually correct for this with these constants.
#ifdef USE_ESP32
uint32_t timing_constant = 12;
#else
uint32_t timing_constant = 14;
#endif
// measure from start value directly, to get best accurate timing no matter
// how long pin_mode/delayMicroseconds took
while (micros() - start < timing_constant)
;
// sample bus to read bit from peer
bool r = pin_.digital_read();
// read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
uint32_t now = micros();
if (now - start < 60)
delayMicroseconds(60 - (now - start));
return r;
}
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
for (uint8_t i = 0; i < 8; i++) {
this->write_bit(bool((1u << i) & val));
}
}
void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
for (uint8_t i = 0; i < 64; i++) {
this->write_bit(bool((1ULL << i) & val));
}
}
uint8_t IRAM_ATTR ESPOneWire::read8() {
uint8_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint8_t(this->read_bit()) << i);
}
return ret;
}
uint64_t IRAM_ATTR ESPOneWire::read64() {
uint64_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint64_t(this->read_bit()) << i);
}
return ret;
}
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
this->write8(ONE_WIRE_ROM_SELECT);
this->write64(address);
}
void IRAM_ATTR ESPOneWire::reset_search() {
this->last_discrepancy_ = 0;
this->last_device_flag_ = false;
this->rom_number_ = 0;
}
uint64_t IRAM_ATTR ESPOneWire::search() {
if (this->last_device_flag_) {
return 0u;
}
{
InterruptLock lock;
if (!this->reset()) {
// Reset failed or no devices present
this->reset_search();
return 0u;
}
}
uint8_t id_bit_number = 1;
uint8_t last_zero = 0;
uint8_t rom_byte_number = 0;
bool search_result = false;
uint8_t rom_byte_mask = 1;
{
InterruptLock lock;
// Initiate search
this->write8(ONE_WIRE_ROM_SEARCH);
do {
// read bit
bool id_bit = this->read_bit();
// read its complement
bool cmp_id_bit = this->read_bit();
if (id_bit && cmp_id_bit) {
// No devices participating in search
break;
}
bool branch;
if (id_bit != cmp_id_bit) {
// only chose one branch, the other one doesn't have any devices.
branch = id_bit;
} else {
// there are devices with both 0s and 1s at this bit
if (id_bit_number < this->last_discrepancy_) {
branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0;
} else {
branch = id_bit_number == this->last_discrepancy_;
}
if (!branch) {
last_zero = id_bit_number;
}
}
if (branch) {
// set bit
this->rom_number8_()[rom_byte_number] |= rom_byte_mask;
} else {
// clear bit
this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask;
}
// choose/announce branch
this->write_bit(branch);
id_bit_number++;
rom_byte_mask <<= 1;
if (rom_byte_mask == 0u) {
// go to next byte
rom_byte_number++;
rom_byte_mask = 1;
}
} while (rom_byte_number < 8); // loop through all bytes
}
if (id_bit_number >= 65) {
this->last_discrepancy_ = last_zero;
if (this->last_discrepancy_ == 0) {
// we're at root and have no choices left, so this was the last one.
this->last_device_flag_ = true;
}
search_result = true;
}
search_result = search_result && (this->rom_number8_()[0] != 0);
if (!search_result) {
this->reset_search();
return 0u;
}
return this->rom_number_;
}
std::vector<uint64_t> ESPOneWire::search_vec() {
std::vector<uint64_t> res;
this->reset_search();
uint64_t address;
while ((address = this->search()) != 0u)
res.push_back(address);
return res;
}
void IRAM_ATTR ESPOneWire::skip() {
this->write8(0xCC); // skip ROM
}
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
} // namespace dallas
} // namespace esphome

View File

@ -1,68 +0,0 @@
#pragma once
#include "esphome/core/hal.h"
#include <vector>
namespace esphome {
namespace dallas {
extern const uint8_t ONE_WIRE_ROM_SELECT;
extern const int ONE_WIRE_ROM_SEARCH;
class ESPOneWire {
public:
explicit ESPOneWire(InternalGPIOPin *pin);
/** Reset the bus, should be done before all write operations.
*
* Takes approximately 1ms.
*
* @return Whether the operation was successful.
*/
bool reset();
/// Write a single bit to the bus, takes about 70µs.
void write_bit(bool bit);
/// Read a single bit from the bus, takes about 70µs
bool read_bit();
/// Write a word to the bus. LSB first.
void write8(uint8_t val);
/// Write a 64 bit unsigned integer to the bus. LSB first.
void write64(uint64_t val);
/// Write a command to the bus that addresses all devices by skipping the ROM.
void skip();
/// Read an 8 bit word from the bus.
uint8_t read8();
/// Read an 64-bit unsigned integer from the bus.
uint64_t read64();
/// Select a specific address on the bus for the following command.
void select(uint64_t address);
/// Reset the device search.
void reset_search();
/// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
uint64_t search();
/// Helper that wraps search in a std::vector.
std::vector<uint64_t> search_vec();
protected:
/// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
inline uint8_t *rom_number8_();
ISRInternalGPIOPin pin_;
uint8_t last_discrepancy_{0};
bool last_device_flag_{false};
uint64_t rom_number_{0};
};
} // namespace dallas
} // namespace esphome

View File

@ -1,50 +1,5 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ADDRESS,
CONF_DALLAS_ID,
CONF_INDEX,
CONF_RESOLUTION,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
CONFIG_SCHEMA = cv.invalid(
'The "dallas" sensor is now "dallas_temp"\nhttps://esphome.io/components/sensor/dallas_temp'
)
from . import DallasComponent, dallas_ns
DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sensor)
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
DallasTemperatureSensor,
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent),
cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
cv.Optional(CONF_INDEX): cv.positive_int,
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
}
),
cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX),
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_DALLAS_ID])
var = await sensor.new_sensor(config)
if CONF_ADDRESS in config:
cg.add(var.set_address(config[CONF_ADDRESS]))
else:
cg.add(var.set_index(config[CONF_INDEX]))
if CONF_RESOLUTION in config:
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
cg.add(var.set_parent(hub))
cg.add(hub.register_sensor(var))

View File

@ -0,0 +1 @@
CODEOWNERS = ["@ssieb"]

View File

@ -0,0 +1,172 @@
#include "dallas_temp.h"
#include "esphome/core/log.h"
namespace esphome {
namespace dallas_temp {
static const char *const TAG = "dallas.temp.sensor";
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
static const uint8_t DALLAS_COMMAND_COPY_SCRATCH_PAD = 0x48;
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion_() const {
switch (this->resolution_) {
case 9:
return 94;
case 10:
return 188;
case 11:
return 375;
default:
return 750;
}
}
void DallasTemperatureSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Dallas Temperature Sensor:");
if (this->address_ == 0) {
ESP_LOGW(TAG, " Unable to select an address");
return;
}
LOG_ONE_WIRE_DEVICE(this);
ESP_LOGCONFIG(TAG, " Resolution: %u bits", this->resolution_);
LOG_UPDATE_INTERVAL(this);
}
void DallasTemperatureSensor::update() {
if (this->address_ == 0)
return;
this->status_clear_warning();
this->send_command_(DALLAS_COMMAND_START_CONVERSION);
this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] {
if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
this->publish_state(NAN);
return;
}
float tempc = this->get_temp_c_();
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc);
this->publish_state(tempc);
});
}
void IRAM_ATTR DallasTemperatureSensor::read_scratch_pad_int_() {
for (uint8_t &i : this->scratch_pad_) {
i = this->bus_->read8();
}
}
bool DallasTemperatureSensor::read_scratch_pad_() {
bool success;
{
InterruptLock lock;
success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
if (success)
this->read_scratch_pad_int_();
}
if (!success) {
ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
this->status_set_warning("bus reset failed");
}
return success;
}
void DallasTemperatureSensor::setup() {
ESP_LOGCONFIG(TAG, "setting up Dallas temperature sensor...");
if (!this->check_address_())
return;
if (!this->read_scratch_pad_())
return;
if (!this->check_scratch_pad_())
return;
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
// DS18S20 doesn't support resolution.
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
return;
}
uint8_t res;
switch (this->resolution_) {
case 12:
res = 0x7F;
break;
case 11:
res = 0x5F;
break;
case 10:
res = 0x3F;
break;
case 9:
default:
res = 0x1F;
break;
}
if (this->scratch_pad_[4] == res)
return;
this->scratch_pad_[4] = res;
{
InterruptLock lock;
if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
this->bus_->write8(this->scratch_pad_[2]); // high alarm temp
this->bus_->write8(this->scratch_pad_[3]); // low alarm temp
this->bus_->write8(this->scratch_pad_[4]); // resolution
}
// write value to EEPROM
this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
}
}
bool DallasTemperatureSensor::check_scratch_pad_() {
bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
crc8(this->scratch_pad_, 8));
#endif
if (!chksum_validity) {
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
this->status_set_warning("scratch pad checksum invalid");
}
return chksum_validity;
}
float DallasTemperatureSensor::get_temp_c_() {
int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0];
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
if (this->scratch_pad_[7] != 0x10)
ESP_LOGE(TAG, "unexpected COUNT_PER_C value: %u", this->scratch_pad_[7]);
temp = ((temp & 0xfff7) << 3) + (0x10 - this->scratch_pad_[6]) - 4;
} else {
switch (this->resolution_) {
case 9:
temp &= 0xfff8;
break;
case 10:
temp &= 0xfffc;
break;
case 11:
temp &= 0xfffe;
break;
case 12:
default:
break;
}
}
return temp / 16.0f;
}
} // namespace dallas_temp
} // namespace esphome

View File

@ -0,0 +1,32 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/one_wire/one_wire.h"
namespace esphome {
namespace dallas_temp {
class DallasTemperatureSensor : public PollingComponent, public sensor::Sensor, public one_wire::OneWireDevice {
public:
void setup() override;
void update() override;
void dump_config() override;
/// Set the resolution for this sensor.
void set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
protected:
uint8_t resolution_;
uint8_t scratch_pad_[9] = {0};
/// Get the number of milliseconds we have to wait for the conversion phase.
uint16_t millis_to_wait_for_conversion_() const;
bool read_scratch_pad_();
void read_scratch_pad_int_();
bool check_scratch_pad_();
float get_temp_c_();
};
} // namespace dallas_temp
} // namespace esphome

View File

@ -0,0 +1,43 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import one_wire, sensor
from esphome.const import (
CONF_RESOLUTION,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
dallas_temp_ns = cg.esphome_ns.namespace("dallas_temp")
DallasTemperatureSensor = dallas_temp_ns.class_(
"DallasTemperatureSensor",
cg.PollingComponent,
sensor.Sensor,
one_wire.OneWireDevice,
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
DallasTemperatureSensor,
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
}
)
.extend(one_wire.one_wire_device_schema())
.extend(cv.polling_component_schema("60s"))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await one_wire.register_one_wire_device(var, config)
cg.add(var.set_resolution(config[CONF_RESOLUTION]))

View File

@ -17,7 +17,7 @@ from esphome.const import (
CONF_VSYNC_PIN,
)
from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components.esp32 import add_idf_component
from esphome.cpp_helpers import setup_entity
DEPENDENCIES = ["esp32"]
@ -290,8 +290,11 @@ async def to_code(config):
cg.add_define("USE_ESP32_CAMERA")
if CORE.using_esp_idf:
cg.add_library("espressif/esp32-camera", "1.0.0")
add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
add_idf_component(
name="esp32-camera",
repo="https://github.com/espressif/esp32-camera.git",
ref="v2.0.9",
)
for conf in config.get(CONF_ON_STREAM_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@ -0,0 +1,25 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.const import CONF_ID, CONF_PIN
from esphome.components.one_wire import OneWireBus
from .. import gpio_ns
CODEOWNERS = ["@ssieb"]
GPIOOneWireBus = gpio_ns.class_("GPIOOneWireBus", OneWireBus, cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(GPIOOneWireBus),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))

View File

@ -0,0 +1,199 @@
#include "gpio_one_wire.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace gpio {
static const char *const TAG = "gpio.one_wire";
void GPIOOneWireBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up 1-wire bus...");
this->search();
}
void GPIOOneWireBus::dump_config() {
ESP_LOGCONFIG(TAG, "GPIO 1-wire bus:");
LOG_PIN(" Pin: ", this->t_pin_);
this->dump_devices_(TAG);
}
bool HOT IRAM_ATTR GPIOOneWireBus::reset() {
// See reset here:
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
// Wait for communication to clear (delay G)
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
uint8_t retries = 125;
do {
if (--retries == 0)
return false;
delayMicroseconds(2);
} while (!pin_.digital_read());
bool r;
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
delayMicroseconds(480);
// Release the bus, delay I
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(70);
// sample bus, 0=device(s) present, 1=no device present
r = !pin_.digital_read();
// delay J
delayMicroseconds(410);
return r;
}
void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
// drive bus low
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// from datasheet:
// write 0 low time: t_low0: min=60µs, max=120µs
// write 1 low time: t_low1: min=1µs, max=15µs
// time slot: t_slot: min=60µs, max=120µs
// recovery time: t_rec: min=1µs
// ds18b20 appears to read the bus after roughly 14µs
uint32_t delay0 = bit ? 6 : 60;
uint32_t delay1 = bit ? 54 : 5;
// delay A/C
delayMicroseconds(delay0);
// release bus
pin_.digital_write(true);
// delay B/D
delayMicroseconds(delay1);
}
bool HOT IRAM_ATTR GPIOOneWireBus::read_bit_() {
// drive bus low
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// note: for reading we'll need very accurate timing, as the
// timing for the digital_read() is tight; according to the datasheet,
// we should read at the end of 16µs starting from the bus low
// typically, the ds18b20 pulls the line high after 11µs for a logical 1
// and 29µs for a logical 0
uint32_t start = micros();
// datasheet says >1µs
delayMicroseconds(2);
// release bus, delay E
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// measure from start value directly, to get best accurate timing no matter
// how long pin_mode/delayMicroseconds took
delayMicroseconds(12 - (micros() - start));
// sample bus to read bit from peer
bool r = pin_.digital_read();
// read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
uint32_t now = micros();
if (now - start < 60)
delayMicroseconds(60 - (now - start));
return r;
}
void IRAM_ATTR GPIOOneWireBus::write8(uint8_t val) {
for (uint8_t i = 0; i < 8; i++) {
this->write_bit_(bool((1u << i) & val));
}
}
void IRAM_ATTR GPIOOneWireBus::write64(uint64_t val) {
for (uint8_t i = 0; i < 64; i++) {
this->write_bit_(bool((1ULL << i) & val));
}
}
uint8_t IRAM_ATTR GPIOOneWireBus::read8() {
uint8_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint8_t(this->read_bit_()) << i);
}
return ret;
}
uint64_t IRAM_ATTR GPIOOneWireBus::read64() {
uint64_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint64_t(this->read_bit_()) << i);
}
return ret;
}
void GPIOOneWireBus::reset_search() {
this->last_discrepancy_ = 0;
this->last_device_flag_ = false;
this->address_ = 0;
}
uint64_t IRAM_ATTR GPIOOneWireBus::search_int() {
if (this->last_device_flag_)
return 0u;
uint8_t last_zero = 0;
uint64_t bit_mask = 1;
uint64_t address = this->address_;
// Initiate search
for (int bit_number = 1; bit_number <= 64; bit_number++, bit_mask <<= 1) {
// read bit
bool id_bit = this->read_bit_();
// read its complement
bool cmp_id_bit = this->read_bit_();
if (id_bit && cmp_id_bit) {
// No devices participating in search
return 0;
}
bool branch;
if (id_bit != cmp_id_bit) {
// only chose one branch, the other one doesn't have any devices.
branch = id_bit;
} else {
// there are devices with both 0s and 1s at this bit
if (bit_number < this->last_discrepancy_) {
branch = (address & bit_mask) > 0;
} else {
branch = bit_number == this->last_discrepancy_;
}
if (!branch) {
last_zero = bit_number;
}
}
if (branch) {
address |= bit_mask;
} else {
address &= ~bit_mask;
}
// choose/announce branch
this->write_bit_(branch);
}
this->last_discrepancy_ = last_zero;
if (this->last_discrepancy_ == 0) {
// we're at root and have no choices left, so this was the last one.
this->last_device_flag_ = true;
}
this->address_ = address;
return address;
}
} // namespace gpio
} // namespace esphome

View File

@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/one_wire/one_wire.h"
namespace esphome {
namespace gpio {
class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_pin(InternalGPIOPin *pin) {
this->t_pin_ = pin;
this->pin_ = pin->to_isr();
}
bool reset() override;
void write8(uint8_t val) override;
void write64(uint64_t val) override;
uint8_t read8() override;
uint64_t read64() override;
protected:
InternalGPIOPin *t_pin_;
ISRInternalGPIOPin pin_;
uint8_t last_discrepancy_{0};
bool last_device_flag_{false};
uint64_t address_;
void reset_search() override;
uint64_t search_int() override;
void write_bit_(bool bit);
bool read_bit_();
};
} // namespace gpio
} // namespace esphome

View File

@ -56,7 +56,7 @@ void HE60rCover::endstop_reached_(CoverOperation operation) {
this->position = new_position;
this->current_operation = COVER_OPERATION_IDLE;
if (this->last_command_ == operation) {
float dur = (now - this->start_dir_time_) / 1e3f;
float dur = (float) (now - this->start_dir_time_) / 1e3f;
ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(),
operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur);
}
@ -69,7 +69,6 @@ void HE60rCover::set_current_operation_(cover::CoverOperation operation) {
this->current_operation = operation;
if (operation != COVER_OPERATION_IDLE)
this->last_recompute_time_ = millis();
this->publish_state();
}
}
@ -129,7 +128,7 @@ void HE60rCover::update_() {
if (this->toggles_needed_ != 0) {
if ((this->counter_++ & 0x3) == 0) {
this->toggles_needed_--;
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, this->toggles_needed_);
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%u", this->toggles_needed_);
this->write_byte(TOGGLE_BYTE);
} else {
this->write_byte(QUERY_BYTE);
@ -235,31 +234,28 @@ void HE60rCover::recompute_position_() {
return;
const uint32_t now = millis();
float dir;
float action_dur;
switch (this->current_operation) {
case COVER_OPERATION_OPENING:
dir = 1.0f;
action_dur = this->open_duration_;
break;
case COVER_OPERATION_CLOSING:
dir = -1.0f;
action_dur = this->close_duration_;
break;
default:
return;
}
if (now > this->last_recompute_time_) {
auto diff = now - last_recompute_time_;
auto delta = dir * diff / action_dur;
auto diff = (unsigned) (now - last_recompute_time_);
float delta;
switch (this->current_operation) {
case COVER_OPERATION_OPENING:
delta = (float) diff / (float) this->open_duration_;
break;
case COVER_OPERATION_CLOSING:
delta = -(float) diff / (float) this->close_duration_;
break;
default:
return;
}
// make sure our guesstimate never reaches full open or close.
this->position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
ESP_LOGD(TAG, "Recompute %dms, dir=%f, action_dur=%f, delta=%f, pos=%f", (int) diff, dir, action_dur, delta,
this->position);
auto new_position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
ESP_LOGD(TAG, "Recompute %ums, dir=%u, delta=%f, pos=%f", diff, this->current_operation, delta, new_position);
this->last_recompute_time_ = now;
this->publish_state();
if (this->position != new_position) {
this->position = new_position;
this->publish_state();
}
}
}

View File

@ -25,15 +25,14 @@ class HE60rCover : public cover::Cover, public Component, public uart::UARTDevic
void control(const cover::CoverCall &call) override;
bool is_at_target_() const;
void start_direction_(cover::CoverOperation dir);
void update_operation_(cover::CoverOperation dir);
void endstop_reached_(cover::CoverOperation operation);
void recompute_position_();
void set_current_operation_(cover::CoverOperation operation);
void process_rx_(uint8_t data);
uint32_t open_duration_{0};
uint32_t close_duration_{0};
uint32_t toggles_needed_{0};
unsigned open_duration_{0};
unsigned close_duration_{0};
unsigned toggles_needed_{0};
cover::CoverOperation next_direction_{cover::COVER_OPERATION_IDLE};
cover::CoverOperation last_command_{cover::COVER_OPERATION_IDLE};
uint32_t last_recompute_time_{0};

View File

@ -37,38 +37,14 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
this->set_volume_(volume);
this->unmute_();
}
if (this->i2s_state_ != I2S_STATE_RUNNING) {
return;
}
if (call.get_command().has_value()) {
switch (call.get_command().value()) {
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
if (!this->audio_->isRunning())
this->audio_->pauseResume();
this->state = play_state;
break;
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
if (this->audio_->isRunning())
this->audio_->pauseResume();
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
break;
case media_player::MEDIA_PLAYER_COMMAND_STOP:
this->stop();
break;
case media_player::MEDIA_PLAYER_COMMAND_MUTE:
this->mute_();
break;
case media_player::MEDIA_PLAYER_COMMAND_UNMUTE:
this->unmute_();
break;
case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
this->audio_->pauseResume();
if (this->audio_->isRunning()) {
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
} else {
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
}
break;
case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP: {
float new_volume = this->volume + 0.1f;
if (new_volume > 1.0f)
@ -85,6 +61,36 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
this->unmute_();
break;
}
default:
break;
}
if (this->i2s_state_ != I2S_STATE_RUNNING) {
return;
}
switch (call.get_command().value()) {
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
if (!this->audio_->isRunning())
this->audio_->pauseResume();
this->state = play_state;
break;
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
if (this->audio_->isRunning())
this->audio_->pauseResume();
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
break;
case media_player::MEDIA_PLAYER_COMMAND_STOP:
this->stop();
break;
case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
this->audio_->pauseResume();
if (this->audio_->isRunning()) {
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
} else {
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
}
break;
default:
break;
}
}
this->publish_state();

View File

@ -69,6 +69,7 @@ MODELS = {
"ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay),
"ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay),
"ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay),
"ST7735": ili9xxx_ns.class_("ILI9XXXST7735", ILI9XXXDisplay),
"ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay),
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
@ -134,6 +135,7 @@ def _validate(config):
"ILI9341",
"ILI9342",
"ST7789V",
"ST7735",
]:
raise cv.Invalid("Selected model can't run on ESP8266.")

View File

@ -70,6 +70,7 @@ static const uint8_t ILI9XXX_PWCTR2 = 0xC1;
static const uint8_t ILI9XXX_PWCTR3 = 0xC2;
static const uint8_t ILI9XXX_PWCTR4 = 0xC3;
static const uint8_t ILI9XXX_PWCTR5 = 0xC4;
static const uint8_t ILI9XXX_PWCTR6 = 0xF6;
static const uint8_t ILI9XXX_VMCTR1 = 0xC5;
static const uint8_t ILI9XXX_IFCTR = 0xC6;
static const uint8_t ILI9XXX_VMCTR2 = 0xC7;
@ -91,6 +92,7 @@ static const uint8_t ILI9XXX_GMCTRN1 = 0xE1;
static const uint8_t ILI9XXX_CSCON = 0xF0;
static const uint8_t ILI9XXX_ADJCTL3 = 0xF7;
static const uint8_t ILI9XXX_DELAY = 0xFF; // followed by one byte of delay time in ms
} // namespace ili9xxx
} // namespace esphome

View File

@ -411,11 +411,19 @@ void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
uint8_t cmd, x, num_args;
while ((cmd = *addr++) != 0) {
x = *addr++;
num_args = x & 0x7F;
this->send_command(cmd, addr, num_args);
addr += num_args;
if (x & 0x80)
delay(150); // NOLINT
if (cmd == ILI9XXX_DELAY) {
ESP_LOGD(TAG, "Delay %dms", x);
delay(x);
} else {
num_args = x & 0x7F;
ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
this->send_command(cmd, addr, num_args);
addr += num_args;
if (x & 0x80) {
ESP_LOGD(TAG, "Delay 150ms");
delay(150); // NOLINT
}
}
}
}

View File

@ -35,7 +35,6 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
while ((cmd = *addr++) != 0) {
num_args = *addr++ & 0x7F;
bits = *addr;
esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
switch (cmd) {
case ILI9XXX_MADCTL: {
this->swap_xy_ = (bits & MADCTL_MV) != 0;
@ -51,6 +50,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
break;
}
case ILI9XXX_DELAY:
continue; // no args to skip
default:
break;
}
@ -269,5 +271,11 @@ class ILI9XXXGC9A01A : public ILI9XXXDisplay {
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
};
//----------- ILI9XXX_24_TFT display --------------
class ILI9XXXST7735 : public ILI9XXXDisplay {
public:
ILI9XXXST7735() : ILI9XXXDisplay(INITCMD_ST7735, 128, 160, false) {}
};
} // namespace ili9xxx
} // namespace esphome

View File

@ -370,6 +370,57 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
0x00 // End of list
};
static const uint8_t PROGMEM INITCMD_ST7735[] = {
ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms
ILI9XXX_DELAY, 10,
ILI9XXX_SLPOUT , 0, // Exit Sleep, delay
ILI9XXX_DELAY, 10,
ILI9XXX_PIXFMT , 1, 0x05,
ILI9XXX_FRMCTR1, 3, // 4: Frame rate control, 3 args + delay:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
ILI9XXX_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
ILI9XXX_FRMCTR3, 6, // 5: Framerate - partial mode, 6 args:
0x01, 0x2C, 0x2D, // Dot inversion mode
0x01, 0x2C, 0x2D, // Line inversion mode
ILI9XXX_INVCTR, 1, // 7: Display inversion control, 1 arg:
0x7, // Line inversion
ILI9XXX_PWCTR1, 3, // 7: Power control, 3 args, no delay:
0xA2,
0x02, // -4.6V
0x84, // AUTO mode
ILI9XXX_PWCTR2, 1, // 8: Power control, 1 arg, no delay:
0xC5, // VGH25=2.4C VGSEL=-10 VGH=3 * AVDD
ILI9XXX_PWCTR3, 2, // 9: Power control, 2 args, no delay:
0x0A, // Opamp current small
0x00, // Boost frequency
ILI9XXX_PWCTR4, 2, // 10: Power control, 2 args, no delay:
0x8A, // BCLK/2,
0x2A, // opamp current small & medium low
ILI9XXX_PWCTR5, 2, // 11: Power control, 2 args, no delay:
0x8A, 0xEE,
ILI9XXX_VMCTR1, 1, // 11: Power control, 2 args + delay:
0x0E,
ILI9XXX_GMCTRP1, 16, // 13: Gamma Adjustments (pos. polarity), 16 args + delay:
0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides
0x37, 0x32, 0x29, 0x2d, // accurate colors)
0x29, 0x25, 0x2B, 0x39,
0x00, 0x01, 0x03, 0x10,
ILI9XXX_GMCTRN1, 16, // 14: Gamma Adjustments (neg. polarity), 16 args + delay:
0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides
0x2E, 0x2C, 0x29, 0x2D, // accurate colors)
0x2E, 0x2E, 0x37, 0x3F,
0x00, 0x00, 0x02, 0x10,
ILI9XXX_MADCTL , 1, 0x00, // Memory Access Control, BGR
ILI9XXX_NORON , 0,
ILI9XXX_DELAY, 10,
ILI9XXX_DISPON , 0, // Display on
ILI9XXX_DELAY, 10,
00, // endo of list
};
// clang-format on
} // namespace ili9xxx
} // namespace esphome

View File

@ -9,8 +9,6 @@ import re
import requests
from magic import Magic
from PIL import Image
from esphome import core
from esphome.components import font
from esphome import external_files
@ -267,6 +265,9 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
def load_svg_image(file: bytes, resize: tuple[int, int]):
# Local import only to allow "validate_pillow_installed" to run *before* importing it
from PIL import Image
# This import is only needed in case of SVG images; adding it
# to the top would force configurations not using SVG to also have it
# installed for no reason.
@ -286,6 +287,9 @@ def load_svg_image(file: bytes, resize: tuple[int, int]):
async def to_code(config):
# Local import only to allow "validate_pillow_installed" to run *before* importing it
from PIL import Image
conf_file = config[CONF_FILE]
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:

View File

@ -0,0 +1,40 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS
CODEOWNERS = ["@ssieb"]
IS_PLATFORM_COMPONENT = True
CONF_ONE_WIRE_ID = "one_wire_id"
one_wire_ns = cg.esphome_ns.namespace("one_wire")
OneWireBus = one_wire_ns.class_("OneWireBus")
OneWireDevice = one_wire_ns.class_("OneWireDevice")
def one_wire_device_schema():
"""Create a schema for a 1-wire device.
:return: The 1-wire device schema, `extend` this in your config schema.
"""
schema = cv.Schema(
{
cv.GenerateID(CONF_ONE_WIRE_ID): cv.use_id(OneWireBus),
cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
}
)
return schema
async def register_one_wire_device(var, config):
"""Register an 1-wire device with the given config.
Sets the 1-wire bus to use and the 1-wire address.
This is a coroutine, you need to await it with a 'yield' expression!
"""
parent = await cg.get_variable(config[CONF_ONE_WIRE_ID])
cg.add(var.set_one_wire_bus(parent))
if (address := config.get(CONF_ADDRESS)) is not None:
cg.add(var.set_address(address))

View File

@ -0,0 +1,40 @@
#include "one_wire.h"
namespace esphome {
namespace one_wire {
static const char *const TAG = "one_wire";
const std::string &OneWireDevice::get_address_name() {
if (this->address_name_.empty())
this->address_name_ = std::string("0x") + format_hex(this->address_);
return this->address_name_;
}
std::string OneWireDevice::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
bool OneWireDevice::send_command_(uint8_t cmd) {
if (!this->bus_->select(this->address_))
return false;
this->bus_->write8(cmd);
return true;
}
bool OneWireDevice::check_address_() {
if (this->address_ != 0)
return true;
auto devices = this->bus_->get_devices();
if (devices.empty()) {
ESP_LOGE(TAG, "No devices, can't auto-select address");
return false;
}
if (devices.size() > 1) {
ESP_LOGE(TAG, "More than one device, can't auto-select address");
return false;
}
this->address_ = devices[0];
return true;
}
} // namespace one_wire
} // namespace esphome

View File

@ -0,0 +1,44 @@
#pragma once
#include "one_wire_bus.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace one_wire {
#define LOG_ONE_WIRE_DEVICE(this) \
ESP_LOGCONFIG(TAG, " Address: %s (%s)", this->get_address_name().c_str(), \
LOG_STR_ARG(this->bus_->get_model_str(this->address_ & 0xff)));
class OneWireDevice {
public:
/// @brief store the address of the device
/// @param address of the device
void set_address(uint64_t address) { this->address_ = address; }
/// @brief store the pointer to the OneWireBus to use
/// @param bus pointer to the OneWireBus object
void set_one_wire_bus(OneWireBus *bus) { this->bus_ = bus; }
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
const std::string &get_address_name();
std::string unique_id();
protected:
uint64_t address_{0};
OneWireBus *bus_{nullptr}; ///< pointer to OneWireBus instance
std::string address_name_;
/// @brief find an address if necessary
/// should be called from setup
bool check_address_();
/// @brief send command on the bus
/// @param cmd command to send
bool send_command_(uint8_t cmd);
};
} // namespace one_wire
} // namespace esphome

View File

@ -0,0 +1,88 @@
#include "one_wire_bus.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace one_wire {
static const char *const TAG = "one_wire";
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
static const uint8_t DALLAS_MODEL_DS1822 = 0x22;
static const uint8_t DALLAS_MODEL_DS18B20 = 0x28;
static const uint8_t DALLAS_MODEL_DS1825 = 0x3B;
static const uint8_t DALLAS_MODEL_DS28EA00 = 0x42;
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
const uint8_t ONE_WIRE_ROM_SEARCH = 0xF0;
const std::vector<uint64_t> &OneWireBus::get_devices() { return this->devices_; }
bool IRAM_ATTR OneWireBus::select(uint64_t address) {
if (!this->reset())
return false;
this->write8(ONE_WIRE_ROM_SELECT);
this->write64(address);
return true;
}
void OneWireBus::search() {
this->devices_.clear();
this->reset_search();
uint64_t address;
while (true) {
{
InterruptLock lock;
if (!this->reset()) {
// Reset failed or no devices present
return;
}
this->write8(ONE_WIRE_ROM_SEARCH);
address = this->search_int();
}
if (address == 0)
break;
auto *address8 = reinterpret_cast<uint8_t *>(&address);
if (crc8(address8, 7) != address8[7]) {
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
} else {
this->devices_.push_back(address);
}
}
}
void OneWireBus::skip() {
this->write8(0xCC); // skip ROM
}
const LogString *OneWireBus::get_model_str(uint8_t model) {
switch (model) {
case DALLAS_MODEL_DS18S20:
return LOG_STR("DS18S20");
case DALLAS_MODEL_DS1822:
return LOG_STR("DS1822");
case DALLAS_MODEL_DS18B20:
return LOG_STR("DS18B20");
case DALLAS_MODEL_DS1825:
return LOG_STR("DS1825");
case DALLAS_MODEL_DS28EA00:
return LOG_STR("DS28EA00");
default:
return LOG_STR("Unknown");
}
}
void OneWireBus::dump_devices_(const char *tag) {
if (this->devices_.empty()) {
ESP_LOGW(tag, " Found no devices!");
} else {
ESP_LOGCONFIG(tag, " Found devices:");
for (auto &address : this->devices_) {
ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff)));
}
}
}
} // namespace one_wire
} // namespace esphome

View File

@ -0,0 +1,61 @@
#pragma once
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <vector>
namespace esphome {
namespace one_wire {
class OneWireBus {
public:
/** Reset the bus, should be done before all write operations.
*
* Takes approximately 1ms.
*
* @return Whether the operation was successful.
*/
virtual bool reset() = 0;
/// Write a word to the bus. LSB first.
virtual void write8(uint8_t val) = 0;
/// Write a 64 bit unsigned integer to the bus. LSB first.
virtual void write64(uint64_t val) = 0;
/// Write a command to the bus that addresses all devices by skipping the ROM.
void skip();
/// Read an 8 bit word from the bus.
virtual uint8_t read8() = 0;
/// Read an 64-bit unsigned integer from the bus.
virtual uint64_t read64() = 0;
/// Select a specific address on the bus for the following command.
bool select(uint64_t address);
/// Return the list of found devices.
const std::vector<uint64_t> &get_devices();
/// Search for 1-Wire devices on the bus.
void search();
/// Get the description string for this model.
const LogString *get_model_str(uint8_t model);
protected:
std::vector<uint64_t> devices_;
/// log the found devices
void dump_devices_(const char *tag);
/// Reset the device search.
virtual void reset_search() = 0;
/// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
virtual uint64_t search_int() = 0;
};
} // namespace one_wire
} // namespace esphome

View File

@ -0,0 +1 @@
CODEOWNERS = ["@clydebarrow"]

View File

@ -0,0 +1,72 @@
import subprocess
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import display
from esphome.const import (
CONF_ID,
CONF_DIMENSIONS,
CONF_WIDTH,
CONF_HEIGHT,
CONF_LAMBDA,
PLATFORM_HOST,
)
sdl_ns = cg.esphome_ns.namespace("sdl")
Sdl = sdl_ns.class_("Sdl", display.Display, cg.Component)
CONF_SDL_OPTIONS = "sdl_options"
CONF_SDL_ID = "sdl_id"
def get_sdl_options(value):
if value != "":
return value
try:
return subprocess.check_output(["sdl2-config", "--cflags", "--libs"]).decode()
except Exception as e:
raise cv.Invalid("Unable to run sdl2-config - have you installed sdl2?") from e
CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Sdl),
cv.Optional(CONF_SDL_OPTIONS, default=""): get_sdl_options,
cv.Required(CONF_DIMENSIONS): cv.Any(
cv.dimensions,
cv.Schema(
{
cv.Required(CONF_WIDTH): cv.int_,
cv.Required(CONF_HEIGHT): cv.int_,
}
),
),
}
)
),
cv.only_on(PLATFORM_HOST),
)
async def to_code(config):
for option in config[CONF_SDL_OPTIONS].split():
cg.add_build_flag(option)
cg.add_build_flag("-DSDL_BYTEORDER=4321")
var = cg.new_Pvariable(config[CONF_ID])
await display.register_display(var, config)
dimensions = config[CONF_DIMENSIONS]
if isinstance(dimensions, dict):
cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT]))
else:
(width, height) = dimensions
cg.add(var.set_dimensions(width, height))
if lamb := config.get(CONF_LAMBDA):
lambda_ = await cg.process_lambda(
lamb, [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View File

@ -0,0 +1,96 @@
#ifdef USE_HOST
#include "sdl_esphome.h"
#include "esphome/components/display/display_color_utils.h"
namespace esphome {
namespace sdl {
void Sdl::setup() {
ESP_LOGD(TAG, "Starting setup");
SDL_Init(SDL_INIT_VIDEO);
this->window_ = SDL_CreateWindow(App.get_name().c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
this->width_, this->height_, 0);
this->renderer_ = SDL_CreateRenderer(this->window_, -1, SDL_RENDERER_SOFTWARE);
this->texture_ =
SDL_CreateTexture(this->renderer_, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STATIC, this->width_, this->height_);
SDL_SetTextureBlendMode(this->texture_, SDL_BLENDMODE_BLEND);
ESP_LOGD(TAG, "Setup Complete");
}
void Sdl::update() {
this->do_update_();
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_))
return;
SDL_Rect rect{this->x_low_, this->y_low_, this->x_high_ + 1 - this->x_low_, this->y_high_ + 1 - this->y_low_};
this->x_low_ = this->width_;
this->y_low_ = this->height_;
this->x_high_ = 0;
this->y_high_ = 0;
SDL_RenderCopy(this->renderer_, this->texture_, &rect, &rect);
SDL_RenderPresent(this->renderer_);
}
void Sdl::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
SDL_Rect rect{x_start, y_start, w, h};
if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || big_endian) {
display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
x_pad);
} else {
auto stride = x_offset + w + x_pad;
auto data = ptr + (stride * y_offset + x_offset) * 2;
SDL_UpdateTexture(this->texture_, &rect, data, stride * 2);
}
SDL_RenderCopy(this->renderer_, this->texture_, &rect, &rect);
SDL_RenderPresent(this->renderer_);
}
void Sdl::draw_pixel_at(int x, int y, Color color) {
SDL_Rect rect{x, y, 1, 1};
auto data = (display::ColorUtil::color_to_565(color, display::COLOR_ORDER_RGB));
SDL_UpdateTexture(this->texture_, &rect, &data, 2);
if (x < this->x_low_)
this->x_low_ = x;
if (y < this->y_low_)
this->y_low_ = y;
if (x > this->x_high_)
this->x_high_ = x;
if (y > this->y_high_)
this->y_high_ = y;
}
void Sdl::loop() {
SDL_Event e;
if (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_QUIT:
exit(0);
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
if (e.button.button == 1) {
this->mouse_x = e.button.x;
this->mouse_y = e.button.y;
this->mouse_down = e.button.state != 0;
}
break;
case SDL_MOUSEMOTION:
if (e.motion.state & 1) {
this->mouse_x = e.button.x;
this->mouse_y = e.button.y;
this->mouse_down = true;
} else {
this->mouse_down = false;
}
break;
default:
ESP_LOGV(TAG, "Event %d", e.type);
break;
}
}
}
} // namespace sdl
} // namespace esphome
#endif

View File

@ -0,0 +1,54 @@
#pragma once
#ifdef USE_HOST
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/components/display/display.h"
#define SDL_MAIN_HANDLED
#include "SDL.h"
namespace esphome {
namespace sdl {
constexpr static const char *const TAG = "sdl";
class Sdl : public display::Display {
public:
display::DisplayType get_display_type() override { return display::DISPLAY_TYPE_COLOR; }
void update() override;
void loop() override;
void setup() override;
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
void draw_pixel_at(int x, int y, Color color) override;
void set_dimensions(uint16_t width, uint16_t height) {
this->width_ = width;
this->height_ = height;
}
int get_width() override { return this->width_; }
int get_height() override { return this->height_; }
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void dump_config() override { LOG_DISPLAY("", "SDL", this); }
int mouse_x{};
int mouse_y{};
bool mouse_down{};
protected:
int get_width_internal() override { return this->width_; }
int get_height_internal() override { return this->height_; }
int width_{};
int height_{};
SDL_Renderer *renderer_{};
SDL_Window *window_{};
SDL_Texture *texture_{};
uint16_t x_low_{0};
uint16_t y_low_{0};
uint16_t x_high_{0};
uint16_t y_high_{0};
};
} // namespace sdl
} // namespace esphome
#endif

View File

@ -0,0 +1,22 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.components import touchscreen
from ..display import Sdl, sdl_ns, CONF_SDL_ID
SdlTouchscreen = sdl_ns.class_("SdlTouchscreen", touchscreen.Touchscreen)
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(SdlTouchscreen),
cv.GenerateID(CONF_SDL_ID): cv.use_id(Sdl),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_parented(var, config[CONF_SDL_ID])
await touchscreen.register_touchscreen(var, config)

View File

@ -0,0 +1,26 @@
#pragma once
#ifdef USE_HOST
#include "../sdl_esphome.h"
#include "esphome/components/touchscreen/touchscreen.h"
namespace esphome {
namespace sdl {
class SdlTouchscreen : public touchscreen::Touchscreen, public Parented<Sdl> {
public:
void setup() override {
this->x_raw_max_ = this->display_->get_width();
this->y_raw_max_ = this->display_->get_height();
}
void update_touches() override {
if (this->parent_->mouse_down) {
add_raw_touch_position_(0, this->parent_->mouse_x, this->parent_->mouse_y);
}
}
};
} // namespace sdl
} // namespace esphome
#endif

View File

@ -7,6 +7,7 @@ from esphome.const import (
CONF_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_FIRMWARE,
)
from esphome.core import CORE, coroutine_with_priority
@ -24,6 +25,7 @@ PerformAction = update_ns.class_("PerformAction", automation.Action)
IsAvailableCondition = update_ns.class_("IsAvailableCondition", automation.Condition)
DEVICE_CLASSES = [
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_FIRMWARE,
]

View File

@ -8,6 +8,7 @@ from esphome.const import (
CONF_INC_PIN,
CONF_UD_PIN,
CONF_INITIAL_VALUE,
CONF_STEP_DELAY,
)
CODEOWNERS = ["@EtienneMD"]
@ -26,6 +27,13 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INITIAL_VALUE, default=1.0): cv.float_range(
min=0.01, max=1.0
),
cv.Optional(CONF_STEP_DELAY, default="1us"): cv.All(
cv.positive_time_period_microseconds,
cv.Range(
min=cv.TimePeriod(microseconds=1),
max=cv.TimePeriod(microseconds=100),
),
),
}
)
)
@ -44,3 +52,4 @@ async def to_code(config):
cg.add(var.set_ud_pin(ud_pin))
cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
cg.add(var.set_step_delay(config[CONF_STEP_DELAY]))

View File

@ -22,9 +22,9 @@ void X9cOutput::trim_value(int change_amount) {
for (int i = 0; i < abs(change_amount); i++) { // Move wiper
this->inc_pin_->digital_write(true);
delayMicroseconds(1);
delayMicroseconds(this->step_delay_);
this->inc_pin_->digital_write(false);
delayMicroseconds(1);
delayMicroseconds(this->step_delay_);
}
delayMicroseconds(100); // Let value settle
@ -69,6 +69,7 @@ void X9cOutput::dump_config() {
LOG_PIN(" Increment Pin: ", this->inc_pin_);
LOG_PIN(" Up/Down Pin: ", this->ud_pin_);
ESP_LOGCONFIG(TAG, " Initial Value: %f", this->initial_value_);
ESP_LOGCONFIG(TAG, " Step Delay: %d", this->step_delay_);
LOG_FLOAT_OUTPUT(this);
}

View File

@ -13,6 +13,7 @@ class X9cOutput : public output::FloatOutput, public Component {
void set_inc_pin(InternalGPIOPin *pin) { inc_pin_ = pin; }
void set_ud_pin(InternalGPIOPin *pin) { ud_pin_ = pin; }
void set_initial_value(float initial_value) { initial_value_ = initial_value; }
void set_step_delay(int step_delay) { step_delay_ = step_delay; }
void setup() override;
void dump_config() override;
@ -26,6 +27,7 @@ class X9cOutput : public output::FloatOutput, public Component {
InternalGPIOPin *ud_pin_;
float initial_value_;
float pot_value_;
int step_delay_;
};
} // namespace x9c

View File

@ -23,7 +23,7 @@ from esphome.const import (
CONF_EXTERNAL_COMPONENTS,
TARGET_PLATFORMS,
)
from esphome.core import CORE, EsphomeError
from esphome.core import CORE, EsphomeError, DocumentRange
from esphome.helpers import indent
from esphome.util import safe_print, OrderedDict
@ -139,7 +139,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
)
def run_validation_steps(self):
while self._validation_tasks:
while self._validation_tasks and not self.errors:
task = heapq.heappop(self._validation_tasks)
task.step.run(self)
@ -184,7 +184,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
def get_deepest_document_range_for_path(
self, path: ConfigPath, get_key: bool = False
) -> ESPHomeDataBase | None:
) -> DocumentRange | None:
data = self
doc_range = None
for index, path_item in enumerate(path):
@ -1123,4 +1123,4 @@ def read_config(command_line_substitutions):
safe_print("")
return None
return OrderedDict(res)
return res

View File

@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2024.6.0-dev"
__version__ = "2024.7.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (
@ -784,6 +784,7 @@ CONF_STATIC_IP = "static_ip"
CONF_STATUS = "status"
CONF_STB_PIN = "stb_pin"
CONF_STEP = "step"
CONF_STEP_DELAY = "step_delay"
CONF_STEP_MODE = "step_mode"
CONF_STEP_PIN = "step_pin"
CONF_STOP = "stop"

View File

@ -17,6 +17,7 @@ import time
from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, TypeVar
from urllib.parse import urlparse
import tornado
import tornado.concurrent
@ -166,6 +167,18 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
# use Popen() with a reading thread instead
self._use_popen = os.name == "nt"
def check_origin(self, origin):
if "ESPHOME_TRUSTED_DOMAINS" not in os.environ:
return super().check_origin(origin)
trusted_domains = [
s.strip() for s in os.environ["ESPHOME_TRUSTED_DOMAINS"].split(",")
]
url = urlparse(origin)
if url.hostname in trusted_domains:
return True
_LOGGER.info("check_origin %s, domain is not trusted", origin)
return False
def open(self, *args: str, **kwargs: str) -> None:
"""Handle new WebSocket connection."""
# Ensure messages from the subprocess are sent immediately

12
esphome/idf_component.yml Normal file
View File

@ -0,0 +1,12 @@
dependencies:
esp-tflite-micro:
git: https://github.com/espressif/esp-tflite-micro.git
esp32_camera:
git: https://github.com/espressif/esp32-camera.git
version: v2.0.9
mdns:
git: https://github.com/espressif/esp-protocols.git
version: mdns-v1.2.5
path: components/mdns
rules:
- if: "idf_version >=5.0"

View File

@ -142,7 +142,6 @@ platform_packages =
framework = espidf
lib_deps =
${common:idf.lib_deps}
espressif/esp32-camera@1.0.0 ; esp32_camera
droscy/esp_wireguard@0.4.1 ; wireguard
build_flags =
${common:idf.build_flags}

View File

@ -12,7 +12,7 @@ pyserial==3.5
platformio==6.1.15 # When updating platformio, also update Dockerfile
esptool==4.7.0
click==8.1.7
esphome-dashboard==20240429.1
esphome-dashboard==20240613.0
aioesphomeapi==24.3.0
zeroconf==0.132.2
python-magic==0.4.27

View File

@ -630,7 +630,7 @@ def lint_trailing_whitespace(fname, match):
"esphome/components/lock/lock.h",
"esphome/components/mqtt/mqtt_component.h",
"esphome/components/number/number.h",
"esphome/components/text/text.h",
"esphome/components/one_wire/one_wire.h",
"esphome/components/output/binary_output.h",
"esphome/components/output/float_output.h",
"esphome/components/nextion/nextion_base.h",
@ -638,6 +638,7 @@ def lint_trailing_whitespace(fname, match):
"esphome/components/sensor/sensor.h",
"esphome/components/stepper/stepper.h",
"esphome/components/switch/switch.h",
"esphome/components/text/text.h",
"esphome/components/text_sensor/text_sensor.h",
"esphome/components/valve/valve.h",
"esphome/core/component.h",

View File

@ -101,8 +101,10 @@ def clang_options(idedata):
# add library include directories using -isystem to suppress their errors
for directory in sorted(set(idedata["includes"]["build"])):
# skip our own directories, we add those later
if not directory.startswith(f"{root_path}/") or directory.startswith(
f"{root_path}/.pio/"
if (
not directory.startswith(f"{root_path}/")
or directory.startswith(f"{root_path}/.pio/")
or directory.startswith(f"{root_path}/managed_components/")
):
cmd.extend(["-isystem", directory])

View File

@ -10,6 +10,7 @@ from homeassistant.components.event import EventDeviceClass
from homeassistant.components.number import NumberDeviceClass
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
from homeassistant.components.update import UpdateDeviceClass
from homeassistant.components.valve import ValveDeviceClass
# pylint: enable=import-error
@ -27,6 +28,7 @@ DOMAINS = {
"number": NumberDeviceClass,
"sensor": SensorDeviceClass,
"switch": SwitchDeviceClass,
"update": UpdateDeviceClass,
"valve": ValveDeviceClass,
}

View File

@ -1,11 +1,11 @@
dallas:
pin: 4
one_wire:
- platform: gpio
pin: 4
sensor:
- platform: dallas
- platform: dallas_temp
address: 0x1C0000031EDD2A28
name: Dallas Temperature
resolution: 9
- platform: dallas
index: 1
- platform: dallas_temp
name: Dallas Temperature

View File

@ -0,0 +1,12 @@
host:
mac_address: "62:23:45:AF:B3:DD"
display:
- platform: sdl
id: sdl_display
update_interval: 1s
auto_clear_enabled: false
show_test_card: true
dimensions:
width: 450
height: 600

View File

@ -0,0 +1 @@
<<: !include common.yaml

View File

@ -5,3 +5,4 @@ output:
inc_pin: 4
ud_pin: 5
initial_value: 0.5
step_delay: 50us

View File

@ -5,3 +5,4 @@ output:
inc_pin: 4
ud_pin: 5
initial_value: 0.5
step_delay: 50us

View File

@ -5,3 +5,4 @@ output:
inc_pin: 14
ud_pin: 15
initial_value: 0.5
step_delay: 50us

View File

@ -5,3 +5,4 @@ output:
inc_pin: 14
ud_pin: 15
initial_value: 0.5
step_delay: 50us

View File

@ -5,3 +5,4 @@ output:
inc_pin: 14
ud_pin: 15
initial_value: 0.5
step_delay: 50us

View File

@ -5,3 +5,4 @@ output:
inc_pin: 4
ud_pin: 5
initial_value: 0.5
step_delay: 50us

View File

@ -315,11 +315,6 @@ as5600:
slow_filter: 8x
fast_filter: lsb6
dallas:
pin:
allow_other_uses: true
number: GPIO23
as3935_spi:
cs_pin:
ignore_strapping_warning: true
@ -714,13 +709,6 @@ sensor:
update_interval: 15s
iir_filter: 16x
i2c_id: i2c_bus
- platform: dallas
address: 0x1C0000031EDD2A28
name: Living Room Temperature
resolution: 9
- platform: dallas
index: 1
name: Living Room Temperature 2
- platform: dht
pin:
allow_other_uses: true