diff --git a/CODEOWNERS b/CODEOWNERS index b04b480780..4ac394345c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -254,6 +254,7 @@ esphome/components/wake_on_lan/* @willwill2will54 esphome/components/web_server_base/* @OttoWinter esphome/components/whirlpool/* @glmnet esphome/components/whynter/* @aeonsablaze +esphome/components/wl_134/* @hobbypunk90 esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs diff --git a/esphome/components/wl_134/__init__.py b/esphome/components/wl_134/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/wl_134/text_sensor.py b/esphome/components/wl_134/text_sensor.py new file mode 100644 index 0000000000..1373df77f4 --- /dev/null +++ b/esphome/components/wl_134/text_sensor.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor, uart +from esphome.const import ( + ICON_FINGERPRINT, +) + +CODEOWNERS = ["@hobbypunk90"] +DEPENDENCIES = ["uart"] +CONF_RESET = "reset" + +wl134_ns = cg.esphome_ns.namespace("wl_134") +Wl134Component = wl134_ns.class_( + "Wl134Component", text_sensor.TextSensor, cg.Component, uart.UARTDevice +) + +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema( + Wl134Component, + icon=ICON_FINGERPRINT, + ) + .extend({cv.Optional(CONF_RESET, default=False): cv.boolean}) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +async def to_code(config): + var = await text_sensor.new_text_sensor(config) + await cg.register_component(var, config) + cg.add(var.set_do_reset(config[CONF_RESET])) + await uart.register_uart_device(var, config) diff --git a/esphome/components/wl_134/wl_134.cpp b/esphome/components/wl_134/wl_134.cpp new file mode 100644 index 0000000000..3ffa0c63ce --- /dev/null +++ b/esphome/components/wl_134/wl_134.cpp @@ -0,0 +1,111 @@ +#include "wl_134.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace wl_134 { + +static const char *const TAG = "wl_134.sensor"; +static const uint8_t ASCII_CR = 0x0D; +static const uint8_t ASCII_NBSP = 0xFF; +static const int MAX_DATA_LENGTH_BYTES = 6; + +void Wl134Component::setup() { this->publish_state(""); } + +void Wl134Component::loop() { + while (this->available() >= RFID134_PACKET_SIZE) { + Wl134Component::Rfid134Error error = this->read_packet_(); + if (error != RFID134_ERROR_NONE) { + ESP_LOGW(TAG, "Error: %d", error); + } + } +} + +Wl134Component::Rfid134Error Wl134Component::read_packet_() { + uint8_t packet[RFID134_PACKET_SIZE]; + packet[RFID134_PACKET_START_CODE] = this->read(); + + // check for the first byte being the packet start code + if (packet[RFID134_PACKET_START_CODE] != 0x02) { + // just out of sync, ignore until we are synced up + return RFID134_ERROR_NONE; + } + + if (!this->read_array(&(packet[RFID134_PACKET_ID]), RFID134_PACKET_SIZE - 1)) { + return RFID134_ERROR_PACKET_SIZE; + } + + if (packet[RFID134_PACKET_END_CODE] != 0x03) { + return RFID134_ERROR_PACKET_END_CODE_MISSMATCH; + } + + // calculate checksum + uint8_t checksum = 0; + for (uint8_t i = RFID134_PACKET_ID; i < RFID134_PACKET_CHECKSUM; i++) { + checksum = checksum ^ packet[i]; + } + + // test checksum + if (checksum != packet[RFID134_PACKET_CHECKSUM]) { + return RFID134_ERROR_PACKET_CHECKSUM; + } + + if (static_cast(~checksum) != static_cast(packet[RFID134_PACKET_CHECKSUM_INVERT])) { + return RFID134_ERROR_PACKET_CHECKSUM_INVERT; + } + + Rfid134Reading reading; + + // convert packet into the reading struct + reading.id = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_ID]), RFID134_PACKET_COUNTRY - RFID134_PACKET_ID); + reading.country = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_COUNTRY]), + RFID134_PACKET_DATA_FLAG - RFID134_PACKET_COUNTRY); + reading.isData = packet[RFID134_PACKET_DATA_FLAG] == '1'; + reading.isAnimal = packet[RFID134_PACKET_ANIMAL_FLAG] == '1'; + reading.reserved0 = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_RESERVED0]), + RFID134_PACKET_RESERVED1 - RFID134_PACKET_RESERVED0); + reading.reserved1 = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_RESERVED1]), + RFID134_PACKET_CHECKSUM - RFID134_PACKET_RESERVED1); + + ESP_LOGV(TAG, "Tag id: %012lld", reading.id); + ESP_LOGV(TAG, "Country: %03d", reading.country); + ESP_LOGV(TAG, "isData: %s", reading.isData ? "true" : "false"); + ESP_LOGV(TAG, "isAnimal: %s", reading.isAnimal ? "true" : "false"); + ESP_LOGV(TAG, "Reserved0: %d", reading.reserved0); + ESP_LOGV(TAG, "Reserved1: %d", reading.reserved1); + + char buf[20]; + sprintf(buf, "%03d%012lld", reading.country, reading.id); + this->publish_state(buf); + if (this->do_reset_) { + this->set_timeout(1000, [this]() { this->publish_state(""); }); + } + + return RFID134_ERROR_NONE; +} + +uint64_t Wl134Component::hex_lsb_ascii_to_uint64_(const uint8_t *text, uint8_t text_size) { + uint64_t value = 0; + uint8_t i = text_size; + do { + i--; + + uint8_t digit = text[i]; + if (digit >= 'A') { + digit = digit - 'A' + 10; + } else { + digit = digit - '0'; + } + value = (value << 4) + digit; + } while (i != 0); + + return value; +} + +void Wl134Component::dump_config() { + ESP_LOGCONFIG(TAG, "WL-134 Sensor:"); + LOG_TEXT_SENSOR("", "Tag", this); + // As specified in the sensor's data sheet + this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); +} +} // namespace wl_134 +} // namespace esphome diff --git a/esphome/components/wl_134/wl_134.h b/esphome/components/wl_134/wl_134.h new file mode 100644 index 0000000000..c0a90de17d --- /dev/null +++ b/esphome/components/wl_134/wl_134.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace wl_134 { + +class Wl134Component : public text_sensor::TextSensor, public Component, public uart::UARTDevice { + public: + enum Rfid134Error { + RFID134_ERROR_NONE, + + // from library + RFID134_ERROR_PACKET_SIZE = 0x81, + RFID134_ERROR_PACKET_END_CODE_MISSMATCH, + RFID134_ERROR_PACKET_CHECKSUM, + RFID134_ERROR_PACKET_CHECKSUM_INVERT + }; + + struct Rfid134Reading { + uint16_t country; + uint64_t id; + bool isData; + bool isAnimal; + uint16_t reserved0; + uint32_t reserved1; + }; + // Nothing really public. + + // ========== INTERNAL METHODS ========== + void setup() override; + void loop() override; + void dump_config() override; + + void set_do_reset(bool do_reset) { this->do_reset_ = do_reset; } + + private: + enum DfMp3Packet { + RFID134_PACKET_START_CODE, + RFID134_PACKET_ID = 1, + RFID134_PACKET_COUNTRY = 11, + RFID134_PACKET_DATA_FLAG = 15, + RFID134_PACKET_ANIMAL_FLAG = 16, + RFID134_PACKET_RESERVED0 = 17, + RFID134_PACKET_RESERVED1 = 21, + RFID134_PACKET_CHECKSUM = 27, + RFID134_PACKET_CHECKSUM_INVERT = 28, + RFID134_PACKET_END_CODE = 29, + RFID134_PACKET_SIZE + }; + + bool do_reset_; + + Rfid134Error read_packet_(); + uint64_t hex_lsb_ascii_to_uint64_(const uint8_t *text, uint8_t text_size); +}; + +} // namespace wl_134 +} // namespace esphome