mirror of https://github.com/arendst/Tasmota.git
288 lines
8.5 KiB
C++
288 lines
8.5 KiB
C++
/*
|
|
xdrv_36_keeloq.ino - Jarolift Keeloq shutter support for Tasmota
|
|
|
|
Copyright (C) 2021 he-so
|
|
|
|
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_KEELOQ
|
|
/*********************************************************************************************\
|
|
* Keeloq shutter support
|
|
*
|
|
* Uses hardware SPI and two user configurable GPIO's (CC1101 GDO0 and CC1101 GDO2)
|
|
*
|
|
* Considering the implementation these two user GPIO's are fake.
|
|
* Only CC1101 GDO0 is used and must always be GPIO05 dictated by the used CC1101 library.
|
|
\*********************************************************************************************/
|
|
|
|
#define XDRV_36 36
|
|
|
|
#include "cc1101.h"
|
|
#include <KeeloqLib.h>
|
|
|
|
#define SYNC_WORD 199
|
|
|
|
#define Lowpulse 400
|
|
#define Highpulse 800
|
|
|
|
const char kJaroliftCommands[] PROGMEM = "Keeloq|" // prefix
|
|
"SendRaw|SendButton|Set";
|
|
|
|
void (* const jaroliftCommand[])(void) PROGMEM = {
|
|
&CmndSendRaw, &CmdSendButton, &CmdSet};
|
|
|
|
CC1101 cc1101;
|
|
|
|
struct JAROLIFT_DEVICE {
|
|
int device_key_msb = 0x0; // stores cryptkey MSB
|
|
int device_key_lsb = 0x0; // stores cryptkey LSB
|
|
uint64_t button = 0x0; // 1000=0x8 up, 0100=0x4 stop, 0010=0x2 down, 0001=0x1 learning
|
|
int disc = 0x0100; // 0x0100 for single channel remote
|
|
uint32_t enc = 0x0; // stores the 32Bit encrypted code
|
|
uint64_t pack = 0; // Contains data to send.
|
|
int count = 0;
|
|
uint32_t serial = 0x0;
|
|
int8_t port_tx;
|
|
int8_t port_rx;
|
|
} jaroliftDevice;
|
|
|
|
void CmdSet(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (XdrvMailbox.payload > 0) {
|
|
char *p;
|
|
uint32_t i = 0;
|
|
uint32_t param[4] = { 0 };
|
|
for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 4; str = strtok_r(nullptr, ", ", &p)) {
|
|
param[i] = strtoul(str, nullptr, 0);
|
|
i++;
|
|
}
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
if (param[i] < 1) { param[i] = 1; } // msb, lsb, serial, counter
|
|
}
|
|
DEBUG_DRIVER_LOG(PSTR("KLQ: params %08x %08x %08x %08x"), param[0], param[1], param[2], param[3]);
|
|
Settings->keeloq_master_msb = param[0];
|
|
Settings->keeloq_master_lsb = param[1];
|
|
Settings->keeloq_serial = param[2];
|
|
Settings->keeloq_count = param[3];
|
|
|
|
jaroliftDevice.serial = param[2];
|
|
jaroliftDevice.count = param[3];
|
|
|
|
GenerateDeviceCryptKey();
|
|
ResponseCmndDone();
|
|
} else {
|
|
DEBUG_DRIVER_LOG(PSTR("KLQ: no payload"));
|
|
}
|
|
} else {
|
|
DEBUG_DRIVER_LOG(PSTR("KLQ: no param"));
|
|
}
|
|
}
|
|
|
|
void GenerateDeviceCryptKey()
|
|
{
|
|
Keeloq k(Settings->keeloq_master_msb, Settings->keeloq_master_lsb);
|
|
jaroliftDevice.device_key_msb = k.decrypt(jaroliftDevice.serial | 0x60000000L);
|
|
jaroliftDevice.device_key_lsb = k.decrypt(jaroliftDevice.serial | 0x20000000L);
|
|
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("KLQ: generated device keys %08x %08x"), jaroliftDevice.device_key_msb, jaroliftDevice.device_key_lsb);
|
|
}
|
|
|
|
void CmdSendButton(void)
|
|
{
|
|
noInterrupts();
|
|
entertx();
|
|
|
|
if (XdrvMailbox.data_len > 0)
|
|
{
|
|
if (XdrvMailbox.payload > 0)
|
|
{
|
|
jaroliftDevice.button = strtoul(XdrvMailbox.data, nullptr, 0);
|
|
DEBUG_DRIVER_LOG(PSTR("KLQ: msb %08x, lsb %08x, serial %08x, disc %08x, button %08x"),
|
|
jaroliftDevice.device_key_msb, jaroliftDevice.device_key_lsb, jaroliftDevice.serial, jaroliftDevice.disc, jaroliftDevice.button);
|
|
AddLog(LOG_LEVEL_DEBUG, PSTR("KLQ: count %08x"), jaroliftDevice.count);
|
|
|
|
CreateKeeloqPacket();
|
|
jaroliftDevice.count++;
|
|
Settings->keeloq_count = jaroliftDevice.count;
|
|
|
|
for(int repeat = 0; repeat <= 1; repeat++)
|
|
{
|
|
uint64_t bitsToSend = jaroliftDevice.pack;
|
|
digitalWrite(jaroliftDevice.port_tx, LOW);
|
|
delayMicroseconds(1150);
|
|
SendSyncPreamble(13);
|
|
delayMicroseconds(3500);
|
|
for(int i=72; i>0; i--)
|
|
{
|
|
SendBit(bitsToSend & 0x0000000000000001);
|
|
bitsToSend >>= 1;
|
|
}
|
|
DEBUG_DRIVER_LOG(PSTR("KLQ: finished sending bits at %d"), micros());
|
|
|
|
delay(16); // delay in loop context is save for wdt
|
|
}
|
|
}
|
|
}
|
|
|
|
interrupts();
|
|
enterrx();
|
|
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void SendBit(byte bitToSend)
|
|
{
|
|
if (bitToSend==1)
|
|
{
|
|
digitalWrite(jaroliftDevice.port_tx, LOW); // Simple encoding of bit state 1
|
|
delayMicroseconds(Lowpulse);
|
|
digitalWrite(jaroliftDevice.port_tx, HIGH);
|
|
delayMicroseconds(Highpulse);
|
|
}
|
|
else
|
|
{
|
|
digitalWrite(jaroliftDevice.port_tx, LOW); // Simple encoding of bit state 0
|
|
delayMicroseconds(Highpulse);
|
|
digitalWrite(jaroliftDevice.port_tx, HIGH);
|
|
delayMicroseconds(Lowpulse);
|
|
}
|
|
}
|
|
|
|
void CmndSendRaw(void)
|
|
{
|
|
DEBUG_DRIVER_LOG(PSTR("KLQ: cmd send called at %d"), micros());
|
|
noInterrupts();
|
|
entertx();
|
|
for(int repeat = 0; repeat <= 1; repeat++)
|
|
{
|
|
if (XdrvMailbox.data_len > 0)
|
|
{
|
|
digitalWrite(jaroliftDevice.port_tx, LOW);
|
|
delayMicroseconds(1150);
|
|
SendSyncPreamble(13);
|
|
delayMicroseconds(3500);
|
|
|
|
for(int i=XdrvMailbox.data_len-1; i>=0; i--)
|
|
{
|
|
SendBit(XdrvMailbox.data[i] == '1');
|
|
}
|
|
DEBUG_DRIVER_LOG(PSTR("KLQ: finished sending bits at %d"), micros());
|
|
|
|
delay(16); // delay in loop context is save for wdt
|
|
}
|
|
interrupts();
|
|
}
|
|
enterrx();
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void enterrx() {
|
|
unsigned char marcState = 0;
|
|
cc1101.setRxState();
|
|
delay(2);
|
|
unsigned long rx_time = micros();
|
|
while (((marcState = cc1101.readStatusReg(CC1101_MARCSTATE)) & 0x1F) != 0x0D )
|
|
{
|
|
if (micros() - rx_time > 50000) break; // Quit when marcState does not change...
|
|
}
|
|
}
|
|
|
|
void entertx() {
|
|
unsigned char marcState = 0;
|
|
cc1101.setTxState();
|
|
delay(2);
|
|
unsigned long rx_time = micros();
|
|
while (((marcState = cc1101.readStatusReg(CC1101_MARCSTATE)) & 0x1F) != 0x13 && 0x14 && 0x15)
|
|
{
|
|
if (micros() - rx_time > 50000) break; // Quit when marcState does not change...
|
|
}
|
|
}
|
|
|
|
void SendSyncPreamble(int l)
|
|
{
|
|
for (int i = 0; i < l; ++i)
|
|
{
|
|
digitalWrite(jaroliftDevice.port_tx, LOW);
|
|
delayMicroseconds(400);
|
|
digitalWrite(jaroliftDevice.port_tx, HIGH);
|
|
delayMicroseconds(380);
|
|
}
|
|
}
|
|
|
|
void CreateKeeloqPacket()
|
|
{
|
|
Keeloq k(jaroliftDevice.device_key_msb, jaroliftDevice.device_key_lsb);
|
|
unsigned int result = (jaroliftDevice.disc << 16) | jaroliftDevice.count;
|
|
jaroliftDevice.pack = (uint64_t)0;
|
|
jaroliftDevice.pack |= jaroliftDevice.serial & 0xfffffffL;
|
|
jaroliftDevice.pack |= (jaroliftDevice.button & 0xfL) << 28;
|
|
jaroliftDevice.pack <<= 32;
|
|
|
|
jaroliftDevice.enc = k.encrypt(result);
|
|
jaroliftDevice.pack |= jaroliftDevice.enc;
|
|
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("KLQ: pack high %08x, low %08x"), jaroliftDevice.pack>>32, jaroliftDevice.pack);
|
|
}
|
|
|
|
void KeeloqInit()
|
|
{
|
|
jaroliftDevice.port_tx = Pin(GPIO_CC1101_GDO2); // Output port for transmission
|
|
jaroliftDevice.port_rx = Pin(GPIO_CC1101_GDO0); // Input port for reception
|
|
|
|
DEBUG_DRIVER_LOG(PSTR("KLQ: cc1101.init()"));
|
|
delay(100);
|
|
cc1101.init();
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("KLQ: CC1101 done"));
|
|
cc1101.setSyncWord(SYNC_WORD, false);
|
|
cc1101.setCarrierFreq(CFREQ_433);
|
|
cc1101.disableAddressCheck();
|
|
|
|
pinMode(jaroliftDevice.port_tx, OUTPUT);
|
|
pinMode(jaroliftDevice.port_rx, INPUT_PULLUP);
|
|
|
|
jaroliftDevice.serial = Settings->keeloq_serial;
|
|
jaroliftDevice.count = Settings->keeloq_count;
|
|
GenerateDeviceCryptKey();
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
* Interface
|
|
\*********************************************************************************************/
|
|
bool Xdrv36(uint32_t function)
|
|
{
|
|
if (!PinUsed(GPIO_CC1101_GDO0) || !PinUsed(GPIO_CC1101_GDO2)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_COMMAND:
|
|
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("KLQ: calling command"));
|
|
result = DecodeCommand(kJaroliftCommands, jaroliftCommand);
|
|
break;
|
|
case FUNC_INIT:
|
|
KeeloqInit();
|
|
DEBUG_DRIVER_LOG(PSTR("KLQ: init done"));
|
|
break;
|
|
case FUNC_ACTIVE:
|
|
result = true;
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif // USE_KEELOQ
|