Integration LightwaveRF switches (#4812)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Christian 2023-09-05 23:33:49 +01:00 committed by GitHub
parent feba9ffdc4
commit 76ebbfefd2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1094 additions and 0 deletions

View file

@ -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

View file

@ -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 <cstring>
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

View file

@ -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

View file

@ -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 <cstring>
#include <Arduino.h>
#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<uint8_t> &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

View file

@ -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<uint8_t> &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

View file

@ -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))

View file

@ -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<uint8_t> &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

View file

@ -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 <vector>
#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<uint8_t> &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<typename... Ts> class SendRawAction : public Action<Ts...> {
public:
SendRawAction(LightWaveRF *parent) : parent_(parent){};
TEMPLATABLE_VALUE(int, repeat);
TEMPLATABLE_VALUE(int, inverted);
TEMPLATABLE_VALUE(int, pulse_length);
TEMPLATABLE_VALUE(std::vector<uint8_t>, 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<uint8_t> &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<uint8_t> msg = this->code_.value(x...);
this->parent_->send_rx(msg, repeats, inverted, pulse_length);
}
protected:
LightWaveRF *parent_;
};
#endif
} // namespace lightwaverf
} // namespace esphome
#endif

View file

@ -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