From 13fabf1cd8eae9fe783a9a83b5d4665b66c5b180 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Tue, 11 Jun 2024 18:05:44 -0700 Subject: [PATCH] change to new 1-wire platform (#6860) Co-authored-by: Samuel Sieb Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 3 + esphome/components/dallas/__init__.py | 24 +- .../components/dallas/dallas_component.cpp | 287 ------------------ esphome/components/dallas/dallas_component.h | 79 ----- esphome/components/dallas/esp_one_wire.cpp | 252 --------------- esphome/components/dallas/esp_one_wire.h | 68 ----- esphome/components/dallas/sensor.py | 51 +--- esphome/components/dallas_temp/__init__.py | 1 + .../components/dallas_temp/dallas_temp.cpp | 172 +++++++++++ esphome/components/dallas_temp/dallas_temp.h | 32 ++ esphome/components/dallas_temp/sensor.py | 43 +++ esphome/components/gpio/one_wire/__init__.py | 25 ++ .../gpio/one_wire/gpio_one_wire.cpp | 199 ++++++++++++ .../components/gpio/one_wire/gpio_one_wire.h | 41 +++ esphome/components/one_wire/__init__.py | 40 +++ esphome/components/one_wire/one_wire.cpp | 40 +++ esphome/components/one_wire/one_wire.h | 44 +++ esphome/components/one_wire/one_wire_bus.cpp | 88 ++++++ esphome/components/one_wire/one_wire_bus.h | 61 ++++ script/ci-custom.py | 3 +- .../{dallas => dallas_temp}/common.yaml | 10 +- .../test.esp32-c3-idf.yaml | 0 .../test.esp32-c3.yaml | 0 .../test.esp32-idf.yaml | 0 .../{dallas => dallas_temp}/test.esp32.yaml | 0 .../{dallas => dallas_temp}/test.esp8266.yaml | 0 .../{dallas => dallas_temp}/test.rp2040.yaml | 0 tests/test1.yaml | 12 - 28 files changed, 802 insertions(+), 773 deletions(-) delete mode 100644 esphome/components/dallas/dallas_component.cpp delete mode 100644 esphome/components/dallas/dallas_component.h delete mode 100644 esphome/components/dallas/esp_one_wire.cpp delete mode 100644 esphome/components/dallas/esp_one_wire.h create mode 100644 esphome/components/dallas_temp/__init__.py create mode 100644 esphome/components/dallas_temp/dallas_temp.cpp create mode 100644 esphome/components/dallas_temp/dallas_temp.h create mode 100644 esphome/components/dallas_temp/sensor.py create mode 100644 esphome/components/gpio/one_wire/__init__.py create mode 100644 esphome/components/gpio/one_wire/gpio_one_wire.cpp create mode 100644 esphome/components/gpio/one_wire/gpio_one_wire.h create mode 100644 esphome/components/one_wire/__init__.py create mode 100644 esphome/components/one_wire/one_wire.cpp create mode 100644 esphome/components/one_wire/one_wire.h create mode 100644 esphome/components/one_wire/one_wire_bus.cpp create mode 100644 esphome/components/one_wire/one_wire_bus.h rename tests/components/{dallas => dallas_temp}/common.yaml (55%) rename tests/components/{dallas => dallas_temp}/test.esp32-c3-idf.yaml (100%) rename tests/components/{dallas => dallas_temp}/test.esp32-c3.yaml (100%) rename tests/components/{dallas => dallas_temp}/test.esp32-idf.yaml (100%) rename tests/components/{dallas => dallas_temp}/test.esp32.yaml (100%) rename tests/components/{dallas => dallas_temp}/test.esp8266.yaml (100%) rename tests/components/{dallas => dallas_temp}/test.rp2040.yaml (100%) diff --git a/CODEOWNERS b/CODEOWNERS index bbb39c26ad..75ea4fe523 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/esphome/components/dallas/__init__.py b/esphome/components/dallas/__init__.py index 0f71399a7c..6c2a9d830e 100644 --- a/esphome/components/dallas/__init__.py +++ b/esphome/components/dallas/__init__.py @@ -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' +) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp deleted file mode 100644 index a51bc369a1..0000000000 --- a/esphome/components/dallas/dallas_component.cpp +++ /dev/null @@ -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 raw_sensors; - raw_sensors = this->one_wire_->search_vec(); - - for (auto &address : raw_sensors) { - auto *address8 = reinterpret_cast(&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 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(&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 diff --git a/esphome/components/dallas/dallas_component.h b/esphome/components/dallas/dallas_component.h deleted file mode 100644 index 10bde7338b..0000000000 --- a/esphome/components/dallas/dallas_component.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esp_one_wire.h" - -#include - -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 sensors_; - std::vector 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 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 index_; - - uint8_t resolution_; - std::string address_name_; - uint8_t scratch_pad_[9] = { - 0, - }; -}; - -} // namespace dallas -} // namespace esphome diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp deleted file mode 100644 index 32ddf07fb6..0000000000 --- a/esphome/components/dallas/esp_one_wire.cpp +++ /dev/null @@ -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 ESPOneWire::search_vec() { - std::vector 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(&this->rom_number_); } - -} // namespace dallas -} // namespace esphome diff --git a/esphome/components/dallas/esp_one_wire.h b/esphome/components/dallas/esp_one_wire.h deleted file mode 100644 index 7544a6fe98..0000000000 --- a/esphome/components/dallas/esp_one_wire.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include "esphome/core/hal.h" -#include - -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 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 diff --git a/esphome/components/dallas/sensor.py b/esphome/components/dallas/sensor.py index c6ebda62c8..69f8fc3b9e 100644 --- a/esphome/components/dallas/sensor.py +++ b/esphome/components/dallas/sensor.py @@ -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)) diff --git a/esphome/components/dallas_temp/__init__.py b/esphome/components/dallas_temp/__init__.py new file mode 100644 index 0000000000..3f73044ca8 --- /dev/null +++ b/esphome/components/dallas_temp/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@ssieb"] diff --git a/esphome/components/dallas_temp/dallas_temp.cpp b/esphome/components/dallas_temp/dallas_temp.cpp new file mode 100644 index 0000000000..fe7c9a95ea --- /dev/null +++ b/esphome/components/dallas_temp/dallas_temp.cpp @@ -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 diff --git a/esphome/components/dallas_temp/dallas_temp.h b/esphome/components/dallas_temp/dallas_temp.h new file mode 100644 index 0000000000..604c9d0cd7 --- /dev/null +++ b/esphome/components/dallas_temp/dallas_temp.h @@ -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 diff --git a/esphome/components/dallas_temp/sensor.py b/esphome/components/dallas_temp/sensor.py new file mode 100644 index 0000000000..ab14a9afd5 --- /dev/null +++ b/esphome/components/dallas_temp/sensor.py @@ -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])) diff --git a/esphome/components/gpio/one_wire/__init__.py b/esphome/components/gpio/one_wire/__init__.py new file mode 100644 index 0000000000..2166e92083 --- /dev/null +++ b/esphome/components/gpio/one_wire/__init__.py @@ -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)) diff --git a/esphome/components/gpio/one_wire/gpio_one_wire.cpp b/esphome/components/gpio/one_wire/gpio_one_wire.cpp new file mode 100644 index 0000000000..f47e8d58e3 --- /dev/null +++ b/esphome/components/gpio/one_wire/gpio_one_wire.cpp @@ -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 diff --git a/esphome/components/gpio/one_wire/gpio_one_wire.h b/esphome/components/gpio/one_wire/gpio_one_wire.h new file mode 100644 index 0000000000..fe949baec3 --- /dev/null +++ b/esphome/components/gpio/one_wire/gpio_one_wire.h @@ -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 diff --git a/esphome/components/one_wire/__init__.py b/esphome/components/one_wire/__init__.py new file mode 100644 index 0000000000..99a1ccd1eb --- /dev/null +++ b/esphome/components/one_wire/__init__.py @@ -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)) diff --git a/esphome/components/one_wire/one_wire.cpp b/esphome/components/one_wire/one_wire.cpp new file mode 100644 index 0000000000..131bc4fbfe --- /dev/null +++ b/esphome/components/one_wire/one_wire.cpp @@ -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 diff --git a/esphome/components/one_wire/one_wire.h b/esphome/components/one_wire/one_wire.h new file mode 100644 index 0000000000..bf10e4f82e --- /dev/null +++ b/esphome/components/one_wire/one_wire.h @@ -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 diff --git a/esphome/components/one_wire/one_wire_bus.cpp b/esphome/components/one_wire/one_wire_bus.cpp new file mode 100644 index 0000000000..a8d29428d3 --- /dev/null +++ b/esphome/components/one_wire/one_wire_bus.cpp @@ -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 &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(&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 diff --git a/esphome/components/one_wire/one_wire_bus.h b/esphome/components/one_wire/one_wire_bus.h new file mode 100644 index 0000000000..6818b17499 --- /dev/null +++ b/esphome/components/one_wire/one_wire_bus.h @@ -0,0 +1,61 @@ +#pragma once + +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include + +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 &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 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 diff --git a/script/ci-custom.py b/script/ci-custom.py index e2ee81f742..9a97d3e4a8 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -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", diff --git a/tests/components/dallas/common.yaml b/tests/components/dallas_temp/common.yaml similarity index 55% rename from tests/components/dallas/common.yaml rename to tests/components/dallas_temp/common.yaml index 7975977107..2f846ca278 100644 --- a/tests/components/dallas/common.yaml +++ b/tests/components/dallas_temp/common.yaml @@ -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 diff --git a/tests/components/dallas/test.esp32-c3-idf.yaml b/tests/components/dallas_temp/test.esp32-c3-idf.yaml similarity index 100% rename from tests/components/dallas/test.esp32-c3-idf.yaml rename to tests/components/dallas_temp/test.esp32-c3-idf.yaml diff --git a/tests/components/dallas/test.esp32-c3.yaml b/tests/components/dallas_temp/test.esp32-c3.yaml similarity index 100% rename from tests/components/dallas/test.esp32-c3.yaml rename to tests/components/dallas_temp/test.esp32-c3.yaml diff --git a/tests/components/dallas/test.esp32-idf.yaml b/tests/components/dallas_temp/test.esp32-idf.yaml similarity index 100% rename from tests/components/dallas/test.esp32-idf.yaml rename to tests/components/dallas_temp/test.esp32-idf.yaml diff --git a/tests/components/dallas/test.esp32.yaml b/tests/components/dallas_temp/test.esp32.yaml similarity index 100% rename from tests/components/dallas/test.esp32.yaml rename to tests/components/dallas_temp/test.esp32.yaml diff --git a/tests/components/dallas/test.esp8266.yaml b/tests/components/dallas_temp/test.esp8266.yaml similarity index 100% rename from tests/components/dallas/test.esp8266.yaml rename to tests/components/dallas_temp/test.esp8266.yaml diff --git a/tests/components/dallas/test.rp2040.yaml b/tests/components/dallas_temp/test.rp2040.yaml similarity index 100% rename from tests/components/dallas/test.rp2040.yaml rename to tests/components/dallas_temp/test.rp2040.yaml diff --git a/tests/test1.yaml b/tests/test1.yaml index 2dacfda536..79cb1bba2b 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -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