/* xdrv_26_ariluxrf.ino - Arilux Rf support for Tasmota Copyright (C) 2021 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 . */ #ifdef USE_LIGHT #ifdef USE_ARILUX_RF /*********************************************************************************************\ * Arilux LC11 Rf support stripped from RCSwitch library \*********************************************************************************************/ #define XDRV_26 26 const uint32_t ARILUX_RF_TIME_AVOID_DUPLICATE = 1000; // Milliseconds const uint8_t ARILUX_RF_MAX_CHANGES = 51; // Pulses (sync + 2 x 24 bits) const uint32_t ARILUX_RF_SEPARATION_LIMIT = 4300; // Microseconds const uint32_t ARILUX_RF_RECEIVE_TOLERANCE = 60; // Percentage struct ARILUX { int rf_timings[ARILUX_RF_MAX_CHANGES]; unsigned long rf_received_value = 0; unsigned long rf_last_received_value = 0; unsigned long rf_last_time = 0; unsigned long rf_lasttime = 0; unsigned int rf_change_count = 0; unsigned int rf_repeat_count = 0; uint8_t rf_toggle = 0; } Arilux; #ifndef USE_WS2812_DMA // Collides with Neopixelbus but solves RF misses void AriluxRfInterrupt(void) IRAM_ATTR; // As iram is tight and it works this way too #endif // USE_WS2812_DMA void AriluxRfInterrupt(void) { unsigned long time = micros(); int duration = time - Arilux.rf_lasttime; if (duration > ARILUX_RF_SEPARATION_LIMIT) { if (abs(duration - Arilux.rf_timings[0]) < 200) { Arilux.rf_repeat_count++; if (Arilux.rf_repeat_count == 2) { unsigned long code = 0; const int delay = Arilux.rf_timings[0] / 31; const int delayTolerance = delay * ARILUX_RF_RECEIVE_TOLERANCE / 100; for (unsigned int i = 1; i < Arilux.rf_change_count -1; i += 2) { code <<= 1; if (abs(Arilux.rf_timings[i] - (delay *3)) < delayTolerance && abs(Arilux.rf_timings[i +1] - delay) < delayTolerance) { code |= 1; } } if (Arilux.rf_change_count > 49) { // Need 1 sync bit and 24 data bits Arilux.rf_received_value = code; } Arilux.rf_repeat_count = 0; } } Arilux.rf_change_count = 0; } if (Arilux.rf_change_count >= ARILUX_RF_MAX_CHANGES) { Arilux.rf_change_count = 0; Arilux.rf_repeat_count = 0; } Arilux.rf_timings[Arilux.rf_change_count++] = duration; Arilux.rf_lasttime = time; } void AriluxRfHandler(void) { unsigned long now = millis(); if (Arilux.rf_received_value && !((Arilux.rf_received_value == Arilux.rf_last_received_value) && (now - Arilux.rf_last_time < ARILUX_RF_TIME_AVOID_DUPLICATE))) { Arilux.rf_last_received_value = Arilux.rf_received_value; Arilux.rf_last_time = now; uint16_t hostcode = Arilux.rf_received_value >> 8 & 0xFFFF; if (Settings->rf_code[1][6] == Settings->rf_code[1][7]) { Settings->rf_code[1][6] = hostcode >> 8 & 0xFF; Settings->rf_code[1][7] = hostcode & 0xFF; } uint16_t stored_hostcode = Settings->rf_code[1][6] << 8 | Settings->rf_code[1][7]; // DEBUG_DRIVER_LOG(PSTR(D_LOG_RFR D_HOST D_CODE " 0x%04X, " D_RECEIVED " 0x%06X"), stored_hostcode, Arilux.rf_received_value); AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_RFR D_HOST D_CODE " 0x%04X, " D_RECEIVED " 0x%06X"), stored_hostcode, Arilux.rf_received_value); if (hostcode == stored_hostcode) { char command[33]; char value = '-'; command[0] = '\0'; uint8_t keycode = Arilux.rf_received_value & 0xFF; switch (keycode) { case 1: // Power On case 3: // Power Off snprintf_P(command, sizeof(command), PSTR(D_CMND_POWER " %d"), (1 == keycode) ? 1 : 0); break; case 2: // Toggle Arilux.rf_toggle++; Arilux.rf_toggle &= 0x3; snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), 200 + Arilux.rf_toggle); break; case 4: // Speed + value = '+'; case 7: // Speed - snprintf_P(command, sizeof(command), PSTR(D_CMND_SPEED " %c"), value); break; case 5: // Scheme + value = '+'; case 8: // Scheme - snprintf_P(command, sizeof(command), PSTR(D_CMND_SCHEME " %c"), value); break; case 6: // Dimmer + value = '+'; case 9: // Dimmer - snprintf_P(command, sizeof(command), PSTR(D_CMND_DIMMER " %c"), value); break; default: { if ((keycode >= 10) && (keycode <= 21)) { snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), keycode -9); } } } if (strlen(command)) { ExecuteCommand(command, SRC_LIGHT); } } } Arilux.rf_received_value = 0; } void AriluxRfInit(void) { if (PinUsed(GPIO_ARIRFRCV) && PinUsed(GPIO_ARIRFSEL)) { if (TasmotaGlobal.module_changed) { Settings->rf_code[1][6] = 0; Settings->rf_code[1][7] = 0; } Arilux.rf_received_value = 0; digitalWrite(Pin(GPIO_ARIRFSEL), 0); // Turn on RF attachInterrupt(Pin(GPIO_ARIRFRCV), AriluxRfInterrupt, CHANGE); } } void AriluxRfDisable(void) { if (PinUsed(GPIO_ARIRFRCV) && PinUsed(GPIO_ARIRFSEL)) { detachInterrupt(Pin(GPIO_ARIRFRCV)); digitalWrite(Pin(GPIO_ARIRFSEL), 1); // Turn off RF } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xdrv26(uint32_t function) { bool result = false; switch (function) { case FUNC_EVERY_50_MSECOND: if (PinUsed(GPIO_ARIRFRCV)) { AriluxRfHandler(); } break; case FUNC_EVERY_SECOND: if (10 == TasmotaGlobal.uptime) { AriluxRfInit(); } // Needs rest before enabling RF interrupts break; case FUNC_INTERRUPT_STOP: AriluxRfDisable(); break; case FUNC_INTERRUPT_START: AriluxRfInit(); break; } return result; } #endif // USE_ARILUX_RF #endif // USE_LIGHT