From 69c78651d509414daa9f7acd44bd44e262447797 Mon Sep 17 00:00:00 2001 From: buxtronix Date: Tue, 23 Mar 2021 16:21:04 +1100 Subject: [PATCH 01/12] Fix BLE UUID matching (#1637) Co-authored-by: Ben Buxton --- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index a8185a8c67..b2d303da15 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -241,7 +241,7 @@ ESPBTUUID ESPBTUUID::as_128bit() const { } bool ESPBTUUID::contains(uint8_t data1, uint8_t data2) const { if (this->uuid_.len == ESP_UUID_LEN_16) { - return (this->uuid_.uuid.uuid16 >> 8) == data2 || (this->uuid_.uuid.uuid16 & 0xFF) == data1; + return (this->uuid_.uuid.uuid16 >> 8) == data2 && (this->uuid_.uuid.uuid16 & 0xFF) == data1; } else if (this->uuid_.len == ESP_UUID_LEN_32) { for (uint8_t i = 0; i < 3; i++) { bool a = ((this->uuid_.uuid.uuid32 >> i * 8) & 0xFF) == data1; From c903eb2d01273719e3deda21d1977fb575eadf8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Elio=20Petten=C3=B2?= Date: Thu, 25 Mar 2021 18:33:06 +0000 Subject: [PATCH 02/12] Add optional bindkey support for CGG1. (#1407) --- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 7 +++++-- esphome/components/xiaomi_cgg1/sensor.py | 4 ++++ esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp | 18 ++++++++++++++++-- esphome/components/xiaomi_cgg1/xiaomi_cgg1.h | 2 ++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 033269f66c..29a34be81c 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -103,7 +103,7 @@ bool parse_xiaomi_message(const std::vector &message, XiaomiParseResult return false; } - while (payload_length > 0) { + while (payload_length > 3) { if (payload[payload_offset + 1] != 0x10) { ESP_LOGVV(TAG, "parse_xiaomi_message(): fixed byte not found, stop parsing residual data."); break; @@ -171,7 +171,10 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service result.type = XiaomiParseResult::TYPE_MUE4094RT; result.name = "MUE4094RT"; result.raw_offset -= 6; - } else if ((raw[2] == 0x47) && (raw[3] == 0x03)) { // round body, e-ink display + } else if ((raw[2] == 0x47) && (raw[3] == 0x03)) { // ClearGrass-branded, round body, e-ink display + result.type = XiaomiParseResult::TYPE_CGG1; + result.name = "CGG1"; + } else if ((raw[2] == 0x48) && (raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys result.type = XiaomiParseResult::TYPE_CGG1; result.name = "CGG1"; } else if ((raw[2] == 0xbc) && (raw[3] == 0x03)) { // VegTrug Grow Care Garden diff --git a/esphome/components/xiaomi_cgg1/sensor.py b/esphome/components/xiaomi_cgg1/sensor.py index 6201df61b8..cedf04d8a6 100644 --- a/esphome/components/xiaomi_cgg1/sensor.py +++ b/esphome/components/xiaomi_cgg1/sensor.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( CONF_BATTERY_LEVEL, + CONF_BINDKEY, CONF_HUMIDITY, CONF_MAC_ADDRESS, CONF_TEMPERATURE, @@ -27,6 +28,7 @@ CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(XiaomiCGG1), + cv.Optional(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE @@ -50,6 +52,8 @@ def to_code(config): yield esp32_ble_tracker.register_ble_device(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + if CONF_BINDKEY in config: + cg.add(var.set_bindkey(config[CONF_BINDKEY])) if CONF_TEMPERATURE in config: sens = yield sensor.new_sensor(config[CONF_TEMPERATURE]) diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index a7c94fafad..86725956e0 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -10,6 +10,7 @@ static const char *TAG = "xiaomi_cgg1"; void XiaomiCGG1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGG1"); + ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); @@ -31,8 +32,9 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { if (res->is_duplicate) { continue; } - if (res->has_encryption) { - ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); + if (res->has_encryption && + (!(xiaomi_ble::decrypt_xiaomi_payload(const_cast &>(service_data.data), this->bindkey_, + this->address_)))) { continue; } if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { @@ -57,6 +59,18 @@ bool XiaomiCGG1::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { return true; } +void XiaomiCGG1::set_bindkey(const std::string &bindkey) { + memset(bindkey_, 0, 16); + if (bindkey.size() != 32) { + return; + } + char temp[3] = {0}; + for (int i = 0; i < 16; i++) { + strncpy(temp, &(bindkey.c_str()[i * 2]), 2); + bindkey_[i] = std::strtoul(temp, NULL, 16); + } +} + } // namespace xiaomi_cgg1 } // namespace esphome diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h index 57f883405c..e1d812e929 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.h @@ -13,6 +13,7 @@ namespace xiaomi_cgg1 { class XiaomiCGG1 : public Component, public esp32_ble_tracker::ESPBTDeviceListener { public: void set_address(uint64_t address) { address_ = address; } + void set_bindkey(const std::string &bindkey); bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; @@ -24,6 +25,7 @@ class XiaomiCGG1 : public Component, public esp32_ble_tracker::ESPBTDeviceListen protected: uint64_t address_; + uint8_t bindkey_[16]; sensor::Sensor *temperature_{nullptr}; sensor::Sensor *humidity_{nullptr}; sensor::Sensor *battery_level_{nullptr}; From 566c1294355ad2f4cc43d8880b6ac819cce90419 Mon Sep 17 00:00:00 2001 From: SenexCrenshaw <35600301+SenexCrenshaw@users.noreply.github.com> Date: Fri, 26 Mar 2021 18:01:37 -0400 Subject: [PATCH 03/12] Buffer allocation and TRUEFALSE templates (#1644) --- esphome/core/helpers.h | 15 +++++++++++++++ esphome/core/log.h | 1 + 2 files changed, 16 insertions(+) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 63706e8a19..5f9ab1fdd1 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -319,3 +319,18 @@ template class Parented { uint32_t fnv1_hash(const std::string &str); } // namespace esphome + +template T *new_buffer(size_t length) { + T *buffer; +#ifdef ARDUINO_ARCH_ESP32 + if (psramFound()) { + buffer = (T *) ps_malloc(length); + } else { + buffer = new T[length]; + } +#else + buffer = new T[length]; +#endif + + return buffer; +} // namespace esphome diff --git a/esphome/core/log.h b/esphome/core/log.h index 361fbe1182..0eec28101f 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -160,5 +160,6 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT ((byte) &0x08 ? '1' : '0'), ((byte) &0x04 ? '1' : '0'), ((byte) &0x02 ? '1' : '0'), ((byte) &0x01 ? '1' : '0') #define YESNO(b) ((b) ? "YES" : "NO") #define ONOFF(b) ((b) ? "ON" : "OFF") +#define TRUEFALSE(b) ((b) ? "TRUE" : "FALSE") } // namespace esphome From 392ed64375a07435ee78c5b011ac6c1ec2683aa5 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Sun, 28 Mar 2021 22:35:39 -0300 Subject: [PATCH 04/12] fix servo not reattaching with same target (#1649) --- esphome/components/servo/servo.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/servo/servo.cpp b/esphome/components/servo/servo.cpp index 6935c34653..57baf4aecf 100644 --- a/esphome/components/servo/servo.cpp +++ b/esphome/components/servo/servo.cpp @@ -52,6 +52,8 @@ void Servo::loop() { void Servo::write(float value) { value = clamp(value, -1.0f, 1.0f); + if (this->target_value_ == value) + this->internal_write(value); this->target_value_ = value; this->source_value_ = this->current_value_; this->state_ = STATE_ATTACHED; From 5ab2ef40792e0bae937aa1b49d6a8e13f6917c5c Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 8 Apr 2021 13:58:01 +0200 Subject: [PATCH 05/12] Fix colorlog removing colors and refactor color code (#1671) --- esphome/__main__.py | 57 +++++------------------------- esphome/api/client.py | 5 +-- esphome/config.py | 31 +++++++++------- esphome/helpers.py | 11 ------ esphome/log.py | 82 +++++++++++++++++++++++++++++++++++++++++++ esphome/mqtt.py | 4 +-- esphome/wizard.py | 69 +++++++++++++++++++----------------- requirements.txt | 1 - 8 files changed, 151 insertions(+), 109 deletions(-) create mode 100644 esphome/log.py diff --git a/esphome/__main__.py b/esphome/__main__.py index 20cb44d11c..79a8c708a4 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -19,7 +19,7 @@ from esphome.const import ( CONF_PLATFORMIO_OPTIONS, ) from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority -from esphome.helpers import color, indent +from esphome.helpers import indent from esphome.util import ( run_external_command, run_external_process, @@ -27,6 +27,7 @@ from esphome.util import ( list_yaml_files, get_serial_ports, ) +from esphome.log import color, setup_log, Fore _LOGGER = logging.getLogger(__name__) @@ -57,7 +58,7 @@ def choose_prompt(options): raise ValueError break except ValueError: - safe_print(color("red", f"Invalid option: '{opt}'")) + safe_print(color(Fore.RED, f"Invalid option: '{opt}'")) return options[opt - 1][1] @@ -263,46 +264,6 @@ def clean_mqtt(config, args): ) -def setup_log(debug=False, quiet=False): - if debug: - log_level = logging.DEBUG - CORE.verbose = True - elif quiet: - log_level = logging.CRITICAL - else: - log_level = logging.INFO - logging.basicConfig(level=log_level) - fmt = "%(levelname)s %(message)s" - colorfmt = f"%(log_color)s{fmt}%(reset)s" - datefmt = "%H:%M:%S" - - logging.getLogger("urllib3").setLevel(logging.WARNING) - - try: - import colorama - - colorama.init(strip=True) - - from colorlog import ColoredFormatter - - logging.getLogger().handlers[0].setFormatter( - ColoredFormatter( - colorfmt, - datefmt=datefmt, - reset=True, - log_colors={ - "DEBUG": "cyan", - "INFO": "green", - "WARNING": "yellow", - "ERROR": "red", - "CRITICAL": "red", - }, - ) - ) - except ImportError: - pass - - def command_wizard(args): from esphome import wizard @@ -442,30 +403,30 @@ def command_update_all(args): click.echo(f"{half_line}{middle_text}{half_line}") for f in files: - print("Updating {}".format(color("cyan", f))) + print("Updating {}".format(color(Fore.CYAN, f))) print("-" * twidth) print() rc = run_external_process( "esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA" ) if rc == 0: - print_bar("[{}] {}".format(color("bold_green", "SUCCESS"), f)) + print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f)) success[f] = True else: - print_bar("[{}] {}".format(color("bold_red", "ERROR"), f)) + print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f)) success[f] = False print() print() print() - print_bar("[{}]".format(color("bold_white", "SUMMARY"))) + print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY"))) failed = 0 for f in files: if success[f]: - print(" - {}: {}".format(f, color("green", "SUCCESS"))) + print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS"))) else: - print(" - {}: {}".format(f, color("bold_red", "FAILED"))) + print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED"))) failed += 1 return failed diff --git a/esphome/api/client.py b/esphome/api/client.py index 84c9890fe0..dd11f79922 100644 --- a/esphome/api/client.py +++ b/esphome/api/client.py @@ -13,7 +13,8 @@ from esphome import const import esphome.api.api_pb2 as pb from esphome.const import CONF_PASSWORD, CONF_PORT from esphome.core import EsphomeError -from esphome.helpers import resolve_ip_address, indent, color +from esphome.helpers import resolve_ip_address, indent +from esphome.log import color, Fore from esphome.util import safe_print _LOGGER = logging.getLogger(__name__) @@ -488,7 +489,7 @@ def run_logs(config, address): text = msg.message if msg.send_failed: text = color( - "white", + Fore.WHITE, "(Message skipped because it was too big to fit in " "TCP buffer - This is only cosmetic)", ) diff --git a/esphome/config.py b/esphome/config.py index 3317196965..0c8e51fdce 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -19,13 +19,14 @@ from esphome.const import ( CONF_SUBSTITUTIONS, ) from esphome.core import CORE, EsphomeError # noqa -from esphome.helpers import color, indent +from esphome.helpers import indent from esphome.util import safe_print, OrderedDict from typing import List, Optional, Tuple, Union # noqa from esphome.core import ConfigType # noqa from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue from esphome.voluptuous_schema import ExtraKeysInvalid +from esphome.log import color, Fore _LOGGER = logging.getLogger(__name__) @@ -790,7 +791,7 @@ def line_info(config, path, highlight=True): if obj: mark = obj.start_mark source = "[source {}:{}]".format(mark.document, mark.line + 1) - return color("cyan", source) + return color(Fore.CYAN, source) return "None" @@ -813,7 +814,9 @@ def dump_dict(config, path, at_root=True): if at_root: error = config.get_error_for_path(path) if error is not None: - ret += "\n" + color("bold_red", _format_vol_invalid(error, config)) + "\n" + ret += ( + "\n" + color(Fore.BOLD_RED, _format_vol_invalid(error, config)) + "\n" + ) if isinstance(conf, (list, tuple)): multiline = True @@ -826,12 +829,14 @@ def dump_dict(config, path, at_root=True): error = config.get_error_for_path(path_) if error is not None: ret += ( - "\n" + color("bold_red", _format_vol_invalid(error, config)) + "\n" + "\n" + + color(Fore.BOLD_RED, _format_vol_invalid(error, config)) + + "\n" ) sep = "- " if config.is_in_error_path(path_): - sep = color("red", sep) + sep = color(Fore.RED, sep) msg, _ = dump_dict(config, path_, at_root=False) msg = indent(msg) inf = line_info(config, path_, highlight=config.is_in_error_path(path_)) @@ -851,12 +856,14 @@ def dump_dict(config, path, at_root=True): error = config.get_error_for_path(path_) if error is not None: ret += ( - "\n" + color("bold_red", _format_vol_invalid(error, config)) + "\n" + "\n" + + color(Fore.BOLD_RED, _format_vol_invalid(error, config)) + + "\n" ) st = f"{k}: " if config.is_in_error_path(path_): - st = color("red", st) + st = color(Fore.RED, st) msg, m = dump_dict(config, path_, at_root=False) inf = line_info(config, path_, highlight=config.is_in_error_path(path_)) @@ -878,7 +885,7 @@ def dump_dict(config, path, at_root=True): if len(conf) > 80: conf = "|-\n" + indent(conf) error = config.get_error_for_path(path) - col = "bold_red" if error else "white" + col = Fore.BOLD_RED if error else Fore.KEEP ret += color(col, str(conf)) elif isinstance(conf, core.Lambda): if is_secret(conf): @@ -886,13 +893,13 @@ def dump_dict(config, path, at_root=True): conf = "!lambda |-\n" + indent(str(conf.value)) error = config.get_error_for_path(path) - col = "bold_red" if error else "white" + col = Fore.BOLD_RED if error else Fore.KEEP ret += color(col, conf) elif conf is None: pass else: error = config.get_error_for_path(path) - col = "bold_red" if error else "white" + col = Fore.BOLD_RED if error else Fore.KEEP ret += color(col, str(conf)) multiline = "\n" in ret @@ -934,13 +941,13 @@ def read_config(command_line_substitutions): if not CORE.verbose: res = strip_default_ids(res) - safe_print(color("bold_red", "Failed config")) + safe_print(color(Fore.BOLD_RED, "Failed config")) safe_print("") for path, domain in res.output_paths: if not res.is_in_error_path(path): continue - errstr = color("bold_red", f"{domain}:") + errstr = color(Fore.BOLD_RED, f"{domain}:") errline = line_info(res, path) if errline: errstr += " " + errline diff --git a/esphome/helpers.py b/esphome/helpers.py index 780a2aa88e..b80d338eef 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -57,17 +57,6 @@ def cpp_string_escape(string, encoding="utf-8"): return '"' + result + '"' -def color(the_color, message=""): - from colorlog.escape_codes import escape_codes, parse_colors - - if not message: - res = parse_colors(the_color) - else: - res = parse_colors(the_color) + message + escape_codes["reset"] - - return res - - def run_system_command(*args): import subprocess diff --git a/esphome/log.py b/esphome/log.py new file mode 100644 index 0000000000..fa79efa833 --- /dev/null +++ b/esphome/log.py @@ -0,0 +1,82 @@ +import logging + +from esphome.core import CORE + + +class AnsiFore: + KEEP = "" + BLACK = "\033[30m" + RED = "\033[31m" + GREEN = "\033[32m" + YELLOW = "\033[33m" + BLUE = "\033[34m" + MAGENTA = "\033[35m" + CYAN = "\033[36m" + WHITE = "\033[37m" + RESET = "\033[39m" + + BOLD_BLACK = "\033[1;30m" + BOLD_RED = "\033[1;31m" + BOLD_GREEN = "\033[1;32m" + BOLD_YELLOW = "\033[1;33m" + BOLD_BLUE = "\033[1;34m" + BOLD_MAGENTA = "\033[1;35m" + BOLD_CYAN = "\033[1;36m" + BOLD_WHITE = "\033[1;37m" + BOLD_RESET = "\033[1;39m" + + +class AnsiStyle: + BRIGHT = "\033[1m" + BOLD = "\033[1m" + DIM = "\033[2m" + THIN = "\033[2m" + NORMAL = "\033[22m" + RESET_ALL = "\033[0m" + + +Fore = AnsiFore() +Style = AnsiStyle() + + +def color(col: str, msg: str, reset: bool = True) -> bool: + if col and not col.startswith("\033["): + raise ValueError("Color must be value from esphome.log.Fore") + s = str(col) + msg + if reset and col: + s += str(Style.RESET_ALL) + return s + + +class ESPHomeLogFormatter(logging.Formatter): + def __init__(self): + super().__init__(fmt="%(levelname)s %(message)s", datefmt="%H:%M:%S", style="%") + + def format(self, record): + formatted = super().format(record) + prefix = { + "DEBUG": Fore.CYAN, + "INFO": Fore.GREEN, + "WARNING": Fore.YELLOW, + "ERROR": Fore.RED, + "CRITICAL": Fore.RED, + }.get(record.levelname, "") + return f"{prefix}{formatted}{Style.RESET_ALL}" + + +def setup_log(debug=False, quiet=False): + import colorama + + if debug: + log_level = logging.DEBUG + CORE.verbose = True + elif quiet: + log_level = logging.CRITICAL + else: + log_level = logging.INFO + logging.basicConfig(level=log_level) + + logging.getLogger("urllib3").setLevel(logging.WARNING) + + colorama.init() + logging.getLogger().handlers[0].setFormatter(ESPHomeLogFormatter()) diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 86937ba37e..9be87b5c5d 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -22,7 +22,7 @@ from esphome.const import ( CONF_USERNAME, ) from esphome.core import CORE, EsphomeError -from esphome.helpers import color +from esphome.log import color, Fore from esphome.util import safe_print _LOGGER = logging.getLogger(__name__) @@ -158,7 +158,7 @@ def get_fingerprint(config): sha1 = hashlib.sha1(cert_der).hexdigest() - safe_print("SHA1 Fingerprint: " + color("cyan", sha1)) + safe_print("SHA1 Fingerprint: " + color(Fore.CYAN, sha1)) safe_print( "Copy the string above into mqtt.ssl_fingerprints section of {}" "".format(CORE.config_path) diff --git a/esphome/wizard.py b/esphome/wizard.py index 620ceb9b59..4ad5c63216 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -6,7 +6,8 @@ import unicodedata import voluptuous as vol import esphome.config_validation as cv -from esphome.helpers import color, get_bool_env, write_file +from esphome.helpers import get_bool_env, write_file +from esphome.log import color, Fore # pylint: disable=anomalous-backslash-in-string from esphome.pins import ESP32_BOARD_PINS, ESP8266_BOARD_PINS @@ -148,13 +149,13 @@ def wizard(path): if not path.endswith(".yaml") and not path.endswith(".yml"): safe_print( "Please make your configuration file {} have the extension .yaml or .yml" - "".format(color("cyan", path)) + "".format(color(Fore.CYAN, path)) ) return 1 if os.path.exists(path): safe_print( "Uh oh, it seems like {} already exists, please delete that file first " - "or chose another configuration file.".format(color("cyan", path)) + "or chose another configuration file.".format(color(Fore.CYAN, path)) ) return 2 safe_print("Hi there!") @@ -171,7 +172,7 @@ def wizard(path): safe_print() safe_print_step(1, CORE_BIG) safe_print( - "First up, please choose a " + color("green", "name") + " for your node." + "First up, please choose a " + color(Fore.GREEN, "name") + " for your node." ) safe_print( "It should be a unique name that can be used to identify the device later." @@ -179,12 +180,12 @@ def wizard(path): sleep(1) safe_print( "For example, I like calling the node in my living room {}.".format( - color("bold_white", "livingroom") + color(Fore.BOLD_WHITE, "livingroom") ) ) safe_print() sleep(1) - name = input(color("bold_white", "(name): ")) + name = input(color(Fore.BOLD_WHITE, "(name): ")) while True: try: @@ -193,7 +194,7 @@ def wizard(path): except vol.Invalid: safe_print( color( - "red", + Fore.RED, f'Oh noes, "{name}" isn\'t a valid name. Names can only ' f"include numbers, lower-case letters, underscores and " f"hyphens.", @@ -202,12 +203,12 @@ def wizard(path): name = strip_accents(name).lower().replace(" ", "_") name = "".join(c for c in name if c in ALLOWED_NAME_CHARS) safe_print( - 'Shall I use "{}" as the name instead?'.format(color("cyan", name)) + 'Shall I use "{}" as the name instead?'.format(color(Fore.CYAN, name)) ) sleep(0.5) name = default_input("(name [{}]): ", name) - safe_print('Great! Your node is now called "{}".'.format(color("cyan", name))) + safe_print('Great! Your node is now called "{}".'.format(color(Fore.CYAN, name))) sleep(1) safe_print_step(2, ESP_BIG) safe_print( @@ -216,16 +217,16 @@ def wizard(path): ) safe_print( "Are you using an " - + color("green", "ESP32") + + color(Fore.GREEN, "ESP32") + " or " - + color("green", "ESP8266") + + color(Fore.GREEN, "ESP8266") + " platform? (Choose ESP8266 for Sonoff devices)" ) while True: sleep(0.5) safe_print() safe_print("Please enter either ESP32 or ESP8266.") - platform = input(color("bold_white", "(ESP32/ESP8266): ")) + platform = input(color(Fore.BOLD_WHITE, "(ESP32/ESP8266): ")) try: platform = vol.All(vol.Upper, vol.Any("ESP32", "ESP8266"))(platform) break @@ -235,7 +236,7 @@ def wizard(path): '"{}". Please try again.'.format(platform) ) safe_print( - "Thanks! You've chosen {} as your platform.".format(color("cyan", platform)) + "Thanks! You've chosen {} as your platform.".format(color(Fore.CYAN, platform)) ) safe_print() sleep(1) @@ -250,37 +251,39 @@ def wizard(path): ) safe_print( - "Next, I need to know what " + color("green", "board") + " you're using." + "Next, I need to know what " + color(Fore.GREEN, "board") + " you're using." ) sleep(0.5) - safe_print("Please go to {} and choose a board.".format(color("green", board_link))) + safe_print( + "Please go to {} and choose a board.".format(color(Fore.GREEN, board_link)) + ) if platform == "ESP32": - safe_print("(Type " + color("green", "esp01_1m") + " for Sonoff devices)") + safe_print("(Type " + color(Fore.GREEN, "esp01_1m") + " for Sonoff devices)") safe_print() # Don't sleep because user needs to copy link if platform == "ESP32": - safe_print('For example "{}".'.format(color("bold_white", "nodemcu-32s"))) + safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "nodemcu-32s"))) boards = list(ESP32_BOARD_PINS.keys()) else: - safe_print('For example "{}".'.format(color("bold_white", "nodemcuv2"))) + safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "nodemcuv2"))) boards = list(ESP8266_BOARD_PINS.keys()) safe_print("Options: {}".format(", ".join(sorted(boards)))) while True: - board = input(color("bold_white", "(board): ")) + board = input(color(Fore.BOLD_WHITE, "(board): ")) try: board = vol.All(vol.Lower, vol.Any(*boards))(board) break except vol.Invalid: safe_print( - color("red", f'Sorry, I don\'t think the board "{board}" exists.') + color(Fore.RED, f'Sorry, I don\'t think the board "{board}" exists.') ) safe_print() sleep(0.25) safe_print() safe_print( - "Way to go! You've chosen {} as your board.".format(color("cyan", board)) + "Way to go! You've chosen {} as your board.".format(color(Fore.CYAN, board)) ) safe_print() sleep(1) @@ -291,20 +294,20 @@ def wizard(path): sleep(1) safe_print( "First, what's the " - + color("green", "SSID") + + color(Fore.GREEN, "SSID") + f" (the name) of the WiFi network {name} I should connect to?" ) sleep(1.5) - safe_print('For example "{}".'.format(color("bold_white", "Abraham Linksys"))) + safe_print('For example "{}".'.format(color(Fore.BOLD_WHITE, "Abraham Linksys"))) while True: - ssid = input(color("bold_white", "(ssid): ")) + ssid = input(color(Fore.BOLD_WHITE, "(ssid): ")) try: ssid = cv.ssid(ssid) break except vol.Invalid: safe_print( color( - "red", + Fore.RED, 'Unfortunately, "{}" doesn\'t seem to be a valid SSID. ' "Please try again.".format(ssid), ) @@ -314,20 +317,20 @@ def wizard(path): safe_print( 'Thank you very much! You\'ve just chosen "{}" as your SSID.' - "".format(color("cyan", ssid)) + "".format(color(Fore.CYAN, ssid)) ) safe_print() sleep(0.75) safe_print( "Now please state the " - + color("green", "password") + + color(Fore.GREEN, "password") + " of the WiFi network so that I can connect to it (Leave empty for no password)" ) safe_print() - safe_print('For example "{}"'.format(color("bold_white", "PASSWORD42"))) + safe_print('For example "{}"'.format(color(Fore.BOLD_WHITE, "PASSWORD42"))) sleep(0.5) - psk = input(color("bold_white", "(PSK): ")) + psk = input(color(Fore.BOLD_WHITE, "(PSK): ")) safe_print( "Perfect! WiFi is now set up (you can create static IPs and so on later)." ) @@ -340,12 +343,12 @@ def wizard(path): ) safe_print( "This can be insecure if you do not trust the WiFi network. Do you want to set " - "a " + color("green", "password") + " for connecting to this ESP?" + "a " + color(Fore.GREEN, "password") + " for connecting to this ESP?" ) safe_print() sleep(0.25) safe_print("Press ENTER for no password") - password = input(color("bold_white", "(password): ")) + password = input(color(Fore.BOLD_WHITE, "(password): ")) wizard_write( path=path, @@ -359,8 +362,8 @@ def wizard(path): safe_print() safe_print( - color("cyan", "DONE! I've now written a new configuration file to ") - + color("bold_cyan", path) + color(Fore.CYAN, "DONE! I've now written a new configuration file to ") + + color(Fore.BOLD_CYAN, path) ) safe_print() safe_print("Next steps:") diff --git a/requirements.txt b/requirements.txt index 9ab99e0bb4..9cb8a91f5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ voluptuous==0.12.1 PyYAML==5.4.1 paho-mqtt==1.5.1 colorama==0.4.4 -colorlog==4.7.2 tornado==6.1 protobuf==3.15.6 tzlocal==2.1 From b680649113c3e6cb20672cbd4ef35a26e53cdd54 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Thu, 8 Apr 2021 09:22:30 -0300 Subject: [PATCH 06/12] Fix servo detach chopped PWM (#1650) --- esphome/components/esp8266_pwm/esp8266_pwm.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index 96290871e0..b3fd2398f3 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -37,6 +37,11 @@ void HOT ESP8266PWM::write_state(float state) { uint32_t duty_off = total_time_us - duty_on; if (duty_on == 0) { + // This is a hacky fix for servos: Servo PWM high time is maximum 2.4ms by default + // The frequency check is to affect this fix for servos mostly as the frequency is usually 50-300 hz + if (this->pin_->digital_read() && 50 <= this->frequency_ && this->frequency_ <= 300) { + delay(3); + } stopWaveform(this->pin_->get_pin()); this->pin_->digital_write(this->pin_->is_inverted()); } else if (duty_off == 0) { From 06f566346daefd0eeecbe68019a5af1ffdb533f8 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 8 Apr 2021 14:37:55 +0200 Subject: [PATCH 07/12] Fix sensor.sensor_schema interface changed (#1659) --- esphome/components/sensor/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index a10c5d7326..c5df0ca97c 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -1,4 +1,5 @@ import math +from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv @@ -180,8 +181,12 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( ) -def sensor_schema(unit_of_measurement_, icon_, accuracy_decimals_, device_class_): - # type: (str, str, int, str) -> cv.Schema +def sensor_schema( + unit_of_measurement_: str, + icon_: str, + accuracy_decimals_: int, + device_class_: Optional[str] = DEVICE_CLASS_EMPTY, +) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement_ != UNIT_EMPTY: schema = schema.extend( From 6ec0f80b765f721ec064a0b1403b01cc4414876f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 9 Apr 2021 10:27:18 +0200 Subject: [PATCH 08/12] Sensor Average Filter Fix Floating Pointer Error Accumulating (#1624) --- esphome/components/sensor/filter.cpp | 18 +++++++++++++----- esphome/components/sensor/filter.h | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 6dfb11b9c9..57ffe9b482 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -148,10 +148,10 @@ void SlidingWindowMovingAverageFilter::set_window_size(size_t window_size) { thi optional SlidingWindowMovingAverageFilter::new_value(float value) { if (!isnan(value)) { if (this->queue_.size() == this->window_size_) { - this->sum_ -= this->queue_.front(); - this->queue_.pop(); + this->sum_ -= this->queue_[0]; + this->queue_.pop_front(); } - this->queue_.push(value); + this->queue_.push_back(value); this->sum_ += value; } float average; @@ -161,8 +161,16 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { average = this->sum_ / this->queue_.size(); ESP_LOGVV(TAG, "SlidingWindowMovingAverageFilter(%p)::new_value(%f) -> %f", this, value, average); - if (++this->send_at_ >= this->send_every_) { - this->send_at_ = 0; + if (++this->send_at_ % this->send_every_ == 0) { + if (this->send_at_ >= 10000) { + // Recalculate to prevent floating point error accumulating + this->sum_ = 0; + for (auto v : this->queue_) + this->sum_ += v; + average = this->sum_ / this->queue_.size(); + this->send_at_ = 0; + } + ESP_LOGVV(TAG, "SlidingWindowMovingAverageFilter(%p)::new_value(%f) SENDING", this, value); return average; } diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 651d2a8986..5b06d002fa 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -162,7 +162,7 @@ class SlidingWindowMovingAverageFilter : public Filter { protected: float sum_{0.0}; - std::queue queue_; + std::deque queue_; size_t send_every_; size_t send_at_; size_t window_size_; From d83d214497d42142fcf11cf42417d346dc45a967 Mon Sep 17 00:00:00 2001 From: Richard Klingler Date: Wed, 21 Apr 2021 12:10:45 +0200 Subject: [PATCH 09/12] Added / to default glyphs (#1691) Co-authored-by: Charlie Root --- esphome/components/font/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index e79d311dab..c414d37c40 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -72,7 +72,7 @@ def validate_truetype_file(value): DEFAULT_GLYPHS = ( - ' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' + ' !"%()+,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' ) CONF_RAW_DATA_ID = "raw_data_id" From be70a96651e3a67da300c74e687cc7d34e0be496 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Wed, 28 Apr 2021 19:50:24 -0300 Subject: [PATCH 10/12] RC522 fixes (#1479) --- esphome/components/rc522/rc522.cpp | 792 +++++++-------------- esphome/components/rc522/rc522.h | 109 ++- esphome/components/rc522_i2c/rc522_i2c.cpp | 30 - esphome/components/rc522_spi/rc522_spi.cpp | 19 +- 4 files changed, 330 insertions(+), 620 deletions(-) diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 8182c1e8c9..ced58760ad 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -7,22 +7,38 @@ namespace esphome { namespace rc522 { +static const uint8_t WAIT_I_RQ = 0x30; // RxIRq and IdleIRq + static const char *TAG = "rc522"; static const uint8_t RESET_COUNT = 5; -void format_uid(char *buf, const uint8_t *uid, uint8_t uid_length) { +std::string format_buffer(uint8_t *b, uint8_t len) { + char buf[32]; int offset = 0; - for (uint8_t i = 0; i < uid_length; i++) { + for (uint8_t i = 0; i < len; i++) { const char *format = "%02X"; - if (i + 1 < uid_length) + if (i + 1 < len) + format = "%02X-"; + offset += sprintf(buf + offset, format, b[i]); + } + return std::string(buf); +} + +std::string format_uid(std::vector &uid) { + char buf[32]; + int offset = 0; + for (uint8_t i = 0; i < uid.size(); i++) { + const char *format = "%02X"; + if (i + 1 < uid.size()) format = "%02X-"; offset += sprintf(buf + offset, format, uid[i]); } + return std::string(buf); } void RC522::setup() { - initialize_pending_ = true; + state_ = STATE_SETUP; // Pull device out of power down / reset state. // First set the resetPowerDownPin as digital input, to check the MFRC522 power down mode. @@ -48,7 +64,7 @@ void RC522::setup() { } void RC522::initialize_() { - // Per originall code, wait 50 ms + // Per original code, wait 50 ms if (millis() - reset_timeout_ < 50) return; @@ -75,9 +91,8 @@ void RC522::initialize_() { pcd_write_register(TX_ASK_REG, 0x40); pcd_write_register(MODE_REG, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC // command to 0x6363 (ISO 14443-3 part 6.2.4) - pcd_antenna_on_(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) - initialize_pending_ = false; + state_ = STATE_INIT; } void RC522::dump_config() { @@ -99,76 +114,163 @@ void RC522::dump_config() { } } +void RC522::update() { + if (state_ == STATE_INIT) { + pcd_antenna_on_(); + pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. + buffer_[0] = PICC_CMD_REQA; + pcd_transceive_data_(1); + state_ = STATE_PICC_REQUEST_A; + } else { + ESP_LOGW(TAG, "Communication takes longer than update interval: %d", state_); + } +} + void RC522::loop() { // First check reset is needed if (reset_count_ > 0) { pcd_reset_(); return; } - if (initialize_pending_) { + if (state_ == STATE_SETUP) { initialize_(); return; } - if (millis() - update_wait_ < this->update_interval_) - return; + StatusCode status = STATUS_ERROR; // For lint passing. TODO: refactor this + if (awaiting_comm_) { + if (state_ == STATE_SELECT_SERIAL_DONE) + status = await_crc_(); + else + status = await_transceive_(); - auto status = picc_is_new_card_present_(); - - static StatusCode LAST_STATUS = StatusCode::STATUS_OK; - - if (status != LAST_STATUS) { - ESP_LOGD(TAG, "Status is now: %d", status); - LAST_STATUS = status; - } - - if (status == STATUS_ERROR) // No card - { - // ESP_LOGE(TAG, "Error"); - // mark_failed(); - return; - } - - if (status != STATUS_OK) // We can receive STATUS_TIMEOUT when no card, or unexpected status. - return; - - // Try process card - if (!picc_read_card_serial_()) { - ESP_LOGW(TAG, "Requesting tag read failed!"); - return; - }; - - if (uid_.size < 4) { - return; - ESP_LOGW(TAG, "Read serial size: %d", uid_.size); - } - - update_wait_ = millis(); - - bool report = true; - // 1. Go through all triggers - for (auto *trigger : this->triggers_) - trigger->process(uid_.uiduint8_t, uid_.size); - - // 2. Find a binary sensor - for (auto *tag : this->binary_sensors_) { - if (tag->process(uid_.uiduint8_t, uid_.size)) { - // 2.1 if found, do not dump - report = false; + if (status == STATUS_WAITING) { + return; } + awaiting_comm_ = false; + ESP_LOGV(TAG, "finished communication status: %d, state: %d", status, state_); } - if (report) { - char buf[32]; - format_uid(buf, uid_.uiduint8_t, uid_.size); - ESP_LOGD(TAG, "Found new tag '%s'", buf); - } -} + switch (state_) { + case STATE_PICC_REQUEST_A: { + if (status == STATUS_TIMEOUT) { // no tag present + for (auto *obj : this->binary_sensors_) + obj->on_scan_end(); // reset the binary sensors + ESP_LOGV(TAG, "CMD_REQA -> TIMEOUT (no tag present) %d", status); + state_ = STATE_DONE; + } else if (status != STATUS_OK) { + ESP_LOGW(TAG, "CMD_REQA -> Not OK %d", status); + state_ = STATE_DONE; + } else if (back_length_ != 2) { // || *valid_bits_ != 0) { // ATQA must be exactly 16 bits. + ESP_LOGW(TAG, "CMD_REQA -> OK, but unexpacted back_length_ of %d", back_length_); + state_ = STATE_DONE; + } else { + state_ = STATE_READ_SERIAL; + } + if (state_ == STATE_DONE) { + // Don't wait another loop cycle + pcd_antenna_off_(); + } + break; + } + case STATE_READ_SERIAL: { + ESP_LOGV(TAG, "STATE_READ_SERIAL (%d)", status); + switch (uid_idx_) { + case 0: + buffer_[0] = PICC_CMD_SEL_CL1; + break; + case 3: + buffer_[0] = PICC_CMD_SEL_CL2; + break; + case 6: + buffer_[0] = PICC_CMD_SEL_CL3; + break; + default: + ESP_LOGE(TAG, "uid_idx_ invalid, uid_idx_ = %d", uid_idx_); + state_ = STATE_DONE; + } + buffer_[1] = 32; + pcd_transceive_data_(2); + state_ = STATE_SELECT_SERIAL; + break; + } + case STATE_SELECT_SERIAL: { + buffer_[1] = 0x70; // select + // todo: set CRC + buffer_[6] = buffer_[2] ^ buffer_[3] ^ buffer_[4] ^ buffer_[5]; + pcd_calculate_crc_(buffer_, 7); + state_ = STATE_SELECT_SERIAL_DONE; + break; + } + case STATE_SELECT_SERIAL_DONE: { + send_len_ = 6; + pcd_transceive_data_(9); + state_ = STATE_READ_SERIAL_DONE; + break; + } + case STATE_READ_SERIAL_DONE: { + if (status != STATUS_OK || back_length_ != 3) { + if (status == STATUS_TIMEOUT) + ESP_LOGV(TAG, "STATE_READ_SERIAL_DONE -> TIMEOUT (no tag present) %d", status); + else + ESP_LOGW(TAG, "Unexpected response. Read status is %d. Read bytes: %d (%s)", status, back_length_, + format_buffer(buffer_, 9).c_str()); -void RC522::update() { - for (auto *obj : this->binary_sensors_) - obj->on_scan_end(); -} + state_ = STATE_DONE; + uid_idx_ = 0; + + pcd_antenna_off_(); + return; + } + + // copy the uid + bool cascade = buffer_[2] == PICC_CMD_CT; // todo: should be determined based on select response (buffer[6]) + for (uint8_t i = 2 + cascade; i < 6; i++) + uid_buffer_[uid_idx_++] = buffer_[i]; + ESP_LOGVV(TAG, "copied uid to idx %d last byte is 0x%x, cascade is %d", uid_idx_, uid_buffer_[uid_idx_ - 1], + cascade); + + if (cascade) { // there is more bytes in the UID + state_ = STATE_READ_SERIAL; + return; + } + + std::vector rfid_uid(std::begin(uid_buffer_), std::begin(uid_buffer_) + uid_idx_); + uid_idx_ = 0; + // ESP_LOGD(TAG, "Processing '%s'", format_uid(rfid_uid).c_str()); + pcd_antenna_off_(); + state_ = STATE_INIT; // scan again on next update + bool report = true; + + for (auto *tag : this->binary_sensors_) { + if (tag->process(rfid_uid)) { + report = false; + } + } + + if (this->current_uid_ == rfid_uid) { + return; + } + + this->current_uid_ = rfid_uid; + + for (auto *trigger : this->triggers_) + trigger->process(rfid_uid); + + if (report) { + ESP_LOGD(TAG, "Found new tag '%s'", format_uid(rfid_uid).c_str()); + } + break; + } + case STATE_DONE: { + this->current_uid_ = {}; + state_ = STATE_INIT; + break; + } + default: + break; + } +} // namespace rc522 /** * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. @@ -176,14 +278,14 @@ void RC522::update() { void RC522::pcd_reset_() { // The datasheet does not mention how long the SoftRest command takes to complete. // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) - // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let - // us be generous: 50ms. + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. + // Let us be generous: 50ms. if (millis() - reset_timeout_ < 50) return; if (reset_count_ == RESET_COUNT) { - ESP_LOGV(TAG, "Soft reset..."); + ESP_LOGI(TAG, "Soft reset..."); // Issue the SoftReset command. pcd_write_register(COMMAND_REG, PCD_SOFT_RESET); } @@ -199,6 +301,7 @@ void RC522::pcd_reset_() { if (--reset_count_ == 0) { ESP_LOGE(TAG, "Unable to reset RC522."); + this->error_code_ = RESET_FAILED; mark_failed(); } } @@ -215,49 +318,13 @@ void RC522::pcd_antenna_on_() { } /** - * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or - * selection. 7 bit frame. Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - - * probably due do bad antenna design. - * - * @return STATUS_OK on success, STATUS_??? otherwise. + * Turns the antenna off by disabling pins TX1 and TX2. */ -RC522::StatusCode RC522::picc_request_a_( - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. -) { - return picc_reqa_or_wupa_(PICC_CMD_REQA, buffer_atqa, buffer_size); -} - -/** - * Transmits REQA or WUPA commands. - * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna - * design. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::picc_reqa_or_wupa_( - uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. -) { - uint8_t valid_bits; - RC522::StatusCode status; - - if (buffer_atqa == nullptr || *buffer_size < 2) { // The ATQA response is 2 uint8_ts long. - return STATUS_NO_ROOM; +void RC522::pcd_antenna_off_() { + uint8_t value = pcd_read_register(TX_CONTROL_REG); + if ((value & 0x03) != 0x00) { + pcd_write_register(TX_CONTROL_REG, value & ~0x03); } - pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. - valid_bits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) - // uint8_t. TxLastBits = BitFramingReg[2..0] - status = pcd_transceive_data_(&command, 1, buffer_atqa, buffer_size, &valid_bits); - if (status != STATUS_OK) - return status; - if (*buffer_size != 2 || valid_bits != 0) { // ATQA must be exactly 16 bits. - ESP_LOGVV(TAG, "picc_reqa_or_wupa_() -> STATUS_ERROR"); - return STATUS_ERROR; - } - - return STATUS_OK; } /** @@ -280,140 +347,86 @@ void RC522::pcd_clear_register_bit_mask_(PcdRegister reg, ///< The register to pcd_write_register(reg, tmp & (~mask)); // clear bit mask } -/** - * Executes the Transceive command. - * CRC validation can only be done if backData and backLen are specified. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::pcd_transceive_data_( - uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. - uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. - uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. - uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. - uint8_t - *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. Default nullptr. - uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be - ///< validated. -) { - uint8_t wait_i_rq = 0x30; // RxIRq and IdleIRq - auto ret = pcd_communicate_with_picc_(PCD_TRANSCEIVE, wait_i_rq, send_data, send_len, back_data, back_len, valid_bits, - rx_align, check_crc); - - if (ret == STATUS_OK && *back_len == 5) - ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ) -> %d [%x, %x, %x, %x, %x]", send_len, ret, back_data[0], - back_data[1], back_data[2], back_data[3], back_data[4]); - else - ESP_LOGVV(TAG, "pcd_transceive_data_(..., %d, ... ) -> %d", send_len, ret); - return ret; -} - /** * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. * CRC validation can only be done if backData and backLen are specified. * * @return STATUS_OK on success, STATUS_??? otherwise. */ -RC522::StatusCode RC522::pcd_communicate_with_picc_( - uint8_t command, ///< The command to execute. One of the PCD_Command enums. - uint8_t wait_i_rq, ///< The bits in the ComIrqReg register that signals successful completion of the command. - uint8_t *send_data, ///< Pointer to the data to transfer to the FIFO. - uint8_t send_len, ///< Number of uint8_ts to transfer to the FIFO. - uint8_t *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. - uint8_t *back_len, ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. - uint8_t *valid_bits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. - uint8_t rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool check_crc ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be - ///< validated. -) { - ESP_LOGVV(TAG, "pcd_communicate_with_picc_(%d, %d,... %d)", command, wait_i_rq, check_crc); - +void RC522::pcd_transceive_data_(uint8_t send_len) { + ESP_LOGV(TAG, "PCD TRANSCEIVE: RX: %s", format_buffer(buffer_, send_len).c_str()); + delayMicroseconds(1000); // we need 1 ms delay between antenna on and those communication commands + send_len_ = send_len; // Prepare values for BitFramingReg - uint8_t tx_last_bits = valid_bits ? *valid_bits : 0; - uint8_t bit_framing = - (rx_align << 4) + tx_last_bits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) + // uint8_t. TxLastBits = BitFramingReg[2..0] + uint8_t bit_framing = (buffer_[0] == PICC_CMD_REQA) ? 7 : 0; - pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command. - pcd_write_register(COM_IRQ_REG, 0x7F); // Clear all seven interrupt request bits - pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization - pcd_write_register(FIFO_DATA_REG, send_len, send_data); // Write sendData to the FIFO - pcd_write_register(BIT_FRAMING_REG, bit_framing); // Bit adjustments - pcd_write_register(COMMAND_REG, command); // Execute the command - if (command == PCD_TRANSCEIVE) { - pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80); // StartSend=1, transmission of data starts - } + pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command. + pcd_write_register(COM_IRQ_REG, 0x7F); // Clear all seven interrupt request bits + pcd_write_register(FIFO_LEVEL_REG, 0x80); // FlushBuffer = 1, FIFO initialization + pcd_write_register(FIFO_DATA_REG, send_len_, buffer_); // Write sendData to the FIFO + pcd_write_register(BIT_FRAMING_REG, bit_framing); // Bit adjustments + pcd_write_register(COMMAND_REG, PCD_TRANSCEIVE); // Execute the command + pcd_set_register_bit_mask_(BIT_FRAMING_REG, 0x80); // StartSend=1, transmission of data starts + awaiting_comm_ = true; + awaiting_comm_time_ = millis(); +} - // Wait for the command to complete. - // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops - // transmitting. Each iteration of the do-while-loop takes 17.86μs. - // TODO check/modify for other architectures than Arduino Uno 16bit - uint16_t i; - for (i = 2000; i > 0; i--) { - uint8_t n = pcd_read_register( - COM_IRQ_REG); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq - if (n & wait_i_rq) { // One of the interrupts that signal success has been set. - break; - } - if (n & 0x01) { // Timer interrupt - nothing received in 25ms - return STATUS_TIMEOUT; - } - } - // 35.7ms and nothing happend. Communication with the MFRC522 might be down. - if (i == 0) { +RC522::StatusCode RC522::await_transceive_() { + if (millis() - awaiting_comm_time_ < 2) // wait at least 2 ms + return STATUS_WAITING; + uint8_t n = pcd_read_register( + COM_IRQ_REG); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq + if (n & 0x01) { // Timer interrupt - nothing received in 25ms + back_length_ = 0; + error_counter_ = 0; // reset the error counter return STATUS_TIMEOUT; } + if (!(n & WAIT_I_RQ)) { // None of the interrupts that signal success has been set. + // Wait for the command to complete. + if (millis() - awaiting_comm_time_ < 40) + return STATUS_WAITING; + back_length_ = 0; + ESP_LOGW(TAG, "Communication with the MFRC522 might be down, reset in %d", + 10 - error_counter_); // todo: trigger reset? + if (error_counter_++ > 10) + setup(); + return STATUS_TIMEOUT; + } // Stop now if any errors except collisions were detected. uint8_t error_reg_value = pcd_read_register( ERROR_REG); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr if (error_reg_value & 0x13) { // BufferOvfl ParityErr ProtocolErr return STATUS_ERROR; } + error_counter_ = 0; // reset the error counter - uint8_t valid_bits_local = 0; - - // If the caller wants data back, get it from the MFRC522. - if (back_data && back_len) { - uint8_t n = pcd_read_register(FIFO_LEVEL_REG); // Number of uint8_ts in the FIFO - if (n > *back_len) { - return STATUS_NO_ROOM; - } - *back_len = n; // Number of uint8_ts returned - pcd_read_register(FIFO_DATA_REG, n, back_data, rx_align); // Get received data from FIFO - valid_bits_local = - pcd_read_register(CONTROL_REG) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last - // received uint8_t. If this value is 000b, the whole uint8_t is valid. - if (valid_bits) { - *valid_bits = valid_bits_local; - } - } + n = pcd_read_register(FIFO_LEVEL_REG); // Number of uint8_ts in the FIFO + if (n > sizeof(buffer_)) + return STATUS_NO_ROOM; + if (n > sizeof(buffer_) - send_len_) + send_len_ = sizeof(buffer_) - n; // simply overwrite the sent values + back_length_ = n; // Number of uint8_ts returned + pcd_read_register(FIFO_DATA_REG, n, buffer_ + send_len_, rx_align_); // Get received data from FIFO + uint8_t valid_bits_local = + pcd_read_register(CONTROL_REG) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last + // received uint8_t. If this value is 000b, the whole uint8_t is valid. // Tell about collisions if (error_reg_value & 0x08) { // CollErr + ESP_LOGW(TAG, "collision error, received %d bytes + %d bits (but anticollision not implemented)", + back_length_ - (valid_bits_local > 0), valid_bits_local); return STATUS_COLLISION; } - - // Perform CRC_A validation if requested. - if (back_data && back_len && check_crc) { - // In this case a MIFARE Classic NAK is not OK. - if (*back_len == 1 && valid_bits_local == 4) { - return STATUS_MIFARE_NACK; - } - // We need at least the CRC_A value and all 8 bits of the last uint8_t must be received. - if (*back_len < 2 || valid_bits_local != 0) { - return STATUS_CRC_WRONG; - } - // Verify CRC_A - do our own calculation and store the control in controlBuffer. - uint8_t control_buffer[2]; - RC522::StatusCode status = pcd_calculate_crc_(&back_data[0], *back_len - 2, &control_buffer[0]); - if (status != STATUS_OK) { - return status; - } - if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) { - return STATUS_CRC_WRONG; - } + // Tell about collisions + if (valid_bits_local) { + ESP_LOGW(TAG, "only %d valid bits received, tag distance to high? Error code is 0x%x", valid_bits_local, + error_reg_value); // TODO: is this always due to collissions? + return STATUS_ERROR; } + ESP_LOGV(TAG, "received %d bytes: %s", back_length_, format_buffer(buffer_ + send_len_, back_length_).c_str()); return STATUS_OK; } @@ -424,10 +437,8 @@ RC522::StatusCode RC522::pcd_communicate_with_picc_( * @return STATUS_OK on success, STATUS_??? otherwise. */ -RC522::StatusCode RC522::pcd_calculate_crc_( - uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. - uint8_t length, ///< In: The number of uint8_ts to transfer. - uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. +void RC522::pcd_calculate_crc_(uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. + uint8_t length ///< In: The number of uint8_ts to transfer. ) { ESP_LOGVV(TAG, "pcd_calculate_crc_(..., %d, ...)", length); pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop any active command. @@ -436,323 +447,50 @@ RC522::StatusCode RC522::pcd_calculate_crc_( pcd_write_register(FIFO_DATA_REG, length, data); // Write data to the FIFO pcd_write_register(COMMAND_REG, PCD_CALC_CRC); // Start the calculation - // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs. - // TODO check/modify for other architectures than Arduino Uno 16bit + awaiting_comm_ = true; + awaiting_comm_time_ = millis(); +} - // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. - for (uint16_t i = 5000; i > 0; i--) { - // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved - uint8_t n = pcd_read_register(DIV_IRQ_REG); - if (n & 0x04) { // CRCIRq bit set - calculation done - pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop calculating CRC for new content in the FIFO. - // Transfer the result from the registers to the result buffer - result[0] = pcd_read_register(CRC_RESULT_REG_L); - result[1] = pcd_read_register(CRC_RESULT_REG_H); +RC522::StatusCode RC522::await_crc_() { + if (millis() - awaiting_comm_time_ < 2) // wait at least 2 ms + return STATUS_WAITING; - ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK"); - return STATUS_OK; - } + // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved + uint8_t n = pcd_read_register(DIV_IRQ_REG); + if (n & 0x04) { // CRCIRq bit set - calculation done + pcd_write_register(COMMAND_REG, PCD_IDLE); // Stop calculating CRC for new content in the FIFO. + // Transfer the result from the registers to the result buffer + buffer_[7] = pcd_read_register(CRC_RESULT_REG_L); + buffer_[8] = pcd_read_register(CRC_RESULT_REG_H); + + ESP_LOGVV(TAG, "pcd_calculate_crc_() STATUS_OK"); + return STATUS_OK; } - ESP_LOGVV(TAG, "pcd_calculate_crc_() TIMEOUT"); + if (millis() - awaiting_comm_time_ < 89) + return STATUS_WAITING; + + ESP_LOGD(TAG, "pcd_calculate_crc_() TIMEOUT"); // 89ms passed and nothing happend. Communication with the MFRC522 might be down. return STATUS_TIMEOUT; } -/** - * Returns STATUS_OK if a PICC responds to PICC_CMD_REQA. - * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::picc_is_new_card_present_() { - uint8_t buffer_atqa[2]; - uint8_t buffer_size = sizeof(buffer_atqa); - - // Reset baud rates - pcd_write_register(TX_MODE_REG, 0x00); - pcd_write_register(RX_MODE_REG, 0x00); - // Reset ModWidthReg - pcd_write_register(MOD_WIDTH_REG, 0x26); - - auto result = picc_request_a_(buffer_atqa, &buffer_size); - - ESP_LOGV(TAG, "picc_is_new_card_present_() -> %d", result); +bool RC522BinarySensor::process(std::vector &data) { + bool result = true; + if (data.size() != this->uid_.size()) + result = false; + else { + for (uint8_t i = 0; i < data.size(); i++) { + if (data[i] != this->uid_[i]) { + result = false; + break; + } + } + } + this->publish_state(result); + this->found_ = result; return result; } - -/** - * Simple wrapper around PICC_Select. - * Returns true if a UID could be read. - * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first. - * The read UID is available in the class variable uid. - * - * @return bool - */ -bool RC522::picc_read_card_serial_() { - RC522::StatusCode result = picc_select_(&this->uid_); - ESP_LOGVV(TAG, "picc_select_(...) -> %d", result); - return (result == STATUS_OK); -} - -/** - * Transmits SELECT/ANTICOLLISION commands to select a single PICC. - * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or - * PICC_WakeupA(). On success: - * - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the - * ISO/IEC 14443-3 draft.) - * - The UID size and value of the chosen PICC is returned in *uid along with the SAK. - * - * A PICC UID consists of 4, 7 or 10 uint8_ts. - * Only 4 uint8_ts can be specified in a SELECT command, so for the longer UIDs two or three iterations are used: - * UID size Number of UID uint8_ts Cascade levels Example of PICC - * ======== =================== ============== =============== - * single 4 1 MIFARE Classic - * double 7 2 MIFARE Ultralight - * triple 10 3 Not currently in use? - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -RC522::StatusCode RC522::picc_select_( - Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. - uint8_t valid_bits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply - ///< uid->size. -) { - bool uid_complete; - bool select_done; - bool use_cascade_tag; - uint8_t cascade_level = 1; - RC522::StatusCode result; - uint8_t count; - uint8_t check_bit; - uint8_t index; - uint8_t uid_index; // The first index in uid->uiduint8_t[] that is used in the current Cascade Level. - int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level. - uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 uint8_t standard frame + 2 uint8_ts CRC_A - uint8_t buffer_used; // The number of uint8_ts used in the buffer, ie the number of uint8_ts to transfer to the FIFO. - uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received. - uint8_t tx_last_bits; // Used in BitFramingReg. The number of valid bits in the last transmitted uint8_t. - uint8_t *response_buffer; - uint8_t response_length; - - // Description of buffer structure: - // uint8_t 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 - // uint8_t 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete - // uint8_ts, - // Low nibble: Extra bits. uint8_t 2: UID-data or CT See explanation below. CT means Cascade Tag. uint8_t - // 3: UID-data uint8_t 4: UID-data uint8_t 5: UID-data uint8_t 6: BCC Block Check Character - XOR of - // uint8_ts 2-5 uint8_t 7: CRC_A uint8_t 8: CRC_A The BCC and CRC_A are only transmitted if we know all the UID bits - // of the current Cascade Level. - // - // Description of uint8_ts 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) - // UID size Cascade level uint8_t2 uint8_t3 uint8_t4 uint8_t5 - // ======== ============= ===== ===== ===== ===== - // 4 uint8_ts 1 uid0 uid1 uid2 uid3 - // 7 uint8_ts 1 CT uid0 uid1 uid2 - // 2 uid3 uid4 uid5 uid6 - // 10 uint8_ts 1 CT uid0 uid1 uid2 - // 2 CT uid3 uid4 uid5 - // 3 uid6 uid7 uid8 uid9 - - // Sanity checks - if (valid_bits > 80) { - return STATUS_INVALID; - } - - ESP_LOGVV(TAG, "picc_select_(&, %d)", valid_bits); - - // Prepare MFRC522 - pcd_clear_register_bit_mask_(COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. - - // Repeat Cascade Level loop until we have a complete UID. - uid_complete = false; - while (!uid_complete) { - // Set the Cascade Level in the SEL uint8_t, find out if we need to use the Cascade Tag in uint8_t 2. - switch (cascade_level) { - case 1: - buffer[0] = PICC_CMD_SEL_CL1; - uid_index = 0; - use_cascade_tag = valid_bits && uid->size > 4; // When we know that the UID has more than 4 uint8_ts - break; - - case 2: - buffer[0] = PICC_CMD_SEL_CL2; - uid_index = 3; - use_cascade_tag = valid_bits && uid->size > 7; // When we know that the UID has more than 7 uint8_ts - break; - - case 3: - buffer[0] = PICC_CMD_SEL_CL3; - uid_index = 6; - use_cascade_tag = false; // Never used in CL3. - break; - - default: - return STATUS_INTERNAL_ERROR; - break; - } - - // How many UID bits are known in this Cascade Level? - current_level_known_bits = valid_bits - (8 * uid_index); - if (current_level_known_bits < 0) { - current_level_known_bits = 0; - } - // Copy the known bits from uid->uiduint8_t[] to buffer[] - index = 2; // destination index in buffer[] - if (use_cascade_tag) { - buffer[index++] = PICC_CMD_CT; - } - uint8_t uint8_ts_to_copy = current_level_known_bits / 8 + - (current_level_known_bits % 8 - ? 1 - : 0); // The number of uint8_ts needed to represent the known bits for this level. - if (uint8_ts_to_copy) { - uint8_t maxuint8_ts = - use_cascade_tag ? 3 : 4; // Max 4 uint8_ts in each Cascade Level. Only 3 left if we use the Cascade Tag - if (uint8_ts_to_copy > maxuint8_ts) { - uint8_ts_to_copy = maxuint8_ts; - } - for (count = 0; count < uint8_ts_to_copy; count++) { - buffer[index++] = uid->uiduint8_t[uid_index + count]; - } - } - // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits - if (use_cascade_tag) { - current_level_known_bits += 8; - } - - // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. - select_done = false; - while (!select_done) { - // Find out how many bits and uint8_ts to send and receive. - if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT. - - if (response_length < 4) { - ESP_LOGW(TAG, "Not enough data received."); - return STATUS_INVALID; - } - - // Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); - buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole uint8_ts - // Calculate BCC - Block Check Character - buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; - // Calculate CRC_A - result = pcd_calculate_crc_(buffer, 7, &buffer[7]); - if (result != STATUS_OK) { - return result; - } - tx_last_bits = 0; // 0 => All 8 bits are valid. - buffer_used = 9; - // Store response in the last 3 uint8_ts of buffer (BCC and CRC_A - not needed after tx) - response_buffer = &buffer[6]; - response_length = 3; - } else { // This is an ANTICOLLISION. - // Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); - tx_last_bits = current_level_known_bits % 8; - count = current_level_known_bits / 8; // Number of whole uint8_ts in the UID part. - index = 2 + count; // Number of whole uint8_ts: SEL + NVB + UIDs - buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits - buffer_used = index + (tx_last_bits ? 1 : 0); - // Store response in the unused part of buffer - response_buffer = &buffer[index]; - response_length = sizeof(buffer) - index; - } - - // Set bit adjustments - rx_align = tx_last_bits; // Having a separate variable is overkill. But it makes the next line easier to read. - pcd_write_register( - BIT_FRAMING_REG, - (rx_align << 4) + tx_last_bits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] - - // Transmit the buffer and receive the response. - result = pcd_transceive_data_(buffer, buffer_used, response_buffer, &response_length, &tx_last_bits, rx_align); - if (result == STATUS_COLLISION) { // More than one PICC in the field => collision. - uint8_t value_of_coll_reg = pcd_read_register( - COLL_REG); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] - if (value_of_coll_reg & 0x20) { // CollPosNotValid - return STATUS_COLLISION; // Without a valid collision position we cannot continue - } - uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32. - if (collision_pos == 0) { - collision_pos = 32; - } - if (collision_pos <= current_level_known_bits) { // No progress - should not happen - return STATUS_INTERNAL_ERROR; - } - // Choose the PICC with the bit set. - current_level_known_bits = collision_pos; - count = current_level_known_bits % 8; // The bit to modify - check_bit = (current_level_known_bits - 1) % 8; - index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First uint8_t is index 0. - if (response_length > 2) // Note: Otherwise buffer[index] might be not initialized - buffer[index] |= (1 << check_bit); - } else if (result != STATUS_OK) { - return result; - } else { // STATUS_OK - if (current_level_known_bits >= 32) { // This was a SELECT. - select_done = true; // No more anticollision - // We continue below outside the while. - } else { // This was an ANTICOLLISION. - // We now have all 32 bits of the UID in this Cascade Level - current_level_known_bits = 32; - // Run loop again to do the SELECT. - } - } - } // End of while (!selectDone) - - // We do not check the CBB - it was constructed by us above. - - // Copy the found UID uint8_ts from buffer[] to uid->uiduint8_t[] - index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] - uint8_ts_to_copy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; - for (count = 0; count < uint8_ts_to_copy; count++) { - uid->uiduint8_t[uid_index + count] = buffer[index++]; - } - - // Check response SAK (Select Acknowledge) - if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 uint8_t + CRC_A). - return STATUS_ERROR; - } - // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those uint8_ts are not needed - // anymore. - result = pcd_calculate_crc_(response_buffer, 1, &buffer[2]); - if (result != STATUS_OK) { - return result; - } - if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) { - return STATUS_CRC_WRONG; - } - if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes - cascade_level++; - } else { - uid_complete = true; - uid->sak = response_buffer[0]; - } - } // End of while (!uidComplete) - - // Set correct uid->size - uid->size = 3 * cascade_level + 1; - - return STATUS_OK; -} - -bool RC522BinarySensor::process(const uint8_t *data, uint8_t len) { - if (len != this->uid_.size()) - return false; - - for (uint8_t i = 0; i < len; i++) { - if (data[i] != this->uid_[i]) - return false; - } - - this->publish_state(true); - this->found_ = true; - return true; -} -void RC522Trigger::process(const uint8_t *uid, uint8_t uid_length) { - char buf[32]; - format_uid(buf, uid, uid_length); - this->trigger(std::string(buf)); -} +void RC522Trigger::process(std::vector &data) { this->trigger(format_uid(data)); } } // namespace rc522 } // namespace esphome diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h index cabcf8db0b..7fb49e97fd 100644 --- a/esphome/components/rc522/rc522.h +++ b/esphome/components/rc522/rc522.h @@ -26,6 +26,33 @@ class RC522 : public PollingComponent { void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } protected: + // Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. + // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered + enum StatusCode : uint8_t { + STATUS_OK, // Success + STATUS_WAITING, // Waiting result from RC522 chip + STATUS_ERROR, // Error in communication + STATUS_COLLISION, // Collission detected + STATUS_TIMEOUT, // Timeout in communication. + STATUS_NO_ROOM, // A buffer is not big enough. + STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-) + STATUS_INVALID, // Invalid argument. + STATUS_CRC_WRONG, // The CRC_A does not match + STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK. + }; + + enum State { + STATE_NONE = 0, + STATE_SETUP, + STATE_INIT, + STATE_PICC_REQUEST_A, + STATE_READ_SERIAL, + STATE_SELECT_SERIAL, + STATE_SELECT_SERIAL_DONE, + STATE_READ_SERIAL_DONE, + STATE_DONE, + } state_{STATE_NONE}; + enum PcdRegister : uint8_t { // Page 0: Command and status // 0x00 // reserved for future use @@ -150,33 +177,11 @@ class RC522 : public PollingComponent { PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 uint8_t page to the PICC. }; - // Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. - // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered - enum StatusCode : uint8_t { - STATUS_OK, // Success - STATUS_ERROR, // Error in communication - STATUS_COLLISION, // Collission detected - STATUS_TIMEOUT, // Timeout in communication. - STATUS_NO_ROOM, // A buffer is not big enough. - STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-) - STATUS_INVALID, // Invalid argument. - STATUS_CRC_WRONG, // The CRC_A does not match - STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK. - }; - - // A struct used for passing the UID of a PICC. - using Uid = struct { - uint8_t size; // Number of uint8_ts in the UID. 4, 7 or 10. - uint8_t uiduint8_t[10]; - uint8_t sak; // The SAK (Select acknowledge) uint8_t returned from the PICC after successful selection. - }; - - Uid uid_; - uint32_t update_wait_{0}; - void pcd_reset_(); void initialize_(); void pcd_antenna_on_(); + void pcd_antenna_off_(); + virtual uint8_t pcd_read_register(PcdRegister reg ///< The register to read from. One of the PCD_Register enums. ) = 0; @@ -202,15 +207,6 @@ class RC522 : public PollingComponent { uint8_t *values ///< The values to write. uint8_t array. ) = 0; - StatusCode picc_request_a_( - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. - ); - StatusCode picc_reqa_or_wupa_( - uint8_t command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA - uint8_t *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in - uint8_t *buffer_size ///< Buffer size, at least two uint8_ts. Also number of uint8_ts returned if STATUS_OK. - ); void pcd_set_register_bit_mask_(PcdRegister reg, ///< The register to update. One of the PCD_Register enums. uint8_t mask ///< The bits to set. ); @@ -218,38 +214,33 @@ class RC522 : public PollingComponent { uint8_t mask ///< The bits to clear. ); - StatusCode pcd_transceive_data_(uint8_t *send_data, uint8_t send_len, uint8_t *back_data, uint8_t *back_len, - uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); - StatusCode pcd_communicate_with_picc_(uint8_t command, uint8_t wait_i_rq, uint8_t *send_data, uint8_t send_len, - uint8_t *back_data = nullptr, uint8_t *back_len = nullptr, - uint8_t *valid_bits = nullptr, uint8_t rx_align = 0, bool check_crc = false); - StatusCode pcd_calculate_crc_( - uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. - uint8_t length, ///< In: The number of uint8_ts to transfer. - uint8_t *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low uint8_t first. - ); - RC522::StatusCode picc_is_new_card_present_(); - bool picc_read_card_serial_(); - StatusCode picc_select_( - Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. - uint8_t valid_bits = 0 ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also - ///< supply uid->size. + void pcd_transceive_data_(uint8_t send_len); + + void pcd_calculate_crc_(uint8_t *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. + uint8_t length ///< In: The number of uint8_ts to transfer. ); - /** Read a data frame from the RC522 and return the result as a vector. - * - * Note that is_ready needs to be checked first before requesting this method. - * - * On failure, an empty vector is returned. - */ - std::vector r_c522_read_data_(); + bool awaiting_comm_; + uint32_t awaiting_comm_time_; + StatusCode await_transceive_(); + StatusCode await_crc_(); + + uint8_t buffer_[9]; ///< buffer for communication, the first bits [0..back_idx-1] are for tx , + ///< [back_idx..back_idx+back_len] for rx + uint8_t send_len_; // index of first byte for RX + uint8_t back_length_; ///< In: Max number of uint8_ts to write to *backData. Out: The number of uint8_ts returned. + uint8_t uid_buffer_[10]; // buffer to construct the uid (for 7 and 10 bit uids) + uint8_t uid_idx_ = 0; // number of read uid bytes e.g. index of the next available position in uid_buffer + uint8_t error_counter_ = 0; // to reset if unresponsive + uint8_t rx_align_; + uint8_t *valid_bits_; GPIOPin *reset_pin_{nullptr}; uint8_t reset_count_{0}; uint32_t reset_timeout_{0}; - bool initialize_pending_{false}; std::vector binary_sensors_; std::vector triggers_; + std::vector current_uid_; enum RC522Error { NONE = 0, @@ -261,7 +252,7 @@ class RC522BinarySensor : public binary_sensor::BinarySensor { public: void set_uid(const std::vector &uid) { uid_ = uid; } - bool process(const uint8_t *data, uint8_t len); + bool process(std::vector &data); void on_scan_end() { if (!this->found_) { @@ -277,7 +268,7 @@ class RC522BinarySensor : public binary_sensor::BinarySensor { class RC522Trigger : public Trigger { public: - void process(const uint8_t *uid, uint8_t uid_length); + void process(std::vector &data); }; } // namespace rc522 diff --git a/esphome/components/rc522_i2c/rc522_i2c.cpp b/esphome/components/rc522_i2c/rc522_i2c.cpp index 8248e79b50..fe88f567c0 100644 --- a/esphome/components/rc522_i2c/rc522_i2c.cpp +++ b/esphome/components/rc522_i2c/rc522_i2c.cpp @@ -36,10 +36,6 @@ void RC522I2C::pcd_read_register(PcdRegister reg, ///< The register to read fro return; } - std::string buf; - buf = "Rx"; - char cstrb[20]; - uint8_t b = values[0]; read_bytes(reg >> 1, values, count); @@ -69,31 +65,5 @@ void RC522I2C::pcd_write_register(PcdRegister reg, ///< The register to write t write_bytes(reg >> 1, values, count); } -// bool RC522I2C::write_data(const std::vector &data) { -// return this->write_bytes_raw(data.data(), data.size()); } - -// bool RC522I2C::read_data(std::vector &data, uint8_t len) { -// delay(5); - -// std::vector ready; -// ready.resize(1); -// uint32_t start_time = millis(); -// while (true) { -// if (this->read_bytes_raw(ready.data(), 1)) { -// if (ready[0] == 0x01) -// break; -// } - -// if (millis() - start_time > 100) { -// ESP_LOGV(TAG, "Timed out waiting for readiness from RC522!"); -// return false; -// } -// } - -// data.resize(len + 1); -// this->read_bytes_raw(data.data(), len + 1); -// return true; -// } - } // namespace rc522_i2c } // namespace esphome diff --git a/esphome/components/rc522_spi/rc522_spi.cpp b/esphome/components/rc522_spi/rc522_spi.cpp index 61236393e4..1865b36da6 100644 --- a/esphome/components/rc522_spi/rc522_spi.cpp +++ b/esphome/components/rc522_spi/rc522_spi.cpp @@ -32,7 +32,7 @@ uint8_t RC522Spi::pcd_read_register(PcdRegister reg ///< The register to read f transfer_byte(0x80 | reg); value = read_byte(); disable(); - ESP_LOGV(TAG, "read_register_(%x) -> %x", reg, value); + ESP_LOGVV(TAG, "read_register_(%d) -> %d", reg, value); return value; } @@ -45,9 +45,11 @@ void RC522Spi::pcd_read_register(PcdRegister reg, ///< The register to read fro uint8_t *values, ///< uint8_t array to store the values in. uint8_t rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. ) { +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE std::string buf; buf = "Rx"; char cstrb[20]; +#endif if (count == 0) { return; } @@ -68,25 +70,30 @@ void RC522Spi::pcd_read_register(PcdRegister reg, ///< The register to read fro values[0] = (values[0] & ~mask) | (value & mask); index++; +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE sprintf(cstrb, " %x", values[0]); buf.append(cstrb); +#endif } while (index < count) { values[index] = transfer_byte(address); // Read value and tell that we want to read the same address again. +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE sprintf(cstrb, " %x", values[index]); buf.append(cstrb); +#endif index++; } values[index] = transfer_byte(0); // Read the final uint8_t. Send 0 to stop reading. +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE buf = buf + " "; sprintf(cstrb, "%x", values[index]); buf.append(cstrb); ESP_LOGVV(TAG, "read_register_array_(%x, %d, , %d) -> %s", reg, count, rx_align, buf.c_str()); - +#endif disable(); } @@ -108,21 +115,25 @@ void RC522Spi::pcd_write_register(PcdRegister reg, ///< The register to write t uint8_t count, ///< The number of uint8_ts to write to the register uint8_t *values ///< The values to write. uint8_t array. ) { +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE std::string buf; buf = "Tx"; + char cstrb[20]; +#endif enable(); transfer_byte(reg); - char cstrb[20]; for (uint8_t index = 0; index < count; index++) { transfer_byte(values[index]); +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE sprintf(cstrb, " %x", values[index]); buf.append(cstrb); +#endif } disable(); - ESP_LOGVV(TAG, "write_register_(%x, %d) -> %s", reg, count, buf.c_str()); + ESP_LOGVV(TAG, "write_register_(%d, %d) -> %s", reg, count, buf.c_str()); } } // namespace rc522_spi From 37bc0b3b5a3020b2aa4b6beaa2f722f340c46115 Mon Sep 17 00:00:00 2001 From: Alex <33379584+alexyao2015@users.noreply.github.com> Date: Tue, 4 May 2021 21:31:38 -0500 Subject: [PATCH 11/12] Do not call component update on failed components (#1392) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/core/base_automation.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index d2656290bc..fa49786d1d 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -266,7 +266,11 @@ template class UpdateComponentAction : public Action { public: UpdateComponentAction(PollingComponent *component) : component_(component) {} - void play(Ts... x) override { this->component_->update(); } + void play(Ts... x) override { + if (this->component_->is_failed()) + return; + this->component_->update(); + } protected: PollingComponent *component_; From 13dbdd9b16ea081db7239cfdd094f7a614cf6dce Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 5 May 2021 22:35:18 +1200 Subject: [PATCH 12/12] Bump version to v1.17.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f8b7da0664..237a356c74 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 17 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}"