mirror of
https://github.com/esphome/esphome.git
synced 2026-10-15 02:32:49 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb37842041 | ||
|
|
c1fa73470e | ||
|
|
e41d16ba7e | ||
|
|
5171113c87 | ||
|
|
5718e4b779 |
@@ -5,6 +5,7 @@
|
||||
#include "display_color_utils.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::display {
|
||||
@@ -52,40 +53,65 @@ void Display::line_at_angle(int x, int y, int angle, int start_radius, int stop_
|
||||
this->line(x1, y1, x2, y2, color);
|
||||
}
|
||||
|
||||
void Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
size_t line_stride = x_offset + w + x_pad; // length of each source line in pixels
|
||||
uint32_t color_value;
|
||||
// flatten keeps to_color() inlined in the pixel loop; in PSRAM builds GCC at
|
||||
// -Os stops inlining it once there are two call sites, which costs a call and
|
||||
// spills per pixel. Without PSRAM there is one call site and it changes
|
||||
// nothing, and draw_pixel_at() stays a virtual call either way.
|
||||
template<ColorBitness BITNESS>
|
||||
static void __attribute__((flatten))
|
||||
draw_packed_pixels(Display *display, int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
const size_t line_stride = x_offset + w + x_pad; // length of each source line in pixels
|
||||
// BITNESS is a constant here, so to_color()'s three per pixel divisions
|
||||
// become a multiply and shift and its bitness switch folds.
|
||||
const auto draw_source_pixel = [&](int x, int y, size_t source_idx) ESPHOME_ALWAYS_INLINE {
|
||||
display->draw_pixel_at(
|
||||
x + x_start, y + y_start,
|
||||
ColorUtil::to_color(ColorUtil::read_packed<BITNESS>(ptr, source_idx, big_endian), order, BITNESS));
|
||||
};
|
||||
// The source and frame buffers are row-major, so walking rows keeps the read
|
||||
// and the write sequential. When the display swaps the axes before writing,
|
||||
// walking columns keeps the write sequential instead; with the frame buffer
|
||||
// in PSRAM a strided write costs more than a strided read.
|
||||
#ifdef USE_PSRAM
|
||||
if (display->pixel_axes_swapped()) {
|
||||
size_t column_idx = y_offset * line_stride + x_offset;
|
||||
for (int x = 0; x != w; x++, column_idx++) {
|
||||
size_t source_idx = column_idx;
|
||||
for (int y = 0; y != h; y++, source_idx += line_stride) {
|
||||
draw_source_pixel(x, y, source_idx);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// Row walk: builds without PSRAM, and PSRAM builds whose axes are not swapped.
|
||||
for (int y = 0; y != h; y++) {
|
||||
size_t source_idx = (y_offset + y) * line_stride + x_offset;
|
||||
size_t source_idx_mod;
|
||||
for (int x = 0; x != w; x++, source_idx++) {
|
||||
switch (bitness) {
|
||||
default:
|
||||
color_value = ptr[source_idx];
|
||||
break;
|
||||
case COLOR_BITNESS_565:
|
||||
source_idx_mod = source_idx * 2;
|
||||
if (big_endian) {
|
||||
color_value = (ptr[source_idx_mod] << 8) + ptr[source_idx_mod + 1];
|
||||
} else {
|
||||
color_value = ptr[source_idx_mod] + (ptr[source_idx_mod + 1] << 8);
|
||||
}
|
||||
break;
|
||||
case COLOR_BITNESS_888:
|
||||
source_idx_mod = source_idx * 3;
|
||||
if (big_endian) {
|
||||
color_value = (ptr[source_idx_mod + 0] << 16) + (ptr[source_idx_mod + 1] << 8) + ptr[source_idx_mod + 2];
|
||||
} else {
|
||||
color_value = ptr[source_idx_mod + 0] + (ptr[source_idx_mod + 1] << 8) + (ptr[source_idx_mod + 2] << 16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
this->draw_pixel_at(x + x_start, y + y_start, ColorUtil::to_color(color_value, order, bitness));
|
||||
draw_source_pixel(x, y, source_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
switch (bitness) {
|
||||
case COLOR_BITNESS_565:
|
||||
draw_packed_pixels<COLOR_BITNESS_565>(this, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset,
|
||||
x_pad);
|
||||
break;
|
||||
case COLOR_BITNESS_888:
|
||||
draw_packed_pixels<COLOR_BITNESS_888>(this, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset,
|
||||
x_pad);
|
||||
break;
|
||||
default:
|
||||
draw_packed_pixels<COLOR_BITNESS_332>(this, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset,
|
||||
x_pad);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HOT Display::horizontal_line(int x, int y, int width, Color color) {
|
||||
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
||||
for (int i = x; i < x + width; i++)
|
||||
|
||||
@@ -139,6 +139,11 @@ enum DisplayRotation {
|
||||
DISPLAY_ROTATION_270_DEGREES = 270,
|
||||
};
|
||||
|
||||
/// Whether a rotation swaps the x and y axes.
|
||||
constexpr bool rotation_swaps_axes(DisplayRotation rotation) {
|
||||
return rotation == DISPLAY_ROTATION_90_DEGREES || rotation == DISPLAY_ROTATION_270_DEGREES;
|
||||
}
|
||||
|
||||
const int EDGES_TRIGON = 3;
|
||||
const int EDGES_TRIANGLE = 3;
|
||||
const int EDGES_TETRAGON = 4;
|
||||
@@ -717,6 +722,12 @@ class Display : public PollingComponent {
|
||||
|
||||
virtual DisplayRotation get_rotation() const { return this->rotation_; }
|
||||
|
||||
/// True when draw_pixel_at() swaps x and y before writing, so pixels that are
|
||||
/// neighbours along x land a row apart in the frame buffer. Not the same as
|
||||
/// get_width()/get_height() swapping: a display that rotates in hardware
|
||||
/// reports swapped dimensions but writes unswapped pixels.
|
||||
virtual bool pixel_axes_swapped() const { return rotation_swaps_axes(this->rotation_); }
|
||||
|
||||
/** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays,
|
||||
* returns the type the display is currently configured to.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::display {
|
||||
enum ColorOrder : uint8_t { COLOR_ORDER_RGB = 0, COLOR_ORDER_BGR = 1, COLOR_ORDER_GRB = 2 };
|
||||
@@ -10,6 +11,27 @@ inline static uint8_t esp_scale(uint8_t i, uint8_t scale, uint8_t max_value = 25
|
||||
|
||||
class ColorUtil {
|
||||
public:
|
||||
/// Reads one packed pixel of the given bitness from a source buffer into the
|
||||
/// value to_color() takes. BITNESS is a template argument so the byte layout
|
||||
/// folds; pass it on to to_color() as a constant too, and its channel scaling
|
||||
/// divides by a constant, which the compiler turns into a multiply and shift.
|
||||
template<ColorBitness BITNESS>
|
||||
static ESPHOME_ALWAYS_INLINE uint32_t read_packed(const uint8_t *ptr, size_t index, bool big_endian) {
|
||||
if constexpr (BITNESS == COLOR_BITNESS_565) {
|
||||
const size_t idx = index * 2;
|
||||
if (big_endian)
|
||||
return (ptr[idx] << 8) + ptr[idx + 1];
|
||||
return ptr[idx] + (ptr[idx + 1] << 8);
|
||||
} else if constexpr (BITNESS == COLOR_BITNESS_888) {
|
||||
const size_t idx = index * 3;
|
||||
if (big_endian)
|
||||
return (ptr[idx + 0] << 16) + (ptr[idx + 1] << 8) + ptr[idx + 2];
|
||||
return ptr[idx + 0] + (ptr[idx + 1] << 8) + (ptr[idx + 2] << 16);
|
||||
} else {
|
||||
return ptr[index];
|
||||
}
|
||||
}
|
||||
|
||||
static Color to_color(uint32_t colorcode, ColorOrder color_order,
|
||||
ColorBitness color_bitness = ColorBitness::COLOR_BITNESS_888, bool right_bit_aligned = true) {
|
||||
uint8_t first_color, second_color, third_color;
|
||||
|
||||
@@ -3,21 +3,18 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "epaper_spi");
|
||||
static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
|
||||
|
||||
static constexpr const char *const EPAPER_STATE_STRINGS[] = {
|
||||
"IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
|
||||
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
|
||||
};
|
||||
PROGMEM_STRING_TABLE(EPaperStateStrings, "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
|
||||
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP", "Unknown");
|
||||
|
||||
const char *EPaperBase::epaper_state_to_string_() {
|
||||
if (auto idx = static_cast<unsigned>(this->state_); idx < std::size(EPAPER_STATE_STRINGS))
|
||||
return EPAPER_STATE_STRINGS[idx];
|
||||
return "Unknown";
|
||||
const LogString *EPaperBase::epaper_state_to_string_() {
|
||||
return EPaperStateStrings::get_log_str(static_cast<uint8_t>(this->state_), EPaperStateStrings::LAST_INDEX);
|
||||
}
|
||||
|
||||
void EPaperBase::setup() {
|
||||
@@ -120,7 +117,7 @@ void EPaperBase::update_effective_transform_() {
|
||||
|
||||
void EPaperBase::update() {
|
||||
if (this->state_ != EPaperState::IDLE) {
|
||||
ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
|
||||
ESP_LOGE(TAG, "Display already in state %s", LOG_STR_ARG(epaper_state_to_string_()));
|
||||
return;
|
||||
}
|
||||
this->set_state_(EPaperState::UPDATE);
|
||||
@@ -160,7 +157,7 @@ void EPaperBase::loop() {
|
||||
}
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
if (now - this->waiting_for_idle_last_print_ >= 1000) {
|
||||
ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
|
||||
ESP_LOGV(TAG, "Waiting for idle in state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
|
||||
this->waiting_for_idle_last_print_ = millis();
|
||||
}
|
||||
#endif
|
||||
@@ -173,7 +170,7 @@ void EPaperBase::loop() {
|
||||
// The busy pin never cleared: give up on this update so later ones are not refused, and make the next one full
|
||||
void EPaperBase::abandon_update_() {
|
||||
ESP_LOGE(TAG, "Display stayed busy for %u s in state %s; check the busy pin", (unsigned) this->busy_timeout_seconds_,
|
||||
this->epaper_state_to_string_());
|
||||
LOG_STR_ARG(this->epaper_state_to_string_()));
|
||||
this->status_set_warning(LOG_STR("Busy pin timeout"));
|
||||
if (this->state_ == EPaperState::TRANSFER_DATA)
|
||||
this->abort_transfer();
|
||||
@@ -193,10 +190,10 @@ void EPaperBase::abandon_update_() {
|
||||
* Should a subclassed class need to override this, the method will need to be made virtual.
|
||||
*/
|
||||
void EPaperBase::process_state_() {
|
||||
ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
|
||||
ESP_LOGV(TAG, "Process state entered in state %s", LOG_STR_ARG(epaper_state_to_string_()));
|
||||
switch (this->state_) {
|
||||
default:
|
||||
ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
|
||||
ESP_LOGE(TAG, "Display is in unhandled state %s", LOG_STR_ARG(epaper_state_to_string_()));
|
||||
this->set_state_(EPaperState::IDLE);
|
||||
break;
|
||||
case EPaperState::IDLE:
|
||||
@@ -266,7 +263,7 @@ void EPaperBase::process_state_() {
|
||||
}
|
||||
|
||||
void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
|
||||
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
|
||||
ESP_LOGV(TAG, "Exit state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
|
||||
this->state_ = state;
|
||||
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
|
||||
// allow subclasses to nominate delays
|
||||
@@ -274,7 +271,7 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
|
||||
delay = this->next_delay_;
|
||||
this->next_delay_ = 0;
|
||||
this->delay_until_ = millis() + delay;
|
||||
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
|
||||
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", LOG_STR_ARG(this->epaper_state_to_string_()), delay,
|
||||
TRUEFALSE(this->waiting_for_idle_));
|
||||
if (state == EPaperState::IDLE) {
|
||||
this->disable_loop();
|
||||
|
||||
@@ -106,6 +106,7 @@ class EPaperBase : public Display,
|
||||
|
||||
int get_width() override { return this->effective_transform_ & SWAP_XY ? this->height_ : this->width_; }
|
||||
int get_height() override { return this->effective_transform_ & SWAP_XY ? this->width_ : this->height_; }
|
||||
bool pixel_axes_swapped() const override { return this->effective_transform_ & SWAP_XY; }
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
||||
@@ -120,7 +121,7 @@ class EPaperBase : public Display,
|
||||
bool is_using_partial_update_() const { return this->full_update_every_ > 1; }
|
||||
void process_state_();
|
||||
|
||||
const char *epaper_state_to_string_();
|
||||
const LogString *epaper_state_to_string_();
|
||||
bool is_idle_() const;
|
||||
void setup_pins_() const;
|
||||
virtual bool reset();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "hon_climate.h"
|
||||
#include "hon_packet.h"
|
||||
|
||||
@@ -20,6 +21,27 @@ constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
|
||||
const uint8_t ONE_BUF[] = {0x00, 0x01};
|
||||
const uint8_t ZERO_BUF[] = {0x00, 0x00};
|
||||
|
||||
// Alarm texts indexed by alarm code; the last entry is the fallback for unknown codes
|
||||
PROGMEM_STRING_TABLE(
|
||||
HonAlarmMessages, "Outdoor module failure", "Outdoor defrost sensor failure",
|
||||
"Outdoor compressor exhaust sensor failure", "Outdoor EEPROM abnormality", "Indoor coil sensor failure",
|
||||
"Indoor-outdoor communication failure", "Power supply overvoltage protection",
|
||||
"Communication failure between panel and indoor unit", "Outdoor compressor overheat protection",
|
||||
"Outdoor environmental sensor abnormality", "Full water protection", "Indoor EEPROM failure",
|
||||
"Outdoor out air sensor failure", "CBD and module communication failure", "Indoor DC fan failure",
|
||||
"Outdoor DC fan failure", "Door switch failure", "Dust filter needs cleaning reminder", "Water shortage protection",
|
||||
"Humidity sensor failure", "Indoor temperature sensor failure", "Manipulator limit failure",
|
||||
"Indoor PM2.5 sensor failure", "Outdoor PM2.5 sensor failure", "Indoor heating overload/high load alarm",
|
||||
"Outdoor AC current protection", "Outdoor compressor operation abnormality", "Outdoor DC current protection",
|
||||
"Outdoor no-load failure", "CT current abnormality", "Indoor cooling freeze protection",
|
||||
"High and low pressure protection", "Compressor out air temperature is too high",
|
||||
"Outdoor evaporator sensor failure", "Outdoor cooling overload", "Water pump drainage failure",
|
||||
"Three-phase power supply failure", "Four-way valve failure", "External alarm/scraper flow switch failure",
|
||||
"Temperature cutoff protection alarm", "Different mode operation failure", "Electronic expansion valve failure",
|
||||
"Dual heat source sensor Tw failure", "Communication failure with the wired controller",
|
||||
"Indoor unit address duplication failure", "50Hz zero crossing failure", "Outdoor unit failure",
|
||||
"Formaldehyde sensor failure", "VOC sensor failure", "CO2 sensor failure", "Firewall failure", "Unknown");
|
||||
|
||||
HonClimate::HonClimate()
|
||||
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
@@ -687,9 +709,17 @@ void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, boo
|
||||
if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) {
|
||||
bool alarm_status = (packet[2 + i] & alarm_bit) != 0;
|
||||
int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO;
|
||||
const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT
|
||||
? esphome::haier::hon_protocol::HON_ALARM_MESSAGES[alarm_code].c_str()
|
||||
: "Unknown";
|
||||
#ifdef USE_ESP8266
|
||||
// Static so a delayed on_alarm automation can still read it, but only until the next
|
||||
// alarm change, including a later one in this packet; `code` stays correct
|
||||
static char alarm_message[HonAlarmMessages::MAX_LENGTH + 1];
|
||||
ESPHOME_strncpy_P(alarm_message,
|
||||
reinterpret_cast<const char *>(
|
||||
HonAlarmMessages::get_progmem_str(alarm_code, HonAlarmMessages::LAST_INDEX)),
|
||||
sizeof(alarm_message));
|
||||
#else
|
||||
const char *alarm_message = HonAlarmMessages::get_progmem_str(alarm_code, HonAlarmMessages::LAST_INDEX);
|
||||
#endif
|
||||
esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated",
|
||||
alarm_code, alarm_message);
|
||||
if (alarm_status) {
|
||||
|
||||
@@ -197,60 +197,4 @@ enum class SubcommandsControl : uint16_t {
|
||||
// content: all values like in status packet)
|
||||
};
|
||||
|
||||
const std::string HON_ALARM_MESSAGES[] = {
|
||||
"Outdoor module failure",
|
||||
"Outdoor defrost sensor failure",
|
||||
"Outdoor compressor exhaust sensor failure",
|
||||
"Outdoor EEPROM abnormality",
|
||||
"Indoor coil sensor failure",
|
||||
"Indoor-outdoor communication failure",
|
||||
"Power supply overvoltage protection",
|
||||
"Communication failure between panel and indoor unit",
|
||||
"Outdoor compressor overheat protection",
|
||||
"Outdoor environmental sensor abnormality",
|
||||
"Full water protection",
|
||||
"Indoor EEPROM failure",
|
||||
"Outdoor out air sensor failure",
|
||||
"CBD and module communication failure",
|
||||
"Indoor DC fan failure",
|
||||
"Outdoor DC fan failure",
|
||||
"Door switch failure",
|
||||
"Dust filter needs cleaning reminder",
|
||||
"Water shortage protection",
|
||||
"Humidity sensor failure",
|
||||
"Indoor temperature sensor failure",
|
||||
"Manipulator limit failure",
|
||||
"Indoor PM2.5 sensor failure",
|
||||
"Outdoor PM2.5 sensor failure",
|
||||
"Indoor heating overload/high load alarm",
|
||||
"Outdoor AC current protection",
|
||||
"Outdoor compressor operation abnormality",
|
||||
"Outdoor DC current protection",
|
||||
"Outdoor no-load failure",
|
||||
"CT current abnormality",
|
||||
"Indoor cooling freeze protection",
|
||||
"High and low pressure protection",
|
||||
"Compressor out air temperature is too high",
|
||||
"Outdoor evaporator sensor failure",
|
||||
"Outdoor cooling overload",
|
||||
"Water pump drainage failure",
|
||||
"Three-phase power supply failure",
|
||||
"Four-way valve failure",
|
||||
"External alarm/scraper flow switch failure",
|
||||
"Temperature cutoff protection alarm",
|
||||
"Different mode operation failure",
|
||||
"Electronic expansion valve failure",
|
||||
"Dual heat source sensor Tw failure",
|
||||
"Communication failure with the wired controller",
|
||||
"Indoor unit address duplication failure",
|
||||
"50Hz zero crossing failure",
|
||||
"Outdoor unit failure",
|
||||
"Formaldehyde sensor failure",
|
||||
"VOC sensor failure",
|
||||
"CO2 sensor failure",
|
||||
"Firewall failure",
|
||||
};
|
||||
|
||||
constexpr size_t HON_ALARM_COUNT = sizeof(HON_ALARM_MESSAGES) / sizeof(HON_ALARM_MESSAGES[0]);
|
||||
|
||||
} // namespace esphome::haier::hon_protocol
|
||||
|
||||
@@ -204,6 +204,7 @@ class IT8951Display : public Display,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
||||
int get_width() override { return (this->effective_transform_ & TRANSFORM_SWAP_XY) ? this->height_ : this->width_; }
|
||||
int get_height() override { return (this->effective_transform_ & TRANSFORM_SWAP_XY) ? this->width_ : this->height_; }
|
||||
bool pixel_axes_swapped() const override { return this->effective_transform_ & TRANSFORM_SWAP_XY; }
|
||||
|
||||
protected:
|
||||
int get_height_internal() override { return this->height_; }
|
||||
|
||||
@@ -130,19 +130,8 @@ class MipiSpi : public display::Display,
|
||||
}
|
||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||
|
||||
int get_width() override {
|
||||
if (this->rotation_ == display::DISPLAY_ROTATION_90_DEGREES ||
|
||||
this->rotation_ == display::DISPLAY_ROTATION_270_DEGREES)
|
||||
return HEIGHT;
|
||||
return WIDTH;
|
||||
}
|
||||
|
||||
int get_height() override {
|
||||
if (this->rotation_ == display::DISPLAY_ROTATION_90_DEGREES ||
|
||||
this->rotation_ == display::DISPLAY_ROTATION_270_DEGREES)
|
||||
return WIDTH;
|
||||
return HEIGHT;
|
||||
}
|
||||
int get_width() override { return display::rotation_swaps_axes(this->rotation_) ? HEIGHT : WIDTH; }
|
||||
int get_height() override { return display::rotation_swaps_axes(this->rotation_) ? WIDTH : HEIGHT; }
|
||||
|
||||
void set_init_sequence(const uint8_t *sequence, size_t len) {
|
||||
this->init_sequence_ = sequence;
|
||||
@@ -621,6 +610,9 @@ class MipiSpiBuffer
|
||||
#endif
|
||||
}
|
||||
|
||||
// With hardware rotation the buffer is written in drawn coordinates.
|
||||
bool pixel_axes_swapped() const override { return !HAS_HARDWARE_ROTATION && display::Display::pixel_axes_swapped(); }
|
||||
|
||||
// Draw a pixel at the given coordinates.
|
||||
void draw_pixel_at(int x, int y, Color color) override {
|
||||
if (this->is_point_clipped(x, y))
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
"""TMP102 threshold status entity."""
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import CONF_TMP102_ID, TMP102Component, validate_unique_child
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(CONF_TMP102_ID): cv.use_id(TMP102Component),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
return validate_unique_child(config, "text_sensor")
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_TMP102_TEXT_SENSOR")
|
||||
parent = await cg.get_variable(config[CONF_TMP102_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
cg.add(parent.set_threshold_status_text_sensor(var))
|
||||
@@ -1,8 +1,14 @@
|
||||
#include "tmp102.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
#include "esphome/core/progmem.h"
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
namespace esphome::tmp102 {
|
||||
|
||||
@@ -12,6 +18,30 @@ static constexpr uint32_t READ_TEMP_TIMEOUT_ID = 0;
|
||||
|
||||
static constexpr uint8_t TMP102_REGISTER_TEMPERATURE = 0x00;
|
||||
static constexpr float TMP102_CONVERSION_FACTOR = 0.0625f;
|
||||
|
||||
enum TMP102ThresholdStatus : uint8_t {
|
||||
TMP102_THRESHOLD_STATUS_NOT_CONFIGURED,
|
||||
TMP102_THRESHOLD_STATUS_SETUP_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_OK,
|
||||
TMP102_THRESHOLD_STATUS_RESTORE_RESET,
|
||||
TMP102_THRESHOLD_STATUS_RESTORE_RESET_SAVE_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_PREFERENCE_SAVE_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_RECOVERY_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_RECOVERED,
|
||||
TMP102_THRESHOLD_STATUS_WRITE_HIGH_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_WRITE_LOW_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_SENSOR_SETUP_FAILED,
|
||||
TMP102_THRESHOLD_STATUS_COUNT,
|
||||
};
|
||||
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
PROGMEM_STRING_TABLE(ThresholdStatusStrings, "Not configured", "Setup failed", "OK", "Restored thresholds reset",
|
||||
"Restore reset; save failed", "Preference save failed", "Configuration recovery failed",
|
||||
"Configuration restored", "Write failed: high threshold", "Write failed: low threshold",
|
||||
"Rejected: sensor setup failed");
|
||||
static_assert(ThresholdStatusStrings::COUNT == TMP102_THRESHOLD_STATUS_COUNT);
|
||||
#endif
|
||||
|
||||
#if defined(USE_TMP102_CONFIGURE) || defined(USE_TMP102_BINARY_SENSOR)
|
||||
static constexpr uint8_t TMP102_REGISTER_CONFIGURATION = 0x01;
|
||||
static constexpr uint16_t TMP102_ALERT_BIT = 0x0020;
|
||||
@@ -42,15 +72,30 @@ static uint16_t encode_temperature(float temperature, bool extended) {
|
||||
return static_cast<uint16_t>(static_cast<uint32_t>(static_cast<uint16_t>(steps)) << (extended ? 3 : 4));
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(USE_TMP102_CONFIGURE) || defined(USE_TMP102_TEXT_SENSOR)
|
||||
void TMP102Component::setup() {
|
||||
if (!this->configure_)
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
if (this->configure_) {
|
||||
this->setup_configured_();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_NOT_CONFIGURED);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
void TMP102Component::setup_configured_() {
|
||||
bool corrected = false;
|
||||
#ifdef USE_TMP102_NUMBER
|
||||
float restored[2]{};
|
||||
this->prepare_number_limits_(restored);
|
||||
bool restore_corrected[2]{};
|
||||
corrected = this->prepare_number_limits_(restored, restore_corrected);
|
||||
#endif
|
||||
if (!this->write_configuration_()) {
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_SETUP_FAILED);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -61,14 +106,17 @@ void TMP102Component::setup() {
|
||||
if (control == nullptr)
|
||||
continue;
|
||||
const float value = this->get_limit_temperature_(static_cast<TMP102LimitType>(limit));
|
||||
if (value != restored[limit])
|
||||
if (restore_corrected[limit] || value != restored[limit])
|
||||
saved &= control->save_value_(value);
|
||||
control->publish_state(value);
|
||||
}
|
||||
if (!saved) {
|
||||
ESP_LOGW(TAG, "Failed to save threshold preferences");
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_RESTORE_RESET_SAVE_FAILED);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
this->publish_threshold_status_(corrected ? TMP102_THRESHOLD_STATUS_RESTORE_RESET : TMP102_THRESHOLD_STATUS_OK);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -83,6 +131,9 @@ void TMP102Component::dump_config() {
|
||||
#ifdef USE_TMP102_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Alert", this->alert_binary_sensor_);
|
||||
#endif
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR(" ", "Threshold Status", this->threshold_status_text_sensor_);
|
||||
#endif
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
if (!this->configure_)
|
||||
return;
|
||||
@@ -203,6 +254,9 @@ bool TMP102Component::check_configuration_() {
|
||||
this->set_warning_and_invalidate_alert_();
|
||||
if (!this->write_configuration_()) {
|
||||
ESP_LOGW(TAG, "Failed to restore configuration and thresholds");
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_RECOVERY_FAILED);
|
||||
} else {
|
||||
this->publish_threshold_status_(TMP102_THRESHOLD_STATUS_RECOVERED);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -228,22 +282,25 @@ void TMP102Component::set_limit_control(TMP102LimitNumber *number) {
|
||||
this->limit_controls_[number->limit_type_] = number;
|
||||
}
|
||||
|
||||
void TMP102Component::prepare_number_limits_(float *restored) {
|
||||
bool TMP102Component::prepare_number_limits_(float *restored, bool *restore_corrected) {
|
||||
const bool extended = this->config_ & TMP102_EXTENDED_MODE_BIT;
|
||||
uint16_t raw[2] = {this->low_limit_, this->high_limit_};
|
||||
bool corrected = false;
|
||||
for (uint8_t limit = 0; limit < 2; ++limit) {
|
||||
TMP102LimitNumber *control = this->limit_controls_[limit];
|
||||
if (control == nullptr)
|
||||
continue;
|
||||
restored[limit] = control->load_value_(decode_temperature(raw[limit], extended));
|
||||
restored[limit] = control->load_value_(decode_temperature(raw[limit], extended), restore_corrected[limit]);
|
||||
corrected |= restore_corrected[limit];
|
||||
raw[limit] = encode_temperature(restored[limit], extended);
|
||||
}
|
||||
if (static_cast<int16_t>(raw[TMP102_LIMIT_LOW]) > static_cast<int16_t>(raw[TMP102_LIMIT_HIGH])) {
|
||||
ESP_LOGW(TAG, "Restored thresholds are inverted; using configured values");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
this->low_limit_ = raw[TMP102_LIMIT_LOW];
|
||||
this->high_limit_ = raw[TMP102_LIMIT_HIGH];
|
||||
return corrected;
|
||||
}
|
||||
|
||||
float TMP102Component::get_limit_temperature_(TMP102LimitType limit) const {
|
||||
@@ -261,10 +318,13 @@ bool TMP102Component::set_limit_temperature_(TMP102LimitType limit, float temper
|
||||
ESP_LOGW(TAG, "Rejected %s threshold %.4f°C: crosses %s threshold %.4f°C",
|
||||
high ? LOG_STR_LITERAL("high") : LOG_STR_LITERAL("low"), decode_temperature(raw, extended),
|
||||
high ? LOG_STR_LITERAL("low") : LOG_STR_LITERAL("high"), decode_temperature(other, extended));
|
||||
this->publish_threshold_rejection_(high, decode_temperature(raw, extended), decode_temperature(other, extended));
|
||||
return false;
|
||||
}
|
||||
if (!this->write_register_(static_cast<uint8_t>(TMP102_REGISTER_LOW_LIMIT + limit), raw)) {
|
||||
this->status_set_warning();
|
||||
this->publish_threshold_status_(high ? TMP102_THRESHOLD_STATUS_WRITE_HIGH_FAILED
|
||||
: TMP102_THRESHOLD_STATUS_WRITE_LOW_FAILED);
|
||||
return false;
|
||||
}
|
||||
(high ? this->high_limit_ : this->low_limit_) = raw;
|
||||
@@ -273,6 +333,32 @@ bool TMP102Component::set_limit_temperature_(TMP102LimitType limit, float temper
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
void TMP102Component::publish_threshold_status_(uint8_t status) {
|
||||
if (this->threshold_status_text_sensor_ == nullptr)
|
||||
return;
|
||||
char text[40];
|
||||
ESPHOME_strncpy_P(
|
||||
text,
|
||||
reinterpret_cast<ESPHOME_PGM_P>(ThresholdStatusStrings::get_progmem_str(status, TMP102_THRESHOLD_STATUS_OK)),
|
||||
sizeof(text));
|
||||
text[sizeof(text) - 1] = '\0';
|
||||
this->threshold_status_text_sensor_->publish_state(text);
|
||||
}
|
||||
|
||||
void TMP102Component::publish_threshold_rejection_(bool high, float value, float other) {
|
||||
if (this->threshold_status_text_sensor_ == nullptr)
|
||||
return;
|
||||
char text[96];
|
||||
if (high) {
|
||||
ESPHOME_snprintf_P(text, sizeof(text), ESPHOME_PSTR("Rejected: high %.4f°C crosses low %.4f°C"), value, other);
|
||||
} else {
|
||||
ESPHOME_snprintf_P(text, sizeof(text), ESPHOME_PSTR("Rejected: low %.4f°C crosses high %.4f°C"), value, other);
|
||||
}
|
||||
this->threshold_status_text_sensor_->publish_state(text);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_BINARY_SENSOR
|
||||
void TMP102Component::invalidate_alert_() {
|
||||
if (this->alert_binary_sensor_ != nullptr)
|
||||
@@ -294,7 +380,7 @@ bool TMP102Component::read_alert_state_() {
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_NUMBER
|
||||
float TMP102LimitNumber::load_value_(float fallback) {
|
||||
float TMP102LimitNumber::load_value_(float fallback, bool &corrected) {
|
||||
float value = fallback;
|
||||
if (this->restore_value_) {
|
||||
this->pref_ = this->make_entity_preference<float>();
|
||||
@@ -303,6 +389,7 @@ float TMP102LimitNumber::load_value_(float fallback) {
|
||||
if (!std::isfinite(value) || value < this->traits.get_min_value() || value > this->traits.get_max_value()) {
|
||||
ESP_LOGW(TAG, "Invalid restored threshold; using configured value");
|
||||
value = fallback;
|
||||
corrected = true;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -313,6 +400,7 @@ void TMP102LimitNumber::control(float value) {
|
||||
// A chip that failed setup holds nothing the entity could show
|
||||
if (this->parent_->is_failed()) {
|
||||
ESP_LOGW(TAG, "Rejected threshold change: sensor setup failed");
|
||||
this->parent_->publish_threshold_status_(TMP102_THRESHOLD_STATUS_SENSOR_SETUP_FAILED);
|
||||
return;
|
||||
}
|
||||
const bool accepted = this->parent_->set_limit_temperature_(this->limit_type_, value);
|
||||
@@ -320,6 +408,9 @@ void TMP102LimitNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
if (accepted && !this->save_value_(value)) {
|
||||
ESP_LOGW(TAG, "Failed to save threshold preference");
|
||||
this->parent_->publish_threshold_status_(TMP102_THRESHOLD_STATUS_PREFERENCE_SAVE_FAILED);
|
||||
} else if (accepted) {
|
||||
this->parent_->publish_threshold_status_(TMP102_THRESHOLD_STATUS_OK);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -12,11 +12,20 @@
|
||||
#include "esphome/core/preferences.h"
|
||||
#endif
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome::tmp102 {
|
||||
|
||||
#ifdef USE_HOST
|
||||
namespace testing {
|
||||
class TMP102Test;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TMP102_NUMBER
|
||||
enum TMP102LimitType : uint8_t {
|
||||
TMP102_LIMIT_LOW = 0,
|
||||
@@ -31,8 +40,13 @@ class TMP102Component final : public PollingComponent, public i2c::I2CDevice, pu
|
||||
#ifdef USE_TMP102_BINARY_SENSOR
|
||||
SUB_BINARY_SENSOR(alert)
|
||||
#endif
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
SUB_TEXT_SENSOR(threshold_status)
|
||||
#endif
|
||||
#if defined(USE_TMP102_CONFIGURE) || defined(USE_TMP102_TEXT_SENSOR)
|
||||
void setup() override;
|
||||
#endif
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
void set_configuration(uint16_t config, uint16_t high_limit, uint16_t low_limit) {
|
||||
this->config_ = config;
|
||||
this->high_limit_ = high_limit;
|
||||
@@ -47,6 +61,13 @@ class TMP102Component final : public PollingComponent, public i2c::I2CDevice, pu
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
#ifdef USE_TMP102_TEXT_SENSOR
|
||||
void publish_threshold_status_(uint8_t status);
|
||||
void publish_threshold_rejection_(bool high, float value, float other);
|
||||
#else
|
||||
void publish_threshold_status_(uint8_t) {}
|
||||
void publish_threshold_rejection_(bool, float, float) {}
|
||||
#endif
|
||||
void set_warning_and_invalidate_alert_() {
|
||||
this->status_set_warning();
|
||||
#ifdef USE_TMP102_BINARY_SENSOR
|
||||
@@ -58,6 +79,7 @@ class TMP102Component final : public PollingComponent, public i2c::I2CDevice, pu
|
||||
void invalidate_alert_();
|
||||
#endif
|
||||
#ifdef USE_TMP102_CONFIGURE
|
||||
void setup_configured_();
|
||||
bool write_register_(uint8_t reg, uint16_t value);
|
||||
bool write_configuration_();
|
||||
bool check_configuration_();
|
||||
@@ -67,11 +89,10 @@ class TMP102Component final : public PollingComponent, public i2c::I2CDevice, pu
|
||||
friend class TMP102LimitNumber;
|
||||
bool set_limit_temperature_(TMP102LimitType limit, float temperature);
|
||||
float get_limit_temperature_(TMP102LimitType limit) const;
|
||||
void prepare_number_limits_(float *restored);
|
||||
bool prepare_number_limits_(float *restored, bool *restore_corrected);
|
||||
|
||||
TMP102LimitNumber *limit_controls_[2]{};
|
||||
#endif
|
||||
|
||||
uint16_t config_{0};
|
||||
uint16_t high_limit_{0};
|
||||
uint16_t low_limit_{0};
|
||||
@@ -87,7 +108,10 @@ class TMP102LimitNumber final : public number::Number, public Parented<TMP102Com
|
||||
|
||||
protected:
|
||||
friend class TMP102Component;
|
||||
float load_value_(float fallback);
|
||||
#ifdef USE_HOST
|
||||
friend class testing::TMP102Test;
|
||||
#endif
|
||||
float load_value_(float fallback, bool &corrected);
|
||||
bool save_value_(float value);
|
||||
void control(float value) override;
|
||||
|
||||
|
||||
@@ -272,6 +272,7 @@
|
||||
#define USE_TMP102_BINARY_SENSOR
|
||||
#define USE_TMP102_CONFIGURE
|
||||
#define USE_TMP102_NUMBER
|
||||
#define USE_TMP102_TEXT_SENSOR
|
||||
#define USE_TOUCHSCREEN
|
||||
#define USE_UART_DEBUGGER
|
||||
#define USE_UART_WAKE_LOOP_ON_RX
|
||||
|
||||
+28
-7
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -68,6 +69,7 @@ template<size_t N> struct FixedString {
|
||||
};
|
||||
|
||||
/// Compile-time string table that packs strings into a single blob with offset lookup.
|
||||
/// Blobs over 255 bytes have no offsets and are scanned, so keep those for rarely read strings.
|
||||
/// Use PROGMEM_STRING_TABLE macro to instantiate with proper flash placement on ESP8266.
|
||||
///
|
||||
/// Example:
|
||||
@@ -78,6 +80,11 @@ template<size_t N> struct FixedString {
|
||||
template<FixedString... Strs> struct ProgmemStringTable {
|
||||
static constexpr size_t COUNT = sizeof...(Strs);
|
||||
static constexpr size_t BLOB_SIZE = (0 + ... + (Strs.size() + 1));
|
||||
/// Length of the longest string, for sizing a RAM copy on ESP8266
|
||||
static constexpr size_t MAX_LENGTH = std::max({Strs.size()...});
|
||||
/// One-byte offsets address up to 255 bytes; a larger blob has no offset table and is scanned instead,
|
||||
/// which costs less flash than wider offsets for the rarely read tables that get this big.
|
||||
static constexpr bool INDEXED = BLOB_SIZE <= UINT8_MAX;
|
||||
|
||||
/// Generate packed string blob at compile time
|
||||
static constexpr auto make_blob() {
|
||||
@@ -91,18 +98,28 @@ template<FixedString... Strs> struct ProgmemStringTable {
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Generate offset table at compile time (uint8_t limits blob to 255 bytes)
|
||||
/// Generate offset table at compile time
|
||||
static constexpr auto make_offsets() {
|
||||
static_assert(COUNT > 0, "PROGMEM_STRING_TABLE must contain at least one string");
|
||||
static_assert(COUNT <= 255, "PROGMEM_STRING_TABLE supports at most 255 strings with uint8_t indices");
|
||||
static_assert(BLOB_SIZE <= 255, "PROGMEM_STRING_TABLE blob exceeds 255 bytes; use fewer/shorter strings");
|
||||
std::array<uint8_t, COUNT> result{};
|
||||
size_t pos = 0, idx = 0;
|
||||
((result[idx++] = static_cast<uint8_t>(pos), pos += Strs.size() + 1), ...);
|
||||
return result;
|
||||
if constexpr (INDEXED) {
|
||||
std::array<uint8_t, COUNT> result{};
|
||||
size_t pos = 0, idx = 0;
|
||||
((result[idx++] = static_cast<uint8_t>(pos), pos += Strs.size() + 1), ...);
|
||||
return result;
|
||||
} else {
|
||||
return std::array<uint8_t, 0>{};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Skip idx strings in a packed NUL-separated blob that may live in flash.
|
||||
inline const char *progmem_skip_strings(const char *blob, uint8_t idx) {
|
||||
while (idx-- > 0)
|
||||
blob += ESPHOME_strlen_P(blob) + 1;
|
||||
return blob;
|
||||
}
|
||||
|
||||
// Forward declaration for LogString (defined in log.h)
|
||||
struct LogString;
|
||||
|
||||
@@ -115,6 +132,7 @@ struct LogString;
|
||||
static constexpr size_t COUNT = Table::COUNT; \
|
||||
static constexpr uint8_t LAST_INDEX = COUNT - 1; \
|
||||
static constexpr size_t BLOB_SIZE = Table::BLOB_SIZE; \
|
||||
static constexpr size_t MAX_LENGTH = Table::MAX_LENGTH; \
|
||||
static constexpr auto BLOB ESPHOME_PROGMEM_STRING_TABLE_STORAGE = Table::make_blob(); \
|
||||
static constexpr auto OFFSETS ESPHOME_PROGMEM_STRING_TABLE_STORAGE = Table::make_offsets(); \
|
||||
static const char *get_(uint8_t idx, uint8_t fallback) { \
|
||||
@@ -122,7 +140,10 @@ struct LogString;
|
||||
idx = fallback; \
|
||||
/* std::launder is used here to prevent the inter-procedural analysis that */ \
|
||||
/* causes the false positive that the string is not null terminated */ \
|
||||
return std::launder(&BLOB[::esphome::progmem_read_byte(&OFFSETS[idx])]); \
|
||||
if constexpr (Table::INDEXED) \
|
||||
return std::launder(&BLOB[::esphome::progmem_read_byte(&OFFSETS[idx])]); \
|
||||
else \
|
||||
return std::launder(::esphome::progmem_skip_strings(BLOB.data(), idx)); \
|
||||
} \
|
||||
static ::ProgmemStr get_progmem_str(uint8_t idx, uint8_t fallback) { \
|
||||
return reinterpret_cast<::ProgmemStr>(get_(idx, fallback)); \
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
esphome:
|
||||
name: tmp102-test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
i2c:
|
||||
sda: GPIO21
|
||||
scl: GPIO22
|
||||
|
||||
sensor:
|
||||
- platform: tmp102
|
||||
id: parent_sensor
|
||||
name: Temperature
|
||||
|
||||
text_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: parent_sensor
|
||||
name: First Status
|
||||
- platform: tmp102
|
||||
tmp102_id: parent_sensor
|
||||
name: Second Status
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
esphome:
|
||||
name: tmp102-test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
i2c:
|
||||
sda: GPIO21
|
||||
scl: GPIO22
|
||||
|
||||
sensor:
|
||||
- platform: tmp102
|
||||
id: parent_sensor
|
||||
name: Temperature
|
||||
|
||||
text_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: parent_sensor
|
||||
name: Threshold Status
|
||||
@@ -58,6 +58,30 @@ def test_duplicate_alert_entities_are_rejected(
|
||||
)
|
||||
|
||||
|
||||
def test_status_entity_is_read_only(
|
||||
generate_main: Callable[[Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
source = generate_main(component_config_path("status.yaml"))
|
||||
assert "set_threshold_status_text_sensor" in source
|
||||
assert "set_configuration" not in source
|
||||
defines = " ".join(define.name for define in CORE.defines)
|
||||
assert "USE_TMP102_TEXT_SENSOR" in defines
|
||||
assert "USE_TMP102_CONFIGURE" not in defines
|
||||
|
||||
|
||||
def test_duplicate_status_entities_are_rejected(
|
||||
component_config_path: Callable[[str], Path],
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
CORE.config_path = component_config_path("duplicate_status.yaml")
|
||||
assert read_config({}) is None
|
||||
assert (
|
||||
"Only one TMP102 text_sensor may reference each temperature sensor"
|
||||
in capsys.readouterr().out
|
||||
)
|
||||
|
||||
|
||||
def test_threshold_numbers_enable_configuration_once(
|
||||
generate_main: Callable[[Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::core::testing {
|
||||
|
||||
PROGMEM_STRING_TABLE(SmallStrings, "OFF", "HEAT", "COOL", "UNKNOWN");
|
||||
|
||||
// Eight strings of 40 characters: a 328-byte blob, past what one-byte offsets can address, so it is scanned.
|
||||
PROGMEM_STRING_TABLE(LargeStrings, "0123456789012345678901234567890123456789",
|
||||
"1123456789012345678901234567890123456789", "2123456789012345678901234567890123456789",
|
||||
"3123456789012345678901234567890123456789", "4123456789012345678901234567890123456789",
|
||||
"5123456789012345678901234567890123456789", "6123456789012345678901234567890123456789",
|
||||
"7123456789012345678901234567890123456789");
|
||||
|
||||
// Five 50-character strings pack into exactly 255 bytes; one more byte switches to scanning.
|
||||
PROGMEM_STRING_TABLE(Exactly255, "01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789");
|
||||
PROGMEM_STRING_TABLE(Exactly256, "01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789x");
|
||||
|
||||
TEST(ProgmemStringTable, ScansInsteadOfIndexingAfter255Bytes) {
|
||||
static_assert(Exactly255::BLOB_SIZE == 255);
|
||||
static_assert(Exactly255::Table::INDEXED);
|
||||
static_assert(Exactly256::BLOB_SIZE == 256);
|
||||
static_assert(!Exactly256::Table::INDEXED);
|
||||
static_assert(Exactly256::OFFSETS.empty());
|
||||
EXPECT_EQ(Exactly255::OFFSETS[4], 204);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(Exactly256::get_progmem_str(4, 0)),
|
||||
"01234567890123456789012345678901234567890123456789x");
|
||||
}
|
||||
|
||||
TEST(ProgmemStringTable, SmallTableKeepsOneByteOffsets) {
|
||||
static_assert(SmallStrings::BLOB_SIZE <= 255);
|
||||
static_assert(SmallStrings::Table::INDEXED);
|
||||
static_assert(SmallStrings::MAX_LENGTH == 7);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(SmallStrings::get_log_str(0, SmallStrings::LAST_INDEX)), "OFF");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(SmallStrings::get_log_str(2, SmallStrings::LAST_INDEX)), "COOL");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(SmallStrings::get_log_str(200, SmallStrings::LAST_INDEX)), "UNKNOWN");
|
||||
}
|
||||
|
||||
TEST(ProgmemStringTable, LargeTableIsScanned) {
|
||||
static_assert(LargeStrings::BLOB_SIZE > 255);
|
||||
static_assert(!LargeStrings::Table::INDEXED);
|
||||
static_assert(LargeStrings::MAX_LENGTH == 40);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(0, LargeStrings::LAST_INDEX)),
|
||||
"0123456789012345678901234567890123456789");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(7, LargeStrings::LAST_INDEX)),
|
||||
"7123456789012345678901234567890123456789");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(8, LargeStrings::LAST_INDEX)),
|
||||
"7123456789012345678901234567890123456789");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(3, LargeStrings::LAST_INDEX)),
|
||||
"3123456789012345678901234567890123456789");
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
@@ -0,0 +1,20 @@
|
||||
from pathlib import Path
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ESPHOME, CONF_INCLUDES
|
||||
from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
TESTS_DIR = Path(__file__).parent
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# The column walk in Display::draw_pixels_at() is only built with PSRAM; define it
|
||||
# when display's own tests are built so both walk orders run, and leave other
|
||||
# components' test binaries on the plain build.
|
||||
async def to_code_testing(config: ConfigType) -> None:
|
||||
if any(Path(p) == TESTS_DIR for p in CORE.config[CONF_ESPHOME][CONF_INCLUDES]):
|
||||
cg.add_define("USE_PSRAM")
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
@@ -0,0 +1,109 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/components/display/display.h"
|
||||
|
||||
namespace esphome::display::testing {
|
||||
|
||||
// Records every pixel write; pixel_axes_swapped() picks the walk order.
|
||||
class RecordingDisplay : public Display {
|
||||
public:
|
||||
void set_axes_swapped(bool swapped) { this->axes_swapped_ = swapped; }
|
||||
bool pixel_axes_swapped() const override { return this->axes_swapped_; }
|
||||
void draw_pixel_at(int x, int y, Color color) override { this->pixels_.emplace_back(x, y, color.raw_32); }
|
||||
void update() override {}
|
||||
DisplayType get_display_type() override { return DisplayType::DISPLAY_TYPE_COLOR; }
|
||||
int get_width_internal() override { return 64; }
|
||||
int get_height_internal() override { return 64; }
|
||||
|
||||
std::vector<std::tuple<int, int, uint32_t>> sorted_pixels() const {
|
||||
auto sorted = this->pixels_;
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
return sorted;
|
||||
}
|
||||
size_t count() const { return this->pixels_.size(); }
|
||||
const std::vector<std::tuple<int, int, uint32_t>> &pixels() const { return this->pixels_; }
|
||||
|
||||
protected:
|
||||
bool axes_swapped_{false};
|
||||
std::vector<std::tuple<int, int, uint32_t>> pixels_;
|
||||
};
|
||||
|
||||
// A 7x5 region with a 2 pixel left offset, a 3 line top offset and 4 pixels of
|
||||
// right padding, so the source index arithmetic of both walks is exercised.
|
||||
static constexpr int W = 7, H = 5, X_OFFSET = 2, Y_OFFSET = 3, X_PAD = 4;
|
||||
static constexpr int LINE_STRIDE = X_OFFSET + W + X_PAD;
|
||||
static constexpr int LINES = Y_OFFSET + H;
|
||||
|
||||
static std::vector<uint8_t> make_source(int bytes_per_pixel) {
|
||||
std::vector<uint8_t> source(LINE_STRIDE * LINES * bytes_per_pixel);
|
||||
uint8_t value = 1;
|
||||
for (auto &byte : source)
|
||||
byte = value++;
|
||||
return source;
|
||||
}
|
||||
|
||||
static void expect_walks_match(ColorBitness bitness, int bytes_per_pixel, bool big_endian) {
|
||||
const auto source = make_source(bytes_per_pixel);
|
||||
RecordingDisplay rows;
|
||||
RecordingDisplay columns;
|
||||
columns.set_axes_swapped(true);
|
||||
rows.draw_pixels_at(10, 20, W, H, source.data(), COLOR_ORDER_RGB, bitness, big_endian, X_OFFSET, Y_OFFSET, X_PAD);
|
||||
columns.draw_pixels_at(10, 20, W, H, source.data(), COLOR_ORDER_RGB, bitness, big_endian, X_OFFSET, Y_OFFSET, X_PAD);
|
||||
EXPECT_EQ(rows.count(), static_cast<size_t>(W * H));
|
||||
EXPECT_EQ(columns.count(), static_cast<size_t>(W * H));
|
||||
EXPECT_EQ(rows.sorted_pixels(), columns.sorted_pixels());
|
||||
}
|
||||
|
||||
TEST(DrawPixelsAt, ColumnWalkMatchesRowWalk332) { expect_walks_match(COLOR_BITNESS_332, 1, true); }
|
||||
TEST(DrawPixelsAt, ColumnWalkMatchesRowWalk565BigEndian) { expect_walks_match(COLOR_BITNESS_565, 2, true); }
|
||||
TEST(DrawPixelsAt, ColumnWalkMatchesRowWalk565LittleEndian) { expect_walks_match(COLOR_BITNESS_565, 2, false); }
|
||||
TEST(DrawPixelsAt, ColumnWalkMatchesRowWalk888BigEndian) { expect_walks_match(COLOR_BITNESS_888, 3, true); }
|
||||
TEST(DrawPixelsAt, ColumnWalkMatchesRowWalk888LittleEndian) { expect_walks_match(COLOR_BITNESS_888, 3, false); }
|
||||
|
||||
// One known pixel per bitness and byte order, so a decode that swapped bytes or channels
|
||||
// could not pass the walk comparison above by being wrong the same way twice.
|
||||
static void expect_golden(ColorBitness bitness, std::initializer_list<uint8_t> bytes, bool big_endian, Color expected) {
|
||||
const std::vector<uint8_t> source(bytes);
|
||||
RecordingDisplay display;
|
||||
display.draw_pixels_at(3, 4, 1, 1, source.data(), COLOR_ORDER_RGB, bitness, big_endian, 0, 0, 0);
|
||||
ASSERT_EQ(display.count(), 1u);
|
||||
EXPECT_EQ(display.pixels()[0], std::make_tuple(3, 4, expected.raw_32));
|
||||
}
|
||||
|
||||
TEST(DrawPixelsAt, Golden565BigEndian) {
|
||||
expect_golden(COLOR_BITNESS_565, {0xE0, 0x07}, true, Color(230, 0, 57)); // 0xE007: r 28, g 0, b 7
|
||||
}
|
||||
TEST(DrawPixelsAt, Golden565LittleEndian) {
|
||||
expect_golden(COLOR_BITNESS_565, {0xE0, 0x07}, false, Color(0, 255, 0)); // 0x07E0: pure green
|
||||
}
|
||||
TEST(DrawPixelsAt, Golden888BigEndian) {
|
||||
expect_golden(COLOR_BITNESS_888, {0x10, 0x20, 0x30}, true, Color(0x10, 0x20, 0x30));
|
||||
}
|
||||
TEST(DrawPixelsAt, Golden888LittleEndian) {
|
||||
expect_golden(COLOR_BITNESS_888, {0x10, 0x20, 0x30}, false, Color(0x30, 0x20, 0x10));
|
||||
}
|
||||
TEST(DrawPixelsAt, Golden332) {
|
||||
expect_golden(COLOR_BITNESS_332, {0xE3}, true, Color(255, 0, 255)); // r 7 of 7, g 0, b 3 of 3
|
||||
}
|
||||
|
||||
// The column walk really is a different order, otherwise the tests above prove nothing.
|
||||
TEST(DrawPixelsAt, ColumnWalkChangesOrder) {
|
||||
const auto source = make_source(1);
|
||||
RecordingDisplay rows;
|
||||
RecordingDisplay columns;
|
||||
columns.set_axes_swapped(true);
|
||||
rows.draw_pixels_at(0, 0, W, H, source.data(), COLOR_ORDER_RGB, COLOR_BITNESS_332, true, X_OFFSET, Y_OFFSET, X_PAD);
|
||||
columns.draw_pixels_at(0, 0, W, H, source.data(), COLOR_ORDER_RGB, COLOR_BITNESS_332, true, X_OFFSET, Y_OFFSET,
|
||||
X_PAD);
|
||||
ASSERT_GT(rows.count(), 1u);
|
||||
ASSERT_GT(columns.count(), 1u);
|
||||
EXPECT_EQ(std::get<0>(rows.pixels()[1]), 1); // the row walk steps x first
|
||||
EXPECT_EQ(std::get<1>(columns.pixels()[1]), 1); // the column walk steps y first
|
||||
}
|
||||
|
||||
} // namespace esphome::display::testing
|
||||
@@ -6,11 +6,12 @@ from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
async def to_code_testing(config: ConfigType) -> None:
|
||||
for platform in ("sensor", "binary_sensor", "number"):
|
||||
for platform in ("sensor", "binary_sensor", "number", "text_sensor"):
|
||||
CORE.testing_ensure_platform_registered(platform)
|
||||
cg.add_define("USE_TMP102_CONFIGURE")
|
||||
cg.add_define("USE_TMP102_BINARY_SENSOR")
|
||||
cg.add_define("USE_TMP102_NUMBER")
|
||||
cg.add_define("USE_TMP102_TEXT_SENSOR")
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
manifest.dependencies = manifest.dependencies + [
|
||||
@@ -18,4 +19,5 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
"sensor",
|
||||
"binary_sensor",
|
||||
"number",
|
||||
"text_sensor",
|
||||
]
|
||||
|
||||
@@ -30,3 +30,8 @@ binary_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: tmp102_extended_temperature
|
||||
name: TMP102 Alert
|
||||
|
||||
text_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: tmp102_extended_temperature
|
||||
name: TMP102 Threshold Status
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
sensor:
|
||||
- platform: tmp102
|
||||
id: tmp102_text_sensor_parent
|
||||
name: TMP102 Temperature
|
||||
|
||||
text_sensor:
|
||||
- platform: tmp102
|
||||
tmp102_id: tmp102_text_sensor_parent
|
||||
name: TMP102 Threshold Status
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "esphome/components/host/preferences.h"
|
||||
#include "esphome/components/tmp102/tmp102.h"
|
||||
@@ -29,6 +30,7 @@ class RegisterBus final : public i2c::I2CBus {
|
||||
unsigned transactions{0};
|
||||
unsigned writes{0};
|
||||
uint8_t pointer{0};
|
||||
std::function<void()> on_write;
|
||||
|
||||
i2c::ErrorCode write_readv(uint8_t address, const uint8_t *data, size_t size, uint8_t *out,
|
||||
size_t out_size) override {
|
||||
@@ -42,6 +44,10 @@ class RegisterBus final : public i2c::I2CBus {
|
||||
return i2c::ERROR_NOT_ACKNOWLEDGED;
|
||||
++this->writes;
|
||||
this->registers[this->pointer] = (uint16_t(data[1]) << 8) | data[2];
|
||||
if (this->on_write) {
|
||||
auto callback = std::move(this->on_write);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (out_size != 0) {
|
||||
@@ -59,6 +65,7 @@ class TMP102Test : public ::testing::Test {
|
||||
RegisterBus bus_;
|
||||
TMP102Component chip_;
|
||||
binary_sensor::BinarySensor alert_;
|
||||
text_sensor::TextSensor status_;
|
||||
TMP102LimitNumber high_{TMP102_LIMIT_HIGH};
|
||||
TMP102LimitNumber low_{TMP102_LIMIT_LOW};
|
||||
unsigned publications_{0};
|
||||
@@ -122,6 +129,8 @@ class TMP102Test : public ::testing::Test {
|
||||
static void save(TMP102LimitNumber &number, float value) {
|
||||
ASSERT_TRUE(number.make_entity_preference<float>().save(&value));
|
||||
}
|
||||
|
||||
static void fail_saves(TMP102LimitNumber &number) { number.pref_ = {}; }
|
||||
};
|
||||
|
||||
TEST_F(TMP102Test, LegacySetupAndContinuousReadDoNotWriteRegisters) {
|
||||
@@ -136,6 +145,24 @@ TEST_F(TMP102Test, LegacySetupAndContinuousReadDoNotWriteRegisters) {
|
||||
EXPECT_EQ(this->bus_.registers, original);
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, ReadOnlyStatusDoesNotConfigureOrWriteRegisters) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
const auto original = this->bus_.registers;
|
||||
this->chip_.setup();
|
||||
EXPECT_EQ(this->bus_.transactions, 0);
|
||||
EXPECT_EQ(this->bus_.registers, original);
|
||||
ASSERT_TRUE(this->status_.has_state());
|
||||
EXPECT_EQ(this->status_.state, "Not configured");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, ConfiguredSetupPublishesStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->configure();
|
||||
this->chip_.setup();
|
||||
ASSERT_TRUE(this->status_.has_state());
|
||||
EXPECT_EQ(this->status_.state, "OK");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, SampleFormatOverridesConfiguredModeInBothDirections) {
|
||||
for (bool configured_extended : {false, true}) {
|
||||
this->configure(configured_extended ? 0x0010 : 0, configured_extended ? 0x2800 : 0x5000,
|
||||
@@ -408,6 +435,7 @@ TEST_F(TMP102Test, FailureOfAnySetupWriteMarksComponentFailed) {
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, InvalidRestoredPairIsRepairedAfterSuccessfulSetup) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 20);
|
||||
save(this->low_, 35);
|
||||
@@ -416,30 +444,53 @@ TEST_F(TMP102Test, InvalidRestoredPairIsRepairedAfterSuccessfulSetup) {
|
||||
EXPECT_FLOAT_EQ(this->low_.state, 25);
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 30);
|
||||
EXPECT_FLOAT_EQ(saved(this->low_), 25);
|
||||
EXPECT_EQ(this->status_.state, "Restored thresholds reset");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, InvalidRestoredPairAndSaveFailurePublishCombinedStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 20);
|
||||
save(this->low_, 35);
|
||||
this->bus_.on_write = [this]() {
|
||||
fail_saves(this->high_);
|
||||
fail_saves(this->low_);
|
||||
};
|
||||
this->chip_.setup();
|
||||
EXPECT_FLOAT_EQ(this->high_.state, 30);
|
||||
EXPECT_FLOAT_EQ(this->low_.state, 25);
|
||||
EXPECT_EQ(this->status_.state, "Restore reset; save failed");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, InvalidRestoredValuesAreRepaired) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, NAN);
|
||||
save(this->low_, INFINITY);
|
||||
this->chip_.setup();
|
||||
EXPECT_FLOAT_EQ(this->high_.state, 30);
|
||||
EXPECT_FLOAT_EQ(this->low_.state, 25);
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 30);
|
||||
EXPECT_FLOAT_EQ(saved(this->low_), 25);
|
||||
EXPECT_EQ(this->status_.state, "Restored thresholds reset");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, FailedSetupDoesNotOverwritePreferences) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 90);
|
||||
save(this->low_, 85);
|
||||
this->bus_.fail_write = 3;
|
||||
this->chip_.setup();
|
||||
EXPECT_TRUE(this->chip_.is_failed());
|
||||
EXPECT_EQ(this->status_.state, "Setup failed");
|
||||
EXPECT_FALSE(this->high_.has_state());
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 90);
|
||||
EXPECT_FLOAT_EQ(saved(this->low_), 85);
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, FailedSetupRejectsThresholdChanges) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 90);
|
||||
this->bus_.fail_write = 3;
|
||||
@@ -450,6 +501,7 @@ TEST_F(TMP102Test, FailedSetupRejectsThresholdChanges) {
|
||||
EXPECT_FALSE(this->high_.has_state());
|
||||
EXPECT_EQ(this->bus_.writes, writes);
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 90);
|
||||
EXPECT_EQ(this->status_.state, "Rejected: sensor setup failed");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, SetupOnlySavesChangedThresholds) {
|
||||
@@ -464,6 +516,7 @@ TEST_F(TMP102Test, SetupOnlySavesChangedThresholds) {
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, FailedNumberWriteKeepsAcceptedState) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
save(this->high_, 90);
|
||||
save(this->low_, 85);
|
||||
@@ -475,6 +528,47 @@ TEST_F(TMP102Test, FailedNumberWriteKeepsAcceptedState) {
|
||||
EXPECT_FLOAT_EQ(this->high_.state, 90);
|
||||
EXPECT_EQ(publications, 1);
|
||||
EXPECT_FLOAT_EQ(saved(this->high_), 90);
|
||||
EXPECT_EQ(this->status_.state, "Write failed: high threshold");
|
||||
|
||||
this->bus_.fail_write = 2;
|
||||
this->low_.make_call().set_value(80).perform();
|
||||
EXPECT_FLOAT_EQ(this->low_.state, 85);
|
||||
EXPECT_FLOAT_EQ(saved(this->low_), 85);
|
||||
EXPECT_EQ(this->status_.state, "Write failed: low threshold");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, PreferenceSaveFailurePublishesStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
this->chip_.setup();
|
||||
fail_saves(this->high_);
|
||||
this->high_.make_call().set_value(35).perform();
|
||||
EXPECT_FLOAT_EQ(this->high_.state, 35);
|
||||
EXPECT_EQ(this->status_.state, "Preference save failed");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, RejectedNumberWritePublishesStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->numbers_();
|
||||
this->chip_.setup();
|
||||
this->high_.make_call().set_value(20).perform();
|
||||
EXPECT_EQ(this->status_.state, "Rejected: high 20.0000°C crosses low 25.0000°C");
|
||||
this->high_.make_call().set_value(35).perform();
|
||||
EXPECT_EQ(this->status_.state, "OK");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, ConfigurationRecoveryPublishesStatus) {
|
||||
this->chip_.set_threshold_status_text_sensor(&this->status_);
|
||||
this->configure();
|
||||
this->chip_.setup();
|
||||
this->bus_.registers[1] = 0;
|
||||
this->chip_.update();
|
||||
EXPECT_EQ(this->status_.state, "Configuration restored");
|
||||
|
||||
this->bus_.registers[1] = 0;
|
||||
this->bus_.fail_write = 1;
|
||||
this->chip_.update();
|
||||
EXPECT_EQ(this->status_.state, "Configuration recovery failed");
|
||||
}
|
||||
|
||||
TEST_F(TMP102Test, RestoreDisabledNeverChangesSavedValue) {
|
||||
|
||||
Reference in New Issue
Block a user