diff --git a/CODEOWNERS b/CODEOWNERS index 384db8098f..498cfcac01 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -150,6 +150,7 @@ esphome/components/ledc/* @OttoWinter esphome/components/libretiny/* @kuba2k2 esphome/components/libretiny_pwm/* @kuba2k2 esphome/components/light/* @esphome/core +esphome/components/lightwaverf/* @max246 esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core diff --git a/esphome/components/lightwaverf/LwRx.cpp b/esphome/components/lightwaverf/LwRx.cpp new file mode 100644 index 0000000000..2b1ad5e870 --- /dev/null +++ b/esphome/components/lightwaverf/LwRx.cpp @@ -0,0 +1,427 @@ +// LwRx.cpp +// +// LightwaveRF 434MHz receiver interface for Arduino +// +// Author: Bob Tidey (robert@tideys.net) + +#ifdef USE_ESP8266 + +#include "LwRx.h" +#include + +namespace esphome { +namespace lightwaverf { + +/** + Pin change interrupt routine that identifies 1 and 0 LightwaveRF bits + and constructs a message when a valid packet of data is received. +**/ + +void IRAM_ATTR LwRx::rx_process_bits(LwRx *args) { + uint8_t event = args->rx_pin_isr_.digital_read(); // start setting event to the current value + uint32_t curr = micros(); // the current time in microseconds + + uint16_t dur = (curr - args->rx_prev); // unsigned int + args->rx_prev = curr; + // set event based on input and duration of previous pulse + if (dur < 120) { // 120 very short + } else if (dur < 500) { // normal short pulse + event += 2; + } else if (dur < 2000) { // normal long pulse + event += 4; + } else if (dur > 5000) { // gap between messages + event += 6; + } else { // 2000 > 5000 + event = 8; // illegal gap + } + // state machine transitions + switch (args->rx_state) { + case RX_STATE_IDLE: + switch (event) { + case 7: // 1 after a message gap + args->rx_state = RX_STATE_MSGSTARTFOUND; + break; + } + break; + case RX_STATE_MSGSTARTFOUND: + switch (event) { + case 2: // 0 160->500 + // nothing to do wait for next positive edge + break; + case 3: // 1 160->500 + args->rx_num_bytes = 0; + args->rx_state = RX_STATE_BYTESTARTFOUND; + break; + default: + // not good start again + args->rx_state = RX_STATE_IDLE; + break; + } + break; + case RX_STATE_BYTESTARTFOUND: + switch (event) { + case 2: // 0 160->500 + // nothing to do wait for next positive edge + break; + case 3: // 1 160->500 + args->rx_state = RX_STATE_GETBYTE; + args->rx_num_bits = 0; + break; + case 5: // 0 500->1500 + args->rx_state = RX_STATE_GETBYTE; + // Starts with 0 so put this into uint8_t + args->rx_num_bits = 1; + args->rx_buf[args->rx_num_bytes] = 0; + break; + default: + // not good start again + args->rx_state = RX_STATE_IDLE; + break; + } + break; + case RX_STATE_GETBYTE: + switch (event) { + case 2: // 0 160->500 + // nothing to do wait for next positive edge but do stats + if (args->lwrx_stats_enable) { + args->lwrx_stats[RX_STAT_HIGH_MAX] = std::max(args->lwrx_stats[RX_STAT_HIGH_MAX], dur); + args->lwrx_stats[RX_STAT_HIGH_MIN] = std::min(args->lwrx_stats[RX_STAT_HIGH_MIN], dur); + args->lwrx_stats[RX_STAT_HIGH_AVE] = + args->lwrx_stats[RX_STAT_HIGH_AVE] - (args->lwrx_stats[RX_STAT_HIGH_AVE] >> 4) + dur; + } + break; + case 3: // 1 160->500 + // a single 1 + args->rx_buf[args->rx_num_bytes] = args->rx_buf[args->rx_num_bytes] << 1 | 1; + args->rx_num_bits++; + if (args->lwrx_stats_enable) { + args->lwrx_stats[RX_STAT_LOW1_MAX] = std::max(args->lwrx_stats[RX_STAT_LOW1_MAX], dur); + args->lwrx_stats[RX_STAT_LOW1_MIN] = std::min(args->lwrx_stats[RX_STAT_LOW1_MIN], dur); + args->lwrx_stats[RX_STAT_LOW1_AVE] = + args->lwrx_stats[RX_STAT_LOW1_AVE] - (args->lwrx_stats[RX_STAT_LOW1_AVE] >> 4) + dur; + } + break; + case 5: // 1 500->1500 + // a 1 followed by a 0 + args->rx_buf[args->rx_num_bytes] = args->rx_buf[args->rx_num_bytes] << 2 | 2; + args->rx_num_bits++; + args->rx_num_bits++; + if (args->lwrx_stats_enable) { + args->lwrx_stats[RX_STAT_LOW0_MAX] = std::max(args->lwrx_stats[RX_STAT_LOW0_MAX], dur); + args->lwrx_stats[RX_STAT_LOW0_MIN] = std::min(args->lwrx_stats[RX_STAT_LOW0_MIN], dur); + args->lwrx_stats[RX_STAT_LOW0_AVE] = + args->lwrx_stats[RX_STAT_LOW0_AVE] - (args->lwrx_stats[RX_STAT_LOW0_AVE] >> 4) + dur; + } + break; + default: + // not good start again + args->rx_state = RX_STATE_IDLE; + break; + } + if (args->rx_num_bits >= 8) { + args->rx_num_bytes++; + args->rx_num_bits = 0; + if (args->rx_num_bytes >= RX_MSGLEN) { + uint32_t curr_millis = millis(); + if (args->rx_repeats > 0) { + if ((curr_millis - args->rx_prevpkttime) / 100 > args->rx_timeout) { + args->rx_repeatcount = 1; + } else { + // Test message same as last one + int16_t i = RX_MSGLEN; // int + do { + i--; + } while ((i >= 0) && (args->rx_msg[i] == args->rx_buf[i])); + if (i < 0) { + args->rx_repeatcount++; + } else { + args->rx_repeatcount = 1; + } + } + } else { + args->rx_repeatcount = 0; + } + args->rx_prevpkttime = curr_millis; + // If last message hasn't been read it gets overwritten + memcpy(args->rx_msg, args->rx_buf, RX_MSGLEN); + if (args->rx_repeats == 0 || args->rx_repeatcount == args->rx_repeats) { + if (args->rx_pairtimeout != 0) { + if ((curr_millis - args->rx_pairstarttime) / 100 <= args->rx_pairtimeout) { + if (args->rx_msg[3] == RX_CMD_ON) { + args->rx_addpairfrommsg_(); + } else if (args->rx_msg[3] == RX_CMD_OFF) { + args->rx_remove_pair_(&args->rx_msg[2]); + } + } + } + if (args->rx_report_message_()) { + args->rx_msgcomplete = true; + } + args->rx_pairtimeout = 0; + } + // And cycle round for next one + args->rx_state = RX_STATE_IDLE; + } else { + args->rx_state = RX_STATE_BYTESTARTFOUND; + } + } + break; + } +} + +/** + Test if a message has arrived +**/ +bool LwRx::lwrx_message() { return (this->rx_msgcomplete); } + +/** + Set translate mode +**/ +void LwRx::lwrx_settranslate(bool rxtranslate) { this->rx_translate = rxtranslate; } +/** + Transfer a message to user buffer +**/ +bool LwRx::lwrx_getmessage(uint8_t *buf, uint8_t len) { + bool ret = true; + int16_t j = 0; // int + if (this->rx_msgcomplete && len <= RX_MSGLEN) { + for (uint8_t i = 0; ret && i < RX_MSGLEN; i++) { + if (this->rx_translate || (len != RX_MSGLEN)) { + j = this->rx_find_nibble_(this->rx_msg[i]); + if (j < 0) + ret = false; + } else { + j = this->rx_msg[i]; + } + switch (len) { + case 4: + if (i == 9) + buf[2] = j; + if (i == 2) + buf[3] = j; + case 2: + if (i == 3) + buf[0] = j; + if (i == 0) + buf[1] = j << 4; + if (i == 1) + buf[1] += j; + break; + case 10: + buf[i] = j; + break; + } + } + this->rx_msgcomplete = false; + } else { + ret = false; + } + return ret; +} + +/** + Return time in milliseconds since last packet received +**/ +uint32_t LwRx::lwrx_packetinterval() { return millis() - this->rx_prevpkttime; } + +/** + Set up repeat filtering of received messages +**/ +void LwRx::lwrx_setfilter(uint8_t repeats, uint8_t timeout) { + this->rx_repeats = repeats; + this->rx_timeout = timeout; +} + +/** + Add a pair to filter received messages + pairdata is device,dummy,5*addr,room + pairdata is held in translated form to make comparisons quicker +**/ +uint8_t LwRx::lwrx_addpair(const uint8_t *pairdata) { + if (this->rx_paircount < RX_MAXPAIRS) { + for (uint8_t i = 0; i < 8; i++) { + this->rx_pairs[rx_paircount][i] = RX_NIBBLE[pairdata[i]]; + } + this->rx_paircommit_(); + } + return this->rx_paircount; +} + +/** + Make a pair from next message successfully received +**/ +void LwRx::lwrx_makepair(uint8_t timeout) { + this->rx_pairtimeout = timeout; + this->rx_pairstarttime = millis(); +} + +/** + Get pair data (translated back to nibble form +**/ +uint8_t LwRx::lwrx_getpair(uint8_t *pairdata, uint8_t pairnumber) { + if (pairnumber < this->rx_paircount) { + int16_t j; // int + for (uint8_t i = 0; i < 8; i++) { + j = this->rx_find_nibble_(this->rx_pairs[pairnumber][i]); + if (j >= 0) + pairdata[i] = j; + } + } + return this->rx_paircount; +} + +/** + Clear all pairing +**/ +void LwRx::lwrx_clearpairing_() { rx_paircount = 0; } + +/** + Return stats on high and low pulses +**/ +bool LwRx::lwrx_getstats_(uint16_t *stats) { // unsigned int + if (this->lwrx_stats_enable) { + memcpy(stats, this->lwrx_stats, 2 * RX_STAT_COUNT); + return true; + } else { + return false; + } +} + +/** + Set stats mode +**/ +void LwRx::lwrx_setstatsenable_(bool rx_stats_enable) { + this->lwrx_stats_enable = rx_stats_enable; + if (!this->lwrx_stats_enable) { + // clear down stats when disabling + memcpy(this->lwrx_stats, LWRX_STATSDFLT, sizeof(LWRX_STATSDFLT)); + } +} +/** + Set pairs behaviour +**/ +void LwRx::lwrx_set_pair_mode(bool pair_enforce, bool pair_base_only) { + this->rx_pairEnforce = pair_enforce; + this->rx_pairBaseOnly = pair_base_only; +} + +/** + Set things up to receive LightWaveRF 434Mhz messages + pin must be 2 or 3 to trigger interrupts + !!! For Spark, any pin will work +**/ +void LwRx::lwrx_setup(InternalGPIOPin *pin) { + // rx_pin = pin; + pin->setup(); + this->rx_pin_isr_ = pin->to_isr(); + pin->attach_interrupt(&LwRx::rx_process_bits, this, gpio::INTERRUPT_ANY_EDGE); + + memcpy(this->lwrx_stats, LWRX_STATSDFLT, sizeof(LWRX_STATSDFLT)); +} + +/** + Check a message to see if it should be reported under pairing / mood / all off rules + returns -1 if none found +**/ +bool LwRx::rx_report_message_() { + if (this->rx_pairEnforce && this->rx_paircount == 0) { + return false; + } else { + bool all_devices; + // True if mood to device 15 or Off cmd with Allof paramater + all_devices = ((this->rx_msg[3] == RX_CMD_MOOD && this->rx_msg[2] == RX_DEV_15) || + (this->rx_msg[3] == RX_CMD_OFF && this->rx_msg[0] == RX_PAR0_ALLOFF)); + return (rx_check_pairs_(&this->rx_msg[2], all_devices) != -1); + } +} +/** + Find nibble from byte + returns -1 if none found +**/ +int16_t LwRx::rx_find_nibble_(uint8_t data) { // int + int16_t i = 15; // int + do { + if (RX_NIBBLE[i] == data) + break; + i--; + } while (i >= 0); + return i; +} + +/** + add pair from message buffer +**/ +void LwRx::rx_addpairfrommsg_() { + if (this->rx_paircount < RX_MAXPAIRS) { + memcpy(this->rx_pairs[this->rx_paircount], &this->rx_msg[2], 8); + this->rx_paircommit_(); + } +} + +/** + check and commit pair +**/ +void LwRx::rx_paircommit_() { + if (this->rx_paircount == 0 || this->rx_check_pairs_(this->rx_pairs[this->rx_paircount], false) < 0) { + this->rx_paircount++; + } +} + +/** + Check to see if message matches one of the pairs + if mode is pairBase only then ignore device and room + if allDevices is true then ignore the device number + Returns matching pair number, -1 if not found, -2 if no pairs defined +**/ +int16_t LwRx::rx_check_pairs_(const uint8_t *buf, bool all_devices) { // int + if (this->rx_paircount == 0) { + return -2; + } else { + int16_t pair = this->rx_paircount; // int + int16_t j = -1; // int + int16_t jstart, jend; // int + if (this->rx_pairBaseOnly) { + // skip room(8) and dev/cmd (0,1) + jstart = 7; + jend = 2; + } else { + // include room in comparison + jstart = 8; + // skip device comparison if allDevices true + jend = (all_devices) ? 2 : 0; + } + while (pair > 0 && j < 0) { + pair--; + j = jstart; + while (j > jend) { + j--; + if (j != 1) { + if (this->rx_pairs[pair][j] != buf[j]) { + j = -1; + } + } + } + } + return (j >= 0) ? pair : -1; + } +} + +/** + Remove an existing pair matching the buffer +**/ +void LwRx::rx_remove_pair_(uint8_t *buf) { + int16_t pair = this->rx_check_pairs_(buf, false); // int + if (pair >= 0) { + while (pair < this->rx_paircount - 1) { + for (uint8_t j = 0; j < 8; j++) { + this->rx_pairs[pair][j] = this->rx_pairs[pair + 1][j]; + } + pair++; + } + this->rx_paircount--; + } +} + +} // namespace lightwaverf +} // namespace esphome +#endif diff --git a/esphome/components/lightwaverf/LwRx.h b/esphome/components/lightwaverf/LwRx.h new file mode 100644 index 0000000000..7200f9a51c --- /dev/null +++ b/esphome/components/lightwaverf/LwRx.h @@ -0,0 +1,142 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace lightwaverf { + +// LwRx.h +// +// LightwaveRF 434MHz receiver for Arduino +// +// Author: Bob Tidey (robert@tideys.net) + +static const uint8_t RX_STAT_HIGH_AVE = 0; +static const uint8_t RX_STAT_HIGH_MAX = 1; +static const uint8_t RX_STAT_HIGH_MIN = 2; +static const uint8_t RX_STAT_LOW0_AVE = 3; +static const uint8_t RX_STAT_LOW0_MAX = 4; +static const uint8_t RX_STAT_LOW0_MIN = 5; +static const uint8_t RX_STAT_LOW1_AVE = 6; +static const uint8_t RX_STAT_LOW1_MAX = 7; +static const uint8_t RX_STAT_LOW1_MIN = 8; +static const uint8_t RX_STAT_COUNT = 9; + +// sets maximum number of pairings which can be held +static const uint8_t RX_MAXPAIRS = 10; + +static const uint8_t RX_NIBBLE[] = {0xF6, 0xEE, 0xED, 0xEB, 0xDE, 0xDD, 0xDB, 0xBE, + 0xBD, 0xBB, 0xB7, 0x7E, 0x7D, 0x7B, 0x77, 0x6F}; +static const uint8_t RX_CMD_OFF = 0xF6; // raw 0 +static const uint8_t RX_CMD_ON = 0xEE; // raw 1 +static const uint8_t RX_CMD_MOOD = 0xED; // raw 2 +static const uint8_t RX_PAR0_ALLOFF = 0x7D; // param 192-255 all off (12 in msb) +static const uint8_t RX_DEV_15 = 0x6F; // device 15 + +static const uint8_t RX_MSGLEN = 10; // expected length of rx message + +static const uint8_t RX_STATE_IDLE = 0; +static const uint8_t RX_STATE_MSGSTARTFOUND = 1; +static const uint8_t RX_STATE_BYTESTARTFOUND = 2; +static const uint8_t RX_STATE_GETBYTE = 3; + +// Gather stats for pulse widths (ave is x 16) +static const uint16_t LWRX_STATSDFLT[RX_STAT_COUNT] = {5000, 0, 5000, 20000, 0, 2500, 4000, 0, 500}; // usigned int + +class LwRx { + public: + // Seup must be called once, set up pin used to receive data + void lwrx_setup(InternalGPIOPin *pin); + + // Set translate to determine whether translating from nibbles to bytes in message + // Translate off only applies to 10char message returns + void lwrx_settranslate(bool translate); + + // Check to see whether message available + bool lwrx_message(); + + // Get a message, len controls format (2 cmd+param, 4 cmd+param+room+device),10 full message + bool lwrx_getmessage(uint8_t *buf, uint8_t len); + + // Setup repeat filter + void lwrx_setfilter(uint8_t repeats, uint8_t timeout); + + // Add pair, if no pairing set then all messages are received, returns number of pairs + uint8_t lwrx_addpair(const uint8_t *pairdata); + + // Get pair data into buffer for the pairnumber. Returns current paircount + // Use pairnumber 255 to just get current paircount + uint8_t lwrx_getpair(uint8_t *pairdata, uint8_t pairnumber); + + // Make a pair from next message received within timeout 100mSec + // This call returns immediately whilst message checking continues + void lwrx_makepair(uint8_t timeout); + + // Set pair mode controls + void lwrx_set_pair_mode(bool pair_enforce, bool pair_base_only); + + // Returns time from last packet received in msec + // Can be used to determine if Rx may be still receiving repeats + uint32_t lwrx_packetinterval(); + + static void rx_process_bits(LwRx *arg); + + // Pairing data + uint8_t rx_paircount = 0; + uint8_t rx_pairs[RX_MAXPAIRS][8]; + // set false to responds to all messages if no pairs set up + bool rx_pairEnforce = false; + // set false to use Address, Room and Device in pairs, true just the Address part + bool rx_pairBaseOnly = false; + + uint8_t rx_pairtimeout = 0; // 100msec units + + // Repeat filters + uint8_t rx_repeats = 2; // msg must be repeated at least this number of times + uint8_t rx_repeatcount = 0; + uint8_t rx_timeout = 20; // reset repeat window after this in 100mSecs + uint32_t rx_prevpkttime = 0; // last packet time in milliseconds + uint32_t rx_pairstarttime = 0; // last msg time in milliseconds + + // Receive mode constants and variables + uint8_t rx_msg[RX_MSGLEN]; // raw message received + uint8_t rx_buf[RX_MSGLEN]; // message buffer during reception + + uint32_t rx_prev; // time of previous interrupt in microseconds + + bool rx_msgcomplete = false; // set high when message available + bool rx_translate = true; // Set false to get raw data + + uint8_t rx_state = 0; + + uint8_t rx_num_bits = 0; // number of bits in the current uint8_t + uint8_t rx_num_bytes = 0; // number of bytes received + + uint16_t lwrx_stats[RX_STAT_COUNT]; // unsigned int + + bool lwrx_stats_enable = true; + + protected: + void lwrx_clearpairing_(); + + // Return stats on pulse timings + bool lwrx_getstats_(uint16_t *stats); + + // Enable collection of stats on pulse timings + void lwrx_setstatsenable_(bool rx_stats_enable); + + // internal support functions + bool rx_report_message_(); + int16_t rx_find_nibble_(uint8_t data); // int + void rx_addpairfrommsg_(); + void rx_paircommit_(); + void rx_remove_pair_(uint8_t *buf); + int16_t rx_check_pairs_(const uint8_t *buf, bool all_devices); // int + + ISRInternalGPIOPin rx_pin_isr_; + InternalGPIOPin *rx_pin_; +}; + +} // namespace lightwaverf +} // namespace esphome diff --git a/esphome/components/lightwaverf/LwTx.cpp b/esphome/components/lightwaverf/LwTx.cpp new file mode 100644 index 0000000000..2f46b04b2d --- /dev/null +++ b/esphome/components/lightwaverf/LwTx.cpp @@ -0,0 +1,208 @@ +// LwTx.cpp +// +// LightwaveRF 434MHz tx interface for Arduino +// +// Author: Bob Tidey (robert@tideys.net) +#ifdef USE_ESP8266 + +#include "LwTx.h" +#include +#include +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace lightwaverf { + +static const uint8_t TX_NIBBLE[] = {0xF6, 0xEE, 0xED, 0xEB, 0xDE, 0xDD, 0xDB, 0xBE, + 0xBD, 0xBB, 0xB7, 0x7E, 0x7D, 0x7B, 0x77, 0x6F}; + +static const uint8_t TX_STATE_IDLE = 0; +static const uint8_t TX_STATE_MSGSTART = 1; +static const uint8_t TX_STATE_BYTESTART = 2; +static const uint8_t TX_STATE_SENDBYTE = 3; +static const uint8_t TX_STATE_MSGEND = 4; +static const uint8_t TX_STATE_GAPSTART = 5; +static const uint8_t TX_STATE_GAPEND = 6; +/** + Set translate mode +**/ +void LwTx::lwtx_settranslate(bool txtranslate) { tx_translate = txtranslate; } + +static void IRAM_ATTR isr_t_xtimer(LwTx *arg) { + // Set low after toggle count interrupts + arg->tx_toggle_count--; + if (arg->tx_toggle_count == arg->tx_trail_count) { + // ESP_LOGD("lightwaverf.sensor", "timer") + arg->tx_pin->digital_write(arg->txoff); + } else if (arg->tx_toggle_count == 0) { + arg->tx_toggle_count = arg->tx_high_count; // default high pulse duration + switch (arg->tx_state) { + case TX_STATE_IDLE: + if (arg->tx_msg_active) { + arg->tx_repeat = 0; + arg->tx_state = TX_STATE_MSGSTART; + } + break; + case TX_STATE_MSGSTART: + arg->tx_pin->digital_write(arg->txon); + arg->tx_num_bytes = 0; + arg->tx_state = TX_STATE_BYTESTART; + break; + case TX_STATE_BYTESTART: + arg->tx_pin->digital_write(arg->txon); + arg->tx_bit_mask = 0x80; + arg->tx_state = TX_STATE_SENDBYTE; + break; + case TX_STATE_SENDBYTE: + if (arg->tx_buf[arg->tx_num_bytes] & arg->tx_bit_mask) { + arg->tx_pin->digital_write(arg->txon); + } else { + // toggle count for the 0 pulse + arg->tx_toggle_count = arg->tx_low_count; + } + arg->tx_bit_mask >>= 1; + if (arg->tx_bit_mask == 0) { + arg->tx_num_bytes++; + if (arg->tx_num_bytes >= esphome::lightwaverf::LwTx::TX_MSGLEN) { + arg->tx_state = TX_STATE_MSGEND; + } else { + arg->tx_state = TX_STATE_BYTESTART; + } + } + break; + case TX_STATE_MSGEND: + arg->tx_pin->digital_write(arg->txon); + arg->tx_state = TX_STATE_GAPSTART; + arg->tx_gap_repeat = arg->tx_gap_multiplier; + break; + case TX_STATE_GAPSTART: + arg->tx_toggle_count = arg->tx_gap_count; + if (arg->tx_gap_repeat == 0) { + arg->tx_state = TX_STATE_GAPEND; + } else { + arg->tx_gap_repeat--; + } + break; + case TX_STATE_GAPEND: + arg->tx_repeat++; + if (arg->tx_repeat >= arg->tx_repeats) { + // disable timer nterrupt + arg->lw_timer_stop(); + arg->tx_msg_active = false; + arg->tx_state = TX_STATE_IDLE; + } else { + arg->tx_state = TX_STATE_MSGSTART; + } + break; + } + } +} + +/** + Check for send free +**/ +bool LwTx::lwtx_free() { return !this->tx_msg_active; } + +/** + Send a LightwaveRF message (10 nibbles in bytes) +**/ +void LwTx::lwtx_send(const std::vector &msg) { + if (this->tx_translate) { + for (uint8_t i = 0; i < TX_MSGLEN; i++) { + this->tx_buf[i] = TX_NIBBLE[msg[i] & 0xF]; + ESP_LOGD("lightwaverf.sensor", "%x ", msg[i]); + } + } else { + // memcpy(tx_buf, msg, tx_msglen); + } + this->lw_timer_start(); + this->tx_msg_active = true; +} + +/** + Set 5 char address for future messages +**/ +void LwTx::lwtx_setaddr(const uint8_t *addr) { + for (uint8_t i = 0; i < 5; i++) { + this->tx_buf[i + 4] = TX_NIBBLE[addr[i] & 0xF]; + } +} + +/** + Send a LightwaveRF message (10 nibbles in bytes) +**/ +void LwTx::lwtx_cmd(uint8_t command, uint8_t parameter, uint8_t room, uint8_t device) { + // enable timer 2 interrupts + this->tx_buf[0] = TX_NIBBLE[parameter >> 4]; + this->tx_buf[1] = TX_NIBBLE[parameter & 0xF]; + this->tx_buf[2] = TX_NIBBLE[device & 0xF]; + this->tx_buf[3] = TX_NIBBLE[command & 0xF]; + this->tx_buf[9] = TX_NIBBLE[room & 0xF]; + this->lw_timer_start(); + this->tx_msg_active = true; +} + +/** + Set things up to transmit LightWaveRF 434Mhz messages +**/ +void LwTx::lwtx_setup(InternalGPIOPin *pin, uint8_t repeats, bool inverted, int u_sec) { + pin->setup(); + tx_pin = pin; + + tx_pin->pin_mode(gpio::FLAG_OUTPUT); + tx_pin->digital_write(txoff); + + if (repeats > 0 && repeats < 40) { + this->tx_repeats = repeats; + } + if (inverted) { + this->txon = 0; + this->txoff = 1; + } else { + this->txon = 1; + this->txoff = 0; + } + + int period1 = 330; + /* + if (period > 32 && period < 1000) { + period1 = period; + } else { + // default 330 uSec + period1 = 330; + }*/ + this->espPeriod = 5 * period1; + timer1_isr_init(); +} + +void LwTx::lwtx_set_tick_counts(uint8_t low_count, uint8_t high_count, uint8_t trail_count, uint8_t gap_count) { + this->tx_low_count = low_count; + this->tx_high_count = high_count; + this->tx_trail_count = trail_count; + this->tx_gap_count = gap_count; +} + +void LwTx::lwtx_set_gap_multiplier(uint8_t gap_multiplier) { this->tx_gap_multiplier = gap_multiplier; } + +void LwTx::lw_timer_start() { + { + InterruptLock lock; + static LwTx *arg = this; // NOLINT + timer1_attachInterrupt([] { isr_t_xtimer(arg); }); + timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); + timer1_write(this->espPeriod); + } +} + +void LwTx::lw_timer_stop() { + { + InterruptLock lock; + timer1_disable(); + timer1_detachInterrupt(); + } +} + +} // namespace lightwaverf +} // namespace esphome +#endif diff --git a/esphome/components/lightwaverf/LwTx.h b/esphome/components/lightwaverf/LwTx.h new file mode 100644 index 0000000000..719826640e --- /dev/null +++ b/esphome/components/lightwaverf/LwTx.h @@ -0,0 +1,92 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace lightwaverf { + +// LxTx.h +// +// LightwaveRF 434MHz tx interface for Arduino +// +// Author: Bob Tidey (robert@tideys.net) + +// Include basic library header and set default TX pin +static const uint8_t TX_PIN_DEFAULT = 13; + +class LwTx { + public: + // Sets up basic parameters must be called at least once + void lwtx_setup(InternalGPIOPin *pin, uint8_t repeats, bool inverted, int u_sec); + + // Allows changing basic tick counts from their defaults + void lwtx_set_tick_counts(uint8_t low_count, uint8_t high_count, uint8_t trail_count, uint8_t gap_count); + + // Allws multiplying the gap period for creating very large gaps + void lwtx_set_gap_multiplier(uint8_t gap_multiplier); + + // determines whether incoming data or should be translated from nibble data + void lwtx_settranslate(bool txtranslate); + + // Checks whether tx is free to accept a new message + bool lwtx_free(); + + // Basic send of new 10 char message, not normally needed if setaddr and cmd are used. + void lwtx_send(const std::vector &msg); + + // Sets up 5 char address which will be used to form messages for lwtx_cmd + void lwtx_setaddr(const uint8_t *addr); + + // Send Command + void lwtx_cmd(uint8_t command, uint8_t parameter, uint8_t room, uint8_t device); + + // Allows changing basic tick counts from their defaults + void lw_timer_start(); + + // Allws multiplying the gap period for creating very large gaps + void lw_timer_stop(); + + // These set the pulse durationlws in ticks. ESP uses 330uSec base tick, else use 140uSec + uint8_t tx_low_count = 3; // total number of ticks in a low (990 uSec) + uint8_t tx_high_count = 2; // total number of ticks in a high (660 uSec) + uint8_t tx_trail_count = 1; // tick count to set line low (330 uSec) + + uint8_t tx_toggle_count = 3; + + static const uint8_t TX_MSGLEN = 10; // the expected length of the message + + // Transmit mode constants and variables + uint8_t tx_repeats = 12; // Number of repeats of message sent + uint8_t txon = 1; + uint8_t txoff = 0; + bool tx_msg_active = false; // set true to activate message sending + bool tx_translate = true; // Set false to send raw data + + uint8_t tx_buf[TX_MSGLEN]; // the message buffer during reception + uint8_t tx_repeat = 0; // counter for repeats + uint8_t tx_state = 0; + uint16_t tx_gap_repeat = 0; // unsigned int + + // Use with low repeat counts + uint8_t tx_gap_count = 33; // Inter-message gap count (10.9 msec) + uint32_t espPeriod = 0; // Holds interrupt timer0 period + uint32_t espNext = 0; // Holds interrupt next count + + // Gap multiplier byte is used to multiply gap if longer periods are needed for experimentation + // If gap is 255 (35msec) then this to give a max of 9 seconds + // Used with low repeat counts to find if device times out + uint8_t tx_gap_multiplier = 0; // Gap extension byte + + uint8_t tx_bit_mask = 0; // bit mask in current byte + uint8_t tx_num_bytes = 0; // number of bytes sent + + InternalGPIOPin *tx_pin; + + protected: + uint32_t duty_on_; + uint32_t duty_off_; +}; + +} // namespace lightwaverf +} // namespace esphome diff --git a/esphome/components/lightwaverf/__init__.py b/esphome/components/lightwaverf/__init__.py new file mode 100644 index 0000000000..4e96dda663 --- /dev/null +++ b/esphome/components/lightwaverf/__init__.py @@ -0,0 +1,83 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome import automation + +from esphome.const import ( + CONF_READ_PIN, + CONF_ID, + CONF_NAME, + CONF_WRITE_PIN, + CONF_REPEAT, + CONF_INVERTED, + CONF_PULSE_LENGTH, + CONF_CODE, +) +from esphome.cpp_helpers import gpio_pin_expression + +CODEOWNERS = ["@max246"] + +lightwaverf_ns = cg.esphome_ns.namespace("lightwaverf") + + +LIGHTWAVERFComponent = lightwaverf_ns.class_( + "LightWaveRF", cg.Component, cg.PollingComponent +) +LightwaveRawAction = lightwaverf_ns.class_("SendRawAction", automation.Action) + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(LIGHTWAVERFComponent), + cv.Optional(CONF_READ_PIN, default=13): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_WRITE_PIN, default=14): pins.internal_gpio_input_pin_schema, + } +).extend(cv.polling_component_schema("1s")) + + +LIGHTWAVE_SEND_SCHEMA = cv.Any( + cv.int_range(min=1), + cv.Schema( + { + cv.GenerateID(): cv.use_id(LIGHTWAVERFComponent), + cv.Required(CONF_NAME): cv.string, + cv.Required(CONF_CODE): cv.All( + [cv.Any(cv.hex_uint8_t)], + cv.Length(min=10), + ), + cv.Optional(CONF_REPEAT, default=10): cv.int_, + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + cv.Optional(CONF_PULSE_LENGTH, default=330): cv.int_, + } + ), +) + + +@automation.register_action( + "lightwaverf.send_raw", + LightwaveRawAction, + LIGHTWAVE_SEND_SCHEMA, +) +async def send_raw_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + + repeats = await cg.templatable(config[CONF_REPEAT], args, int) + inverted = await cg.templatable(config[CONF_INVERTED], args, bool) + pulse_length = await cg.templatable(config[CONF_PULSE_LENGTH], args, int) + code = config[CONF_CODE] + + cg.add(var.set_repeats(repeats)) + cg.add(var.set_inverted(inverted)) + cg.add(var.set_pulse_length(pulse_length)) + cg.add(var.set_data(code)) + return var + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + pin_read = await gpio_pin_expression(config[CONF_READ_PIN]) + pin_write = await gpio_pin_expression(config[CONF_WRITE_PIN]) + cg.add(var.set_pin(pin_write, pin_read)) diff --git a/esphome/components/lightwaverf/lightwaverf.cpp b/esphome/components/lightwaverf/lightwaverf.cpp new file mode 100644 index 0000000000..89cbdae6e1 --- /dev/null +++ b/esphome/components/lightwaverf/lightwaverf.cpp @@ -0,0 +1,67 @@ +#include "esphome/core/log.h" + +#ifdef USE_ESP8266 + +#include "lightwaverf.h" + +namespace esphome { +namespace lightwaverf { + +static const char *const TAG = "lightwaverf.sensor"; + +static const uint8_t DEFAULT_REPEAT = 10; +static const bool DEFAULT_INVERT = false; +static const uint32_t DEFAULT_TICK = 330; + +void LightWaveRF::setup() { + ESP_LOGCONFIG(TAG, "Setting up Lightwave RF..."); + + this->lwtx_.lwtx_setup(pin_tx_, DEFAULT_REPEAT, DEFAULT_INVERT, DEFAULT_TICK); + this->lwrx_.lwrx_setup(pin_rx_); +} + +void LightWaveRF::update() { this->read_tx(); } + +void LightWaveRF::read_tx() { + if (this->lwrx_.lwrx_message()) { + this->lwrx_.lwrx_getmessage(msg_, msglen_); + print_msg_(msg_, msglen_); + } +} + +void LightWaveRF::send_rx(const std::vector &msg, uint8_t repeats, bool inverted, int u_sec) { + this->lwtx_.lwtx_setup(pin_tx_, repeats, inverted, u_sec); + + uint32_t timeout = 0; + if (this->lwtx_.lwtx_free()) { + this->lwtx_.lwtx_send(msg); + timeout = millis(); + ESP_LOGD(TAG, "[%i] msg start", timeout); + } + while (!this->lwtx_.lwtx_free() && millis() < (timeout + 1000)) { + delay(10); + } + timeout = millis() - timeout; + ESP_LOGD(TAG, "[%u] msg sent: %i", millis(), timeout); +} + +void LightWaveRF::print_msg_(uint8_t *msg, uint8_t len) { + char buffer[65]; + ESP_LOGD(TAG, " Received code (len:%i): ", len); + + for (int i = 0; i < len; i++) { + sprintf(&buffer[i * 6], "0x%02x, ", msg[i]); + } + ESP_LOGD(TAG, "[%s]", buffer); +} + +void LightWaveRF::dump_config() { + ESP_LOGCONFIG(TAG, "Lightwave RF:"); + LOG_PIN(" Pin TX: ", this->pin_tx_); + LOG_PIN(" Pin RX: ", this->pin_rx_); + LOG_UPDATE_INTERVAL(this); +} +} // namespace lightwaverf +} // namespace esphome + +#endif diff --git a/esphome/components/lightwaverf/lightwaverf.h b/esphome/components/lightwaverf/lightwaverf.h new file mode 100644 index 0000000000..b9f2abfcb3 --- /dev/null +++ b/esphome/components/lightwaverf/lightwaverf.h @@ -0,0 +1,70 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/automation.h" + +#include + +#include "LwRx.h" +#include "LwTx.h" + +namespace esphome { +namespace lightwaverf { + +#ifdef USE_ESP8266 + +class LightWaveRF : public PollingComponent { + public: + void set_pin(InternalGPIOPin *pin_tx, InternalGPIOPin *pin_rx) { + pin_tx_ = pin_tx; + pin_rx_ = pin_rx; + } + void update() override; + void setup() override; + void dump_config() override; + void read_tx(); + void send_rx(const std::vector &msg, uint8_t repeats, bool inverted, int u_sec); + + protected: + void print_msg_(uint8_t *msg, uint8_t len); + uint8_t msg_[10]; + uint8_t msglen_ = 10; + InternalGPIOPin *pin_tx_; + InternalGPIOPin *pin_rx_; + LwRx lwrx_; + LwTx lwtx_; +}; + +template class SendRawAction : public Action { + public: + SendRawAction(LightWaveRF *parent) : parent_(parent){}; + TEMPLATABLE_VALUE(int, repeat); + TEMPLATABLE_VALUE(int, inverted); + TEMPLATABLE_VALUE(int, pulse_length); + TEMPLATABLE_VALUE(std::vector, code); + + void set_repeats(const int &data) { repeat_ = data; } + void set_inverted(const int &data) { inverted_ = data; } + void set_pulse_length(const int &data) { pulse_length_ = data; } + void set_data(const std::vector &data) { code_ = data; } + + void play(Ts... x) { + int repeats = this->repeat_.value(x...); + int inverted = this->inverted_.value(x...); + int pulse_length = this->pulse_length_.value(x...); + std::vector msg = this->code_.value(x...); + + this->parent_->send_rx(msg, repeats, inverted, pulse_length); + } + + protected: + LightWaveRF *parent_; +}; + +#endif +} // namespace lightwaverf +} // namespace esphome +#endif diff --git a/tests/test3.yaml b/tests/test3.yaml index abfd133c99..5d30e415fb 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1188,6 +1188,10 @@ qr_code: - id: homepage_qr value: https://esphome.io/index.html +lightwaverf: + read_pin: 13 + write_pin: 14 + alarm_control_panel: - platform: template id: alarmcontrolpanel1