Tasmota/tasmota/tasmota_xdrv_driver/xdrv_47_ftc532.ino

244 lines
8.9 KiB
C++

/*
xdrv_47_ftc532.ino - FTC532 touch buttons support for Tasmota
Copyright (C) 2021 Peter Franck and Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_FTC532
/*********************************************************************************************\
THE PLAN [tm]: OUTCOME:
============== ========
appear in a dropdown (D_SENSOR_FTC532 "FTC532")
select pin (GPIO_FTC532)
attach interrupt to pin DONE
ISR updating all 8 inputs DONE
de-bouncing for 50 ms DONE
change report every 250 ms REPORTS EVERY 50MS
Webserver display "00001001" DONE & REMOVED
MQTT message DONE
Rules hook DONE
detach interrupt before restart POINTLESS
THE PROTOCOL [tm]:
==================
LEAD-IN = 3015 µs HIGH, 755 µs LOW
S = 377 µs HIGH, 377 µs LOW
L = 377 µs HIGH, 1130 µs LOW
GROUP1 GROUP2
---------------------------
ALL OFF: SSSSLLLL SSSSLLLL
1 ON : LSSSSLLL SSSSLLLL
2 ON : SLSSLSLL SSSSLLLL
3 ON : SSLSLLSL SSSSLLLL
8 ON : SSSSLLLL SSSLLLLS
123 ON : LLLSSSSL SSSSLLLL
Timing of an ALL OFF frame in clock cycles T=377µs, triggering on rising edge:
IDLE-2222444422224444-IDLE
*********************
* About this driver *
*********************
This driver implements the reverse engineered protocol of the FTC532 touch controller.
The protocol encodes the bitmap of touched keys in variable length pulses comprising a
fixed length frame. These frames are then sent out continuously from the FTC532 chip.
The first version of this driver was working fine on well behaved hardware. After being
released to the field and installed on crappy hardware in noisy environments it developed
a habit of random 'ghost' switchings at night, much to the chagrin of some users.
This is almost a re-write containing more timing (and other) checks in order to detect
and mitigate various incarnations of noise.
If you should still experience 'ghost switching' issues a solution may be increasing
FTC532_DEBOUNCE to 2 or higher. That will enable the de-bouncing code, at the expense of
'snappiness' of touch reactions. Higher values will accumulate more samples in 50 ms steps
before actually firing the rules trigger. It will eat a few bytes off your RAM and Flash
budget, too.
Usage:
------
This driver does not actually switch anything. It is a pure "rules" driver that solely emits
{"FTC532":{"KEYS":"XX"}} JSON messages to be used in a rule or by an MQTT broker. "XX" stands
for the hexadecimal (big endian) representation of a bitmap of keys currently touched, where
e.g. "00" means "no key touched" while "03" means "keys 1 and 2 touched simultaneously".
Selecting "FTC532" on a GPIO will awake the driver. This driver can only be selected once.
\*********************************************************************************************/
#define XDRV_47 47
#define FTC532_DEBOUNCE 0 // no. of cycles, < 2 disables the code
#define FTC532_KEYS 4 // number of key pins on chip
#define FTC532_KEYS_MAX 8 // number of key slots in protocol
#define FTC532_STATE_WAITING 0x1
#define FTC532_STATE_READING 0x2
#define FTC532_STATE_COMPLETE 0x4
// Rising edge timing in microseconds
#define FTC532_BIT 377
#define FTC532_NOISE (FTC532_BIT * 3 / 2)
#define FTC532_SHORT (FTC532_BIT * 2)
#define FTC532_LONG (FTC532_BIT * 4)
#define FTC532_IDLE (FTC532_BIT * 10)
#define FTC532_MAX (FTC532_BIT * 58)
struct FTC532 {
volatile uint32_t rxtime; // ISR timer memory
volatile uint16_t tsmp = 0; // buffer for bit-coded time samples
volatile uint16_t sample = 0xF0F0; // valid samples
volatile uint16_t rxbit; // ISR bit counter
volatile uint16_t state; // ISR state
uint8_t keys = 0; // bitmap of active keys
uint8_t old_keys = 0; // previously active keys
bool present = false; // driver active
#if FTC532_DEBOUNCE > 1
uint8_t key_cnt = 0; // used to de-bounce
#endif // FTC532_DEBOUNCE > 1
#ifdef DEBUG_FTC532
volatile uint16_t e_inv = 0; // inverted key error counter
volatile uint16_t e_frame = 0; // frame error counter
volatile uint16_t e_noise = 0; // noise detection counter
volatile bool valid = 0; // did we ever receive valid data?
#endif // DEBUG_FTC532
} Ftc532;
const char ftc532_json[] PROGMEM = "\"FTC532\":{\"KEYS\":\"";
void IRAM_ATTR ftc532_ISR(void) { // Hardware interrupt routine, triggers on rising edge
uint32_t time = micros();
uint32_t time_diff = time - Ftc532.rxtime;
Ftc532.rxtime = time;
if (Ftc532.state & (FTC532_STATE_WAITING | FTC532_STATE_COMPLETE)) {
if (time_diff > FTC532_LONG + FTC532_SHORT) { // new frame
Ftc532.rxbit = 0;
if (Ftc532.state & FTC532_STATE_COMPLETE) {
Ftc532.sample = Ftc532.tsmp; // copy completed frame
#ifdef DEBUG_FTC532
Ftc532.valid = true;
#endif // DEBUG_FTC532
}
Ftc532.state = FTC532_STATE_READING;
Ftc532.tsmp = 0;
} else {
Ftc532.state = FTC532_STATE_WAITING;
}
return;
}
// FTC532_STATE_READING starts here
if (time_diff > FTC532_LONG + FTC532_BIT) {
#ifdef DEBUG_FTC532
++Ftc532.e_frame; // frame error
#endif // DEBUG_FTC532
Ftc532.state = FTC532_STATE_WAITING;
return;
}
if (time_diff > FTC532_SHORT + FTC532_BIT) {
Ftc532.tsmp |= (1 << Ftc532.rxbit); // LONG
} else if (time_diff < FTC532_NOISE) { // NOISE (SHORT now implicitly default)
#ifdef DEBUG_FTC532
++Ftc532.e_noise;
#endif // DEBUG_FTC532
Ftc532.state = FTC532_STATE_WAITING;
return;
}
++Ftc532.rxbit;
if (Ftc532.rxbit == FTC532_KEYS_MAX * 2) { // frame complete
Ftc532.state = FTC532_STATE_COMPLETE;
}
}
void ftc532_init(void) { // Initialize
if (!PinUsed(GPIO_FTC532)) { return; }
Ftc532.state = FTC532_STATE_WAITING;
pinMode(Pin(GPIO_FTC532), INPUT_PULLUP);
Ftc532.rxtime = micros();
attachInterrupt(Pin(GPIO_FTC532), ftc532_ISR, RISING);
Ftc532.present = true;
}
void ftc532_update(void) { // Usually called every 50 ms
if ((Ftc532.sample & 0xF0F0) == ((~Ftc532.sample & 0x0F0F) << 4) && (Ftc532.sample >> 8) == 0xF0) {
Ftc532.keys = Ftc532.sample & 0xF;
if (Ftc532.keys != Ftc532.old_keys) {
#if FTC532_DEBOUNCE > 1
if (++Ftc532.key_cnt >= FTC532_DEBOUNCE) {
#endif // FTC532_DEBOUNCE > 1
#ifdef DEBUG_FTC532
AddLog(LOG_LEVEL_DEBUG, PSTR("FTC: SAM=%04X KEY=%X OLD=%X INV=%u NOI=%u FRM=%u OK=%u TIME=%lu Pin=%u"),
Ftc532.sample, Ftc532.keys, Ftc532.old_keys, Ftc532.e_inv, Ftc532.e_noise, Ftc532.e_frame,
Ftc532.valid, Ftc532.rxtime, Pin(GPIO_FTC532));
#endif // DEBUG_FTC532
ftc532_publish();
Ftc532.old_keys = Ftc532.keys;
#if FTC532_DEBOUNCE > 1
Ftc532.key_cnt = 0;
}
} else {
Ftc532.key_cnt = 0;
#endif // FTC532_DEBOUNCE > 1
}
}
#ifdef DEBUG_FTC532
else {
++Ftc532.e_inv;
AddLog(LOG_LEVEL_DEBUG, PSTR("FTC: ILL SAM=%04X"), Ftc532.sample);
}
#endif // DEBUG_FTC532
}
void ftc532_show() {
ResponseAppend_P(PSTR(",%s%02X\"}"), ftc532_json, Ftc532.keys); // Hex keys need JSON quotes
}
void ftc532_publish(void) {
Response_P(PSTR("{%s%02X\"}}"), ftc532_json, Ftc532.keys); // Hex keys need JSON quotes
MqttPublishTeleSensor();
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv47(uint32_t function) {
bool result = false;
if (FUNC_INIT == function) {
// Initialize driver
ftc532_init();
} else if (Ftc532.present) {
switch (function) {
// timed callback functions
case FUNC_EVERY_50_MSECOND:
ftc532_update();
break;
// Generate JSON telemetry string
case FUNC_JSON_APPEND:
ftc532_show();
break;
case FUNC_ACTIVE:
result = true;
break;
}
}
// Return bool result
return result;
}
#endif // USE_FTC532