diff --git a/esphome/components/dynamic_lamp/__init__.py b/esphome/components/dynamic_lamp/__init__.py new file mode 100644 index 0000000000..5cc07288cd --- /dev/null +++ b/esphome/components/dynamic_lamp/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +from esphome.components import i2c, output +from esphome.components.time import RealTimeClock +import esphome.config_validation as cv +from esphome.const import CONF_ID + +DEPENDENCIES = ["i2c", "fram", "output", "time"] +CODEOWNERS = ["@p1ngb4ck"] +MULTI_CONF = False + +fram_ns = cg.esphome_ns.namespace("fram") +FRAMComponent = fram_ns.class_("FRAM", cg.Component, i2c.I2CDevice) +dynamic_lamp_ns = cg.esphome_ns.namespace('dynamic_lamp') +DynamicLampComponent = dynamic_lamp_ns.class_('DynamicLampComponent', cg.Component) +CONF_DYNAMIC_LAMP_ID = "dynamic_lamp_id" + +CONF_RTC = 'rtc' +CONF_FRAM = 'fram' +CONF_SAVE_MODE = 'save_mode' +CONF_AVAILABLE_OUTPUTS = 'available_outputs' +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(CONF_ID): cv.declare_id(DynamicLampComponent), + cv.Required(CONF_RTC): cv.use_id(RealTimeClock), + cv.Required(CONF_FRAM): cv.use_id(FRAMComponent), + cv.Required(CONF_AVAILABLE_OUTPUTS): [cv.use_id(output.FloatOutput)], + cv.Optional(CONF_SAVE_MODE, default=0): cv.int_range(0, 2), +}).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable( + config[CONF_ID], + await cg.get_variable(config[CONF_RTC]), + await cg.get_variable(config[CONF_FRAM]), + ) + await cg.register_component(var, config) + for outputPointer in config.get(CONF_AVAILABLE_OUTPUTS, []): + idstr_ = str(outputPointer) + output_ = await cg.get_variable(outputPointer) + cg.add(var.add_available_output(output_, idstr_)) + cg.add(var.set_save_mode(config[CONF_SAVE_MODE])) diff --git a/esphome/components/dynamic_lamp/dynamic_lamp.cpp b/esphome/components/dynamic_lamp/dynamic_lamp.cpp new file mode 100644 index 0000000000..e434015f21 --- /dev/null +++ b/esphome/components/dynamic_lamp/dynamic_lamp.cpp @@ -0,0 +1,524 @@ +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" +#include "dynamic_lamp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace esphome { +namespace dynamic_lamp { + +static const char *TAG = "dynamic_lamp"; + +void DynamicLampComponent::setup() { + this->begin(); +} + +void DynamicLampComponent::begin() { + for (uint8_t i=0; i < 16; i++) { + this->active_lamps_[i] = CombinedLamp(); + this->active_lamps_[i].active = false; + this->available_outputs_[i] = LinkedOutput{ false, false, "", 0, nullptr, 0.0, 0, 0, 0.0f, 1.0f, false }; + } + this->restore_lamp_settings_(); + this->restore_timers_(); +} + +void DynamicLampComponent::loop() { + uint8_t i = 0; + for (i = 0; i < this->lamp_count_; i++) { + if (this->active_lamps_[i].active == true && this->active_lamps_[i].update_ == true) { + uint8_t j = 0; + for (j = 0; j < 16; j++) { + uint8_t k = 0; + uint8_t l = j; + if (j > 7) { + k = 1; + l = j - 8; + } + bool output_in_use = static_cast(this->active_lamps_[i].used_outputs[k] & (1 << l)); + if (output_in_use == true) { + // Update level + float new_state; + new_state = this->active_lamps_[i].state_; + switch (this->available_outputs_[j].mode) { + case MODE_EQUAL: + if (this->available_outputs_[j].min_value && new_state < *this->available_outputs_[j].min_value) { + new_state = *this->available_outputs_[j].min_value; + } + else if (this->available_outputs_[j].max_value && new_state > *this->available_outputs_[j].max_value) { + new_state = *this->available_outputs_[j].max_value; + } + break; + case MODE_STATIC: + new_state = this->available_outputs_[j].mode_value; + break; + case MODE_PERCENTAGE: + new_state = this->active_lamps_[i].state_ * this->available_outputs_[j].mode_value; + if (this->available_outputs_[j].min_value && new_state < *this->available_outputs_[j].min_value) { + new_state = *this->available_outputs_[j].min_value; + } + else if (this->available_outputs_[j].max_value && new_state > *this->available_outputs_[j].max_value) { + new_state = *this->available_outputs_[j].max_value; + } + break; + case MODE_FUNCTION: + // ToDo - yet to be implemented + ESP_LOGW(TAG, "Mode %d for output %s is not implemented yet, sorry", this->available_outputs_[j].mode, this->available_outputs_[j].output_id.c_str()); + this->status_set_warning(); + continue; + default: + // Unknown + ESP_LOGW(TAG, "Unknown mode %d for output %s", this->available_outputs_[j].mode, this->available_outputs_[j].output_id.c_str()); + this->status_set_warning(); + continue; + } + ESP_LOGV(TAG, "Setting output %s to level %f", this->available_outputs_[j].output_id.c_str(), new_state); + this->available_outputs_[j].output->set_level(new_state); + } + } + this->active_lamps_[i].update_ = false; + } + } +} + +void DynamicLampComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Dynamic Lamp feature loaded"); + switch(this->save_mode_) { + case SAVE_MODE_NONE: + ESP_LOGCONFIG(TAG, "Save mode set to NONE"); + break; + case SAVE_MODE_LOCAL: + ESP_LOGCONFIG(TAG, "Save mode set to LOCAL"); + break; + case SAVE_MODE_FRAM: + ESP_LOGCONFIG(TAG, "Save mode set to FRAM"); + break; + default: + ESP_LOGCONFIG(TAG, "Currently only NONE(0), LOCAL(1) & FRAM(2) save modes supported, ignoring value %" PRIu8 " and defaulting to NONE!", this->save_mode_); + this->save_mode_ = 0; + } + for (uint8_t i = 0; i < 16; i++) { + if (this->available_outputs_[i].available == true) { + ESP_LOGCONFIG(TAG, "Using output with id %s as output number %" PRIu8 "", this->available_outputs_[i].output_id.c_str(), i); + } + } +} + +void DynamicLampComponent::set_save_mode(uint8_t save_mode) { + this->save_mode_ = save_mode; +} + +void DynamicLampComponent::add_available_output(output::FloatOutput * output, std::string output_id) { + uint8_t counter = 0; + while (this->available_outputs_[counter].available == true) { + counter++; + } + if (counter > 15) { + ESP_LOGW(TAG, "No more outputs available, max 16 outputs supported!"); + this->status_set_warning(); + return; + } + this->available_outputs_[counter].available = true; + this->available_outputs_[counter].output_id = output_id; + this->available_outputs_[counter].output = output; + this->available_outputs_[counter].output_index = counter; + counter++; +} + +void DynamicLampComponent::add_lamp(std::string name) { + if (this->lamp_count_ < 15) { + this->active_lamps_[this->lamp_count_].active = true; + strncpy(reinterpret_cast(this->active_lamps_[this->lamp_count_].name), name.c_str(), 16); + this->active_lamps_[this->lamp_count_].validation_byte = 'L'; + this->active_lamps_[this->lamp_count_].lamp_index = this->lamp_count_; + this->active_lamps_[this->lamp_count_].used_outputs[0] = 0; + this->active_lamps_[this->lamp_count_].used_outputs[1] = 0; + this->fram_->write((0x0000 + (this->lamp_count_ * 24)), reinterpret_cast(&this->active_lamps_[this->lamp_count_]), 24); + this->lamp_count_++; + ESP_LOGV(TAG, "Added new lamp %s, total lamps now %" PRIu8 "", name.c_str(), this->lamp_count_); + return; + } + ESP_LOGW(TAG, "No more lamps available, max 16 lamps supported!"); + this->status_set_warning(); +} + +void DynamicLampComponent::remove_lamp(std::string lamp_name) { + uint8_t i = 0; + while (i < this->lamp_count_) { + std::string str(this->active_lamps_[i].name, this->active_lamps_[i].name + sizeof this->active_lamps_[i].name / sizeof this->active_lamps_[i].name[0]); + if (str == lamp_name) { + for (uint8_t j = 0; j < 16; j++) { + uint8_t k = 0; + uint8_t l = j; + if (j > 7) { + k = 1; + l = j - 8; + } + bool output_in_use = static_cast(this->active_lamps_[i].used_outputs[k] & (1 << l)); + if (output_in_use == true) { + this->available_outputs_[j].in_use = false; + this->active_lamps_[i].used_outputs[j] = false; + } + } + uint16_t memaddress = 0 + (i * 24); + unsigned char empty_lamp[24]; + for (uint8_t m = 0; m < 24; m++) { + empty_lamp[m] = 0xff; + } + this->fram_->write(memaddress, empty_lamp, 24); + this->active_lamps_[i].active = false; + this->lamp_count_--; + ESP_LOGV(TAG, "Removed lamp %s, total lamps now %" PRIu8 "", this->active_lamps_[i].name, this->lamp_count_); + return; + } + i++; + } + this->status_set_warning(); + ESP_LOGW(TAG, "No lamp with name %s defined !", lamp_name.c_str()); +} + +void DynamicLampComponent::add_output_to_lamp(std::string lamp_name, LinkedOutput *output) { + if (output->available == false) { + ESP_LOGW(TAG, "Output %s is not available, ignoring!", output->output_id.c_str()); + this->status_set_warning(); + return; + } + if (output->in_use == true) { + ESP_LOGW(TAG, "Output %s is already in use, ignoring!", output->output_id.c_str()); + this->status_set_warning(); + return; + } + uint8_t i = 0; + while (i < 16) { + std::string str(this->active_lamps_[i].name, this->active_lamps_[i].name + sizeof this->active_lamps_[i].name / sizeof this->active_lamps_[i].name[0]); + if (str == lamp_name) { + uint8_t j = 0; + if (output->output_index > 7) { + j = 1; + } + this->active_lamps_[i].used_outputs[j] |= 1 << (output->output_index % 8); + output->in_use = true; + this->fram_->write((0x0000 + (this->active_lamps_[i].lamp_index * 24)), reinterpret_cast(&this->active_lamps_[i]), 24); + ESP_LOGV(TAG, "Added output %s to lamp %s", output->output_id.c_str(), lamp_name.c_str()); + return; + } + i++; + } + this->status_set_warning(); + ESP_LOGW(TAG, "No lamp with name %s defined !", lamp_name.c_str()); +} + +void DynamicLampComponent::remove_output_from_lamp(std::string lamp_name, LinkedOutput *output) { + uint8_t i = 0; + while (i < 16) { + std::string str(this->active_lamps_[i].name, this->active_lamps_[i].name + sizeof this->active_lamps_[i].name / sizeof this->active_lamps_[i].name[0]); + if (str == lamp_name) { + uint8_t k = output->output_index; + uint8_t j = 0; + if (output->output_index > 7) { + j = 1; + k = output->output_index - 8; + } + this->active_lamps_[i].used_outputs[j] &= ~(1 << k); + this->fram_->write((0x0000 + (i * 24)), reinterpret_cast(&this->active_lamps_[i]), 24); + output->in_use = false; + ESP_LOGV(TAG, "Removed output %s from lamp %s", output->output_id.c_str(), lamp_name.c_str()); + return; + } + i++; + } + this->status_set_warning(); + ESP_LOGW(TAG, "No lamp with name %s defined !", lamp_name.c_str()); +} + +std::array DynamicLampComponent::get_lamp_outputs(uint8_t lamp_number) { + std::array bool_array; + for (uint8_t i = 0; i < 16; i++) { + uint8_t j = 0; + uint8_t k = i; + if (i > 7) { + j = 1; + k = i - 8; + } + bool_array[i] = static_cast(this->active_lamps_[lamp_number].used_outputs[j] & (1 << k)); + } + return bool_array; +} + +uint8_t DynamicLampComponent::get_lamp_index_by_name_(std::string lamp_name) { + uint8_t i = 0; + for (i = 0; i < this->lamp_count_; i++) { + std::string str(this->active_lamps_[i].name, this->active_lamps_[i].name + sizeof this->active_lamps_[i].name / sizeof this->active_lamps_[i].name[0]); + if (str == lamp_name) { + return i; + } + } + this->status_set_warning(); + ESP_LOGW(TAG, "No lamp with name %s defined !", lamp_name.c_str()); + return 255; +} + +std::array DynamicLampComponent::get_lamp_outputs_by_name_(std::string lamp_name) { + uint8_t lamp_index = this->get_lamp_index_by_name_(lamp_name); + if (lamp_index == 255) { + std::array bool_array; + return bool_array; + } + return this->get_lamp_outputs(lamp_index); +} + +bool DynamicLampComponent::add_timer(std::string timer_desc, std::string lamp_list_str, bool timer_active, uint8_t action, uint8_t hour, + uint8_t minute, bool monday, bool tuesday, bool wednesday, bool thursday, + bool friday, bool saturday, bool sunday) { + std::vector lamp_list = this->build_lamp_list_from_list_str_(lamp_list_str); + DynamicLampTimer new_timer; + strncpy(reinterpret_cast(new_timer.timer_desc), timer_desc.c_str(), 36); + unsigned char lamp_list_bytes[2] = {0, 0}; + for (uint8_t i = 0; i < lamp_list.size(); i++) { + if (lamp_list[i] == true && this->active_lamps_[i].active != true) { + ESP_LOGW(TAG, "Ignoring lamp number %" PRIu8 " as there is no active lamp with that index!", i); + continue; + } + if (lamp_list[i] == true) { + lamp_list_bytes[i / 8] |= 1 << (i % 8); + } + } + memcpy(new_timer.lamp_list, lamp_list_bytes, 2); + new_timer.in_use = true; + new_timer.validation_bytes[0] = 'V'; + new_timer.validation_bytes[1] = 'D'; + new_timer.validation_bytes[2] = 'L'; + new_timer.validation_bytes[3] = 'T'; + new_timer.active = timer_active; + new_timer.action = action; + new_timer.hour = hour; + new_timer.minute = minute; + new_timer.monday = monday; + new_timer.tuesday = tuesday; + new_timer.wednesday = wednesday; + new_timer.thursday = thursday; + new_timer.friday = friday; + new_timer.saturday = saturday; + new_timer.sunday = sunday; + ESPTime now = this->rtc_->now(); + time_t begin_date = now.timestamp; + now.increment_day(); + time_t end_date = now.timestamp; + new_timer.begin_date = begin_date; + new_timer.end_date = end_date; + unsigned char* timer_as_bytes = static_cast(static_cast(&new_timer)); + ESP_LOGV(TAG, "Added new timer %s with lamp-list %s, active %d, action %d, hour %d, minute %d, monday %d, tuesday %d, wednesday %d, thursday %d, friday %d, saturday %d, sunday %d", + new_timer.timer_desc, lamp_list_str.c_str(), new_timer.active, new_timer.action, new_timer.hour, new_timer.minute, new_timer.monday, + new_timer.tuesday, new_timer.wednesday, new_timer.thursday, new_timer.friday, new_timer.saturday, new_timer.sunday); + uint8_t save_slot; + for (save_slot = 0; save_slot < 128; save_slot++) { + if (this->timers_[save_slot].in_use != true) { + break; + } + } + if (save_slot == 128) { + ESP_LOGW(TAG, "No more timer slots available, max 128 timers supported!"); + this->status_set_warning(); + return false; + } + this->timers_[save_slot] = new_timer; + this->fram_->write((0x4000 + (save_slot * 64)), timer_as_bytes, 64); + return true; +} + +std::vector DynamicLampComponent::build_lamp_list_from_list_str_(std::string lamp_list_str) { + std::vector lamp_list_vector = this->split_to_int_vector_(lamp_list_str); + std::vector lamp_list; + while (lamp_list.size() < 16) { + lamp_list.push_back(false); + } + if (lamp_list_vector.size() > 16) { + ESP_LOGW(TAG, "Too many lamps in list, only 16 supported!"); + this->status_set_warning(); + return lamp_list; + } + for (uint8_t i = 0; i < lamp_list_vector.size(); i++) { + uint8_t lamp_index = lamp_list_vector[i]; + if (lamp_index > 15) { + ESP_LOGW(TAG, "Lamp index %" PRIu8 " is out of range, only [0-15] supported!", lamp_list_vector[i]); + this->status_set_warning(); + return lamp_list; + } + lamp_list[lamp_index] = true; + } + return lamp_list; +} + +void DynamicLampComponent::read_fram_timers_to_log() { + DynamicLampTimer timer; + for (uint8_t i = 0; i < 128; i++) { + this->fram_->read((0x4000 + (i * 64)), reinterpret_cast(&timer), 64); + if (timer.validation_bytes[0] == 'V' && timer.validation_bytes[1] == 'D' && timer.validation_bytes[2] == 'L' && timer.validation_bytes[3] == 'T' && timer.in_use == true) { + std::string lamp_names_str = ""; + for (uint8_t j = 0; j < 16; j++) { + uint8_t k = 0; + uint8_t l = j; + if (j > 7) { + k = 1; + l = j - 8; + } + //bool lamp_included = static_cast(timer.lamp_list[j / 8] & (1 << (j % 8))); + bool lamp_included = static_cast(timer.lamp_list[k] & (1 << l)); + if (lamp_included == true && this->active_lamps_[j].active == true) { + if (lamp_names_str.length() > 0) { + lamp_names_str += ", "; + } + std::string str(this->active_lamps_[j].name, this->active_lamps_[j].name + sizeof this->active_lamps_[j].name / sizeof this->active_lamps_[j].name[0]); + lamp_names_str += str; + } + } + ESP_LOGV(TAG, "Timer %s found: [ active: %d, action: %d, hour: %d, minute: %d, monday: %d, tuesday: %d, wednesday: %d, thursday: %d, friday: %d, saturday: %d, sunday: %d ]", + timer.timer_desc, timer.active, timer.action, timer.hour, timer.minute, timer.monday, timer.tuesday, + timer.wednesday, timer.thursday, timer.friday, timer.saturday, timer.sunday); + ESP_LOGV(TAG, "Timer active for lamps %s", lamp_names_str.c_str()); + } + } +} + +void DynamicLampComponent::read_initialized_timers_to_log() { + DynamicLampTimer timer; + for (uint8_t i = 0; i < 128; i++) { + if (this->timers_[i].in_use == true) { + timer = this->timers_[i]; + std::string lamp_names_str = ""; + for (uint8_t j = 0; j < 16; j++) { + uint8_t k = 0; + uint8_t l = j; + if (j > 7) { + k = 1; + l = j - 8; + } + //bool lamp_included = static_cast(timer.lamp_list[j / 8] & (1 << (j % 8))); + bool lamp_included = static_cast(timer.lamp_list[k] & (1 << l)); + if (lamp_included == true && this->active_lamps_[j].active == true) { + if (lamp_names_str.length() > 0) { + lamp_names_str += ", "; + } + std::string str(this->active_lamps_[j].name, this->active_lamps_[j].name + sizeof this->active_lamps_[j].name / sizeof this->active_lamps_[j].name[0]); + lamp_names_str += str; + } + } + ESP_LOGV(TAG, "Timer %s found: [ active: %d, action: %d, hour: %d, minute: %d, monday: %d, tuesday: %d, wednesday: %d, thursday: %d, friday: %d, saturday: %d, sunday: %d ]", + timer.timer_desc, timer.active, timer.action, timer.hour, timer.minute, timer.monday, timer.tuesday, + timer.wednesday, timer.thursday, timer.friday, timer.saturday, timer.sunday); + ESP_LOGV(TAG, "Timer active for lamps %s", lamp_names_str.c_str()); + } + } +} + +bool DynamicLampComponent::write_state_(uint8_t lamp_number, float state) { + if (this->active_lamps_[lamp_number].active == true) { + this->active_lamps_[lamp_number].state_ = state; + this->active_lamps_[lamp_number].update_ = true; + return true; + } + return false; +} + +std::string DynamicLampComponent::get_lamp_name(uint8_t lamp_number) { + std::string str(this->active_lamps_[lamp_number].name, this->active_lamps_[lamp_number].name + sizeof this->active_lamps_[lamp_number].name / sizeof this->active_lamps_[lamp_number].name[0]); + return str; +} + +void DynamicLampComponent::set_lamp_values_(uint8_t lamp_number, bool active, uint16_t selected_outputs, uint8_t mode, uint8_t mode_value) { + +} + +void DynamicLampComponent::restore_lamp_settings_() { + switch (this->save_mode_) { + case SAVE_MODE_LOCAL: + // ToDo - yet to be implemented + ESP_LOGW(TAG, "Save mode LOCAL not implemented yet, sorry"); + this->status_set_warning(); + break; + case SAVE_MODE_FRAM: + CombinedLamp lamp; + for (uint8_t i=0; i < 16; i++) { + this->fram_->read((0x0000 + (i * 24)), reinterpret_cast(&lamp), 24); + if (lamp.validation_byte == 'L' && lamp.active == true) { + this->active_lamps_[i] = lamp; + for (uint8_t j = 0; j < 16; j++) { + uint8_t k = 0; + uint8_t l = j; + if (j > 7) { + k = 1; + l = j - 8; + } + bool output_in_use = static_cast(lamp.used_outputs[k] & (1 << l)); + if (output_in_use == true) { + this->available_outputs_[j].in_use = true; + } + } + this->lamp_count_++; + } else { + lamp = CombinedLamp(); + this->active_lamps_[i] = lamp; + this->active_lamps_[i].active = false; + } + } + break; + } +} + +void DynamicLampComponent::restore_timers_() { + DynamicLampTimer timer; + switch (this->save_mode_) { + case SupportedSaveModes::SAVE_MODE_NONE: + for (uint8_t i = 0; i < 128; i++) { + timer = DynamicLampTimer(); + this->timers_[i] = timer; + this->timers_[i].in_use = false; + } + break; + case SupportedSaveModes::SAVE_MODE_LOCAL: + // ToDo - yet to be implemented + ESP_LOGW(TAG, "Save mode LOCAL not implemented yet, sorry"); + this->status_set_warning(); + break; + case SupportedSaveModes::SAVE_MODE_FRAM: + std::string lamp_names_str; + for (uint8_t i = 0; i < 128; i++) { + this->fram_->read((0x4000 + (i * 64)), reinterpret_cast(&timer), 64); + if (timer.validation_bytes[0] == 'V' && timer.validation_bytes[1] == 'D' && timer.validation_bytes[2] == 'L' && timer.validation_bytes[3] == 'T' && timer.in_use == true) { + this->timers_[i] = timer; + } else { + timer = DynamicLampTimer(); + this->timers_[i] = timer; + this->timers_[i].in_use = false; + } + } + break; + } +} + +void DynamicLampComponent::clear_fram() { + this->fram_->clear(); + ESP_LOGV(TAG, "Cleared FRAM"); +} + +std::vector DynamicLampComponent::split_to_int_vector_(std::string lamp_list_str) { + std::vector tokens; + std::stringstream sstream(lamp_list_str); + std::string segment; + while(std::getline(sstream, segment, ',')) { + tokens.push_back(static_cast(atoi(segment.c_str()))); + } + return tokens; +} + +} // namespace dynamic_lamp +} // namespace esphome diff --git a/esphome/components/dynamic_lamp/dynamic_lamp.h b/esphome/components/dynamic_lamp/dynamic_lamp.h new file mode 100644 index 0000000000..a0f95ce761 --- /dev/null +++ b/esphome/components/dynamic_lamp/dynamic_lamp.h @@ -0,0 +1,140 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/optional.h" +#include "esphome/core/log.h" +#include "esphome/core/time.h" +#include "esphome/components/time/real_time_clock.h" +#include "esphome/components/output/float_output.h" +#include "esphome/components/light/light_output.h" +#include "esphome/components/fram/FRAM.h" + +namespace esphome { +namespace dynamic_lamp { + +enum SupportedSaveModes : uint8_t { + SAVE_MODE_NONE = 0, + SAVE_MODE_LOCAL = 1, + SAVE_MODE_FRAM = 2 +}; + +enum LinkedOutputModeIdx : uint8_t { + MODE_EQUAL = 0, + MODE_STATIC = 1, + MODE_PERCENTAGE = 2, + MODE_FUNCTION = 3 +}; + +struct LinkedOutput { + bool available = false; + bool in_use = false; + std::string output_id; + uint8_t output_index; + output::FloatOutput *output; + float state; + uint8_t mode = 0; + float mode_value = 0; + optional min_value; + optional max_value; + bool update_level = false; +}; + +enum DynamicLampIdx : uint8_t { + LAMP_1 = 0, + LAMP_2 = 1, + LAMP_3 = 2, + LAMP_4 = 3, + LAMP_5 = 4, + LAMP_6 = 5, + LAMP_7 = 6, + LAMP_8 = 7, + LAMP_9 = 8, + LAMP_10 = 9, + LAMP_11 = 10, + LAMP_12 = 11, + LAMP_13 = 12, + LAMP_14 = 13, + LAMP_15 = 14, + LAMP_16 = 15, +}; + +struct CombinedLamp { + unsigned char validation_byte; + uint8_t lamp_index : 4; + bool active : 1; + bool update_ : 1; + unsigned char :0; + unsigned char name[16]; + float state_; + unsigned char used_outputs[2]; +}; + +struct DynamicLampTimer { + unsigned char validation_bytes[4]; + unsigned char timer_desc[36]; + unsigned char lamp_list[2]; + bool in_use : 1; + uint8_t action : 3; + uint8_t hour : 5; + uint8_t minute : 6; + bool active : 1; + bool monday : 1; + bool tuesday : 1; + bool wednesday : 1; + bool thursday : 1; + bool friday : 1; + bool saturday : 1; + bool sunday : 1; + bool respect_dst : 1; + time_t begin_date : 64; + time_t end_date : 64; +}; + +class DynamicLamp; + +class DynamicLampComponent : public Component { + public: + explicit DynamicLampComponent(time::RealTimeClock *rtc, fram::FRAM *fram) : rtc_(rtc), fram_(fram) {} + void setup() override; + void loop() override; + void dump_config() override; + void begin(); + void add_available_output(output::FloatOutput * output, std::string output_id); + void set_save_mode(uint8_t save_mode); + void add_lamp(std::string name); + void remove_lamp(std::string name); + std::string get_lamp_name(uint8_t lamp_number); + void add_output_to_lamp(std::string lamp_name, LinkedOutput *output); + void remove_output_from_lamp(std::string lamp_name, LinkedOutput *output); + std::array get_lamp_outputs(uint8_t lamp_number); + bool add_timer(std::string timer_desc, std::string lamp_name, bool timer_active, uint8_t mode, uint8_t hour, + uint8_t minute, bool monday, bool tuesday, bool wednesday, bool thursday, + bool friday, bool saturday, bool sunday); + void read_initialized_timers_to_log(); + void read_fram_timers_to_log(); + void clear_fram(); + + protected: + friend class DynamicLamp; + time::RealTimeClock *rtc_; + fram::FRAM *fram_; + void restore_lamp_settings_(); + void restore_timers_(); + void set_lamp_values_(uint8_t lamp_number, bool active, uint16_t selected_outputs, uint8_t mode, uint8_t mode_value); + bool write_state_(uint8_t lamp_number, float state); + uint8_t get_lamp_index_by_name_(std::string lamp_name); + std::array get_lamp_outputs_by_name_(std::string lamp_name); + std::vector split_to_int_vector_(std::string lamp_list_str); + std::vector build_lamp_list_from_list_str_(std::string lamp_list_str); + + CombinedLamp active_lamps_[16]; + LinkedOutput available_outputs_[16]; + DynamicLampTimer timers_[128]; + uint8_t save_mode_; + uint8_t lamp_count_ = 0; +}; + + +} // namespace dynamic_lamp +} // namespace esphome diff --git a/esphome/components/dynamic_lamp/output/__init__.py b/esphome/components/dynamic_lamp/output/__init__.py new file mode 100644 index 0000000000..36987b5c44 --- /dev/null +++ b/esphome/components/dynamic_lamp/output/__init__.py @@ -0,0 +1,51 @@ +import esphome.codegen as cg +from esphome.components import output +import esphome.config_validation as cv +from esphome.const import CONF_CHANNEL, CONF_ID + +from .. import CONF_DYNAMIC_LAMP_ID, DynamicLampComponent, dynamic_lamp_ns + +DEPENDENCIES = ["dynamic_lamp"] + +DynamicLamp = dynamic_lamp_ns.class_( + "DynamicLamp", output.FloatOutput, cg.Parented.template(DynamicLampComponent) +) + +DynamicLampIdx = dynamic_lamp_ns.enum("DynamicLampIdx") +CHANNEL_OPTIONS = { + "A": DynamicLampIdx.LAMP_1, + "B": DynamicLampIdx.LAMP_2, + "C": DynamicLampIdx.LAMP_3, + "D": DynamicLampIdx.LAMP_4, + "E": DynamicLampIdx.LAMP_5, + "F": DynamicLampIdx.LAMP_6, + "G": DynamicLampIdx.LAMP_7, + "H": DynamicLampIdx.LAMP_8, + "I": DynamicLampIdx.LAMP_9, + "J": DynamicLampIdx.LAMP_10, + "K": DynamicLampIdx.LAMP_11, + "L": DynamicLampIdx.LAMP_12, + "M": DynamicLampIdx.LAMP_13, + "N": DynamicLampIdx.LAMP_14, + "O": DynamicLampIdx.LAMP_15, + "P": DynamicLampIdx.LAMP_16, +} + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(DynamicLamp), + cv.GenerateID(CONF_DYNAMIC_LAMP_ID): cv.use_id(DynamicLampComponent), + cv.Required(CONF_CHANNEL): cv.enum(CHANNEL_OPTIONS, upper=True), + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_DYNAMIC_LAMP_ID]) + var = cg.new_Pvariable( + config[CONF_ID], + parent, + config[CONF_CHANNEL], + ) + await output.register_output(var, config) + await cg.register_parented(var, config[CONF_DYNAMIC_LAMP_ID]) diff --git a/esphome/components/dynamic_lamp/output/dynamic_lamp_output.cpp b/esphome/components/dynamic_lamp/output/dynamic_lamp_output.cpp new file mode 100644 index 0000000000..6a37e100de --- /dev/null +++ b/esphome/components/dynamic_lamp/output/dynamic_lamp_output.cpp @@ -0,0 +1,14 @@ +#include "dynamic_lamp_output.h" + +namespace esphome { +namespace dynamic_lamp { + +void DynamicLamp::write_state(float state) { + if (this->parent_->write_state_(this->lamp_, state)) + { + this->state_ = state; + } +} + +} // namespace dynamic_lamp +} // namespace esphome diff --git a/esphome/components/dynamic_lamp/output/dynamic_lamp_output.h b/esphome/components/dynamic_lamp/output/dynamic_lamp_output.h new file mode 100644 index 0000000000..5f826a053b --- /dev/null +++ b/esphome/components/dynamic_lamp/output/dynamic_lamp_output.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../dynamic_lamp.h" + +namespace esphome { +namespace dynamic_lamp { + +class DynamicLamp : public output::FloatOutput, public Parented { + public: + DynamicLamp(DynamicLampComponent *parent, DynamicLampIdx lamp) : parent_(parent), lamp_(lamp) {} + + protected: + void write_state(float state) override; + DynamicLampComponent *parent_; + DynamicLampIdx lamp_; + float state_; +}; + +} // namespace dynamic_lamp +} // namespace esphome diff --git a/esphome/components/dynamic_on_time/__init__.py b/esphome/components/dynamic_on_time/__init__.py new file mode 100644 index 0000000000..c55e86bec2 --- /dev/null +++ b/esphome/components/dynamic_on_time/__init__.py @@ -0,0 +1,79 @@ +''' +tbd +''' +from esphome.const import ( + CONF_ID, + CONF_ON_TIME, + CONF_THEN, + CONF_HOUR, + CONF_MINUTE, +) +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome import automation +from esphome.components.number import Number +from esphome.components.switch import Switch +from esphome.components.time import RealTimeClock + +MULTI_CONF = True +DEPENDENCIES = ["time", "number", "switch"] +CODEOWNERS = ["@hostcc"] + +CONF_RTC = 'rtc' +CONF_MON = 'mon' +CONF_TUE = 'tue' +CONF_WED = 'wed' +CONF_THU = 'thu' +CONF_FRI = 'fri' +CONF_SAT = 'sat' +CONF_SUN = 'sun' +CONF_DISABLED = 'disabled' + +dynamic_on_time_ns = cg.esphome_ns.namespace("dynamic_on_time") +DynamicOnTimeComponent = dynamic_on_time_ns.class_( + "DynamicOnTime", cg.Component +) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(DynamicOnTimeComponent), + cv.Required(CONF_RTC): cv.use_id(RealTimeClock), + cv.Required(CONF_HOUR): cv.use_id(Number), + cv.Required(CONF_MINUTE): cv.use_id(Number), + cv.Required(CONF_MON): cv.use_id(Switch), + cv.Required(CONF_TUE): cv.use_id(Switch), + cv.Required(CONF_WED): cv.use_id(Switch), + cv.Required(CONF_THU): cv.use_id(Switch), + cv.Required(CONF_FRI): cv.use_id(Switch), + cv.Required(CONF_SAT): cv.use_id(Switch), + cv.Required(CONF_SUN): cv.use_id(Switch), + cv.Required(CONF_DISABLED): cv.use_id(Switch), + cv.Required(CONF_ON_TIME): automation.validate_automation({}), +}).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + ''' + tbd + ''' + actions = [] + for conf in config[CONF_ON_TIME]: + actions.extend(await automation.build_action_list( + conf[CONF_THEN], cg.TemplateArguments(), []) + ) + + var = cg.new_Pvariable( + config[CONF_ID], + await cg.get_variable(config[CONF_RTC]), + await cg.get_variable(config[CONF_HOUR]), + await cg.get_variable(config[CONF_MINUTE]), + await cg.get_variable(config[CONF_MON]), + await cg.get_variable(config[CONF_TUE]), + await cg.get_variable(config[CONF_WED]), + await cg.get_variable(config[CONF_THU]), + await cg.get_variable(config[CONF_FRI]), + await cg.get_variable(config[CONF_SAT]), + await cg.get_variable(config[CONF_SUN]), + await cg.get_variable(config[CONF_DISABLED]), + actions, + ) + await cg.register_component(var, config) diff --git a/esphome/components/dynamic_on_time/dynamic_on_time.cpp b/esphome/components/dynamic_on_time/dynamic_on_time.cpp new file mode 100644 index 0000000000..17fc82b67c --- /dev/null +++ b/esphome/components/dynamic_on_time/dynamic_on_time.cpp @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2023 Ilia Sotnikov + +#include "dynamic_on_time.h" // NOLINT(build/include_subdir) +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace dynamic_on_time { + +static const char *tag = "dynamic_on_time"; + +DynamicOnTime::DynamicOnTime( + time::RealTimeClock *rtc, + number::Number *hour, + number::Number *minute, + switch_::Switch *mon, + switch_::Switch *tue, + switch_::Switch *wed, + switch_::Switch *thu, + switch_::Switch *fri, + switch_::Switch *sat, + switch_::Switch *sun, + switch_::Switch *disabled, + std::vector *> actions): + rtc_(rtc), + hour_(hour), minute_(minute), + mon_(mon), tue_(tue), wed_(wed), thu_(thu), fri_(fri), sat_(sat), + sun_(sun), disabled_(disabled), actions_(actions) {} + +std::vector DynamicOnTime::flags_to_days_of_week_( + bool mon, bool tue, bool wed, bool thu, bool fri, bool sat, bool sun +) { + // Numeric representation for days of week (starts from Sun internally) + std::vector days_of_week = { 1, 2, 3, 4, 5, 6, 7 }; + std::vector flags = { sun, mon, tue, wed, thu, fri, sat }; + + // Translate set of bool flags into vector of corresponding numeric + // representation. This uses 'erase-remove' approach ( + // https://en.cppreference.com/w/cpp/algorithm/remove, + // https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom) + days_of_week.erase( + std::remove_if( + std::begin(days_of_week), std::end(days_of_week), + [&](uint8_t& arg) { return !flags[&arg - days_of_week.data()]; }), + days_of_week.end()); + + return days_of_week; +} + +void DynamicOnTime::setup() { + // Update the configuration initially, ensuring all entities are created + // before a callback would be delivered to them + this->update_schedule_(); + + // Register the cron trigger component + App.register_component(this->trigger_); + + // The `Number` and `Switch` has no common base type with + // `add_on_state_callback`, and solutions to properly cast to derived + // class in single loop over vector of base class instances seemingly imply + // more code than just two loops + for (number::Number *comp : {this->hour_, this->minute_}) { + comp->add_on_state_callback([this](float value) { + this->update_schedule_(); + }); + } + + for (switch_::Switch *comp : { + this->mon_, this->tue_, this->wed_, this->thu_, this->fri_, this->sat_, + this->sun_, this->disabled_ + }) { + comp->add_on_state_callback([this](bool value) { + this->update_schedule_(); + }); + } +} + +void DynamicOnTime::update_schedule_() { + // CronTrigger doesn't allow its configuration to be reset programmatically, + // so its instance is either created initially, or reinitialized in place if + // allocated already + if (this->trigger_ != nullptr) { + // Use 'placement new' (https://en.cppreference.com/w/cpp/language/new) to + // reinitialize existing CronTrigger instance in place + this->trigger_->~CronTrigger(); + new (this->trigger_) time::CronTrigger(this->rtc_); + } else { + this->trigger_ = new time::CronTrigger(this->rtc_); + } + + // (Re)create the automation instance but only if scheduled actions aren't + // disabled + if (this->automation_ != nullptr) { + delete this->automation_; + this->automation_ = nullptr; + } + + if (!this->disabled_->state) { + this->automation_ = new Automation<>(this->trigger_); + // Add requested actions to it + this->automation_->add_actions(this->actions_); + } + + // All remaining logic is active regardless of scheduled actions are + // disabled, since callbacks from Switch/Number components being active still + // need to be processed otherwise inputs will be lost + // + // Set trigger to fire on zeroth second of configured time + this->trigger_->add_second(0); + // Enable all days of months for the schedule + for (uint8_t i = 1; i <= 31; i++) + this->trigger_->add_day_of_month(i); + // Same but for months + for (uint8_t i = 1; i <= 12; i++) + this->trigger_->add_month(i); + // Configure hour/minute of the schedule from corresponding components' state + this->trigger_->add_hour(static_cast(this->hour_->state)); + this->trigger_->add_minute(static_cast(this->minute_->state)); + // Similarly but for days of week translating set of components' state to + // vector of numeric representation as `CrontTrigger::add_days_of_week()` + // requires + this->days_of_week_ = this->flags_to_days_of_week_( + this->mon_->state, this->tue_->state, this->wed_->state, + this->thu_->state, this->fri_->state, this->sat_->state, + this->sun_->state); + this->trigger_->add_days_of_week(this->days_of_week_); + + // Initiate updating the cached value for the next schedule + this->next_schedule_.reset(); + + // Log the configuration + this->dump_config(); +} + +optional DynamicOnTime::get_next_schedule() { + if (this->disabled_->state || this->days_of_week_.empty()) + return {}; + + ESPTime now = this->rtc_->now(); + + if (now < this->next_schedule_.value_or(now)) + return this->next_schedule_; + + ESP_LOGVV(tag, "Non-cached calculation of next schedule"); + + // Calculate timestamp for the start of the week with time being hour/time of + // the schedule + time_t start_of_week = now.timestamp + - (now.second + now.hour * 3600 + now.minute * 60 + now.day_of_week * 86400) + + (3600 * static_cast(this->hour_->state) + + 60 * static_cast(this->minute_->state)); + + time_t next = 0, first = 0; + for (auto next_day : this->days_of_week_) { + // Calculate the timestamp for next day in schedule + next = start_of_week + 86400 * next_day; + // Capture timestamp for the first scheduled day + if (!first) + first = next; + // Exit if timestamp corresponds of later date in the schedule found + if (next > now.timestamp) + break; + } + // No later date has been found, use the earlier of scheduled ones plus one + // week + if (next < now.timestamp) + next = first + 7 * 86400; + + return this->next_schedule_ = ESPTime::from_epoch_local(next); +} + +void DynamicOnTime::dump_config() { + ESP_LOGCONFIG(tag, "Cron trigger details:"); + ESP_LOGCONFIG(tag, "Disabled: %s", ONOFF(this->disabled_->state)); + ESP_LOGCONFIG( + tag, "Hour (source: '%s'): %.0f", + this->hour_->get_name().c_str(), this->hour_->state); + ESP_LOGCONFIG( + tag, "Minute (source: '%s'): %.0f", + this->minute_->get_name().c_str(), this->minute_->state); + ESP_LOGCONFIG( + tag, "Mon (source: '%s'): %s", + this->mon_->get_name().c_str(), ONOFF(this->mon_->state)); + ESP_LOGCONFIG( + tag, "Tue (source: '%s'): %s", + this->tue_->get_name().c_str(), ONOFF(this->tue_->state)); + ESP_LOGCONFIG( + tag, "Wed (source: '%s'): %s", + this->wed_->get_name().c_str(), ONOFF(this->wed_->state)); + ESP_LOGCONFIG( + tag, "Thu (source: '%s'): %s", + this->thu_->get_name().c_str(), ONOFF(this->thu_->state)); + ESP_LOGCONFIG( + tag, "Fri (source: '%s'): %s", + this->fri_->get_name().c_str(), ONOFF(this->fri_->state)); + ESP_LOGCONFIG( + tag, "Sat (source: '%s'): %s", + this->sat_->get_name().c_str(), ONOFF(this->sat_->state)); + ESP_LOGCONFIG( + tag, "Sun (source: '%s'): %s", + this->sun_->get_name().c_str(), ONOFF(this->sun_->state)); + + auto schedule = this->get_next_schedule(); + if (schedule.has_value()) + ESP_LOGCONFIG( + tag, "Next schedule: %s", + schedule.value().strftime("%a %H:%M:%S %m/%d/%Y").c_str()); +} + +} // namespace dynamic_on_time +} // namespace esphome diff --git a/esphome/components/dynamic_on_time/dynamic_on_time.h b/esphome/components/dynamic_on_time/dynamic_on_time.h new file mode 100644 index 0000000000..be6b25f865 --- /dev/null +++ b/esphome/components/dynamic_on_time/dynamic_on_time.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2023 Ilia Sotnikov + +#pragma once +#include +#include "esphome/components/time/real_time_clock.h" +#include "esphome/components/time/automation.h" +#include "esphome/components/number/number.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace dynamic_on_time { + +class DynamicOnTime : public Component { + public: + explicit DynamicOnTime( + time::RealTimeClock *, number::Number *, number::Number *, + switch_::Switch *, switch_::Switch *, switch_::Switch *, switch_::Switch *, + switch_::Switch *, switch_::Switch *, switch_::Switch *, switch_::Switch *, + std::vector *>); + + void setup() override; + void dump_config() override; + + optional get_next_schedule(); + + protected: + time::RealTimeClock *rtc_; + number::Number *hour_; + number::Number *minute_; + switch_::Switch *mon_; + switch_::Switch *tue_; + switch_::Switch *wed_; + switch_::Switch *thu_; + switch_::Switch *fri_; + switch_::Switch *sat_; + switch_::Switch *sun_; + switch_::Switch *disabled_; + std::vector *> actions_; + time::CronTrigger *trigger_{nullptr}; + Automation<> *automation_{nullptr}; + std::vector days_of_week_{}; + + std::vector flags_to_days_of_week_( + bool, bool, bool, bool, bool, bool, bool); + + void update_schedule_(); + optional next_schedule_{}; +}; + +} // namespace dynamic_on_time +} // namespace esphome diff --git a/esphome/components/fram/FRAM.cpp b/esphome/components/fram/FRAM.cpp new file mode 100644 index 0000000000..ff578f1ff5 --- /dev/null +++ b/esphome/components/fram/FRAM.cpp @@ -0,0 +1,629 @@ +// +// FILE: FRAM.cpp +// AUTHOR: Rob Tillaart +// VERSION: 0.5.3 +// DATE: 2018-01-24 +// PURPOSE: Arduino library for I2C FRAM +// URL: https://github.com/RobTillaart/FRAM_I2C +// +// ESPHome port: https://github.com/sharkydog/esphome-fram + +#include "FRAM.h" + +namespace esphome { +namespace fram { + +// used for metadata and sleep +const uint8_t FRAM_SLAVE_ID_ = 0x7C; // == 0xF8 +const uint8_t FRAM_SLEEP_CMD = 0x86; // +static const char * const TAG = "fram"; + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM PUBLIC +// + +void FRAM::setup() +{ + if (!this->isConnected()) + { + ESP_LOGE(TAG, "Device on address 0x%x not found!", this->address_); + this->mark_failed(); + } + else if (!this->getSize()) + { + ESP_LOGW(TAG, "Device on address 0x%x returned 0 size, set size in config!", this->address_); + } +} + +void FRAM::dump_config() +{ + ESP_LOGCONFIG(TAG, "FRAM:"); + ESP_LOGCONFIG(TAG, " Address: 0x%x", this->address_); + + bool ok = this->isConnected(); + + if (!ok) { + ESP_LOGE(TAG, " Device not found!"); + } + + if (this->_sizeBytes) { + ESP_LOGCONFIG(TAG, " Size: %uKiB", this->_sizeBytes / 1024UL); + } else if(ok) { + ESP_LOGW(TAG, " Size: 0KiB, set size in config!"); + } +} + + +bool FRAM::isConnected() +{ + i2c::ErrorCode err = this->bus_->write(this->address_, nullptr, 0, true); + return (err == i2c::ERROR_OK); +} + + +void FRAM::write8(uint16_t memaddr, uint8_t value) +{ + uint8_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint8_t)); +} + + +void FRAM::write16(uint16_t memaddr, uint16_t value) +{ + uint16_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint16_t)); +} + + +void FRAM::write32(uint16_t memaddr, uint32_t value) +{ + uint32_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint32_t)); +} + + +void FRAM::writeFloat(uint16_t memaddr, float value) +{ + float val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(float)); +} + + +void FRAM::writeDouble(uint16_t memaddr, double value) +{ + double val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(double)); +} + + +void FRAM::write(uint16_t memaddr, uint8_t * obj, uint16_t size) +{ + const int blocksize = 24; + uint8_t * p = obj; + while (size >= blocksize) + { + this->_writeBlock(memaddr, p, blocksize); + memaddr += blocksize; + p += blocksize; + size -= blocksize; + } + // remaining + if (size > 0) + { + this->_writeBlock(memaddr, p, size); + } +} + + +uint8_t FRAM::read8(uint16_t memaddr) +{ + uint8_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint8_t)); + return val; +} + + +uint16_t FRAM::read16(uint16_t memaddr) +{ + uint16_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint16_t)); + return val; +} + + +uint32_t FRAM::read32(uint16_t memaddr) +{ + uint32_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint32_t)); + return val; +} + + +float FRAM::readFloat(uint16_t memaddr) +{ + float val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(float)); + return val; +} + + +double FRAM::readDouble(uint16_t memaddr) +{ + double val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(double)); + return val; +} + + +void FRAM::read(uint16_t memaddr, uint8_t * obj, uint16_t size) +{ + const uint8_t blocksize = 24; + uint8_t * p = obj; + while (size >= blocksize) + { + this->_readBlock(memaddr, p, blocksize); + memaddr += blocksize; + p += blocksize; + size -= blocksize; + } + // remainder + if (size > 0) + { + this->_readBlock(memaddr, p, size); + } +} + + +//////////////////////////////////////////////////////////////////////// + + +int32_t FRAM::readUntil(uint16_t memaddr, char * buf, uint16_t buflen, char separator) +{ + // read and fill the buffer at once. + this->read(memaddr, (uint8_t *)buf, buflen); + for (uint16_t length = 0; length < buflen; length++) + { + if (buf[length] == separator) + { + buf[length] = 0; // replace separator => \0 EndChar + return length; + } + } + // entry does not fit in given buffer. + return (int32_t)-1; +} + + +int32_t FRAM::readLine(uint16_t memaddr, char * buf, uint16_t buflen) +{ + // read and fill the buffer at once. + this->read(memaddr, (uint8_t *)buf, buflen); + for (uint16_t length = 0; length < buflen-1; length++) + { + if (buf[length] == '\n') + { + buf[length + 1] = 0; // add \0 EndChar after '\n' + return length + 1; + } + } + // entry does not fit in given buffer. + return (int32_t)-1; +} + + +//////////////////////////////////////////////////////////////////////// + + +uint16_t FRAM::getManufacturerID() +{ + return this->_getMetaData(0); +} + + +uint16_t FRAM::getProductID() +{ + return this->_getMetaData(1); +} + + +// NOTE: returns the size in kiloBYTE +uint16_t FRAM::getSize() +{ + uint16_t density = this->_getMetaData(2); + uint16_t size = 0; + if (density > 0) + { + size = (1UL << density); + this->_sizeBytes = size * 1024UL; + } + return size; +} + + +uint32_t FRAM::getSizeBytes() +{ + return this->_sizeBytes; +} + + +// override to be used when getSize() fails == 0 +void FRAM::setSizeBytes(uint32_t value) +{ + this->_sizeBytes = value; +} + + +uint32_t FRAM::clear(uint8_t value) +{ + uint8_t buffer[16]; + for (uint8_t i = 0; i < 16; i++) buffer[i] = value; + uint32_t start = 0; + uint32_t end = this->_sizeBytes; + for (uint32_t address = start; address < end; address += 16) + { + this->_writeBlock(address, buffer, 16); + } + return end - start; +} + + +// EXPERIMENTAL - to be confirmed +// page 12 datasheet +// command = S 0xF8 A address A S 86 A P (A = Ack from slave ) +void FRAM::sleep() +{ + uint8_t addr = this->address_ << 1; + this->bus_->write(FRAM_SLAVE_ID_, &addr, 1, false); + this->bus_->write(FRAM_SLEEP_CMD >> 1, nullptr, 0, true); +} + + +// page 12 datasheet trec <= 400us +bool FRAM::wakeup(uint32_t trec) +{ + bool b = this->isConnected(); // wakeup + if (trec == 0) return b; + // wait recovery time + delayMicroseconds(trec); + return this->isConnected(); // check recovery OK +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM PROTECTED +// + +// metadata is packed as [....MMMM][MMMMDDDD][PPPPPPPP] +// M = manufacturerID +// D = density => memory size = 2^D KB +// P = product ID (together with D) +// P part might be proprietary +uint16_t FRAM::_getMetaData(uint8_t field) +{ + if (field > 2) return 0; + + uint8_t addr = this->address_ << 1; + this->bus_->write(FRAM_SLAVE_ID_, &addr, 1, false); + + uint8_t data[3] = {0,0,0}; + i2c::ErrorCode err = this->bus_->read(FRAM_SLAVE_ID_, data, 3); + if (err != i2c::ERROR_OK) return 0; + + // MANUFACTURER + if (field == 0) return (data[0] << 4) + (data[1] >> 4); + // PRODUCT ID + if (field == 1) return ((data[1] & 0x0F) << 8) + data[2]; + // DENSITY + // Fujitsu data sheet + // 3 => MB85RC64 = 64 Kbit. + // 5 => MB85RC256 + // 6 => MB85RC512 + // 7 => MB85RC1M + if (field == 2) return data[1] & 0x0F; + return 0; +} + + +void FRAM::_writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + i2c::WriteBuffer buff[2]; + uint8_t maddr[] = { (uint8_t)(memaddr >> 8), (uint8_t)(memaddr & 0xFF) }; + + buff[0].data = maddr; + buff[0].len = 2; + buff[1].data = obj; + buff[1].len = size; + + this->bus_->writev(this->address_, buff, 2, true); +} + + +void FRAM::_readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + uint8_t maddr[] = { (uint8_t)(memaddr >> 8), (uint8_t)(memaddr & 0xFF) }; + this->bus_->write(this->address_, maddr, 2, false); + this->bus_->read(this->address_, obj, size); +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM32 PUBLIC +// + +void FRAM32::write8(uint32_t memaddr, uint8_t value) +{ + uint8_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint8_t)); +} + + +void FRAM32::write16(uint32_t memaddr, uint16_t value) +{ + uint16_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint16_t)); +} + + +void FRAM32::write32(uint32_t memaddr, uint32_t value) +{ + uint32_t val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(uint32_t)); +} + + +void FRAM32::writeFloat(uint32_t memaddr, float value) +{ + float val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(float)); +} + + +void FRAM32::writeDouble(uint32_t memaddr, double value) +{ + double val = value; + this->_writeBlock(memaddr, (uint8_t *)&val, sizeof(double)); +} + + +void FRAM32::write(uint32_t memaddr, uint8_t * obj, uint16_t size) +{ + const int blocksize = 24; + uint8_t * p = obj; + while (size >= blocksize) + { + this->_writeBlock(memaddr, p, blocksize); + memaddr += blocksize; + p += blocksize; + size -= blocksize; + } + // remaining + if (size > 0) + { + this->_writeBlock(memaddr, p, size); + } +} + + +uint8_t FRAM32::read8(uint32_t memaddr) +{ + uint8_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint8_t)); + return val; +} + + +uint16_t FRAM32::read16(uint32_t memaddr) +{ + uint16_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint16_t)); + return val; +} + + +uint32_t FRAM32::read32(uint32_t memaddr) +{ + uint32_t val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(uint32_t)); + return val; +} + + +float FRAM32::readFloat(uint32_t memaddr) +{ + float val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(float)); + return val; +} + + +double FRAM32::readDouble(uint32_t memaddr) +{ + double val; + this->_readBlock(memaddr, (uint8_t *)&val, sizeof(double)); + return val; +} + + +void FRAM32::read(uint32_t memaddr, uint8_t * obj, uint16_t size) +{ + const uint8_t blocksize = 24; + uint8_t * p = obj; + while (size >= blocksize) + { + this->_readBlock(memaddr, p, blocksize); + memaddr += blocksize; + p += blocksize; + size -= blocksize; + } + // remainder + if (size > 0) + { + this->_readBlock(memaddr, p, size); + } +} + + +int32_t FRAM32::readUntil(uint32_t memaddr, char * buf, uint16_t buflen, char separator) +{ + // read and fill the buffer at once. + this->read(memaddr, (uint8_t *)buf, buflen); + for (uint16_t length = 0; length < buflen; length++) + { + if (buf[length] == separator) + { + buf[length] = 0; // replace separator => \0 EndChar + return length; + } + } + // entry does not fit in given buffer. + return (int32_t)-1; +} + + +int32_t FRAM32::readLine(uint32_t memaddr, char * buf, uint16_t buflen) +{ + // read and fill the buffer at once. + this->read(memaddr, (uint8_t *)buf, buflen); + for (uint16_t length = 0; length < buflen-1; length++) + { + if (buf[length] == '\n') + { + buf[length + 1] = 0; // add \0 EndChar after '\n' + return length + 1; + } + } + // entry does not fit in given buffer. + return (int32_t)-1; +} + + +template uint32_t FRAM32::writeObject(uint32_t memaddr, T &obj) +{ + this->write(memaddr, (uint8_t *) &obj, sizeof(obj)); + return memaddr + sizeof(obj); +}; + + +template uint32_t FRAM32::readObject(uint32_t memaddr, T &obj) +{ + this->read(memaddr, (uint8_t *) &obj, sizeof(obj)); + return memaddr + sizeof(obj); +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM32 PROTECTED +// + +void FRAM32::_writeBlock(uint32_t memaddr, uint8_t * obj, uint8_t size) +{ + uint8_t _addr = this->address_; + if (memaddr & 0x00010000) _addr += 0x01; + + i2c::WriteBuffer buff[2]; + uint8_t maddr[] = { (uint8_t)(memaddr >> 8), (uint8_t)(memaddr & 0xFF) }; + + buff[0].data = maddr; + buff[0].len = 2; + buff[1].data = obj; + buff[1].len = size; + + this->bus_->writev(_addr, buff, 2, true); +} + + +void FRAM32::_readBlock(uint32_t memaddr, uint8_t * obj, uint8_t size) +{ + uint8_t _addr = this->address_; + if (memaddr & 0x00010000) _addr += 0x01; + + uint8_t maddr[] = { (uint8_t)(memaddr >> 8), (uint8_t)(memaddr & 0xFF) }; + this->bus_->write(this->address_, maddr, 2, false); + this->bus_->read(_addr, obj, size); +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM11 +// + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM11 PROTECTED +// + +void FRAM11::_writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + // Device uses Address Pages + uint8_t DeviceAddrWithPageBits = this->address_ | ((memaddr & 0x0700) >> 8); + + i2c::WriteBuffer buff[2]; + uint8_t maddr = memaddr & 0xFF; + + buff[0].data = &maddr; + buff[0].len = 1; + buff[1].data = obj; + buff[1].len = size; + + this->bus_->writev(DeviceAddrWithPageBits, buff, 2, true); +} + + +void FRAM11::_readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + // Device uses Address Pages + uint8_t DeviceAddrWithPageBits = this->address_ | ((memaddr & 0x0700) >> 8); + uint8_t maddr = memaddr & 0xFF; + + this->bus_->write(DeviceAddrWithPageBits, &maddr, 1, false); + this->bus_->read(DeviceAddrWithPageBits, obj, size); +} + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM9 +// + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM9 PROTECTED +// + +void FRAM9::_writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + // Device uses Address Pages + uint8_t DeviceAddrWithPageBits = this->address_ | ((memaddr & 0x0100) >> 8); + + i2c::WriteBuffer buff[2]; + uint8_t maddr = memaddr & 0xFF; + + buff[0].data = &maddr; + buff[0].len = 1; + buff[1].data = obj; + buff[1].len = size; + + this->bus_->writev(DeviceAddrWithPageBits, buff, 2, true); +} + + +void FRAM9::_readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size) +{ + // Device uses Address Pages + uint8_t DeviceAddrWithPageBits = this->address_ | ((memaddr & 0x0100) >> 8); + uint8_t maddr = memaddr & 0xFF; + + this->bus_->write(DeviceAddrWithPageBits, &maddr, 1, false); + this->bus_->read(DeviceAddrWithPageBits, obj, size); +} + + +} // namespace fram +} // namespace esphome + +// -- END OF FILE -- diff --git a/esphome/components/fram/FRAM.h b/esphome/components/fram/FRAM.h new file mode 100644 index 0000000000..61c17fd355 --- /dev/null +++ b/esphome/components/fram/FRAM.h @@ -0,0 +1,164 @@ +#pragma once +// +// FILE: FRAM.h +// AUTHOR: Rob Tillaart +// VERSION: 0.5.3 +// DATE: 2018-01-24 +// PURPOSE: Arduino library for I2C FRAM +// URL: https://github.com/RobTillaart/FRAM_I2C +// +// ESPHome port: https://github.com/sharkydog/esphome-fram + +#include "esphome/core/hal.h" +#include "esphome/core/log.h" +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace fram { + +class FRAM : public Component, public i2c::I2CDevice +{ +public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::BUS; } + + bool isConnected(); + + void write8(uint16_t memaddr, uint8_t value); + void write16(uint16_t memaddr, uint16_t value); + void write32(uint16_t memaddr, uint32_t value); + void writeFloat(uint16_t memaddr, float value); + void writeDouble(uint16_t memaddr, double value); + void write(uint16_t memaddr, uint8_t * obj, uint16_t size); + + uint8_t read8(uint16_t memaddr); + uint16_t read16(uint16_t memaddr); + uint32_t read32(uint16_t memaddr); + float readFloat(uint16_t memaddr); + double readDouble(uint16_t memaddr); + void read(uint16_t memaddr, uint8_t * obj, uint16_t size); + + // Experimental 0.5.1 + // readUntil returns length 0.. n of the buffer. + // readUntil does NOT include the separator character. + // readUntil returns -1 if data does not fit into buffer, + // => separator not encountered. + int32_t readUntil(uint16_t memaddr, char * buf, uint16_t buflen, char separator); + // readLine returns length 0.. n of the buffer. + // readLine does include '\n' as end character. + // readLine returns -1 if data does not fit into buffer. + // buffer needs one place for end char '\0'. + int32_t readLine(uint16_t memaddr, char * buf, uint16_t buflen); + + template uint16_t writeObject(uint16_t memaddr, T &obj) + { + this->write(memaddr, (uint8_t *) &obj, sizeof(obj)); + return memaddr + sizeof(obj); + }; + template uint16_t readObject(uint16_t memaddr, T &obj) + { + this->read(memaddr, (uint8_t *) &obj, sizeof(obj)); + return memaddr + sizeof(obj); + } + + // meta info + // Fujitsu = 0x000A, Ramtron = 0x004 + uint16_t getManufacturerID(); + // Proprietary + uint16_t getProductID(); + // Returns size in KILO-BYTE (or 0) + // Verify for all manufacturers. + uint16_t getSize(); + // Returns size in BYTE + uint32_t getSizeBytes(); + // override when getSize() fails == 0 (see readme.md) + void setSizeBytes(uint32_t value); + + // fills FRAM with value, default 0. + uint32_t clear(uint8_t value = 0); + + // 0.3.6 + void sleep(); + // trec <= 400us P12 + bool wakeup(uint32_t trec = 400); + + +protected: + uint32_t _sizeBytes{0}; + + uint16_t _getMetaData(uint8_t id); + + // virtual so derived classes FRAM9/11 use their implementation. + virtual void _writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); + virtual void _readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); +}; + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM32 +// + +class FRAM32 : public FRAM +{ +public: + void write8(uint32_t memaddr, uint8_t value); + void write16(uint32_t memaddr, uint16_t value); + void write32(uint32_t memaddr, uint32_t value); + void writeFloat(uint32_t memaddr, float value); + void writeDouble(uint32_t memaddr, double value); + void write(uint32_t memaddr, uint8_t * obj, uint16_t size); + + uint8_t read8(uint32_t memaddr); + uint16_t read16(uint32_t memaddr); + uint32_t read32(uint32_t memaddr); + float readFloat(uint32_t memaddr); + double readDouble(uint32_t memaddr); + void read(uint32_t memaddr, uint8_t * obj, uint16_t size); + + // readUntil returns length 0.. n of the buffer. + // readUntil returns -1 if data does not fit into buffer, + // => separator not encountered. + int32_t readUntil(uint32_t memaddr, char * buf, uint16_t buflen, char separator); + // buffer needs one place for end char '\0'. + int32_t readLine(uint32_t memaddr, char * buf, uint16_t buflen); + + template uint32_t writeObject(uint32_t memaddr, T &obj); + template uint32_t readObject(uint32_t memaddr, T &obj); + +protected: + void _writeBlock(uint32_t memaddr, uint8_t * obj, uint8_t size); + void _readBlock(uint32_t memaddr, uint8_t * obj, uint8_t size); +}; + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM11 for FRAM that use 11 bits addresses - e.g. MB85RC16 +// + +class FRAM11 : public FRAM +{ +protected: + void _writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); + void _readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); +}; + + +///////////////////////////////////////////////////////////////////////////// +// +// FRAM9 for FRAM that use 9 bits addresses - e.g. MB85RC04 +// +class FRAM9 : public FRAM +{ +protected: + void _writeBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); + void _readBlock(uint16_t memaddr, uint8_t * obj, uint8_t size); +}; + +} // namespace fram +} // namespace esphome + +// -- END OF FILE -- diff --git a/esphome/components/fram/__init__.py b/esphome/components/fram/__init__.py new file mode 100644 index 0000000000..d16623bb69 --- /dev/null +++ b/esphome/components/fram/__init__.py @@ -0,0 +1,64 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +import re +from esphome.components import i2c +from esphome.const import CONF_ID, CONF_TYPE, CONF_SIZE + +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +fram_ns = cg.esphome_ns.namespace("fram") +FRAMComponent = fram_ns.class_("FRAM", cg.Component, i2c.I2CDevice) +FRAM9Component = fram_ns.class_("FRAM9", FRAMComponent) +FRAM11Component = fram_ns.class_("FRAM11", FRAMComponent) +FRAM32Component = fram_ns.class_("FRAM32", FRAMComponent) + +def validate_bytes_1024(value): + value = cv.string(value).lower() + match = re.match(r"^([0-9]+)\s*(\w*)$", value) + + if match is None: + raise cv.Invalid(f"Expected number of bytes with unit, got {value}") + + SUFF_LIST = ["B","KB","KiB","MB","MiB"] + SUFF = { + "": 1, + "b": 1, + "kb": 1000, + "kib": 1024, + "mb": pow(1000,2), + "mib": pow(1024,2) + } + + if match.group(2) not in SUFF: + raise cv.Invalid(f"Invalid metric suffix {match.group(2)}, valid: {SUFF_LIST}") + + return int(int(match.group(1)) * SUFF[match.group(2)]) + + +FRAM_SCHEMA = cv.Schema({ + cv.Optional(CONF_SIZE): validate_bytes_1024 +}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x50)) + +CONFIG_SCHEMA = cv.typed_schema({ + "FRAM": FRAM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(FRAMComponent) + }), + "FRAM9": FRAM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(FRAM9Component) + }), + "FRAM11": FRAM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(FRAM11Component) + }), + "FRAM32": FRAM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(FRAM32Component) + }) +}, key=CONF_TYPE, default_type="FRAM", upper=True) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_SIZE in config: + cg.add(var.setSizeBytes(config[CONF_SIZE])) \ No newline at end of file diff --git a/esphome/components/mcp4461/mcp4461.h b/esphome/components/mcp4461/mcp4461.h index 2742cd8340..392f1ca232 100644 --- a/esphome/components/mcp4461/mcp4461.h +++ b/esphome/components/mcp4461/mcp4461.h @@ -100,7 +100,7 @@ class Mcp4461Component : public Component, public i2c::I2CDevice { /// @param[wiper] wiper for which terminal shall be initialized disabled /// @param[terminal] terminal to disable, one of { 'a', 'b', 'w', 'h' } void initialize_terminal_disabled(Mcp4461WiperIdx wiper, char terminal); - + /// @brief available/required status codes enum ErrorCode { MCP4461_STATUS_OK = 0, // CMD completed successfully @@ -122,68 +122,70 @@ class Mcp4461Component : public Component, public i2c::I2CDevice { /// @brief update write protection status of device void update_write_protection_status_(); /// @brief fetch wiper address for given wiper - /// @param[wiper] wiper to fetch address for, int in range 0-7 - /// @return wiper address from Mcp4461Addresses + /// @param[in] wiper - Wiper to fetch address for, int in range 0-7 + /// @return wiper - Address from Mcp4461Addresses uint8_t get_wiper_address_(uint8_t wiper); /// @brief internal i2c function to read given wiper value - /// @return uint16_t value in range 0-256 representing current wiper state/level + /// @return uint16_t - Value in range 0-256 representing current wiper state/level uint16_t read_wiper_level_(uint8_t wiper); /// @brief fetch device status register values - /// @return uint8_t status register value - see datasheet for bit values + /// @return uint8_t - Status register value - see datasheet for bit values uint8_t get_status_register_(); /// @brief read current level/state of given wiper with validation checks - /// @return uint16_t value in range 0-256 representing current wiper state/level + /// @return uint16_t - Value in range 0-256 representing current wiper state/level uint16_t get_wiper_level_(Mcp4461WiperIdx wiper); /// @brief set level/state of given wiper - /// @return bool - true on success, false on error/warning + /// @return bool - True on success, false on error/warning bool set_wiper_level_(Mcp4461WiperIdx wiper, uint16_t value); /// @brief update current level/state of given wiper - /// @return bool - true on success, false on error/warning + /// @return bool - True on success, false on error/warning bool update_wiper_level_(Mcp4461WiperIdx wiper); /// @brief enable given wiper - /// @param[in] wiper The wiper to enable + /// @param[in] wiper - The wiper to enable void enable_wiper_(Mcp4461WiperIdx wiper); /// @brief disable given wiper - /// @param[in] wiper The wiper to disable + /// @param[in] wiper - The wiper to disable void disable_wiper_(Mcp4461WiperIdx wiper); /// @brief increase given wiper - /// @param[wiper] wiper to increase - /// @return bool - true on success, false on error/warning + /// @param[in] wiper - Wiper to increase + /// @return bool - True on success, false on error/warning bool increase_wiper_(Mcp4461WiperIdx wiper); /// @brief increase given wiper - /// @param[wiper] wiper to increase - /// @return bool - true on success, false on error/warning + /// @param[in] wiper - Wiper to decrease + /// @return bool - True on success, false on error/warning bool decrease_wiper_(Mcp4461WiperIdx wiper); /// @brief enable terminal of wiper - /// @param[wiper] desired wiper for which the terminal shall be enabled + /// @param[in] wiper - The desired wiper for which the terminal shall be enabled + /// @param[in] terminal - The terminal that is to be enabled for given wiper void enable_terminal_(Mcp4461WiperIdx wiper, char terminal); /// @brief disable terminal of wiper - /// @param[wiper] desired wiper for which the terminal shall be disabled + /// @param[in] wiper - The desired wiper for which the terminal shall be disabled + /// @param[in] terminal - The terminal that is to be enabled for given wiper void disable_terminal_(Mcp4461WiperIdx, char terminal); /// @brief check if device is still busy writing to eeprom /// @return bool - true if device is currently writing to eeprom bool is_writing_(); /// @brief wait until timeout if device is busy - /// @param[wait_if_not_ready] set to true to wait until timeout again, if previous write timed out already + /// @param[in] wait_if_not_ready - Set to true to wait until timeout again, if previous write timed out already /// @return bool - true if device eeprom still busy, false if rdy for write to nonvolatile wiper/eeprom bool is_eeprom_ready_for_writing_(bool wait_if_not_ready); /// @brief set wiper level - /// @param[wiper] wiper for which the new state shall be set - /// @param[value] the int value in range 0-256 the wiper shall be set to + /// @param[in] wiper - The wiper for which the new state shall be set + /// @param[in] value - The int value in range 0-256 the wiper shall be set to void write_wiper_level_(uint8_t wiper, uint16_t value); /// @brief internal i2c write function - /// @return bool - true write successful, false if not + /// @return bool - True write successful, false if not bool mcp4461_write_(uint8_t addr, uint16_t data, bool nonvolatile = false); /// @brief calculate correct terminal register values - /// @return uint8_t - calculated terminal register value for current internal terminal states + /// @return uint8_t - Calculated terminal register value for current internal terminal states uint8_t calc_terminal_connector_byte_(Mcp4461TerminalIdx terminal_connector); /// @brief internal function to update terminal registers void update_terminal_register_(Mcp4461TerminalIdx terminal_connector); /// @brief internal function to get terminal register values - /// /// @return uint8_t - get terminal register value of specified terminal + /// @return uint8_t - Get terminal register value of specified terminal uint8_t get_terminal_register_(Mcp4461TerminalIdx terminal_connector); /// @brief internal function to set terminal registers - /// @return bool - true if write successful, false if not + /// @return bool - True if write successful, false if not bool set_terminal_register_(Mcp4461TerminalIdx terminal_connector, uint8_t data); WiperState reg_[8]; diff --git a/esphome/components/mcp4461/output/__init__.py b/esphome/components/mcp4461/output/__init__.py index 2ad3c44b86..6fdd0ca07d 100644 --- a/esphome/components/mcp4461/output/__init__.py +++ b/esphome/components/mcp4461/output/__init__.py @@ -4,7 +4,7 @@ from esphome.components import output from esphome.const import CONF_CHANNEL, CONF_ID, CONF_INITIAL_VALUE from .. import Mcp4461Component, CONF_MCP4461_ID, mcp4461_ns -DEPENDENCIES = ["mcp4461"] +DEPENDENCIES = ["mcp4461", "output"] Mcp4461Wiper = mcp4461_ns.class_( "Mcp4461Wiper", output.FloatOutput, cg.Parented.template(Mcp4461Component)