1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-11 04:15:52 +00:00

Compare commits

...

40 Commits

Author SHA1 Message Date
Jesse Hills
21db43db06 Merge pull request #2808 from esphome/bump-2021.11.3
2021.11.3
2021-11-28 00:01:16 +13:00
Jesse Hills
5009b3029f Bump version to 2021.11.3 2021-11-27 21:13:01 +13:00
Maurice Makaay
57a029189c Add missing nvs_flash_init() to ESP32 preferences code (#2805)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
2021-11-27 21:13:01 +13:00
Jesse Hills
0cb715bb76 Merge pull request #2799 from esphome/bump-2021.11.2
2021.11.2
2021-11-26 09:25:37 +13:00
Jesse Hills
7d03823afd Bump version to 2021.11.2 2021-11-26 09:02:54 +13:00
Oxan van Leeuwen
8e1c9f5042 Fix parsing numbers from null-terminated buffers (#2755) 2021-11-26 09:02:54 +13:00
Samuel Sieb
980b7cda8f Remove floating point ops from the ISR (#2751)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2021-11-26 09:02:53 +13:00
Kamil Trzciński
3a72dd5cb6 esp32_camera_web_server: Improve support for MotionEye (#2777) 2021-11-26 09:02:53 +13:00
Dave T
3178243811 Fix frame scaling for animated gifs (#2750) 2021-11-26 09:02:53 +13:00
Maurice Makaay
d30e2f2a4f Allow UART debug configuration with no after: definition (#2753) 2021-11-26 09:02:53 +13:00
Jesse Hills
6226dae05c Merge pull request #2744 from esphome/bump-2021.11.1
2021.11.1
2021-11-17 23:45:43 +13:00
Jesse Hills
9c6a475a6e Bump version to 2021.11.1 2021-11-17 23:31:38 +13:00
Franck Nijhof
8294d10d5b Re-instate device class update for binary sensors (#2743) 2021-11-17 23:31:38 +13:00
Evgeny
67558bec47 Fix HM3301 AQI index calculator (#2739) 2021-11-17 23:31:38 +13:00
Jesse Hills
84873d4074 Merge pull request #2742 from esphome/bump-2021.11.0
2021.11.0
2021-11-17 22:18:29 +13:00
Jesse Hills
58a0b28a39 Bump version to 2021.11.0 2021-11-17 21:58:30 +13:00
Jesse Hills
b37d3a66cc Merge pull request #2738 from esphome/bump-2021.11.0b9
2021.11.0b9
2021-11-17 10:27:41 +13:00
Jesse Hills
7e495a5e27 Bump version to 2021.11.0b9 2021-11-17 08:00:26 +13:00
rotarykite
c41547fd4a Fix senseair component uart read timeout (#2658)
Co-authored-by: DAVe3283 <DAVe3283+GitHub@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Chua Jun Chieh <junchieh.chua@softspace.com.my>
2021-11-17 08:00:26 +13:00
Ryan Hoffman
0d47d41c85 Use as_reversed_hex_array in ble_sensor to fix UUID parsing (#2737)
#1627 renamed as_hex_array to as_reversed_hex_array but forgot to rename these users.
2021-11-17 08:00:26 +13:00
Jesse Hills
41a3a17456 Merge pull request #2734 from esphome/bump-2021.11.0b8
2021.11.0b8
2021-11-16 13:50:10 +13:00
Jesse Hills
cbbafbcca2 Bump version to 2021.11.0b8 2021-11-16 12:53:56 +13:00
Jesse Hills
c75566b374 Fix zeroconf time comparisons (#2733)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-11-16 12:53:56 +13:00
Jesse Hills
7279f1fcc1 Merge pull request #2732 from esphome/bump-2021.11.0b7
2021.11.0b7
2021-11-16 12:19:07 +13:00
Jesse Hills
d7432f7c10 Bump version to 2021.11.0b7 2021-11-16 11:05:51 +13:00
Jesse Hills
b0a0a153f3 Improv serial/checksum changes (#2731)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-11-16 11:05:51 +13:00
Jesse Hills
024632dbd0 Merge pull request #2730 from esphome/bump-2021.11.0b6
2021.11.0b6
2021-11-16 10:53:11 +13:00
Jesse Hills
0a545a28b9 Bump version to 2021.11.0b6 2021-11-16 09:59:00 +13:00
Jesse Hills
0f2df59998 Add zeroconf as a direct dependency and lock the version (#2729) 2021-11-16 09:58:59 +13:00
Jesse Hills
29a7d32f77 Merge pull request #2725 from esphome/bump-2021.11.0b5
2021.11.0b5
2021-11-15 13:42:59 +13:00
Jesse Hills
687a7e9b2f Bump version to 2021.11.0b5 2021-11-15 12:02:18 +13:00
Alexandre-Jacques St-Jacques
09e8782318 Remove unnecessary duplicate touch_pad_filter_start (#2724) 2021-11-15 12:02:18 +13:00
Jesse Hills
f2aea02210 Merge pull request #2723 from esphome/bump-2021.11.0b4
2021.11.0b4
2021-11-15 11:42:59 +13:00
Jesse Hills
194f922312 Bump version to 2021.11.0b4 2021-11-15 11:03:40 +13:00
Jesse Hills
fea3c48098 Fix indentation of write_lambda for modbus_controller number (#2722) 2021-11-15 11:03:39 +13:00
Sergey V. DUDANOV
c2f57baec2 RemoteTransmitter fix. Bug from version 2021.10. Some changes. (#2706) 2021-11-15 11:03:39 +13:00
Oxan van Leeuwen
f4a140e126 Feed WDT between doing ESP32 touchpad measurements (#2720) 2021-11-15 11:03:39 +13:00
Oxan van Leeuwen
ab506b09fe Restore InterruptLock on wifi-less ESP8266 (#2712) 2021-11-15 11:03:39 +13:00
Krzysztof Białek
87e1cdeedb Allow setting custom command_topic for Select and Number components (#2714) 2021-11-15 11:03:39 +13:00
Jesse Hills
81a36146ef Bump ESPAsyncWebServer to 2.1.0 (#2686) 2021-11-15 11:03:39 +13:00
38 changed files with 342 additions and 196 deletions

View File

@@ -60,6 +60,10 @@ async def to_code(config):
image.seek(frameIndex)
frame = image.convert("L", dither=Image.NONE)
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
)
for pix in pixels:
data[pos] = pix
pos += 1
@@ -69,8 +73,14 @@ async def to_code(config):
pos = 0
for frameIndex in range(frames):
image.seek(frameIndex)
if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE])
frame = image.convert("RGB")
pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}"
)
for pix in pixels:
data[pos] = pix[0]
pos += 1

View File

@@ -73,51 +73,52 @@ AnovaPacket *AnovaCodec::get_stop_request() {
}
void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
memset(this->buf_, 0, 32);
strncpy(this->buf_, (char *) data, length);
char buf[32];
memset(buf, 0, sizeof(buf));
strncpy(buf, (char *) data, std::min<uint16_t>(length, sizeof(buf) - 1));
this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
switch (this->current_query_) {
case READ_DEVICE_STATUS: {
if (!strncmp(this->buf_, "stopped", 7)) {
if (!strncmp(buf, "stopped", 7)) {
this->has_running_ = true;
this->running_ = false;
}
if (!strncmp(this->buf_, "running", 7)) {
if (!strncmp(buf, "running", 7)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case START: {
if (!strncmp(this->buf_, "start", 5)) {
if (!strncmp(buf, "start", 5)) {
this->has_running_ = true;
this->running_ = true;
}
break;
}
case STOP: {
if (!strncmp(this->buf_, "stop", 4)) {
if (!strncmp(buf, "stop", 4)) {
this->has_running_ = true;
this->running_ = false;
}
break;
}
case READ_TARGET_TEMPERATURE: {
this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
this->target_temp_ = parse_number<float>(buf, sizeof(buf)).value_or(0.0f);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case SET_TARGET_TEMPERATURE: {
this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
this->target_temp_ = parse_number<float>(buf, sizeof(buf)).value_or(0.0f);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case READ_CURRENT_TEMPERATURE: {
this->current_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
this->current_temp_ = parse_number<float>(buf, sizeof(buf)).value_or(0.0f);
if (this->fahrenheit_)
this->current_temp_ = ftoc(this->current_temp_);
this->has_current_temp_ = true;
@@ -125,8 +126,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
}
case SET_UNIT:
case READ_UNIT: {
this->unit_ = this->buf_[0];
this->fahrenheit_ = this->buf_[0] == 'f';
this->unit_ = buf[0];
this->fahrenheit_ = buf[0] == 'f';
this->has_unit_ = true;
break;
}

View File

@@ -70,7 +70,6 @@ class AnovaCodec {
bool has_current_temp_;
bool has_unit_;
bool has_running_;
char buf_[32];
bool fahrenheit_;
CurrentQuery current_query_;

View File

@@ -49,6 +49,7 @@ from esphome.const import (
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
)
@@ -82,6 +83,7 @@ DEVICE_CLASSES = [
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
]

View File

@@ -67,7 +67,7 @@ async def to_code(config):
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
@@ -87,7 +87,9 @@ async def to_code(config):
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config:
@@ -108,7 +110,9 @@ async def to_code(config):
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config:

View File

@@ -76,6 +76,7 @@ class ESP32Preferences : public ESPPreferences {
uint32_t current_offset = 0;
void open() {
nvs_flash_init();
esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle);
if (err == 0)
return;

View File

@@ -21,12 +21,19 @@ static const char *const TAG = "esp32_camera_web_server";
#define CONTENT_TYPE "image/jpeg"
#define CONTENT_LENGTH "Content-Length"
static const char *const STREAM_HEADER =
"HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY
"\r\n";
static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n";
static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Connection: close\r\n"
"Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
"\r\n"
"--" PART_BOUNDARY "\r\n";
static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n"
"\r\n"
"No frames send.\r\n"
"--" PART_BOUNDARY "\r\n";
static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
static const char *const STREAM_BOUNDARY = "\r\n"
"--" PART_BOUNDARY "\r\n";
CameraWebServer::CameraWebServer() {}
@@ -45,6 +52,7 @@ void CameraWebServer::setup() {
config.ctrl_port = this->port_;
config.max_open_sockets = 1;
config.backlog_conn = 2;
config.lru_purge_enable = true;
if (httpd_start(&this->httpd_, &config) != ESP_OK) {
mark_failed();
@@ -172,9 +180,6 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
ESP_LOGW(TAG, "STREAM: failed to acquire frame");
res = ESP_FAIL;
}
if (res == ESP_OK) {
res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
}
if (res == ESP_OK) {
size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
res = httpd_send_all(req, part_buf, hlen);
@@ -182,6 +187,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
if (res == ESP_OK) {
res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
}
if (res == ESP_OK) {
res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
}
if (res == ESP_OK) {
frames++;
int64_t frame_time = millis() - last_frame;
@@ -193,7 +201,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
}
if (!frames) {
res = httpd_send_all(req, STREAM_500, strlen(STREAM_500));
res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
}
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);

View File

@@ -1,6 +1,7 @@
#ifdef USE_ESP32
#include "esp32_touch.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
@@ -93,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
if (this->iir_filter_enabled_()) {
ESP_LOGCONFIG(TAG, " IIR Filter: %ums", this->iir_filter_);
touch_pad_filter_start(this->iir_filter_);
} else {
ESP_LOGCONFIG(TAG, " IIR Filter DISABLED");
}
@@ -125,6 +125,8 @@ void ESP32TouchComponent::loop() {
if (should_print) {
ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value);
}
App.feed_wdt();
}
if (should_print) {

View File

@@ -32,7 +32,7 @@ void EZOSensor::update() {
}
void EZOSensor::loop() {
uint8_t buf[20];
uint8_t buf[21];
if (!(this->state_ & EZO_STATE_WAIT)) {
if (this->state_ & EZO_STATE_SEND_TEMP) {
int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
@@ -74,7 +74,7 @@ void EZOSensor::loop() {
if (buf[0] != 1)
return;
float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 1).value_or(0);
float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 2).value_or(0);
this->publish_state(val);
}

View File

@@ -33,7 +33,7 @@ class AQICalculator : public AbstractAQICalculator {
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {

View File

@@ -37,9 +37,7 @@ class CAQICalculator : public AbstractAQICalculator {
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
return aqi;
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {

View File

@@ -2,30 +2,32 @@
namespace improv {
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data) {
return parse_improv_data(data.data(), data.size());
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum) {
return parse_improv_data(data.data(), data.size(), check_checksum);
}
ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
ImprovCommand improv_command;
Command command = (Command) data[0];
uint8_t data_length = data[1];
if (data_length != length - 3) {
if (data_length != length - 2 - check_checksum) {
improv_command.command = UNKNOWN;
return improv_command;
}
uint8_t checksum = data[length - 1];
if (check_checksum) {
uint8_t checksum = data[length - 1];
uint32_t calculated_checksum = 0;
for (uint8_t i = 0; i < length - 1; i++) {
calculated_checksum += data[i];
}
uint32_t calculated_checksum = 0;
for (uint8_t i = 0; i < length - 1; i++) {
calculated_checksum += data[i];
}
if ((uint8_t) calculated_checksum != checksum) {
improv_command.command = BAD_CHECKSUM;
return improv_command;
if ((uint8_t) calculated_checksum != checksum) {
improv_command.command = BAD_CHECKSUM;
return improv_command;
}
}
if (command == WIFI_SETTINGS) {
@@ -46,7 +48,7 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
return improv_command;
}
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum) {
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum, bool add_checksum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
@@ -58,17 +60,19 @@ std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::
}
out.insert(out.begin() + 1, length);
uint32_t calculated_checksum = 0;
if (add_checksum) {
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
}
out.push_back(calculated_checksum);
return out;
}
#ifdef USE_ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum) {
#ifdef ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
@@ -80,14 +84,16 @@ std::vector<uint8_t> build_rpc_response(Command command, const std::vector<Strin
}
out.insert(out.begin() + 1, length);
uint32_t calculated_checksum = 0;
if (add_checksum) {
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
}
out.push_back(calculated_checksum);
return out;
}
#endif // USE_ARDUINO
#endif // ARDUINO
} // namespace improv

View File

@@ -51,12 +51,13 @@ struct ImprovCommand {
std::string password;
};
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data);
ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum = true);
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true);
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum);
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum,
bool add_checksum = true);
#ifdef ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum);
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum = true);
#endif // ARDUINO
} // namespace improv

View File

@@ -98,13 +98,13 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
urls.push_back(webserver_url);
#endif
std::vector<uint8_t> data = improv::build_rpc_response(command, urls);
std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
return data;
}
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos);
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
return data;
};
@@ -140,22 +140,33 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
if (at < 8 + data_len)
return true;
if (at == 8 + data_len) {
if (at == 8 + data_len)
return true;
if (at == 8 + data_len + 1) {
uint8_t checksum = 0x00;
for (uint8_t i = 0; i < at; i++)
checksum += raw[i];
if (checksum != byte) {
ESP_LOGW(TAG, "Error decoding Improv payload");
this->set_error_(improv::ERROR_INVALID_RPC);
return false;
}
if (type == TYPE_RPC) {
this->set_error_(improv::ERROR_NONE);
auto command = improv::parse_improv_data(&raw[9], data_len);
auto command = improv::parse_improv_data(&raw[9], data_len, false);
return this->parse_improv_payload_(command);
}
}
return true;
// If we got here then the command coming is is improv, but not an RPC command
return false;
}
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
switch (command.command) {
case improv::BAD_CHECKSUM:
ESP_LOGW(TAG, "Error decoding Improv payload");
this->set_error_(improv::ERROR_INVALID_RPC);
return false;
case improv::WIFI_SETTINGS: {
wifi::WiFiAP sta{};
sta.set_ssid(command.ssid);
@@ -232,6 +243,12 @@ void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
data[7] = TYPE_RPC_RESPONSE;
data[8] = response.size();
data.insert(data.end(), response.begin(), response.end());
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data.push_back(checksum);
this->write_data_(data);
}

View File

@@ -1,10 +1,11 @@
#ifdef USE_ARDUINO
#include "esphome/core/log.h"
#include "adapter.h"
#include "ac_adapter.h"
namespace esphome {
namespace midea {
namespace ac {
const char *const Constants::TAG = "midea";
const std::string Constants::FREEZE_PROTECTION = "freeze protection";
@@ -171,6 +172,7 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::
traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION);
}
} // namespace ac
} // namespace midea
} // namespace esphome

View File

@@ -2,12 +2,15 @@
#ifdef USE_ARDUINO
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h>
#include "esphome/components/climate/climate_traits.h"
#include "appliance_base.h"
#include "air_conditioner.h"
namespace esphome {
namespace midea {
namespace ac {
using MideaMode = dudanov::midea::ac::Mode;
using MideaSwingMode = dudanov::midea::ac::SwingMode;
@@ -41,6 +44,7 @@ class Converters {
static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities);
};
} // namespace ac
} // namespace midea
} // namespace esphome

View File

@@ -7,6 +7,7 @@
namespace esphome {
namespace midea {
namespace ac {
template<typename... Ts> class MideaActionBase : public Action<Ts...> {
public:
@@ -55,6 +56,7 @@ template<typename... Ts> class PowerOffAction : public MideaActionBase<Ts...> {
void play(Ts... x) override { this->parent_->do_power_off(); }
};
} // namespace ac
} // namespace midea
} // namespace esphome

View File

@@ -2,13 +2,11 @@
#include "esphome/core/log.h"
#include "air_conditioner.h"
#include "adapter.h"
#ifdef USE_REMOTE_TRANSMITTER
#include "midea_ir.h"
#endif
#include "ac_adapter.h"
namespace esphome {
namespace midea {
namespace ac {
static void set_sensor(Sensor *sensor, float value) {
if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value))
@@ -122,7 +120,7 @@ void AirConditioner::dump_config() {
void AirConditioner::do_follow_me(float temperature, bool beeper) {
#ifdef USE_REMOTE_TRANSMITTER
IrFollowMeData data(static_cast<uint8_t>(lroundf(temperature)), beeper);
this->transmit_ir(data);
this->transmitter_.transmit(data);
#else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
#endif
@@ -131,7 +129,7 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) {
void AirConditioner::do_swing_step() {
#ifdef USE_REMOTE_TRANSMITTER
IrSpecialData data(0x01);
this->transmit_ir(data);
this->transmitter_.transmit(data);
#else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
#endif
@@ -143,13 +141,14 @@ void AirConditioner::do_display_toggle() {
} else {
#ifdef USE_REMOTE_TRANSMITTER
IrSpecialData data(0x08);
this->transmit_ir(data);
this->transmitter_.transmit(data);
#else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
#endif
}
}
} // namespace ac
} // namespace midea
} // namespace esphome

View File

@@ -2,17 +2,25 @@
#ifdef USE_ARDUINO
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h>
#include "appliance_base.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace midea {
namespace ac {
using sensor::Sensor;
using climate::ClimateCall;
using climate::ClimatePreset;
using climate::ClimateTraits;
using climate::ClimateMode;
using climate::ClimateSwingMode;
using climate::ClimateFanMode;
class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner> {
class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate {
public:
void dump_config() override;
void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; }
@@ -31,15 +39,26 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>
void do_beeper_off() { this->set_beeper_feedback(false); }
void do_power_on() { this->base_.setPowerState(true); }
void do_power_off() { this->base_.setPowerState(false); }
void set_supported_modes(const std::set<ClimateMode> &modes) { this->supported_modes_ = modes; }
void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { this->supported_swing_modes_ = modes; }
void set_supported_presets(const std::set<ClimatePreset> &presets) { this->supported_presets_ = presets; }
void set_custom_presets(const std::set<std::string> &presets) { this->supported_custom_presets_ = presets; }
void set_custom_fan_modes(const std::set<std::string> &modes) { this->supported_custom_fan_modes_ = modes; }
protected:
void control(const ClimateCall &call) override;
ClimateTraits traits() override;
std::set<ClimateMode> supported_modes_{};
std::set<ClimateSwingMode> supported_swing_modes_{};
std::set<ClimatePreset> supported_presets_{};
std::set<std::string> supported_custom_presets_{};
std::set<std::string> supported_custom_fan_modes_{};
Sensor *outdoor_sensor_{nullptr};
Sensor *humidity_sensor_{nullptr};
Sensor *power_sensor_{nullptr};
};
} // namespace ac
} // namespace midea
} // namespace esphome

View File

@@ -2,84 +2,97 @@
#ifdef USE_ARDUINO
// MideaUART
#include <Appliance/ApplianceBase.h>
#include <Helpers/Logger.h>
// Include global defines
#include "esphome/core/defines.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/climate/climate.h"
#ifdef USE_REMOTE_TRANSMITTER
#include "esphome/components/remote_base/midea_protocol.h"
#include "esphome/components/remote_transmitter/remote_transmitter.h"
#endif
#include <Appliance/ApplianceBase.h>
#include <Helpers/Logger.h>
#include "ir_transmitter.h"
namespace esphome {
namespace midea {
using climate::ClimatePreset;
using climate::ClimateTraits;
using climate::ClimateMode;
using climate::ClimateSwingMode;
using climate::ClimateFanMode;
/* Stream from UART component */
class UARTStream : public Stream {
public:
void set_uart(uart::UARTComponent *uart) { this->uart_ = uart; }
template<typename T>
class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream {
/* Stream interface implementation */
int available() override { return this->uart_->available(); }
int read() override {
uint8_t data;
this->uart_->read_byte(&data);
return data;
}
int peek() override {
uint8_t data;
this->uart_->peek_byte(&data);
return data;
}
size_t write(uint8_t data) override {
this->uart_->write_byte(data);
return 1;
}
size_t write(const uint8_t *data, size_t size) override {
this->uart_->write_array(data, size);
return size;
}
void flush() override { this->uart_->flush(); }
protected:
uart::UARTComponent *uart_;
};
template<typename T> class ApplianceBase : public Component {
static_assert(std::is_base_of<dudanov::midea::ApplianceBase, T>::value,
"T must derive from dudanov::midea::ApplianceBase class");
public:
ApplianceBase() {
this->base_.setStream(this);
this->base_.setStream(&this->stream_);
this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this));
dudanov::midea::ApplianceBase::setLogger(
[](int level, const char *tag, int line, const String &format, va_list args) {
esp_log_vprintf_(level, tag, line, format.c_str(), args);
});
}
bool can_proceed() override {
return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
}
float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
void setup() override { this->base_.setup(); }
void loop() override { this->base_.loop(); }
#ifdef USE_REMOTE_TRANSMITTER
void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_.set_transmitter(transmitter); }
#endif
/* UART communication */
void set_uart_parent(uart::UARTComponent *parent) { this->stream_.set_uart(parent); }
void set_period(uint32_t ms) { this->base_.setPeriod(ms); }
void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); }
void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); }
/* Component methods */
void setup() override { this->base_.setup(); }
void loop() override { this->base_.loop(); }
float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
bool can_proceed() override {
return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
}
void set_beeper_feedback(bool state) { this->base_.setBeeper(state); }
void set_autoconf(bool value) { this->base_.setAutoconf(value); }
void set_supported_modes(const std::set<ClimateMode> &modes) { this->supported_modes_ = modes; }
void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { this->supported_swing_modes_ = modes; }
void set_supported_presets(const std::set<ClimatePreset> &presets) { this->supported_presets_ = presets; }
void set_custom_presets(const std::set<std::string> &presets) { this->supported_custom_presets_ = presets; }
void set_custom_fan_modes(const std::set<std::string> &modes) { this->supported_custom_fan_modes_ = modes; }
virtual void on_status_change() = 0;
#ifdef USE_REMOTE_TRANSMITTER
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
this->transmitter_ = transmitter;
}
void transmit_ir(remote_base::MideaData &data) {
data.finalize();
auto transmit = this->transmitter_->transmit();
remote_base::MideaProtocol().encode(transmit.get_data(), data);
transmit.perform();
}
#endif
int available() override { return uart::UARTDevice::available(); }
int read() override { return uart::UARTDevice::read(); }
int peek() override { return uart::UARTDevice::peek(); }
void flush() override { uart::UARTDevice::flush(); }
size_t write(uint8_t data) override { return uart::UARTDevice::write(data); }
protected:
T base_;
std::set<ClimateMode> supported_modes_{};
std::set<ClimateSwingMode> supported_swing_modes_{};
std::set<ClimatePreset> supported_presets_{};
std::set<std::string> supported_custom_presets_{};
std::set<std::string> supported_custom_fan_modes_{};
UARTStream stream_;
#ifdef USE_REMOTE_TRANSMITTER
remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr};
IrTransmitter transmitter_;
#endif
};

View File

@@ -40,9 +40,9 @@ AUTO_LOAD = ["sensor"]
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
CONF_POWER_USAGE = "power_usage"
CONF_HUMIDITY_SETPOINT = "humidity_setpoint"
midea_ns = cg.esphome_ns.namespace("midea")
AirConditioner = midea_ns.class_("AirConditioner", climate.Climate, cg.Component)
Capabilities = midea_ns.namespace("Constants")
midea_ac_ns = cg.esphome_ns.namespace("midea").namespace("ac")
AirConditioner = midea_ac_ns.class_("AirConditioner", climate.Climate, cg.Component)
Capabilities = midea_ac_ns.namespace("Constants")
def templatize(value):
@@ -156,13 +156,13 @@ CONFIG_SCHEMA = cv.All(
)
# Actions
FollowMeAction = midea_ns.class_("FollowMeAction", automation.Action)
DisplayToggleAction = midea_ns.class_("DisplayToggleAction", automation.Action)
SwingStepAction = midea_ns.class_("SwingStepAction", automation.Action)
BeeperOnAction = midea_ns.class_("BeeperOnAction", automation.Action)
BeeperOffAction = midea_ns.class_("BeeperOffAction", automation.Action)
PowerOnAction = midea_ns.class_("PowerOnAction", automation.Action)
PowerOffAction = midea_ns.class_("PowerOffAction", automation.Action)
FollowMeAction = midea_ac_ns.class_("FollowMeAction", automation.Action)
DisplayToggleAction = midea_ac_ns.class_("DisplayToggleAction", automation.Action)
SwingStepAction = midea_ac_ns.class_("SwingStepAction", automation.Action)
BeeperOnAction = midea_ac_ns.class_("BeeperOnAction", automation.Action)
BeeperOffAction = midea_ac_ns.class_("BeeperOffAction", automation.Action)
PowerOnAction = midea_ac_ns.class_("PowerOnAction", automation.Action)
PowerOffAction = midea_ac_ns.class_("PowerOffAction", automation.Action)
MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
{

View File

@@ -7,6 +7,7 @@
namespace esphome {
namespace midea {
using remote_base::RemoteTransmitterBase;
using IrData = remote_base::MideaData;
class IrFollowMeData : public IrData {
@@ -38,6 +39,20 @@ class IrSpecialData : public IrData {
IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {}
};
class IrTransmitter {
public:
void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
void transmit(IrData &data) {
data.finalize();
auto transmit = this->transmitter_->transmit();
remote_base::MideaProtocol().encode(transmit.get_data(), data);
transmit.perform();
}
protected:
RemoteTransmitterBase *transmitter_{nullptr};
};
} // namespace midea
} // namespace esphome

View File

@@ -129,14 +129,14 @@ async def to_code(config):
return_type=cg.optional.template(float),
)
cg.add(var.set_template(template_))
if CONF_WRITE_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_WRITE_LAMBDA],
[
(ModbusNumber.operator("ptr"), "item"),
(cg.float_, "x"),
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
],
return_type=cg.optional.template(float),
)
cg.add(var.set_write_template(template_))
if CONF_WRITE_LAMBDA in config:
template_ = await cg.process_lambda(
config[CONF_WRITE_LAMBDA],
[
(ModbusNumber.operator("ptr"), "item"),
(cg.float_, "x"),
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
],
return_type=cg.optional.template(float),
)
cg.add(var.set_write_template(template_))

View File

@@ -41,7 +41,7 @@ NumberInRangeCondition = number_ns.class_(
icon = cv.icon
NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
cv.GenerateID(): cv.declare_id(Number),

View File

@@ -30,7 +30,7 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action)
icon = cv.icon
SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
cv.GenerateID(): cv.declare_id(Select),

View File

@@ -141,12 +141,16 @@ void SenseAirComponent::abc_get_period() {
}
bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) {
// Verify we have somewhere to store the response
if (response == nullptr) {
return false;
}
// Write wake up byte required by some S8 sensor models
this->write_byte(0);
this->flush();
delay(5);
this->write_array(command, SENSEAIR_REQUEST_LENGTH);
if (response == nullptr)
return true;
bool ret = this->read_array(response, response_length);
this->flush();
return ret;

View File

@@ -94,17 +94,21 @@ UART_DIRECTIONS = {
"BOTH": UARTDirection.UART_DIRECTION_BOTH,
}
AFTER_DEFAULTS = {CONF_BYTES: 256, CONF_TIMEOUT: "100ms"}
DEBUG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum(
UART_DIRECTIONS, upper=True
),
cv.Optional(CONF_AFTER): cv.Schema(
cv.Optional(CONF_AFTER, default=AFTER_DEFAULTS): cv.Schema(
{
cv.Optional(CONF_BYTES, default=256): cv.validate_bytes,
cv.Optional(
CONF_TIMEOUT, default="100ms"
CONF_BYTES, default=AFTER_DEFAULTS[CONF_BYTES]
): cv.validate_bytes,
cv.Optional(
CONF_TIMEOUT, default=AFTER_DEFAULTS[CONF_TIMEOUT]
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data),
}

View File

@@ -28,4 +28,4 @@ async def to_code(config):
cg.add_library("FS", None)
cg.add_library("Update", None)
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.1")
cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0")

View File

@@ -57,38 +57,46 @@ void IRAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) {
void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) {
switch (message->type) {
case HUMIDITY:
this->humidity = (message->value > 10000) ? NAN : (message->value / 100.0f);
this->humidity = message->value;
break;
case TEMPERATURE:
this->temperature = (message->value > 5970) ? NAN : (message->value / 16.0f - 273.15f);
this->temperature = message->value;
break;
case CO2:
this->co2 = (message->value > 10000) ? NAN : message->value;
break;
default:
this->co2 = message->value;
break;
}
}
bool ZyAuraSensor::publish_state_(sensor::Sensor *sensor, float *value) {
// Sensor doesn't added to configuration
bool ZyAuraSensor::publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value) {
// Sensor wasn't added to configuration
if (sensor == nullptr) {
return true;
}
sensor->publish_state(*value);
float value = NAN;
switch (data_type) {
case HUMIDITY:
value = (*data_value > 10000) ? NAN : (*data_value / 100.0f);
break;
case TEMPERATURE:
value = (*data_value > 5970) ? NAN : (*data_value / 16.0f - 273.15f);
break;
case CO2:
value = (*data_value > 10000) ? NAN : *data_value;
break;
}
sensor->publish_state(value);
// Sensor reported wrong value
if (std::isnan(*value)) {
if (std::isnan(value)) {
ESP_LOGW(TAG, "Sensor reported invalid data. Is the update interval too small?");
this->status_set_warning();
return false;
}
*value = NAN;
*data_value = -1;
return true;
}
@@ -104,9 +112,9 @@ void ZyAuraSensor::dump_config() {
}
void ZyAuraSensor::update() {
bool co2_result = this->publish_state_(this->co2_sensor_, &this->store_.co2);
bool temperature_result = this->publish_state_(this->temperature_sensor_, &this->store_.temperature);
bool humidity_result = this->publish_state_(this->humidity_sensor_, &this->store_.humidity);
bool co2_result = this->publish_state_(CO2, this->co2_sensor_, &this->store_.co2);
bool temperature_result = this->publish_state_(TEMPERATURE, this->temperature_sensor_, &this->store_.temperature);
bool humidity_result = this->publish_state_(HUMIDITY, this->humidity_sensor_, &this->store_.humidity);
if (co2_result && temperature_result && humidity_result) {
this->status_clear_warning();

View File

@@ -42,9 +42,9 @@ class ZaDataProcessor {
class ZaSensorStore {
public:
float co2 = NAN;
float temperature = NAN;
float humidity = NAN;
uint16_t co2 = -1;
uint16_t temperature = -1;
uint16_t humidity = -1;
void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data);
static void interrupt(ZaSensorStore *arg);
@@ -79,7 +79,7 @@ class ZyAuraSensor : public PollingComponent {
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
bool publish_state_(sensor::Sensor *sensor, float *value);
bool publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value);
};
} // namespace zyaura

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2021.11.0b3"
__version__ = "2021.11.3"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
@@ -883,6 +883,7 @@ DEVICE_CLASS_SAFETY = "safety"
DEVICE_CLASS_SMOKE = "smoke"
DEVICE_CLASS_SOUND = "sound"
DEVICE_CLASS_TAMPER = "tamper"
DEVICE_CLASS_UPDATE = "update"
DEVICE_CLASS_VIBRATION = "vibration"
DEVICE_CLASS_WINDOW = "window"
# device classes of both binary_sensor and sensor component

View File

@@ -9,6 +9,7 @@
#ifdef USE_WIFI
#include <ESP8266WiFi.h>
#endif
#include <Arduino.h>
#include <osapi.h>
#elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
#include <Esp.h>
@@ -430,13 +431,8 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
}
#ifdef USE_ESP8266
#ifdef USE_WIFI
IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); }
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); }
#else
IRAM_ATTR InterruptLock::InterruptLock() {}
IRAM_ATTR InterruptLock::~InterruptLock() {}
#endif
#endif
#ifdef USE_ESP32
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }

View File

@@ -362,45 +362,47 @@ std::string str_sanitize(const std::string &str);
/// @name Parsing & formatting
///@{
/// Parse a unsigned decimal number.
/// Parse an unsigned decimal number (requires null-terminated string).
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_unsigned<T>::value), int> = 0>
optional<T> parse_number(const char *str, size_t len) {
char *end = nullptr;
unsigned long value = ::strtoul(str, &end, 10); // NOLINT(google-runtime-int)
if (end == nullptr || end != str + len || value > std::numeric_limits<T>::max())
if (end == str || *end != '\0' || value > std::numeric_limits<T>::max())
return {};
return value;
}
/// Parse an unsigned decimal number.
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_unsigned<T>::value), int> = 0>
optional<T> parse_number(const std::string &str) {
return parse_number<T>(str.c_str(), str.length());
return parse_number<T>(str.c_str(), str.length() + 1);
}
/// Parse a signed decimal number.
/// Parse a signed decimal number (requires null-terminated string).
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), int> = 0>
optional<T> parse_number(const char *str, size_t len) {
char *end = nullptr;
signed long value = ::strtol(str, &end, 10); // NOLINT(google-runtime-int)
if (end == nullptr || end != str + len || value < std::numeric_limits<T>::min() ||
value > std::numeric_limits<T>::max())
if (end == str || *end != '\0' || value < std::numeric_limits<T>::min() || value > std::numeric_limits<T>::max())
return {};
return value;
}
/// Parse a signed decimal number.
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), int> = 0>
optional<T> parse_number(const std::string &str) {
return parse_number<T>(str.c_str(), str.length());
return parse_number<T>(str.c_str(), str.length() + 1);
}
/// Parse a decimal floating-point number.
/// Parse a decimal floating-point number (requires null-terminated string).
template<typename T, enable_if_t<(std::is_same<T, float>::value), int> = 0>
optional<T> parse_number(const char *str, size_t len) {
char *end = nullptr;
float value = ::strtof(str, &end);
if (end == nullptr || end != str + len || value == HUGE_VALF)
if (end == str || *end != '\0' || value == HUGE_VALF)
return {};
return value;
}
/// Parse a decimal floating-point number.
template<typename T, enable_if_t<(std::is_same<T, float>::value), int> = 0>
optional<T> parse_number(const std::string &str) {
return parse_number<T>(str.c_str(), str.length());
return parse_number<T>(str.c_str(), str.length() + 1);
}
///@}

View File

@@ -13,8 +13,9 @@ from zeroconf import (
RecordUpdateListener,
Zeroconf,
ServiceBrowser,
ServiceStateChange,
current_time_millis,
)
from zeroconf._services import ServiceStateChange
_CLASS_IN = 1
_FLAGS_QR_QUERY = 0x0000 # query
@@ -88,7 +89,7 @@ class DashboardStatus(threading.Thread):
entries = self.zc.cache.entries_with_name(key)
if not entries:
return False
now = time.time() * 1000
now = current_time_millis()
return any(
(entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries
@@ -99,7 +100,7 @@ class DashboardStatus(threading.Thread):
self.on_update(
{key: self.host_status(host) for key, host in self.key_to_host.items()}
)
now = time.time() * 1000
now = current_time_millis()
for host in self.query_hosts:
entries = self.zc.cache.entries_with_name(host)
if not entries or all(

View File

@@ -41,7 +41,7 @@ lib_deps =
${common.lib_deps}
ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt
ottowinter/ArduinoJson-esphomelib@5.13.3 ; json
esphome/ESPAsyncWebServer-esphome@2.0.1 ; web_server_base
esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base
fastled/FastLED@3.3.2 ; fastled_base
mikalhart/TinyGPSPlus@1.0.2 ; gps
freekode/TM1651@1.0.1 ; tm1651

View File

@@ -11,6 +11,7 @@ esptool==3.2
click==8.0.3
esphome-dashboard==20211021.1
aioesphomeapi==10.2.0
zeroconf==0.36.13
# esp-idf requires this, but doesn't bundle it by default
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24

View File

@@ -2512,3 +2512,23 @@ teleinfo:
uart_id: uart0
update_interval: 60s
historical_mode: true
number:
- platform: template
id: test_number
state_topic: livingroom/custom_state_topic
command_topic: livingroom/custom_command_topic
min_value: 0
step: 1
max_value: 10
optimistic: true
select:
- platform: template
id: test_select
state_topic: livingroom/custom_state_topic
command_topic: livingroom/custom_command_topic
options:
- one
- two
optimistic: true

View File

@@ -39,6 +39,12 @@ uart:
tx_pin: GPIO22
rx_pin: GPIO23
baud_rate: 115200
# Specifically added for testing debug with no after: definition.
debug:
dummy_receiver: false
direction: rx
sequence:
- lambda: UARTDebug::log_hex(direction, bytes, ':');
ota:
safe_mode: True