├── FanController.pdf ├── .gitignore ├── rf-fans ├── fanimation.h ├── hamptonbay.h ├── hamptonbay2.h ├── hamptonbay3.h ├── hamptonbay4.h ├── config.h.sample ├── rf-fans.h ├── RCSwitch.h ├── hamptonbay.cpp ├── hamptonbay3.cpp ├── hamptonbay2.cpp ├── hamptonbay4.cpp ├── fanimation.cpp ├── rf-fans.ino └── RCSwitch.cpp ├── .vscode └── extensions.json ├── test └── README ├── platformio.ini ├── lib └── README ├── include └── README └── README.md /FanController.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickdk77/hampton-bay-fan-mqtt/HEAD/FanController.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | rf-fans/config.h 2 | .pio 3 | .vscode/.browse.c_cpp.db* 4 | .vscode/c_cpp_properties.json 5 | .vscode/launch.json 6 | .vscode/ipch 7 | *~ 8 | -------------------------------------------------------------------------------- /rf-fans/fanimation.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | void fanimationMQTT(char* topic, char* payloadChar, unsigned int length); 4 | void fanimationRF(int long value, int prot, int bits); 5 | void fanimationMQTTSub(boolean setup); 6 | void fanimationSetup(); 7 | void fanimationSetupEnd(); 8 | 9 | -------------------------------------------------------------------------------- /rf-fans/hamptonbay.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | void hamptonbayMQTT(char* topic, char* payloadChar, unsigned int length); 4 | void hamptonbayRF(int long value, int prot, int bits); 5 | void hamptonbayMQTTSub(boolean setup); 6 | void hamptonbaySetup(); 7 | void hamptonbaySetupEnd(); 8 | 9 | -------------------------------------------------------------------------------- /rf-fans/hamptonbay2.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | void hamptonbay2MQTT(char* topic, char* payloadChar, unsigned int length); 4 | void hamptonbay2RF(int long value, int prot, int bits); 5 | void hamptonbay2MQTTSub(boolean setup); 6 | void hamptonbay2Setup(); 7 | void hamptonbay2SetupEnd(); 8 | 9 | -------------------------------------------------------------------------------- /rf-fans/hamptonbay3.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | void hamptonbay3MQTT(char* topic, char* payloadChar, unsigned int length); 4 | void hamptonbay3RF(int long value, int prot, int bits); 5 | void hamptonbay3MQTTSub(boolean setup); 6 | void hamptonbay3Setup(); 7 | void hamptonbay3SetupEnd(); 8 | 9 | -------------------------------------------------------------------------------- /rf-fans/hamptonbay4.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | void hamptonbay4MQTT(char* topic, char* payloadChar, unsigned int length); 4 | void hamptonbay4RF(int long value, int prot, int bits); 5 | void hamptonbay4MQTTSub(boolean setup); 6 | void hamptonbay4Setup(); 7 | void hamptonbay4SetupEnd(); 8 | 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | description = Provide ESP8266 / ESP32 control of rf based fans and doorbell alerts using MQTT 13 | src_dir = rf-fans 14 | lib_dir = lib 15 | 16 | [env:d1_mini] 17 | platform = espressif8266 18 | board = d1_mini 19 | framework = arduino 20 | monitor_speed = 115200 21 | upload_protocol = esptool 22 | upload_port = COM7 23 | lib_deps = 24 | lsatan/SmartRC-CC1101-Driver-Lib @ ^2.5.5 25 | knolleary/PubSubClient @ ^2.8 26 | lnlp/EasyLed@^1.1.0 27 | adafruit/DHT sensor library@^1.4.4 28 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /rf-fans/config.h.sample: -------------------------------------------------------------------------------- 1 | // Configure wifi settings 2 | #define WIFI_SSID "ssid" 3 | #define WIFI_PASS "pass" 4 | 5 | #define HOSTNAME "rf-fans" 6 | #define OTA_PASS "" 7 | 8 | // Configure MQTT broker settings 9 | #define MQTT_HOST "192.168.1.1" 10 | #define MQTT_PORT 1883 11 | #define MQTT_USER "USER" 12 | #define MQTT_PASS "PASS" 13 | #define MQTT_CLIENT_NAME HOSTNAME 14 | 15 | #define STATUS_LED 16 | #define STATUS_LED_PIN 3 17 | 18 | // Compile in Hampton Bay FAN-9T (303.631mhz) 19 | #define HAMPTONBAY 20 | 21 | // Compile in Hampton Bay (dawnsun) A25-TX028 (304.015mhz approx) 22 | #define HAMPTONBAY2 23 | 24 | // Compile in Hampton Bay (a few of them) UC-7078TR (303.95mhz approx) 25 | #define HAMPTONBAY3 26 | 27 | // Compile in Hampton Bay UC-7078TR Variant (303.95mhz approx) 28 | #define HAMPTONBAY4 29 | 30 | // Compile in Fanimation Slinger and HarberBreeze (303.870mhz approx) 31 | #define FANIMATION 32 | 33 | // Use 6 speeds for fanimation Slinger 34 | //#define FANIMATION6 35 | 36 | #define HAMPTONBAY_BASE_TOPIC "hamptonbay" 37 | #define HAMPTONBAY2_BASE_TOPIC "hamptonbay2" 38 | #define HAMPTONBAY3_BASE_TOPIC "hamptonbay3" 39 | #define HAMPTONBAY4_BASE_TOPIC "hamptonbay4" 40 | #define FANIMATION_BASE_TOPIC "fanimation" 41 | 42 | // Doorbell pins, define to use 43 | #define DOORBELL1 0 44 | #define DOORBELL2 2 45 | //#define DOORBELL3 3 46 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /rf-fans/rf-fans.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RCSwitch.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "config.h" 10 | #ifdef HAMPTONBAY 11 | #include "hamptonbay.h" 12 | #endif 13 | #ifdef HAMPTONBAY3 14 | #include "hamptonbay3.h" 15 | #endif 16 | #ifdef HAMPTONBAY4 17 | #include "hamptonbay4.h" 18 | #endif 19 | #ifdef HAMPTONBAY2 20 | #include "hamptonbay2.h" 21 | #endif 22 | #ifdef FANIMATION 23 | #include "fanimation.h" 24 | #endif 25 | 26 | #ifdef STATUS_LED 27 | #include "EasyLed.h" 28 | #endif 29 | 30 | #ifdef DHT_SENSOR 31 | #include 32 | #include 33 | #include 34 | #endif 35 | 36 | #define TELE_TOPIC "tele/" 37 | #define CMND_TOPIC "cmnd/" 38 | #define STAT_TOPIC "stat/" 39 | 40 | #define STATUS_TOPIC TELE_TOPIC MQTT_CLIENT_NAME "/LWT" 41 | 42 | // Set receive and transmit pin numbers (GDO0 and GDO2) 43 | #ifdef ESP32 // for esp32! Receiver on GPIO pin 4. Transmit on GPIO pin 2. 44 | #define RX_PIN 4 45 | #define TX_PIN 2 46 | #elif ESP8266 // for esp8266! Receiver on pin 4 = D2. Transmit on pin 5 = D1. 47 | #define RX_PIN 4 48 | #define TX_PIN 5 49 | #else // for Arduino! Receiver on interrupt 0 => that is pin #2. Transmit on pin 6. 50 | #define RX_PIN 0 51 | #define TX_PIN 6 52 | #endif 53 | 54 | // Set CC1101 frequency 55 | #ifndef RX_REEQ 56 | #define RX_FREQ 303.870 // Middle ground, transmitters are very wide branded and picks up all of them 57 | #endif 58 | 59 | // Define fan states 60 | #define FAN_HI 1 61 | #define FAN_MED 3 62 | #define FAN_LOW 5 63 | 64 | #define FAN_VI 1 65 | #define FAN_V 2 66 | #define FAN_IV 3 67 | #define FAN_III 4 68 | #define FAN_II 5 69 | #define FAN_I 6 70 | 71 | #define FAN_PCT_HI 100 72 | #define FAN_PCT_MED 67 73 | #define FAN_PCT_LOW 33 74 | #define FAN_PCT_OFF 0 75 | #define FAN_PCT_VI 100 76 | #define FAN_PCT_V 83 77 | #define FAN_PCT_IV 66 78 | #define FAN_PCT_III 50 79 | #define FAN_PCT_II 33 80 | #define FAN_PCT_I 16 81 | 82 | #define FAN_PCT_OVER 5 // if asking for 35% it will round down to 33% 83 | 84 | #define NO_RF_REPEAT_TIME 300 85 | 86 | #define SLEEP_DELAY 50 87 | 88 | #define HEAP_DELAY_SECONDS 60 89 | #define MQTT_REBOOT_SECONDS 2000 90 | 91 | struct fan 92 | { 93 | bool powerState; 94 | bool fade; 95 | bool directionState; 96 | bool lightState; 97 | bool light2State; 98 | bool fanState; 99 | uint8_t fanSpeed; 100 | }; 101 | 102 | extern RCSwitch mySwitch; 103 | extern WiFiClient espClient; 104 | extern PubSubClient client; 105 | 106 | extern const char *fanStateTable[]; 107 | extern const char *fanFullStateTable[]; 108 | extern const byte dipToRfIds[16]; 109 | extern const char *idStrings[16]; 110 | extern char idchars[]; 111 | extern char outTopic[100]; 112 | extern char outPercent[100]; 113 | 114 | #ifdef DOORBELL1 115 | #ifndef DOORBELL_INT 116 | #define DOORBELL_INT 1 117 | #endif 118 | #endif 119 | 120 | #ifdef DOORBELL2 121 | #ifndef DOORBELL_INT 122 | #define DOORBELL_INT 1 123 | #endif 124 | #endif 125 | 126 | #ifdef DOORBELL3 127 | #ifndef DOORBELL_INT 128 | #define DOORBELL_INT 1 129 | #endif 130 | #endif 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hampton Bay/Fanimation/DawnSun Fan MQTT 2 | 3 | ## Features 4 | * Added rc codes received to mqtt 5 | * reboot of disconnected from mqtt for over 30min 6 | * Added support for percentage based speeds 7 | * Added support for Hampton Bay A25-TX028 rf remotes made by dawnsun 8 | * Added support for fanimation 6speed fan remote controls 9 | * All 3 can operate at the same time 10 | * Modified rc-switch to handle the hamptonbay 24bit codes and the fanimation 12bit codes 11 | * Added support to restore current fan state from mqtt retain storage on startup 12 | * Fixed inverted bits for fanimation 13 | * Added sleep delay 14 | * Added support for HarborBreeze UC-9050T/UC-7080T 15 | * Added support for + and - on fan speeds 16 | * Added doorbell support (I have mounted mine in the doorbell for good full house coverage of the rf) 17 | * Stabilize wifi better 18 | * Added txrcswitch to send raw codes 19 | 20 | ## Overview 21 | ESP8266 project enabling MQTT control for a Hampton Bay fan with a wireless receiver. Wireless communication is performed with a CC1101 wireless transceiver operating at 303 MHz. 22 | 23 | This will also monitor for Hampton Bay RF signals so the state will stay in sync even if the original remote is used to control the fan. 24 | 25 | Fan control is not limited to a single dip switch setting, so up to 16 fans can be controlled with one ESP8266. 26 | 27 | ## Dependencies 28 | This project uses the following libraries that are available through the Arduino IDE 29 | * [SmartRC-CC1101-Driver-Lib](https://github.com/LSatan/SmartRC-CC1101-Driver-Lib) by LSatan 30 | * [rc-switch](https://github.com/sui77/rc-switch) by sui77, moved to inline 31 | * [PubSubClient](https://pubsubclient.knolleary.net/) by Nick O'Leary 32 | 33 | ## Hardware 34 | * ESP8266 development board (Tested with a NodeMCU v2 and a D1 Mini) 35 | * CC1101 wireless transceiver 36 | * Wiring info can be found in the [SmartRC-CC1101-Driver-Lib readme](https://github.com/LSatan/SmartRC-CC1101-Driver-Lib#wiring) 37 | * Schematic for d1mini with CC1101 and doorbell [Schematic](https://github.com/patrickdk77/hampton-bay-fan-mqtt/blob/master/FanController.pdf) 38 | 39 | ## Setup 40 | ### Configuration 41 | Change the `WIFI_*` and `MQTT_*` definitions in the sketch to match your network settings before uploading to the ESP. 42 | ### MQTT 43 | By default, the state/command topics will be 44 | * Fan on/off (payload `ON` or `OFF`) 45 | * `stat/hamptonbay//fan` 46 | * `cmnd/hamptonbay//fan` 47 | * Fan speed (payload `low`, `medium`, or `high`; the fanimation 6speed fans can also use I,II,III,IV,V,VI to set any of the six speeds, but will only report 3 speeds back via mqtt as homeassistant only supports 3 speeds) 48 | * `stat/hamptonbay//speed` 49 | * `cmnd/hamptonbay//speed` 50 | * Light on/off (payload `ON` or `OFF`) 51 | * `stat/hamptonbay//light` 52 | * `cmnd/hamptonbay//light` 53 | 54 | `fan_id` is a 4-digit binary number determined by the dip switch settings on the transmitter/receiver where up = 1 and down = 0. For example, the dip setting: 55 | 56 | |1|2|3|4| 57 | |-|-|-|-| 58 | |↑|↓|↓|↓| 59 | 60 | ...corresponds to a fan ID of `1000` 61 | 62 | ### Home Assistant 63 | When mqtt support for percentage speeds is supported it will use percent instead of speed cmnd and stat 64 | 65 | To use this in Home Assistant as an MQTT Fan and MQTT Light, I'm using this config for hamptonbay 66 | ```yaml 67 | fan: 68 | - platform: mqtt 69 | name: "Bedroom Fan" 70 | availability_topic: "tele/rf-fans/LWT" 71 | payload_available: "Online" 72 | payload_not_available: "Offline" 73 | state_topic: "stat/hamptonbay/1000/fan" 74 | command_topic: "cmnd/hamptonbay/1000/fan" 75 | percentage_state_topic: "stat/hamptonbay/1000/percent" 76 | percentage_command_topic: "cmnd/hamptonbay/1000/percent" 77 | preset_mode_state_topic: "stat/hamptonbay/1000/speed" 78 | preset_mode_command_topic: "cmnd/hamptonbay/1000/speed" 79 | preset_modes: 80 | - "low" 81 | - "medium" 82 | - "high" 83 | 84 | light: 85 | - platform: mqtt 86 | name: "Bedroom Fan Light" 87 | availability_topic: "tele/rf-fans/LWT" 88 | payload_available: "Online" 89 | payload_not_available: "Offline" 90 | state_topic: "stat/hamptonbay/1000/light" 91 | command_topic: "cmnd/hamptonbay/1000/light" 92 | ``` 93 | 94 | For HarborBreeze 95 | ```yaml 96 | fan: 97 | - platform: mqtt 98 | name: "Bedroom Fan" 99 | availability_topic: "tele/rf-fans/LWT" 100 | payload_available: "Online" 101 | payload_not_available: "Offline" 102 | state_topic: "stat/fanimation/1000/fan" 103 | command_topic: "cmnd/fanimation/1000/fan" 104 | percentage_state_topic: "stat/fanimation/1000/percent" 105 | percentage_command_topic: "cmnd/fanimation/1000/percent" 106 | preset_mode_state_topic: "stat/fanimation/1000/speed" 107 | preset_mode_command_topic: "cmnd/fanimation/1000/speed" 108 | preset_modes: 109 | - low 110 | - medium 111 | - high 112 | 113 | light: 114 | - platform: mqtt 115 | name: "Bedroom Fan Light" 116 | availability_topic: "tele/rf-fans/LWT" 117 | payload_available: "Online" 118 | payload_not_available: "Offline" 119 | state_topic: "stat/fanimation/1000/light" 120 | command_topic: "cmnd/fanimation/1000/light" 121 | ``` 122 | 123 | For Fanimation (can use low/medium/high, or if FANIMATION6 is defined can also use all 6 speeds) 124 | ```yaml 125 | fan: 126 | - platform: mqtt 127 | name: "Bedroom Fan" 128 | availability_topic: "tele/rf-fans/LWT" 129 | payload_available: "Online" 130 | payload_not_available: "Offline" 131 | state_topic: "stat/fanimation/1000/fan" 132 | command_topic: "cmnd/fanimation/1000/fan" 133 | percentage_state_topic: "stat/fanimation/1000/percent" 134 | percentage_command_topic: "cmnd/fanimation/1000/percent" 135 | preset_mode_state_topic: "stat/fanimation/1000/speed" 136 | preset_mode_command_topic: "cmnd/fanimation/1000/speed" 137 | preset_modes: 138 | - I 139 | - II 140 | - III 141 | - IV 142 | - V 143 | - VI 144 | 145 | light: 146 | - platform: mqtt 147 | name: "Bedroom Fan Light" 148 | availability_topic: "tele/rf-fans/LWT" 149 | payload_available: "Online" 150 | payload_not_available: "Offline" 151 | state_topic: "stat/fanimation/1000/light" 152 | command_topic: "cmnd/fanimation/1000/light" 153 | ``` 154 | 155 | For Doorbells 156 | ```yaml 157 | binary_sensor: 158 | - platform: mqtt 159 | name: "Doorbell Front" 160 | state_topic: "stat/doorbell/1/state" 161 | availability_topic: "tele/rf-fans/LWT" 162 | payload_available: "Online" 163 | payload_not_available: "Offline" 164 | payload_on: "ON" 165 | payload_off: "OFF" 166 | 167 | - platform: mqtt 168 | name: "Doorbell Rear" 169 | state_topic: "stat/doorbell/2/state" 170 | availability_topic: "tele/rf-fans/LWT" 171 | payload_available: "Online" 172 | payload_not_available: "Offline" 173 | payload_on: "ON" 174 | payload_off: "OFF" 175 | ``` -------------------------------------------------------------------------------- /rf-fans/RCSwitch.h: -------------------------------------------------------------------------------- 1 | /* 2 | RCSwitch - Arduino libary for remote control outlet switches 3 | Copyright (c) 2011 Suat Özgür. All right reserved. 4 | 5 | Contributors: 6 | - Andre Koehler / info(at)tomate-online(dot)de 7 | - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com 8 | - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46 9 | - Dominik Fischer / dom_fischer(at)web(dot)de 10 | - Frank Oltmanns / .(at)gmail(dot)com 11 | - Max Horn / max(at)quendi(dot)de 12 | - Robert ter Vehn / .(at)gmail(dot)com 13 | 14 | Project home: https://github.com/sui77/rc-switch/ 15 | 16 | This library is free software; you can redistribute it and/or 17 | modify it under the terms of the GNU Lesser General Public 18 | License as published by the Free Software Foundation; either 19 | version 2.1 of the License, or (at your option) any later version. 20 | 21 | This library is distributed in the hope that it will be useful, 22 | but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 | Lesser General Public License for more details. 25 | 26 | You should have received a copy of the GNU Lesser General Public 27 | License along with this library; if not, write to the Free Software 28 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 29 | */ 30 | #ifndef _RCSwitch_h 31 | #define _RCSwitch_h 32 | 33 | #if defined(ARDUINO) && ARDUINO >= 100 34 | #include "Arduino.h" 35 | #elif defined(ENERGIA) // LaunchPad, FraunchPad and StellarPad specific 36 | #include "Energia.h" 37 | #elif defined(RPI) // Raspberry Pi 38 | #define RaspberryPi 39 | 40 | // Include libraries for RPi: 41 | #include /* memcpy */ 42 | #include /* abs */ 43 | #include 44 | #elif defined(SPARK) 45 | #include "application.h" 46 | #else 47 | #include "WProgram.h" 48 | #endif 49 | 50 | #include 51 | 52 | 53 | // At least for the ATTiny X4/X5, receiving has to be disabled due to 54 | // missing libm depencies (udivmodhi4) 55 | #if defined( __AVR_ATtinyX5__ ) or defined ( __AVR_ATtinyX4__ ) 56 | #define RCSwitchDisableReceiving 57 | #endif 58 | 59 | // Number of maximum high/Low changes per packet. 60 | // We can handle up to (unsigned long) => 32 bit * 2 H/L changes per bit + 2 for sync 61 | #ifdef RCSwitch64 62 | #define RCSWITCH_MAX_CHANGES 131 63 | #else 64 | #define RCSWITCH_MAX_CHANGES 67 65 | #endif 66 | 67 | class RCSwitch { 68 | 69 | public: 70 | RCSwitch(); 71 | 72 | void switchOn(int nGroupNumber, int nSwitchNumber); 73 | void switchOff(int nGroupNumber, int nSwitchNumber); 74 | void switchOn(const char* sGroup, int nSwitchNumber); 75 | void switchOff(const char* sGroup, int nSwitchNumber); 76 | void switchOn(char sFamily, int nGroup, int nDevice); 77 | void switchOff(char sFamily, int nGroup, int nDevice); 78 | void switchOn(const char* sGroup, const char* sDevice); 79 | void switchOff(const char* sGroup, const char* sDevice); 80 | void switchOn(char sGroup, int nDevice); 81 | void switchOff(char sGroup, int nDevice); 82 | 83 | void sendTriState(const char* sCodeWord); 84 | #ifdef RCSwitch64 85 | void send(unsigned long long code, unsigned int length); 86 | #else 87 | void send(unsigned long code, unsigned int length); 88 | #endif 89 | void send(const char* sCodeWord); 90 | 91 | #if not defined( RCSwitchDisableReceiving ) 92 | void enableReceive(int interrupt); 93 | void enableReceive(); 94 | void disableReceive(); 95 | bool available(); 96 | void resetAvailable(); 97 | 98 | #ifdef RCSwitch64 99 | unsigned long long getReceivedValue(); 100 | #else 101 | unsigned long getReceivedValue(); 102 | #endif 103 | unsigned int getReceivedBitlength(); 104 | unsigned int getReceivedDelay(); 105 | unsigned int getReceivedProtocol(); 106 | unsigned int* getReceivedRawdata(); 107 | #endif 108 | 109 | void enableTransmit(int nTransmitterPin); 110 | void disableTransmit(); 111 | void setPulseLength(int nPulseLength); 112 | void setRepeatTransmit(int nRepeatTransmit); 113 | #if not defined( RCSwitchDisableReceiving ) 114 | void setReceiveTolerance(int nPercent); 115 | #endif 116 | 117 | /** 118 | * Description of a single pule, which consists of a high signal 119 | * whose duration is "high" times the base pulse length, followed 120 | * by a low signal lasting "low" times the base pulse length. 121 | * Thus, the pulse overall lasts (high+low)*pulseLength 122 | */ 123 | struct HighLow { 124 | uint8_t high; 125 | uint8_t low; 126 | }; 127 | 128 | /** 129 | * A "protocol" describes how zero and one bits are encoded into high/low 130 | * pulses. 131 | */ 132 | struct Protocol { 133 | /** base pulse length in microseconds, e.g. 350 */ 134 | uint16_t pulseLength; 135 | 136 | HighLow syncFactor; 137 | HighLow zero; 138 | HighLow one; 139 | 140 | /** 141 | * If true, interchange high and low logic levels in all transmissions. 142 | * 143 | * By default, RCSwitch assumes that any signals it sends or receives 144 | * can be broken down into pulses which start with a high signal level, 145 | * followed by a a low signal level. This is e.g. the case for the 146 | * popular PT 2260 encoder chip, and thus many switches out there. 147 | * 148 | * But some devices do it the other way around, and start with a low 149 | * signal level, followed by a high signal level, e.g. the HT6P20B. To 150 | * accommodate this, one can set invertedSignal to true, which causes 151 | * RCSwitch to change how it interprets any HighLow struct FOO: It will 152 | * then assume transmissions start with a low signal lasting 153 | * FOO.high*pulseLength microseconds, followed by a high signal lasting 154 | * FOO.low*pulseLength microseconds. 155 | */ 156 | bool invertedSignal; 157 | }; 158 | 159 | void setProtocol(Protocol protocol); 160 | void setProtocol(int nProtocol); 161 | void setProtocol(int nProtocol, int nPulseLength); 162 | 163 | private: 164 | char* getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus); 165 | char* getCodeWordB(int nGroupNumber, int nSwitchNumber, bool bStatus); 166 | char* getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus); 167 | char* getCodeWordD(char group, int nDevice, bool bStatus); 168 | void transmit(HighLow pulses); 169 | 170 | #if not defined( RCSwitchDisableReceiving ) 171 | static void handleInterrupt(); 172 | static bool receiveProtocol(const int p, unsigned int changeCount); 173 | int nReceiverInterrupt; 174 | #endif 175 | int nTransmitterPin; 176 | int nRepeatTransmit; 177 | 178 | Protocol protocol; 179 | 180 | #if not defined( RCSwitchDisableReceiving ) 181 | static int nReceiveTolerance; 182 | #ifdef RCSwitch64 183 | volatile static unsigned long long nReceivedValue; 184 | #else 185 | volatile static unsigned long nReceivedValue; 186 | #endif 187 | volatile static unsigned int nReceivedBitlength; 188 | volatile static unsigned int nReceivedDelay; 189 | volatile static unsigned int nReceivedProtocol; 190 | const static unsigned int nSeparationLimit; 191 | /* 192 | * timings[0] contains sync timing, followed by a number of bits 193 | */ 194 | static unsigned int timings[RCSWITCH_MAX_CHANGES]; 195 | #endif 196 | 197 | 198 | }; 199 | 200 | #endif 201 | -------------------------------------------------------------------------------- /rf-fans/hamptonbay.cpp: -------------------------------------------------------------------------------- 1 | #include "rf-fans.h" 2 | 3 | 4 | #define BASE_TOPIC HAMPTONBAY_BASE_TOPIC 5 | 6 | #define CMND_BASE_TOPIC CMND_TOPIC BASE_TOPIC 7 | #define STAT_BASE_TOPIC STAT_TOPIC BASE_TOPIC 8 | 9 | #define SUBSCRIBE_TOPIC_CMND CMND_BASE_TOPIC "/#" 10 | 11 | #define SUBSCRIBE_TOPIC_STAT_SETUP STAT_BASE_TOPIC "/#" 12 | 13 | #ifndef HAMPTONBAY_TX_FREQ 14 | #define TX_FREQ 303.631 // FAN-9T 15 | #else 16 | #define TX_FREQ HAMPTONBAY_TX_FREQ 17 | #endif 18 | 19 | // RC-switch settings 20 | #define RF_PROTOCOL 6 21 | #define RF_REPEATS 8 22 | 23 | // Keep track of states for all dip settings 24 | static fan fans[16]; 25 | 26 | static void postStateUpdate(int id) { 27 | sprintf(outTopic, "%s/%s/fan", STAT_BASE_TOPIC, idStrings[id]); 28 | client.publish(outTopic, fans[id].fanState ? "ON":"OFF", true); 29 | sprintf(outTopic, "%s/%s/speed", STAT_BASE_TOPIC, idStrings[id]); 30 | client.publish(outTopic, fanStateTable[fans[id].fanSpeed], true); 31 | sprintf(outTopic, "%s/%s/light", STAT_BASE_TOPIC, idStrings[id]); 32 | client.publish(outTopic, fans[id].lightState ? "ON":"OFF", true); 33 | 34 | sprintf(outTopic, "%s/%s/percent", STAT_BASE_TOPIC, idStrings[id]); 35 | *outPercent='\0'; 36 | if(fans[id].fanState) { 37 | switch(fans[id].fanSpeed) { 38 | case FAN_HI: 39 | sprintf(outPercent,"%d",FAN_PCT_HI); 40 | break; 41 | case FAN_MED: 42 | sprintf(outPercent,"%d",FAN_PCT_MED); 43 | break; 44 | case FAN_LOW: 45 | sprintf(outPercent,"%d",FAN_PCT_LOW); 46 | break; 47 | } 48 | } else 49 | sprintf(outPercent,"%d",FAN_PCT_OFF); 50 | client.publish(outTopic, outPercent, true); 51 | } 52 | 53 | static void transmitState(int fanId) { 54 | mySwitch.disableReceive(); // Receiver off 55 | ELECHOUSE_cc1101.setMHZ(TX_FREQ); 56 | ELECHOUSE_cc1101.SetTx(); // set Transmit on 57 | mySwitch.enableTransmit(TX_PIN); // Transmit on 58 | mySwitch.setRepeatTransmit(RF_REPEATS); // transmission repetitions. 59 | mySwitch.setProtocol(RF_PROTOCOL); // send Received Protocol 60 | 61 | // Build out RF code 62 | // Code follows the 21 bit pattern 63 | // 000aaaa000000lff00000 64 | // Where a is the inversed/reversed dip setting, 65 | // l is light state, ff is fan speed 66 | int fanRf = fans[fanId].fanState ? fans[fanId].fanSpeed : 0; 67 | int rfCode = dipToRfIds[((~fanId)&0x0f)] << 14 | fans[fanId].lightState << 7 | fanRf << 5; 68 | 69 | mySwitch.send(rfCode, 21); // send 21 bit code 70 | mySwitch.disableTransmit(); // set Transmit off 71 | ELECHOUSE_cc1101.setMHZ(RX_FREQ); 72 | ELECHOUSE_cc1101.SetRx(); // set Receive on 73 | mySwitch.enableReceive(RX_PIN); // Receiver on 74 | Serial.print("Sent command hamptonbay: "); 75 | Serial.print(fanId); 76 | Serial.print(" "); 77 | for(int b=21; b>0; b--) { 78 | Serial.print(bitRead(rfCode,b-1)); 79 | } 80 | Serial.println(""); 81 | postStateUpdate(fanId); 82 | } 83 | 84 | void hamptonbayMQTT(char* topic, char* payloadChar, unsigned int length) { 85 | if(strncmp(topic, CMND_BASE_TOPIC, sizeof(CMND_BASE_TOPIC)-1) == 0) { 86 | 87 | // Get ID after the base topic + a slash 88 | char id[5]; 89 | int percent; 90 | memcpy(id, &topic[sizeof(CMND_BASE_TOPIC)], 4); 91 | id[4] = '\0'; 92 | if(strspn(id, idchars)) { 93 | uint8_t idint = strtol(id, (char**) NULL, 2); 94 | char *attr; 95 | // Split by slash after ID in topic to get attribute and action 96 | 97 | attr = strtok(topic+sizeof(CMND_BASE_TOPIC)-1 + 5, "/"); 98 | 99 | if(attr == NULL) return; 100 | 101 | if(strcmp(attr,"percent") ==0) { 102 | percent=atoi(payloadChar); 103 | if(percent > FAN_PCT_OVER) { 104 | fans[idint].fanState = true; 105 | if(percent > (FAN_PCT_MED + FAN_PCT_OVER)) { 106 | fans[idint].fanSpeed=FAN_HI; 107 | } else if(percent > (FAN_PCT_LOW + FAN_PCT_OVER)) { 108 | fans[idint].fanSpeed=FAN_MED; 109 | } else { 110 | fans[idint].fanSpeed=FAN_LOW; 111 | } 112 | } else { 113 | fans[idint].fanState = false; 114 | } 115 | } else if(strcmp(attr,"fan") ==0) { 116 | if(strcmp(payloadChar,"toggle") == 0) { 117 | if(fans[idint].fanState) 118 | strcpy(payloadChar,"off"); 119 | else 120 | strcpy(payloadChar,"on"); 121 | } 122 | if(strcmp(payloadChar,"on") == 0) { 123 | fans[idint].fanState = true; 124 | } else { 125 | fans[idint].fanState = false; 126 | } 127 | } else if(strcmp(attr,"speed") ==0) { 128 | if(strcmp(payloadChar,"+") ==0) { 129 | fans[idint].fanState = true; 130 | switch(fans[idint].fanSpeed) { 131 | case FAN_LOW: 132 | fans[idint].fanSpeed=FAN_MED; 133 | break; 134 | case FAN_MED: 135 | fans[idint].fanSpeed=FAN_HI; 136 | break; 137 | case FAN_HI: 138 | fans[idint].fanSpeed=FAN_HI; 139 | break; 140 | default: 141 | if(fans[idint].fanSpeed>FAN_HI) 142 | fans[idint].fanSpeed--; 143 | break; 144 | } 145 | } else if(strcmp(payloadChar,"-") ==0) { 146 | fans[idint].fanState = true; 147 | switch(fans[idint].fanSpeed) { 148 | case FAN_HI: 149 | fans[idint].fanSpeed=FAN_MED; 150 | break; 151 | case FAN_MED: 152 | fans[idint].fanSpeed=FAN_LOW; 153 | break; 154 | case FAN_LOW: 155 | fans[idint].fanSpeed=FAN_LOW; 156 | break; 157 | default: 158 | if(fans[idint].fanSpeed> 14)&0x0f; 234 | // Got a correct id in the correct protocol 235 | if(id < 16) { 236 | // reverse order of bits 237 | int dipId = dipToRfIds[id]; 238 | // Blank out id in message to get light state 239 | int states = value & 0b11111111; 240 | fans[dipId].lightState = states >> 7; 241 | // Blank out light state to get fan state 242 | switch((states & 0b01111111) >> 5) { 243 | case 0: 244 | fans[dipId].fanState = false; 245 | break; 246 | case 1: 247 | fans[dipId].fanState = true; 248 | fans[dipId].fanSpeed = FAN_HI; 249 | break; 250 | case 2: 251 | fans[dipId].fanState = true; 252 | fans[dipId].fanSpeed = FAN_MED; 253 | break; 254 | case 3: 255 | fans[dipId].fanState = true; 256 | fans[dipId].fanSpeed = FAN_LOW; 257 | break; 258 | } 259 | postStateUpdate(dipId); 260 | } 261 | } 262 | } 263 | 264 | void hamptonbayMQTTSub(boolean setup) { 265 | client.subscribe(SUBSCRIBE_TOPIC_CMND); 266 | if(setup) client.subscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 267 | } 268 | 269 | void hamptonbaySetup() { 270 | // initialize fan struct 271 | for(int i=0; i<16; i++) { 272 | fans[i].lightState = false; 273 | fans[i].fanState = false; 274 | fans[i].fanSpeed = FAN_LOW; 275 | } 276 | } 277 | 278 | void hamptonbaySetupEnd() { 279 | client.unsubscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 280 | } 281 | -------------------------------------------------------------------------------- /rf-fans/hamptonbay3.cpp: -------------------------------------------------------------------------------- 1 | #include "rf-fans.h" 2 | 3 | 4 | #define BASE_TOPIC HAMPTONBAY3_BASE_TOPIC 5 | 6 | #define CMND_BASE_TOPIC CMND_TOPIC BASE_TOPIC 7 | #define STAT_BASE_TOPIC STAT_TOPIC BASE_TOPIC 8 | 9 | #define SUBSCRIBE_TOPIC_CMND CMND_BASE_TOPIC "/#" 10 | 11 | #define SUBSCRIBE_TOPIC_STAT_SETUP STAT_BASE_TOPIC "/#" 12 | 13 | #ifndef HAMPTONBAY3_TX_FREQ 14 | #define TX_FREQ 304.95 // UC7078TR Hampton Bay made by Chia Wei Electric 15 | #else 16 | #define TX_FREQ HAMPTONBAY3_TX_FREQ 17 | #endif 18 | 19 | // RC-switch settings 20 | #define RF_PROTOCOL 13 21 | #define RF_REPEATS 8 22 | 23 | // Keep track of states for all dip settings 24 | static fan fans[16]; 25 | 26 | static int long lastvalue; 27 | static unsigned long lasttime; 28 | 29 | static void postStateUpdate(int id) { 30 | sprintf(outTopic, "%s/%s/direction", STAT_BASE_TOPIC, idStrings[id]); 31 | client.publish(outTopic, fans[id].directionState ? "REVERSE":"FORWARD", true); 32 | sprintf(outTopic, "%s/%s/fan", STAT_BASE_TOPIC, idStrings[id]); 33 | client.publish(outTopic, fans[id].fanState ? "ON":"OFF", true); 34 | sprintf(outTopic, "%s/%s/speed", STAT_BASE_TOPIC, idStrings[id]); 35 | client.publish(outTopic, fanStateTable[fans[id].fanSpeed], true); 36 | sprintf(outTopic, "%s/%s/light", STAT_BASE_TOPIC, idStrings[id]); 37 | client.publish(outTopic, fans[id].lightState ? "ON":"OFF", true); 38 | 39 | sprintf(outTopic, "%s/%s/percent", STAT_BASE_TOPIC, idStrings[id]); 40 | *outPercent='\0'; 41 | if(fans[id].fanState) { 42 | switch(fans[id].fanSpeed) { 43 | case FAN_HI: 44 | sprintf(outPercent,"%d",FAN_PCT_HI); 45 | break; 46 | case FAN_MED: 47 | sprintf(outPercent,"%d",FAN_PCT_MED); 48 | break; 49 | case FAN_LOW: 50 | sprintf(outPercent,"%d",FAN_PCT_LOW); 51 | break; 52 | } 53 | } else 54 | sprintf(outPercent,"%d",FAN_PCT_OFF); 55 | client.publish(outTopic, outPercent, true); 56 | } 57 | 58 | static void transmitState(int fanId, int code) { 59 | mySwitch.disableReceive(); // Receiver off 60 | ELECHOUSE_cc1101.setMHZ(TX_FREQ); 61 | ELECHOUSE_cc1101.SetTx(); // set Transmit on 62 | mySwitch.enableTransmit(TX_PIN); // Transmit on 63 | mySwitch.setRepeatTransmit(RF_REPEATS); // transmission repetitions. 64 | mySwitch.setProtocol(RF_PROTOCOL); // send Received Protocol 65 | 66 | // Build out RF code 67 | // Code follows the 12 bit pattern, built ontop of harberbreeze? 68 | // lLOR??MHaaaa 69 | // Where a is the inversed/reversed dip setting, 70 | // 11111110 H 0xfe 71 | // 11111101 M 0xfd 72 | // 10111111 L 0xbf 73 | // 11011111 Off 0xdf 74 | // 11101111 Reverse 0xef 75 | // 01111111 Light 0x7f 76 | // Harber Breeze UC-7078TR 77 | 78 | int rfCode = ((~fanId) &0x0f) | ((code&0xff)<<4); 79 | 80 | mySwitch.send(rfCode, 12); // send 24 bit code 81 | mySwitch.disableTransmit(); // set Transmit off 82 | ELECHOUSE_cc1101.setMHZ(RX_FREQ); 83 | ELECHOUSE_cc1101.SetRx(); // set Receive on 84 | mySwitch.enableReceive(RX_PIN); // Receiver on 85 | Serial.print("Sent command hamptonbay3: "); 86 | Serial.print(fanId); 87 | Serial.print(" "); 88 | for(int b=24; b>0; b--) { 89 | Serial.print(bitRead(rfCode,b-1)); 90 | } 91 | Serial.println(""); 92 | postStateUpdate(fanId); 93 | } 94 | 95 | void hamptonbay3MQTT(char* topic, char* payloadChar, unsigned int length) { 96 | if(strncmp(topic, CMND_BASE_TOPIC, sizeof(CMND_BASE_TOPIC)-1) == 0) { 97 | 98 | // Get ID after the base topic + a slash 99 | char id[5]; 100 | int percent; 101 | memcpy(id, &topic[sizeof(CMND_BASE_TOPIC)], 4); 102 | id[4] = '\0'; 103 | if(strspn(id, idchars)) { 104 | uint8_t idint = strtol(id, (char**) NULL, 2); 105 | char *attr; 106 | // Split by slash after ID in topic to get attribute and action 107 | 108 | attr = strtok(topic+sizeof(CMND_BASE_TOPIC)-1 + 5, "/"); 109 | 110 | if(attr == NULL) return; 111 | 112 | if(strcmp(attr,"percent") ==0) { 113 | percent=atoi(payloadChar); 114 | if(percent > FAN_PCT_OVER) { 115 | fans[idint].fanState = true; 116 | if(percent > (FAN_PCT_MED + FAN_PCT_OVER)) { 117 | fans[idint].fanSpeed=FAN_HI; 118 | transmitState(idint,0xfe); 119 | } else if(percent > (FAN_PCT_LOW + FAN_PCT_OVER)) { 120 | fans[idint].fanSpeed=FAN_MED; 121 | transmitState(idint,0xfd); 122 | } else { 123 | fans[idint].fanSpeed=FAN_LOW; 124 | transmitState(idint,0xbf); 125 | } 126 | } else { 127 | fans[idint].fanState = false; 128 | transmitState(idint,0xdf); 129 | } 130 | } else if(strcmp(attr,"fan") ==0) { 131 | if(strcmp(payloadChar,"toggle") == 0) { 132 | if(fans[idint].fanState) 133 | strcpy(payloadChar,"off"); 134 | else 135 | strcpy(payloadChar,"on"); 136 | } 137 | if(strcmp(payloadChar,"on") == 0) { 138 | fans[idint].fanState = true; 139 | switch(fans[idint].fanSpeed) { 140 | case FAN_HI: 141 | transmitState(idint,0xfe); 142 | break; 143 | case FAN_MED: 144 | transmitState(idint,0xfd); 145 | break; 146 | case FAN_LOW: 147 | transmitState(idint,0xbf); 148 | break; 149 | } 150 | } else { 151 | fans[idint].fanState = false; 152 | transmitState(idint,0xdf); 153 | } 154 | } else if(strcmp(attr,"speed") ==0) { 155 | if(strcmp(payloadChar,"+") ==0) { 156 | fans[idint].fanState = true; 157 | switch(fans[idint].fanSpeed) { 158 | case FAN_LOW: 159 | fans[idint].fanSpeed=FAN_MED; 160 | transmitState(idint,0xfd); 161 | break; 162 | case FAN_MED: 163 | fans[idint].fanSpeed=FAN_HI; 164 | transmitState(idint,0xfe); 165 | break; 166 | case FAN_HI: 167 | fans[idint].fanSpeed=FAN_HI; 168 | transmitState(idint,0xfe); 169 | break; 170 | default: 171 | if(fans[idint].fanSpeed>FAN_HI) 172 | fans[idint].fanSpeed--; 173 | break; 174 | } 175 | } else if(strcmp(payloadChar,"-") ==0) { 176 | fans[idint].fanState = true; 177 | switch(fans[idint].fanSpeed) { 178 | case FAN_HI: 179 | fans[idint].fanSpeed=FAN_MED; 180 | transmitState(idint,0xfd); 181 | break; 182 | case FAN_MED: 183 | fans[idint].fanSpeed=FAN_LOW; 184 | transmitState(idint,0xbf); 185 | break; 186 | case FAN_LOW: 187 | fans[idint].fanSpeed=FAN_LOW; 188 | transmitState(idint,0xbf); 189 | break; 190 | default: 191 | if(fans[idint].fanSpeed10 && prot<15) && bits == 12 && ((value&0x0c0)==0x0c0)) { 300 | unsigned long t=millis(); 301 | if(value == lastvalue) { 302 | if(t - lasttime < NO_RF_REPEAT_TIME) 303 | return; 304 | lasttime=t; 305 | } 306 | lastvalue=value; 307 | lasttime=t; 308 | int dipId = (~value)&0x0f; 309 | // Got a correct id in the correct protocol 310 | if(dipId < 16) { 311 | // Blank out id in message to get light state 312 | switch((value>>4)&0xff) { 313 | case 0xef: // Direction 314 | fans[dipId].directionState=!(fans[dipId].directionState); 315 | break; 316 | case 0x7f: // Light 317 | fans[dipId].lightState = !(fans[dipId].lightState); 318 | break; 319 | case 0xbf: // Fan Low 320 | fans[dipId].fanState = true; 321 | fans[dipId].fanSpeed = FAN_LOW; 322 | break; 323 | case 0xfd: // Fan Med 324 | fans[dipId].fanState = true; 325 | fans[dipId].fanSpeed = FAN_MED; 326 | break; 327 | case 0xfe: // Fan Hi 328 | fans[dipId].fanState = true; 329 | fans[dipId].fanSpeed = FAN_HI; 330 | break; 331 | case 0xdf: // Fan Off 332 | fans[dipId].fanState = false; 333 | break; 334 | } 335 | postStateUpdate(dipId); 336 | } 337 | } 338 | } 339 | 340 | void hamptonbay3MQTTSub(boolean setup) { 341 | client.subscribe(SUBSCRIBE_TOPIC_CMND); 342 | if(setup) client.subscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 343 | } 344 | 345 | void hamptonbay3Setup() { 346 | lasttime=0; 347 | lastvalue=0; 348 | // initialize fan struct 349 | for(int i=0; i<16; i++) { 350 | fans[i].powerState = false; 351 | fans[i].lightState = false; 352 | fans[i].fanState = false; 353 | fans[i].fanSpeed = FAN_LOW; 354 | fans[i].directionState = false; 355 | } 356 | } 357 | 358 | void hamptonbay3SetupEnd() { 359 | client.unsubscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 360 | } 361 | -------------------------------------------------------------------------------- /rf-fans/hamptonbay2.cpp: -------------------------------------------------------------------------------- 1 | #include "rf-fans.h" 2 | 3 | 4 | #define BASE_TOPIC HAMPTONBAY2_BASE_TOPIC 5 | 6 | #define CMND_BASE_TOPIC CMND_TOPIC BASE_TOPIC 7 | #define STAT_BASE_TOPIC STAT_TOPIC BASE_TOPIC 8 | 9 | #define SUBSCRIBE_TOPIC_CMND CMND_BASE_TOPIC "/#" 10 | 11 | #define SUBSCRIBE_TOPIC_STAT_SETUP STAT_BASE_TOPIC "/#" 12 | 13 | #ifndef HAMPTONBAY2_TX_FREQ 14 | #define TX_FREQ 304.015 // a25-tx028 Hampton 15 | #else 16 | #define TX_FREQ HAMPTONBAY2_TX_FREQ 17 | #endif 18 | 19 | // RC-switch settings 20 | #define RF_PROTOCOL 14 21 | #define RF_REPEATS 8 22 | 23 | // Keep track of states for all dip settings 24 | static fan fans[16]; 25 | 26 | static int long lastvalue; 27 | static unsigned long lasttime; 28 | 29 | static void postStateUpdate(int id) { 30 | sprintf(outTopic, "%s/%s/power", STAT_BASE_TOPIC, idStrings[id]); 31 | client.publish(outTopic, fans[id].powerState ? "ON":"OFF", true); 32 | sprintf(outTopic, "%s/%s/fan", STAT_BASE_TOPIC, idStrings[id]); 33 | client.publish(outTopic, fans[id].fanState ? "ON":"OFF", true); 34 | sprintf(outTopic, "%s/%s/speed", STAT_BASE_TOPIC, idStrings[id]); 35 | client.publish(outTopic, fanStateTable[fans[id].fanSpeed], true); 36 | sprintf(outTopic, "%s/%s/light", STAT_BASE_TOPIC, idStrings[id]); 37 | client.publish(outTopic, fans[id].lightState ? "ON":"OFF", true); 38 | 39 | sprintf(outTopic, "%s/%s/percent", STAT_BASE_TOPIC, idStrings[id]); 40 | *outPercent='\0'; 41 | if(fans[id].fanState) { 42 | switch(fans[id].fanSpeed) { 43 | case FAN_HI: 44 | sprintf(outPercent,"%d",FAN_PCT_HI); 45 | break; 46 | case FAN_MED: 47 | sprintf(outPercent,"%d",FAN_PCT_MED); 48 | break; 49 | case FAN_LOW: 50 | sprintf(outPercent,"%d",FAN_PCT_LOW); 51 | break; 52 | } 53 | } else 54 | sprintf(outPercent,"%d",FAN_PCT_OFF); 55 | client.publish(outTopic, outPercent, true); 56 | } 57 | 58 | static void transmitState(int fanId, int code) { 59 | mySwitch.disableReceive(); // Receiver off 60 | ELECHOUSE_cc1101.setMHZ(TX_FREQ); 61 | ELECHOUSE_cc1101.SetTx(); // set Transmit on 62 | mySwitch.enableTransmit(TX_PIN); // Transmit on 63 | mySwitch.setRepeatTransmit(RF_REPEATS); // transmission repetitions. 64 | mySwitch.setProtocol(RF_PROTOCOL); // send Received Protocol 65 | 66 | // Build out RF code 67 | // Code follows the 24 bit pattern 68 | // 111111000110aaaa011cccdd 69 | // Where a is the inversed/reversed dip setting, 70 | // ccc is the command (111 Power, 101 Fan, 100 Light, 011 Dim/Temp) 71 | // dd is the data value 72 | int rfCode = 0xfc6000 | ((~fanId) &0x0f) << 8 | (code&0xff); 73 | 74 | mySwitch.send(rfCode, 24); // send 24 bit code 75 | mySwitch.disableTransmit(); // set Transmit off 76 | ELECHOUSE_cc1101.setMHZ(RX_FREQ); 77 | ELECHOUSE_cc1101.SetRx(); // set Receive on 78 | mySwitch.enableReceive(RX_PIN); // Receiver on 79 | Serial.print("Sent command hamptonbay2: "); 80 | Serial.print(fanId); 81 | Serial.print(" "); 82 | for(int b=24; b>0; b--) { 83 | Serial.print(bitRead(rfCode,b-1)); 84 | } 85 | Serial.println(""); 86 | postStateUpdate(fanId); 87 | } 88 | 89 | void hamptonbay2MQTT(char* topic, char* payloadChar, unsigned int length) { 90 | if(strncmp(topic, CMND_BASE_TOPIC, sizeof(CMND_BASE_TOPIC)-1) == 0) { 91 | 92 | // Get ID after the base topic + a slash 93 | char id[5]; 94 | int percent; 95 | memcpy(id, &topic[sizeof(CMND_BASE_TOPIC)], 4); 96 | id[4] = '\0'; 97 | if(strspn(id, idchars)) { 98 | uint8_t idint = strtol(id, (char**) NULL, 2); 99 | char *attr; 100 | // Split by slash after ID in topic to get attribute and action 101 | 102 | attr = strtok(topic+sizeof(CMND_BASE_TOPIC)-1 + 5, "/"); 103 | 104 | if(attr==NULL) return; 105 | 106 | if(strcmp(attr,"percent") ==0) { 107 | percent=atoi(payloadChar); 108 | if(percent > FAN_PCT_OVER) { 109 | fans[idint].fanState = true; 110 | if(fans[idint].powerState==false) { 111 | fans[idint].powerState=true; 112 | transmitState(idint,0x7e); // Turn on 113 | } 114 | if(percent > (FAN_PCT_MED + FAN_PCT_OVER)) { 115 | fans[idint].fanSpeed=FAN_HI; 116 | transmitState(idint,0x74); 117 | } else if(percent > (FAN_PCT_LOW + FAN_PCT_OVER)) { 118 | fans[idint].fanSpeed=FAN_MED; 119 | transmitState(idint,0x75); 120 | } else { 121 | fans[idint].fanSpeed=FAN_LOW; 122 | transmitState(idint,0x76); 123 | } 124 | } else { 125 | fans[idint].fanState = false; 126 | transmitState(idint,0x77); 127 | } 128 | } else if(strcmp(attr,"fan") ==0) { 129 | if(strcmp(payloadChar,"toggle") == 0) { 130 | if(fans[idint].fanState) 131 | strcpy(payloadChar,"off"); 132 | else 133 | strcpy(payloadChar,"on"); 134 | } 135 | if(strcmp(payloadChar,"on") == 0) { 136 | fans[idint].fanState = true; 137 | if(fans[idint].powerState==false) { 138 | fans[idint].powerState=true; 139 | transmitState(idint,0x7e); // Turn on 140 | } 141 | switch(fans[idint].fanSpeed) { 142 | case FAN_HI: 143 | transmitState(idint,0x74); 144 | break; 145 | case FAN_MED: 146 | transmitState(idint,0x75); 147 | break; 148 | case FAN_LOW: 149 | transmitState(idint,0x76); 150 | break; 151 | } 152 | } else { 153 | fans[idint].fanState = false; 154 | transmitState(idint,0x77); 155 | } 156 | } else if(strcmp(attr,"speed") ==0) { 157 | if(strcmp(payloadChar,"+") ==0) { 158 | if(fans[idint].powerState==false) { 159 | fans[idint].powerState=true; 160 | transmitState(idint,0x7e); // Turn on 161 | } 162 | fans[idint].fanState = true; 163 | switch(fans[idint].fanSpeed) { 164 | case FAN_LOW: 165 | fans[idint].fanSpeed=FAN_MED; 166 | transmitState(idint,0x75); 167 | break; 168 | case FAN_MED: 169 | fans[idint].fanSpeed=FAN_HI; 170 | transmitState(idint,0x74); 171 | break; 172 | case FAN_HI: 173 | fans[idint].fanSpeed=FAN_HI; 174 | transmitState(idint,0x74); 175 | break; 176 | default: 177 | if(fans[idint].fanSpeed>FAN_HI) 178 | fans[idint].fanSpeed--; 179 | break; 180 | } 181 | } else if(strcmp(payloadChar,"-") ==0) { 182 | if(fans[idint].powerState==false) { 183 | fans[idint].powerState=true; 184 | transmitState(idint,0x7e); // Turn on 185 | } 186 | fans[idint].fanState = true; 187 | switch(fans[idint].fanSpeed) { 188 | case FAN_HI: 189 | fans[idint].fanSpeed=FAN_MED; 190 | transmitState(idint,0x75); 191 | break; 192 | case FAN_MED: 193 | fans[idint].fanSpeed=FAN_LOW; 194 | transmitState(idint,0x76); 195 | break; 196 | case FAN_LOW: 197 | fans[idint].fanSpeed=FAN_LOW; 198 | transmitState(idint,0x76); 199 | break; 200 | default: 201 | if(fans[idint].fanSpeed10 && prot<15) && bits == 24 && ((value&0xfff000)==0xfc6000)) { 324 | unsigned long t=millis(); 325 | if(value == lastvalue) { 326 | if(t - lasttime < NO_RF_REPEAT_TIME) 327 | return; 328 | lasttime=t; 329 | } 330 | lastvalue=value; 331 | lasttime=t; 332 | int dipId = (~value >> 8)&0x0f; 333 | // Got a correct id in the correct protocol 334 | if(dipId < 16) { 335 | // Blank out id in message to get light state 336 | switch(value&0xff) { 337 | case 0x7e: // PowerOn 338 | fans[dipId].powerState = true; 339 | break; 340 | case 0x7d: // PowerOff 341 | fans[dipId].powerState = false; 342 | break; 343 | case 0x72: // LightOn 344 | fans[dipId].lightState = true; 345 | break; 346 | case 0x71: // LightOff 347 | fans[dipId].lightState = false; 348 | break; 349 | case 0x6e: // Light Dim 350 | break; 351 | case 0x6d: // Light Tempature (2k, 3k, 5k) 352 | break; 353 | case 0x74: // Fan High 354 | fans[dipId].fanState = true; 355 | fans[dipId].fanSpeed = FAN_HI; 356 | break; 357 | case 0x75: // Fan Med 358 | fans[dipId].fanState = true; 359 | fans[dipId].fanSpeed = FAN_MED; 360 | break; 361 | case 0x76: // Fan Low 362 | fans[dipId].fanState = true; 363 | fans[dipId].fanSpeed = FAN_LOW; 364 | break; 365 | case 0x77: // Fan Off 366 | fans[dipId].fanState = false; 367 | break; 368 | } 369 | postStateUpdate(dipId); 370 | } 371 | } 372 | } 373 | 374 | void hamptonbay2MQTTSub(boolean setup) { 375 | client.subscribe(SUBSCRIBE_TOPIC_CMND); 376 | if(setup) client.subscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 377 | } 378 | 379 | void hamptonbay2Setup() { 380 | lasttime=0; 381 | lastvalue=0; 382 | // initialize fan struct 383 | for(int i=0; i<16; i++) { 384 | fans[i].powerState = false; 385 | fans[i].lightState = false; 386 | fans[i].fanState = false; 387 | fans[i].fanSpeed = FAN_LOW; 388 | } 389 | } 390 | 391 | void hamptonbay2SetupEnd() { 392 | client.unsubscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 393 | } 394 | -------------------------------------------------------------------------------- /rf-fans/hamptonbay4.cpp: -------------------------------------------------------------------------------- 1 | #include "rf-fans.h" 2 | 3 | #define BASE_TOPIC HAMPTONBAY4_BASE_TOPIC 4 | 5 | #define CMND_BASE_TOPIC CMND_TOPIC BASE_TOPIC 6 | #define STAT_BASE_TOPIC STAT_TOPIC BASE_TOPIC 7 | 8 | #define SUBSCRIBE_TOPIC_CMND CMND_BASE_TOPIC "/#" 9 | 10 | #define SUBSCRIBE_TOPIC_STAT_SETUP STAT_BASE_TOPIC "/#" 11 | 12 | #ifndef HAMPTONBAY4_TX_FREQ 13 | #define TX_FREQ 303.875 // UC7078TR Variant 14 | #else 15 | #define TX_FREQ HAMPTONBAY4_TX_FREQ 16 | #endif 17 | 18 | // RC-switch settings 19 | #define RF_PROTOCOL 12 20 | #define RF_REPEATS 8 21 | 22 | #define FAN_HIGH_CODE 0x5f 23 | #define FAN_MID_CODE 0x6f 24 | #define FAN_LOW_CODE 0x77 25 | #define LIGHT_CODE 0x7e 26 | #define FAN_OFF_CODE 0x7d 27 | 28 | // Keep track of states for all dip settings 29 | static fan fans[16]; 30 | 31 | static int long lastvalue; 32 | static unsigned long lasttime; 33 | 34 | static void postStateUpdate(int id) 35 | { 36 | sprintf(outTopic, "%s/%s/direction", STAT_BASE_TOPIC, idStrings[id]); 37 | client.publish(outTopic, fans[id].directionState ? "REVERSE" : "FORWARD", true); 38 | sprintf(outTopic, "%s/%s/fan", STAT_BASE_TOPIC, idStrings[id]); 39 | client.publish(outTopic, fans[id].fanState ? "ON" : "OFF", true); 40 | sprintf(outTopic, "%s/%s/speed", STAT_BASE_TOPIC, idStrings[id]); 41 | client.publish(outTopic, fanStateTable[fans[id].fanSpeed], true); 42 | sprintf(outTopic, "%s/%s/light", STAT_BASE_TOPIC, idStrings[id]); 43 | client.publish(outTopic, fans[id].lightState ? "ON" : "OFF", true); 44 | 45 | sprintf(outTopic, "%s/%s/percent", STAT_BASE_TOPIC, idStrings[id]); 46 | *outPercent = '\0'; 47 | if (fans[id].fanState) 48 | { 49 | switch (fans[id].fanSpeed) 50 | { 51 | case FAN_HI: 52 | sprintf(outPercent, "%d", FAN_PCT_HI); 53 | break; 54 | case FAN_MED: 55 | sprintf(outPercent, "%d", FAN_PCT_MED); 56 | break; 57 | case FAN_LOW: 58 | sprintf(outPercent, "%d", FAN_PCT_LOW); 59 | break; 60 | } 61 | } 62 | else 63 | sprintf(outPercent, "%d", FAN_PCT_OFF); 64 | client.publish(outTopic, outPercent, true); 65 | } 66 | 67 | static void transmitState(int fanId, int code) 68 | { 69 | mySwitch.disableReceive(); // Receiver off 70 | ELECHOUSE_cc1101.setMHZ(TX_FREQ); 71 | // Serial.print("Configuring frequency hamptonbay4: "); 72 | // Serial.print(TX_FREQ); 73 | // Serial.println(""); 74 | ELECHOUSE_cc1101.SetTx(); // set Transmit on 75 | mySwitch.enableTransmit(TX_PIN); // Transmit on 76 | mySwitch.setRepeatTransmit(RF_REPEATS); // transmission repetitions. 77 | mySwitch.setProtocol(RF_PROTOCOL); // send Received Protocol 78 | 79 | // Build out RF code 80 | // Code follows the 12 bit pattern, built ontop of harberbreeze? 81 | // lLOR??MHaaaa 82 | // Where a is the inversed/reversed dip setting, 83 | // Payload is 7 bits prefixed by 4 bit inverted dip setting 84 | // H 0x5F 85 | // M 0x6F 86 | // L 0x77 87 | // Off 0x7D 88 | // Light 0x7e 89 | // Harber Breeze UC-7078TR 90 | 91 | int rfCode = (((~fanId) & 0x0f) << 7) | (code & 0xff); 92 | 93 | mySwitch.send(rfCode, 12); // send 24 bit code 94 | mySwitch.disableTransmit(); // set Transmit off 95 | ELECHOUSE_cc1101.Init(); 96 | ELECHOUSE_cc1101.setMHZ(RX_FREQ); 97 | ELECHOUSE_cc1101.setRxBW(812.50); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz. 98 | ELECHOUSE_cc1101.SetRx(); // set Receive on 99 | mySwitch.enableReceive(RX_PIN); // Receiver on 100 | Serial.print("Sent command hamptonbay4: "); 101 | Serial.print(fanId); 102 | Serial.print(" "); 103 | for (int b = 24; b > 0; b--) 104 | { 105 | Serial.print(bitRead(rfCode, b - 1)); 106 | } 107 | Serial.println(""); 108 | postStateUpdate(fanId); 109 | } 110 | 111 | void hamptonbay4MQTT(char *topic, char *payloadChar, unsigned int length) 112 | { 113 | if (strncmp(topic, CMND_BASE_TOPIC, sizeof(CMND_BASE_TOPIC) - 1) == 0) 114 | { 115 | 116 | // Get ID after the base topic + a slash 117 | char id[5]; 118 | int percent; 119 | memcpy(id, &topic[sizeof(CMND_BASE_TOPIC)], 4); 120 | id[4] = '\0'; 121 | if (strspn(id, idchars)) 122 | { 123 | uint8_t idint = strtol(id, (char **)NULL, 2); 124 | char *attr; 125 | // Split by slash after ID in topic to get attribute and action 126 | 127 | attr = strtok(topic + sizeof(CMND_BASE_TOPIC) - 1 + 5, "/"); 128 | 129 | if(attr == NULL) return; 130 | 131 | if (strcmp(attr, "percent") == 0) 132 | { 133 | percent = atoi(payloadChar); 134 | if (percent > FAN_PCT_OVER) 135 | { 136 | fans[idint].fanState = true; 137 | if (percent > (FAN_PCT_MED + FAN_PCT_OVER)) 138 | { 139 | fans[idint].fanSpeed = FAN_HI; 140 | transmitState(idint, FAN_HIGH_CODE); 141 | } 142 | else if (percent > (FAN_PCT_LOW + FAN_PCT_OVER)) 143 | { 144 | fans[idint].fanSpeed = FAN_MED; 145 | transmitState(idint, FAN_MID_CODE); 146 | } 147 | else 148 | { 149 | fans[idint].fanSpeed = FAN_LOW; 150 | transmitState(idint, FAN_LOW_CODE); 151 | } 152 | } 153 | else 154 | { 155 | fans[idint].fanState = false; 156 | transmitState(idint, FAN_OFF_CODE); 157 | } 158 | } 159 | else if (strcmp(attr, "fan") == 0) 160 | { 161 | if (strcmp(payloadChar, "toggle") == 0) 162 | { 163 | if (fans[idint].fanState) 164 | strcpy(payloadChar, "off"); 165 | else 166 | strcpy(payloadChar, "on"); 167 | } 168 | if (strcmp(payloadChar, "on") == 0) 169 | { 170 | fans[idint].fanState = true; 171 | switch (fans[idint].fanSpeed) 172 | { 173 | case FAN_HI: 174 | transmitState(idint, FAN_HIGH_CODE); 175 | break; 176 | case FAN_MED: 177 | transmitState(idint, FAN_MID_CODE); 178 | break; 179 | case FAN_LOW: 180 | transmitState(idint, FAN_LOW_CODE); 181 | break; 182 | } 183 | } 184 | else 185 | { 186 | fans[idint].fanState = false; 187 | transmitState(idint, FAN_OFF_CODE); 188 | } 189 | } 190 | else if (strcmp(attr, "speed") == 0) 191 | { 192 | if (strcmp(payloadChar, "+") == 0) 193 | { 194 | fans[idint].fanState = true; 195 | switch (fans[idint].fanSpeed) 196 | { 197 | case FAN_LOW: 198 | fans[idint].fanSpeed = FAN_MED; 199 | transmitState(idint, FAN_MID_CODE); 200 | break; 201 | case FAN_MED: 202 | fans[idint].fanSpeed = FAN_HI; 203 | transmitState(idint, FAN_HIGH_CODE); 204 | break; 205 | case FAN_HI: 206 | fans[idint].fanSpeed = FAN_HI; 207 | transmitState(idint, FAN_HIGH_CODE); 208 | break; 209 | default: 210 | if (fans[idint].fanSpeed > FAN_HI) 211 | fans[idint].fanSpeed--; 212 | break; 213 | } 214 | } 215 | else if (strcmp(payloadChar, "-") == 0) 216 | { 217 | fans[idint].fanState = true; 218 | switch (fans[idint].fanSpeed) 219 | { 220 | case FAN_HI: 221 | fans[idint].fanSpeed = FAN_MED; 222 | transmitState(idint, FAN_MID_CODE); 223 | break; 224 | case FAN_MED: 225 | fans[idint].fanSpeed = FAN_LOW; 226 | transmitState(idint, FAN_LOW_CODE); 227 | break; 228 | case FAN_LOW: 229 | fans[idint].fanSpeed = FAN_LOW; 230 | transmitState(idint, FAN_LOW_CODE); 231 | break; 232 | default: 233 | if (fans[idint].fanSpeed < FAN_LOW) 234 | fans[idint].fanSpeed++; 235 | break; 236 | } 237 | } 238 | else if (strcmp(payloadChar, "high") == 0) 239 | { 240 | fans[idint].fanState = true; 241 | fans[idint].fanSpeed = FAN_HI; 242 | transmitState(idint, FAN_HIGH_CODE); 243 | } 244 | else if (strcmp(payloadChar, "medium") == 0) 245 | { 246 | fans[idint].fanState = true; 247 | fans[idint].fanSpeed = FAN_MED; 248 | transmitState(idint, FAN_MID_CODE); 249 | } 250 | else if (strcmp(payloadChar, "low") == 0) 251 | { 252 | fans[idint].fanState = true; 253 | fans[idint].fanSpeed = FAN_LOW; 254 | transmitState(idint, FAN_LOW_CODE); 255 | } 256 | else 257 | { 258 | fans[idint].fanState = false; 259 | transmitState(idint, FAN_OFF_CODE); 260 | } 261 | } 262 | else if (strcmp(attr, "light") == 0) 263 | { 264 | if (strcmp(payloadChar, "toggle") == 0) 265 | { 266 | if (fans[idint].lightState) 267 | strcpy(payloadChar, "off"); 268 | else 269 | strcpy(payloadChar, "on"); 270 | } 271 | if (strcmp(payloadChar, "on") == 0 && !fans[idint].lightState) 272 | { 273 | fans[idint].lightState = true; 274 | transmitState(idint, LIGHT_CODE); 275 | } 276 | else if (fans[idint].lightState) 277 | { 278 | fans[idint].lightState = false; 279 | transmitState(idint, LIGHT_CODE); 280 | } 281 | // } else if(strcmp(attr,"direction") ==0) { 282 | // if(strcmp(payloadChar,"toggle") == 0) { 283 | // if(fans[idint].directionState) 284 | // strcpy(payloadChar,"forward"); 285 | // else 286 | // strcpy(payloadChar,"reverse"); 287 | // } 288 | // if(strcmp(payloadChar,"reverse") == 0 && !fans[idint].directionState) { 289 | // fans[idint].directionState = true; 290 | // transmitState(idint,0xef); 291 | // } else if(fans[idint].directionState) { 292 | // fans[idint].directionState = false; 293 | // transmitState(idint,0xef); 294 | // } 295 | } 296 | } 297 | else 298 | { 299 | // Invalid ID 300 | return; 301 | } 302 | } 303 | if (strncmp(topic, STAT_BASE_TOPIC, sizeof(STAT_BASE_TOPIC) - 1) == 0) 304 | { 305 | 306 | // Get ID after the base topic + a slash 307 | char id[5]; 308 | memcpy(id, &topic[sizeof(STAT_BASE_TOPIC)], 4); 309 | id[4] = '\0'; 310 | if (strspn(id, idchars)) 311 | { 312 | uint8_t idint = strtol(id, (char **)NULL, 2); 313 | char *attr; 314 | // Split by slash after ID in topic to get attribute and action 315 | 316 | attr = strtok(topic + sizeof(STAT_BASE_TOPIC) - 1 + 5, "/"); 317 | 318 | if (strcmp(attr, "fan") == 0) 319 | { 320 | if (strcmp(payloadChar, "on") == 0) 321 | { 322 | fans[idint].fanState = true; 323 | } 324 | else 325 | { 326 | fans[idint].fanState = false; 327 | } 328 | } 329 | else if (strcmp(attr, "speed") == 0) 330 | { 331 | if (strcmp(payloadChar, "high") == 0) 332 | { 333 | fans[idint].fanSpeed = FAN_HI; 334 | } 335 | else if (strcmp(payloadChar, "medium") == 0) 336 | { 337 | fans[idint].fanSpeed = FAN_MED; 338 | } 339 | else if (strcmp(payloadChar, "low") == 0) 340 | { 341 | fans[idint].fanSpeed = FAN_LOW; 342 | } 343 | } 344 | else if (strcmp(attr, "light") == 0) 345 | { 346 | if (strcmp(payloadChar, "on") == 0) 347 | { 348 | fans[idint].lightState = true; 349 | } 350 | else 351 | { 352 | fans[idint].lightState = false; 353 | } 354 | } 355 | else if (strcmp(attr, "power") == 0) 356 | { 357 | if (strcmp(payloadChar, "on") == 0) 358 | { 359 | fans[idint].powerState = true; 360 | } 361 | else 362 | { 363 | fans[idint].powerState = false; 364 | } 365 | // } else if(strcmp(attr,"direction") ==0) { 366 | // if(strcmp(payloadChar,"reverse") == 0) { 367 | // fans[idint].directionState = true; 368 | // } else { 369 | // fans[idint].directionState = false; 370 | // } 371 | } 372 | } 373 | else 374 | { 375 | // Invalid ID 376 | return; 377 | } 378 | } 379 | } 380 | 381 | void hamptonbay4RF(int long value, int prot, int bits) 382 | { 383 | if ((prot >= 6) && (prot < 14) && bits == 12) 384 | { //&& ((value&0x0c0)==0x0c0) 385 | unsigned long t = millis(); 386 | if (value == lastvalue) 387 | { 388 | if (t - lasttime < NO_RF_REPEAT_TIME) 389 | return; 390 | lasttime = t; 391 | } 392 | lastvalue = value; 393 | lasttime = t; 394 | int dipId = (~value & 0x780) >> 7; 395 | // Serial.print("received valid protocol for hamptonbay4 - dipId: "); 396 | // Serial.print(dipId); 397 | // Serial.println(""); 398 | // Got a correct id in the correct protocol 399 | if (dipId < 16) 400 | { 401 | // Serial.print("received valid dipId for hamptonbay4 - value: "); 402 | // Serial.print((value & 0x7f)); 403 | // Serial.println(""); 404 | // Blank out id in message to get light state 405 | switch (value & 0x7f) 406 | { 407 | case 0xef: // Direction 408 | fans[dipId].directionState = !(fans[dipId].directionState); 409 | break; 410 | case LIGHT_CODE: // Light 411 | Serial.print("received light action"); 412 | fans[dipId].lightState = !(fans[dipId].lightState); 413 | break; 414 | case FAN_LOW_CODE: // Fan Low 415 | Serial.print("received fan low action"); 416 | fans[dipId].fanState = true; 417 | fans[dipId].fanSpeed = FAN_LOW; 418 | break; 419 | case FAN_MID_CODE: // Fan Med 420 | Serial.print("received fan mid action"); 421 | fans[dipId].fanState = true; 422 | fans[dipId].fanSpeed = FAN_MED; 423 | break; 424 | case FAN_HIGH_CODE: // Fan Hi 425 | Serial.print("received fan high action"); 426 | fans[dipId].fanState = true; 427 | fans[dipId].fanSpeed = FAN_HI; 428 | break; 429 | case FAN_OFF_CODE: // Fan Off 430 | Serial.print("received fan off action"); 431 | fans[dipId].fanState = false; 432 | break; 433 | } 434 | postStateUpdate(dipId); 435 | } 436 | } 437 | } 438 | 439 | void hamptonbay4MQTTSub(boolean setup) 440 | { 441 | client.subscribe(SUBSCRIBE_TOPIC_CMND); 442 | if (setup) 443 | client.subscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 444 | } 445 | 446 | void hamptonbay4Setup() 447 | { 448 | lasttime = 0; 449 | lastvalue = 0; 450 | // initialize fan struct 451 | for (int i = 0; i < 16; i++) 452 | { 453 | fans[i].powerState = false; 454 | fans[i].lightState = false; 455 | fans[i].fanState = false; 456 | fans[i].fanSpeed = FAN_HI; 457 | fans[i].directionState = false; 458 | } 459 | } 460 | 461 | void hamptonbay4SetupEnd() 462 | { 463 | client.unsubscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 464 | } 465 | -------------------------------------------------------------------------------- /rf-fans/fanimation.cpp: -------------------------------------------------------------------------------- 1 | #include "rf-fans.h" 2 | 3 | 4 | #define BASE_TOPIC FANIMATION_BASE_TOPIC 5 | 6 | #define CMND_BASE_TOPIC CMND_TOPIC BASE_TOPIC 7 | #define STAT_BASE_TOPIC STAT_TOPIC BASE_TOPIC 8 | 9 | #define SUBSCRIBE_TOPIC_CMND CMND_BASE_TOPIC "/#" 10 | 11 | #define SUBSCRIBE_TOPIC_STAT_SETUP STAT_BASE_TOPIC "/#" 12 | 13 | #ifndef FANIMATION_TX_FREQ 14 | #define TX_FREQ 303.870 // Fanimation 15 | #else 16 | #define TX_FREQ FANIMATION_TX_FREQ 17 | #endif 18 | 19 | // RC-switch settings 20 | //#define RF_PROTOCOL 11 // For Federigo 21 | #define RF_PROTOCOL 13 22 | #define RF_REPEATS 7 23 | 24 | // Keep track of states for all dip settings 25 | static fan fans[16]; 26 | 27 | static int long lastvalue; 28 | static unsigned long lasttime; 29 | 30 | static void postStateUpdate(int id) { 31 | sprintf(outTopic, "%s/%s/direction", STAT_BASE_TOPIC, idStrings[id]); 32 | client.publish(outTopic, fans[id].directionState ? "REVERSE":"FORWARD", true); 33 | sprintf(outTopic, "%s/%s/fan", STAT_BASE_TOPIC, idStrings[id]); 34 | client.publish(outTopic, fans[id].fanState ? "ON":"OFF", true); 35 | sprintf(outTopic, "%s/%s/speed", STAT_BASE_TOPIC, idStrings[id]); 36 | #ifndef FANIMATION6 37 | client.publish(outTopic, fanStateTable[fans[id].fanSpeed], true); 38 | #else 39 | client.publish(outTopic, fanFullStateTable[fans[id].fanSpeed], true); 40 | #endif 41 | sprintf(outTopic, "%s/%s/light", STAT_BASE_TOPIC, idStrings[id]); 42 | client.publish(outTopic, fans[id].lightState ? "ON":"OFF", true); 43 | sprintf(outTopic, "%s/%s/light2", STAT_BASE_TOPIC, idStrings[id]); 44 | client.publish(outTopic, fans[id].light2State ? "ON":"OFF", true); 45 | 46 | sprintf(outTopic, "%s/%s/percent", STAT_BASE_TOPIC, idStrings[id]); 47 | *outPercent='\0'; 48 | if(fans[id].fanState) { 49 | switch(fans[id].fanSpeed) { 50 | case FAN_VI: 51 | sprintf(outPercent,"%d",FAN_PCT_VI); 52 | break; 53 | case FAN_V: 54 | sprintf(outPercent,"%d",FAN_PCT_V); 55 | break; 56 | case FAN_IV: 57 | sprintf(outPercent,"%d",FAN_PCT_IV); 58 | break; 59 | case FAN_III: 60 | sprintf(outPercent,"%d",FAN_PCT_III); 61 | break; 62 | case FAN_II: 63 | sprintf(outPercent,"%d",FAN_PCT_II); 64 | break; 65 | case FAN_I: 66 | sprintf(outPercent,"%d",FAN_PCT_I); 67 | break; 68 | } 69 | } else 70 | sprintf(outPercent,"%d",FAN_PCT_OFF); 71 | client.publish(outTopic, outPercent, true); 72 | } 73 | 74 | static void transmitState(int fanId, int code) { 75 | mySwitch.disableReceive(); // Receiver off 76 | ELECHOUSE_cc1101.setMHZ(TX_FREQ); 77 | ELECHOUSE_cc1101.SetTx(); // set Transmit on 78 | mySwitch.enableTransmit(TX_PIN); // Transmit on 79 | mySwitch.setRepeatTransmit(RF_REPEATS); // transmission repetitions. 80 | mySwitch.setProtocol(RF_PROTOCOL); // send Received Protocol 81 | 82 | // Build out RF code 83 | // Code follows the 12 bit pattern, built ontop of harberbreeze? 84 | // 0aaaadccc1cl 85 | // Where a is the inversed/reversed dip setting, 86 | // ccc1c is the command, no idea what the 1 does yet, fanimation and harberbreeze don't seem to use that bit 87 | // 01111 VI H 88 | // 01110 V H+Off 89 | // 10011 IV M+L 90 | // 10111 III M 91 | // 11010 II L+Off 92 | // 11011 I L 93 | // 11110 Off Off 94 | // 110111 Top Light toggle (Federigo fan) 95 | // 111111 Bad debounce false transmit at end of repeat 96 | // l is the light toggle (bottom if two) 97 | // d is safe to use fade for the light 98 | 99 | // Harber Breeze UC-9050T and UC-7070T 100 | // 0aaaa1hml1fl 101 | // Where a is the inversed/reversed dip setting, 102 | // set 0 to command, h=high, m=med, l=low, f=fan off, l=light 103 | 104 | int rfCode = 0x0000 | (!(fans[fanId].fade&0x01) << 6) | ((~fanId) & 0x0f) << 7 | (code&0x3f); 105 | 106 | mySwitch.send(rfCode, 12); // send 12 bit code 107 | mySwitch.disableTransmit(); // set Transmit off 108 | ELECHOUSE_cc1101.setMHZ(RX_FREQ); 109 | ELECHOUSE_cc1101.SetRx(); // set Receive on 110 | mySwitch.enableReceive(RX_PIN); // Receiver on 111 | Serial.print("Sent command fanimation: "); 112 | Serial.print(fanId); 113 | Serial.print(" "); 114 | for(int b=12; b>0; b--) { 115 | Serial.print(bitRead(rfCode,b-1)); 116 | } 117 | Serial.println(""); 118 | postStateUpdate(fanId); 119 | } 120 | 121 | void fanimationMQTT(char* topic, char* payloadChar, unsigned int length) { 122 | if(strncmp(topic, CMND_BASE_TOPIC, sizeof(CMND_BASE_TOPIC)-1) == 0) { 123 | 124 | // Get ID after the base topic + a slash 125 | char id[5]; 126 | int percent; 127 | memcpy(id, &topic[sizeof(CMND_BASE_TOPIC)], 4); 128 | id[4] = '\0'; 129 | if(strspn(id, idchars)) { 130 | uint8_t idint = strtol(id, (char**) NULL, 2); 131 | char *attr; 132 | // Split by slash after ID in topic to get attribute and action 133 | 134 | attr = strtok(topic+sizeof(CMND_BASE_TOPIC)-1 + 5, "/"); 135 | 136 | if(attr == NULL) return; 137 | 138 | if(strcmp(attr,"percent") == 0) { 139 | percent=atoi(payloadChar); 140 | if(percent > FAN_PCT_OVER) { 141 | fans[idint].fanState = true; 142 | if(percent > (FAN_PCT_V + FAN_PCT_OVER)) { 143 | fans[idint].fanSpeed=FAN_VI; 144 | transmitState(idint,0x1f); 145 | } else if(percent > (FAN_PCT_IV + FAN_PCT_OVER)) { 146 | fans[idint].fanSpeed=FAN_V; 147 | transmitState(idint,0x1d); 148 | } else if(percent > (FAN_PCT_III + FAN_PCT_OVER)) { 149 | fans[idint].fanSpeed=FAN_IV; 150 | transmitState(idint,0x27); 151 | } else if(percent > (FAN_PCT_II + FAN_PCT_OVER)) { 152 | fans[idint].fanSpeed=FAN_III; 153 | transmitState(idint,0x2f); 154 | } else if(percent > (FAN_PCT_I + FAN_PCT_OVER)) { 155 | fans[idint].fanSpeed=FAN_II; 156 | transmitState(idint,0x35); 157 | } else { 158 | fans[idint].fanSpeed=FAN_I; 159 | transmitState(idint,0x37); 160 | } 161 | } else { 162 | fans[idint].fanState = false; 163 | transmitState(idint,0x3d); 164 | } 165 | } else if(strcmp(attr,"fan") == 0) { 166 | if(strcmp(payloadChar,"toggle") == 0) { 167 | if(fans[idint].fanState) 168 | strcpy(payloadChar,"off"); 169 | else 170 | strcpy(payloadChar,"on"); 171 | } 172 | if(strcmp(payloadChar,"on") == 0) { 173 | fans[idint].fanState = true; 174 | switch(fans[idint].fanSpeed) { 175 | case FAN_VI: 176 | transmitState(idint,0x1f); 177 | break; 178 | case FAN_V: 179 | transmitState(idint,0x1d); 180 | break; 181 | case FAN_IV: 182 | transmitState(idint,0x27); 183 | break; 184 | case FAN_III: 185 | transmitState(idint,0x2f); 186 | break; 187 | case FAN_II: 188 | transmitState(idint,0x35); 189 | break; 190 | case FAN_I: 191 | transmitState(idint,0x37); 192 | break; 193 | } 194 | } else { 195 | fans[idint].fanState = false; 196 | transmitState(idint,0x3d); 197 | } 198 | } else if(strcmp(attr,"speed") ==0) { 199 | if(strcmp(payloadChar,"+") ==0) { 200 | fans[idint].fanState = true; 201 | switch(fans[idint].fanSpeed) { 202 | case FAN_I: 203 | fans[idint].fanSpeed=FAN_II; 204 | break; 205 | case FAN_II: 206 | fans[idint].fanSpeed=FAN_III; 207 | break; 208 | case FAN_III: 209 | fans[idint].fanSpeed=FAN_IV; 210 | break; 211 | case FAN_IV: 212 | fans[idint].fanSpeed=FAN_V; 213 | break; 214 | case FAN_V: 215 | fans[idint].fanSpeed=FAN_VI; 216 | break; 217 | case FAN_VI: 218 | fans[idint].fanSpeed=FAN_VI; 219 | break; 220 | default: 221 | if(fans[idint].fanSpeedFAN_I) 224 | fans[idint].fanSpeed=FAN_I; 225 | break; 226 | } 227 | } else if(strcmp(payloadChar,"-") ==0) { 228 | fans[idint].fanState = true; 229 | switch(fans[idint].fanSpeed) { 230 | case FAN_I: 231 | fans[idint].fanSpeed=FAN_I; 232 | break; 233 | case FAN_II: 234 | fans[idint].fanSpeed=FAN_I; 235 | break; 236 | case FAN_III: 237 | fans[idint].fanSpeed=FAN_II; 238 | break; 239 | case FAN_IV: 240 | fans[idint].fanSpeed=FAN_III; 241 | break; 242 | case FAN_V: 243 | fans[idint].fanSpeed=FAN_IV; 244 | break; 245 | case FAN_VI: 246 | fans[idint].fanSpeed=FAN_V; 247 | break; 248 | default: 249 | if(fans[idint].fanSpeedFAN_I) 252 | fans[idint].fanSpeed=FAN_I; 253 | break; 254 | } 255 | } else if(strcmp(payloadChar,"high") ==0) { 256 | fans[idint].fanState = true; 257 | fans[idint].fanSpeed = FAN_HI; 258 | transmitState(idint,0x1f); 259 | } else if(strcmp(payloadChar,"medium") ==0) { 260 | fans[idint].fanState = true; 261 | fans[idint].fanSpeed = FAN_MED; 262 | transmitState(idint,0x2f); 263 | } else if(strcmp(payloadChar,"low") ==0) { 264 | fans[idint].fanState = true; 265 | fans[idint].fanSpeed = FAN_LOW; 266 | transmitState(idint,0x37); 267 | } else if(strcmp(payloadChar,"i") ==0) { 268 | fans[idint].fanState = true; 269 | fans[idint].fanSpeed = FAN_I; 270 | transmitState(idint,0x37); 271 | } else if(strcmp(payloadChar,"ii") ==0) { 272 | fans[idint].fanState = true; 273 | fans[idint].fanSpeed = FAN_II; 274 | transmitState(idint,0x35); 275 | } else if(strcmp(payloadChar,"iii") ==0) { 276 | fans[idint].fanState = true; 277 | fans[idint].fanSpeed = FAN_III; 278 | transmitState(idint,0x2f); 279 | } else if(strcmp(payloadChar,"iv") ==0) { 280 | fans[idint].fanState = true; 281 | fans[idint].fanSpeed = FAN_IV; 282 | transmitState(idint,0x27); 283 | } else if(strcmp(payloadChar,"v") ==0) { 284 | fans[idint].fanState = true; 285 | fans[idint].fanSpeed = FAN_V; 286 | transmitState(idint,0x1d); 287 | } else if(strcmp(payloadChar,"vi") ==0) { 288 | fans[idint].fanState = true; 289 | fans[idint].fanSpeed = FAN_VI; 290 | transmitState(idint,0x1f); 291 | } else { 292 | fans[idint].fanState = false; 293 | transmitState(idint,0x3d); 294 | } 295 | } else if(strcmp(attr,"light") ==0) { 296 | if(strcmp(payloadChar,"toggle") == 0) { 297 | if(fans[idint].lightState) 298 | strcpy(payloadChar,"off"); 299 | else 300 | strcpy(payloadChar,"on"); 301 | } 302 | if(strcmp(payloadChar,"on") == 0 && !fans[idint].lightState) { 303 | fans[idint].lightState = true; 304 | transmitState(idint,0x3e); 305 | } else if (fans[idint].lightState) { 306 | fans[idint].lightState = false; 307 | transmitState(idint,0x3e); 308 | } 309 | } else if(strcmp(attr,"light2") ==0) { 310 | if(strcmp(payloadChar,"toggle") == 0) { 311 | if(fans[idint].light2State) 312 | strcpy(payloadChar,"off"); 313 | else 314 | strcpy(payloadChar,"on"); 315 | } 316 | if(strcmp(payloadChar,"on") == 0 && !fans[idint].light2State) { 317 | fans[idint].light2State = true; 318 | transmitState(idint,0x36); 319 | } else if (fans[idint].light2State) { 320 | fans[idint].light2State = false; 321 | transmitState(idint,0x36); 322 | } 323 | } else if(strcmp(attr,"direction") ==0) { 324 | if(strcmp(payloadChar,"toggle") == 0) { 325 | if(fans[idint].directionState) 326 | strcpy(payloadChar,"forward"); 327 | else 328 | strcpy(payloadChar,"reverse"); 329 | } 330 | if(strcmp(payloadChar,"reverse") == 0 && !fans[idint].directionState) { 331 | fans[idint].directionState = true; 332 | transmitState(idint,0x3b); 333 | } else if (fans[idint].directionState) { 334 | fans[idint].directionState = false; 335 | transmitState(idint,0x3b); 336 | } 337 | } 338 | } else { 339 | // Invalid ID 340 | return; 341 | } 342 | } 343 | if(strncmp(topic, STAT_BASE_TOPIC, sizeof(STAT_BASE_TOPIC)-1) == 0) { 344 | 345 | // Get ID after the base topic + a slash 346 | char id[5]; 347 | memcpy(id, &topic[sizeof(STAT_BASE_TOPIC)], 4); 348 | id[4] = '\0'; 349 | if(strspn(id, idchars)) { 350 | uint8_t idint = strtol(id, (char**) NULL, 2); 351 | char *attr; 352 | // Split by slash after ID in topic to get attribute and action 353 | 354 | attr = strtok(topic+sizeof(STAT_BASE_TOPIC)-1 + 5, "/"); 355 | 356 | if(strcmp(attr,"fan") ==0) { 357 | if(strcmp(payloadChar,"on") == 0) { 358 | fans[idint].fanState = true; 359 | } else { 360 | fans[idint].fanState = false; 361 | } 362 | } else if(strcmp(attr,"speed") ==0) { 363 | if(strcmp(payloadChar,"high") ==0) { 364 | fans[idint].fanSpeed = FAN_HI; 365 | } else if(strcmp(payloadChar,"medium") ==0) { 366 | fans[idint].fanSpeed = FAN_MED; 367 | } else if(strcmp(payloadChar,"low") ==0) { 368 | fans[idint].fanSpeed = FAN_LOW; 369 | } else if(strcmp(payloadChar,"i") ==0) { 370 | fans[idint].fanSpeed = FAN_I; 371 | } else if(strcmp(payloadChar,"ii") ==0) { 372 | fans[idint].fanSpeed = FAN_II; 373 | } else if(strcmp(payloadChar,"iii") ==0) { 374 | fans[idint].fanSpeed = FAN_III; 375 | } else if(strcmp(payloadChar,"iv") ==0) { 376 | fans[idint].fanSpeed = FAN_IV; 377 | } else if(strcmp(payloadChar,"v") ==0) { 378 | fans[idint].fanSpeed = FAN_V; 379 | } else if(strcmp(payloadChar,"vi") ==0) { 380 | fans[idint].fanSpeed = FAN_VI; 381 | } 382 | } else if(strcmp(attr,"light") ==0) { 383 | if(strcmp(payloadChar,"on") == 0) { 384 | fans[idint].lightState = true; 385 | } else { 386 | fans[idint].lightState = false; 387 | } 388 | } else if(strcmp(attr,"light2") ==0) { 389 | if(strcmp(payloadChar,"on") == 0) { 390 | fans[idint].light2State = true; 391 | } else { 392 | fans[idint].light2State = false; 393 | } 394 | } else if(strcmp(attr,"direction") ==0) { 395 | if(strcmp(payloadChar,"reverse") == 0) { 396 | fans[idint].directionState = true; 397 | } else { 398 | fans[idint].directionState = false; 399 | } 400 | } 401 | } else { 402 | // Invalid ID 403 | return; 404 | } 405 | } 406 | } 407 | 408 | void fanimationRF(int long value, int prot, int bits) { 409 | if( (prot >10 && prot<14) && bits == 12 && ((value&0x800)==0x000)) { 410 | unsigned long t=millis(); 411 | if(value == lastvalue) { 412 | if(t - lasttime < NO_RF_REPEAT_TIME) 413 | return; 414 | lasttime=t; 415 | } 416 | lastvalue=value; 417 | lasttime=t; 418 | int dipId = (~value >> 7)&0x0f; 419 | // Got a correct id in the correct protocol 420 | if(dipId < 16) { 421 | // Convert to dip id 422 | if((value&0x40) == 0x40) 423 | fans[dipId].fade=false; 424 | else 425 | fans[dipId].fade=true; 426 | switch(value&0x3f) { 427 | case 0x3b: // Direction 428 | fans[dipId].directionState=!(fans[dipId].directionState); 429 | break; 430 | case 0x36: // Light2 Top 431 | fans[dipId].light2State = !(fans[dipId].light2State); 432 | break; 433 | case 0x3e: // Light 434 | fans[dipId].lightState = !(fans[dipId].lightState); 435 | break; 436 | case 0x37: // Fan I 437 | fans[dipId].fanState = true; 438 | fans[dipId].fanSpeed = FAN_I; 439 | break; 440 | case 0x35: // Fan II 441 | fans[dipId].fanState = true; 442 | fans[dipId].fanSpeed = FAN_II; 443 | break; 444 | case 0x2f: // Fan III 445 | fans[dipId].fanState = true; 446 | fans[dipId].fanSpeed = FAN_III; 447 | break; 448 | case 0x27: // Fan IV 449 | fans[dipId].fanState = true; 450 | fans[dipId].fanSpeed = FAN_IV; 451 | break; 452 | case 0x1d: // Fan V 453 | fans[dipId].fanState = true; 454 | fans[dipId].fanSpeed = FAN_V; 455 | break; 456 | case 0x1f: // Fan VI 457 | fans[dipId].fanState = true; 458 | fans[dipId].fanSpeed = FAN_VI; 459 | break; 460 | case 0x3d: // Fan Off 461 | fans[dipId].fanState = false; 462 | break; 463 | case 0x2d: // Set 464 | break; 465 | case 0x3f: // bad code, debounce false trnsmit 466 | break; 467 | default: 468 | break; 469 | } 470 | postStateUpdate(dipId); 471 | } 472 | } 473 | } 474 | 475 | void fanimationMQTTSub(boolean setup) { 476 | client.subscribe(SUBSCRIBE_TOPIC_CMND); 477 | 478 | if(setup) client.subscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 479 | } 480 | 481 | void fanimationSetup() { 482 | lasttime=0; 483 | lastvalue=0; 484 | // initialize fan struct 485 | for(int i=0; i<16; i++) { 486 | fans[i].fade = false; 487 | fans[i].directionState = false; 488 | fans[i].lightState = false; 489 | fans[i].light2State = false; 490 | fans[i].fanState = false; 491 | fans[i].fanSpeed = FAN_LOW; 492 | } 493 | } 494 | 495 | void fanimationSetupEnd() { 496 | client.unsubscribe(SUBSCRIBE_TOPIC_STAT_SETUP); 497 | } 498 | -------------------------------------------------------------------------------- /rf-fans/rf-fans.ino: -------------------------------------------------------------------------------- 1 | #include "rf-fans.h" 2 | 3 | // fanimation is 6 speed, map the speeds into 3 for homeassistant to not freak out, but retain all locally 4 | const char *fanStateTable[] = { 5 | "off", "high", "high", "medium", "medium", "low", "low" 6 | }; 7 | 8 | const char *fanFullStateTable[] = { 9 | "off", "VI", "V", "IV", "III", "II", "I" 10 | }; 11 | 12 | RCSwitch mySwitch = RCSwitch(); 13 | WiFiClient espClient; 14 | PubSubClient client(espClient); 15 | 16 | WiFiServer TelnetServer(8266); 17 | 18 | #ifdef STATUS_LED 19 | EasyLed led(STATUS_LED_PIN, EasyLed::ActiveLevel::High); 20 | #endif 21 | 22 | #ifdef DHT_SENSOR 23 | DHT_Unified dht(DHT_SENSOR_PIN, DHT_TYPE); 24 | unsigned long lastTempRead = 0; 25 | char dhtTopic[100]; 26 | char outDhtValue[100]; 27 | #endif 28 | 29 | // The ID returned from the RF code appears to be inversed and reversed for hamptonbay 30 | // e.g. a dip setting of on off off off (1000) yields 1110 31 | // Convert between IDs from MQTT from dip switch settings and what is used in the RF codes 32 | const byte dipToRfIds[16] = { 33 | [ 0] = 0, [ 1] = 8, [ 2] = 4, [ 3] = 12, 34 | [ 4] = 2, [ 5] = 10, [ 6] = 6, [ 7] = 14, 35 | [ 8] = 1, [ 9] = 9, [10] = 5, [11] = 13, 36 | [12] = 3, [13] = 11, [14] = 7, [15] = 15, 37 | }; 38 | 39 | const char *idStrings[16] = { 40 | [ 0] = "0000", [ 1] = "0001", [ 2] = "0010", [ 3] = "0011", 41 | [ 4] = "0100", [ 5] = "0101", [ 6] = "0110", [ 7] = "0111", 42 | [ 8] = "1000", [ 9] = "1001", [10] = "1010", [11] = "1011", 43 | [12] = "1100", [13] = "1101", [14] = "1110", [15] = "1111", 44 | }; 45 | 46 | char idchars[] = "01"; 47 | 48 | char outTopic[100]; 49 | char outPercent[100]; 50 | 51 | #ifdef HEAP_DELAY_SECONDS 52 | static long heapSize=0; 53 | static unsigned long heapDelay=0; 54 | #endif 55 | 56 | static unsigned long reconnectReboot=0; 57 | static unsigned long reconnectDelay=0; 58 | static unsigned long setupDelay=0; 59 | static boolean readMQTT=true; 60 | static boolean ignorerf=false; 61 | 62 | #ifndef DOORBELL_COOLDOWN 63 | #define DOORBELL_COOLDOWN 2000 // how many milliseconds before retrigger is allowed 64 | #endif 65 | 66 | #ifdef DOORBELL1 67 | static char doorbell1=1; 68 | static unsigned long doorbell1_milli=DOORBELL_COOLDOWN; 69 | #endif 70 | #ifdef DOORBELL2 71 | static char doorbell2=1; 72 | static unsigned long doorbell2_milli=DOORBELL_COOLDOWN; 73 | #endif 74 | #ifdef DOORBELL3 75 | static char doorbell3=1; 76 | static unsigned long doorbell3_milli=DOORBELL_COOLDOWN; 77 | #endif 78 | 79 | #ifdef DOORBELL_INT 80 | volatile static byte doorbell=0; 81 | 82 | #ifdef DOORBELL1 83 | ICACHE_RAM_ATTR void doorbell1_int() { 84 | doorbell|=0x01; 85 | } 86 | #endif 87 | 88 | #ifdef DOORBELL2 89 | ICACHE_RAM_ATTR void doorbell2_int() { 90 | doorbell|=0x02; 91 | } 92 | #endif 93 | 94 | #ifdef DOORBELL3 95 | ICACHE_RAM_ATTR void doorbell3_int() { 96 | doorbell|=0x04; 97 | } 98 | #endif 99 | #endif 100 | 101 | void setup_wifi() { 102 | delay(10); 103 | // We start by connecting to a WiFi network 104 | Serial.println(); 105 | Serial.print("Connecting to "); 106 | Serial.println(WIFI_SSID); 107 | 108 | WiFi.persistent(false); 109 | WiFi.disconnect(); 110 | delay(200); 111 | WiFi.mode(WIFI_STA); 112 | WiFi.hostname(HOSTNAME); 113 | WiFi.begin(WIFI_SSID, WIFI_PASS); 114 | 115 | while (WiFi.status() != WL_CONNECTED) { 116 | delay(500); 117 | Serial.print("."); 118 | #ifdef STATUS_LED 119 | led.flash(2); 120 | #endif 121 | } 122 | 123 | WiFi.setAutoReconnect(true); 124 | randomSeed(micros()); 125 | 126 | Serial.println(""); 127 | Serial.println("WiFi connected"); 128 | Serial.println("IP address: "); 129 | Serial.println(WiFi.localIP()); 130 | } 131 | 132 | void callback(char* topic, byte* payload, unsigned int length) { 133 | char payloadChar[length + 1]; 134 | // sprintf(payloadChar, "%s", payload); 135 | // payloadChar[length] = '\0'; 136 | 137 | // Convert payload to lowercase 138 | for(unsigned i=0; payload[i] && i300.000 && freq<464.000 ) { 197 | mySwitch.disableReceive(); // Receiver off 198 | ELECHOUSE_cc1101.setMHZ(freq); 199 | ELECHOUSE_cc1101.SetTx(); // set Transmit on 200 | mySwitch.enableTransmit(TX_PIN); // Transmit on 201 | mySwitch.setRepeatTransmit(repeats); // transmission repetitions. 202 | mySwitch.setProtocol(proto); // send Received Protocol 203 | 204 | mySwitch.send(rfCode, bits); // send 12 bit code 205 | mySwitch.disableTransmit(); // set Transmit off 206 | ELECHOUSE_cc1101.setMHZ(RX_FREQ); 207 | ELECHOUSE_cc1101.SetRx(); // set Receive on 208 | mySwitch.enableReceive(RX_PIN); // Receiver on 209 | Serial.print("Sent command raw: "); 210 | Serial.print(freq); 211 | Serial.print(" - "); 212 | Serial.print(proto); 213 | Serial.print(" - "); 214 | Serial.print(rfCode); 215 | Serial.print(" - "); 216 | Serial.print(bits); 217 | Serial.print(" : "); 218 | for(int b=bits; b>0; b--) { 219 | Serial.print(bitRead(rfCode,b-1)); 220 | } 221 | Serial.println(); 222 | } 223 | return; 224 | } 225 | 226 | #ifdef HAMPTONBAY 227 | hamptonbayMQTT(topic,payloadChar,length); 228 | #endif 229 | #ifdef HAMPTONBAY2 230 | hamptonbay2MQTT(topic,payloadChar,length); 231 | #endif 232 | #ifdef HAMPTONBAY3 233 | hamptonbay3MQTT(topic,payloadChar,length); 234 | #endif 235 | #ifdef HAMPTONBAY4 236 | hamptonbay4MQTT(topic,payloadChar,length); 237 | #endif 238 | #ifdef FANIMATION 239 | fanimationMQTT(topic,payloadChar,length); 240 | #endif 241 | } 242 | 243 | void reconnectMQTT() { 244 | Serial.print("Attempting MQTT connection..."); 245 | // Attempt to connect 246 | if (client.connect(MQTT_CLIENT_NAME, MQTT_USER, MQTT_PASS, STATUS_TOPIC, 0, true, "Offline")) { 247 | Serial.println("connected"); 248 | // Once connected, publish an announcement... 249 | client.publish(STATUS_TOPIC, "Online", true); 250 | // ... and resubscribe 251 | client.subscribe(CMND_TOPIC MQTT_CLIENT_NAME "/#"); 252 | #ifdef HAMPTONBAY 253 | hamptonbayMQTTSub(readMQTT); 254 | #endif 255 | #ifdef HAMPTONBAY2 256 | hamptonbay2MQTTSub(readMQTT); 257 | #endif 258 | #ifdef HAMPTONBAY3 259 | hamptonbay3MQTTSub(readMQTT); 260 | #endif 261 | #ifdef HAMPTONBAY4 262 | hamptonbay4MQTTSub(readMQTT); 263 | #endif 264 | #ifdef FANIMATION 265 | fanimationMQTTSub(readMQTT); 266 | #endif 267 | setupDelay=millis()+5000; 268 | readMQTT=false; 269 | reconnectDelay=0; 270 | } else { 271 | Serial.print("failed, rc="); 272 | Serial.print(client.state()); 273 | Serial.println(" try again in 18 seconds"); 274 | #ifdef STATUS_LED 275 | led.flash(4); 276 | #endif 277 | 278 | } 279 | } 280 | 281 | void SleepDelay(uint32_t mseconds) { 282 | if (mseconds) { 283 | for (; mseconds>0; mseconds--) { 284 | delay(1); 285 | if (Serial.available()) { break; } // We need to service serial buffer ASAP as otherwise we get uart buffer overrun 286 | if (mySwitch.available()) { break; } 287 | } 288 | } else { 289 | delay(0); 290 | } 291 | } 292 | 293 | void setup() { 294 | TelnetServer.begin(); 295 | Serial.begin(115200); 296 | 297 | #ifdef STATUS_LED 298 | led.off(); 299 | #endif 300 | 301 | #ifdef HAMPTONBAY 302 | hamptonbaySetup(); 303 | #endif 304 | #ifdef HAMPTONBAY2 305 | hamptonbay2Setup(); 306 | #endif 307 | #ifdef HAMPTONBAY3 308 | hamptonbay3Setup(); 309 | #endif 310 | #ifdef HAMPTONBAY4 311 | hamptonbay4Setup(); 312 | #endif 313 | #ifdef FANIMATION 314 | fanimationSetup(); 315 | #endif 316 | 317 | ELECHOUSE_cc1101.Init(); 318 | ELECHOUSE_cc1101.setMHZ(RX_FREQ); 319 | ELECHOUSE_cc1101.SetRx(); 320 | mySwitch.disableTransmit(); 321 | mySwitch.disableReceive(); 322 | 323 | setup_wifi(); 324 | client.setServer(MQTT_HOST, MQTT_PORT); 325 | client.setCallback(callback); 326 | 327 | mySwitch.enableReceive(RX_PIN); 328 | 329 | #ifdef DHT_SENSOR 330 | dht.begin(); 331 | #endif 332 | 333 | ArduinoOTA.setHostname((const char *)HOSTNAME); 334 | if(sizeof(OTA_PASS)>0) 335 | ArduinoOTA.setPassword((const char *)OTA_PASS); 336 | 337 | ArduinoOTA.onStart([]() { 338 | Serial.println("OTA Start"); 339 | #ifdef STATUS_LED 340 | led.flash(6); 341 | #endif 342 | }); 343 | ArduinoOTA.onEnd([]() { 344 | Serial.println("OTA End"); 345 | Serial.println("Rebooting..."); 346 | #ifdef STATUS_LED 347 | led.flash(6); 348 | #endif 349 | }); 350 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 351 | Serial.printf("Progress: %u%%\r\n", (progress / (total / 100))); 352 | }); 353 | ArduinoOTA.onError([](ota_error_t error) { 354 | #ifdef STATUS_LED 355 | led.flash(7); 356 | #endif 357 | Serial.printf("Error[%u]: ", error); 358 | if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); 359 | else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); 360 | else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); 361 | else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); 362 | else if (error == OTA_END_ERROR) Serial.println("End Failed"); 363 | }); 364 | ArduinoOTA.begin(); 365 | 366 | #ifdef DOORBELL1 367 | pinMode(DOORBELL1,INPUT_PULLUP); 368 | #ifdef DOORBELL_INT 369 | attachInterrupt(digitalPinToInterrupt(DOORBELL1), doorbell1_int, FALLING); 370 | #endif 371 | #endif 372 | #ifdef DOORBELL2 373 | pinMode(DOORBELL2,INPUT_PULLUP); 374 | #ifdef DOORBELL_INT 375 | attachInterrupt(digitalPinToInterrupt(DOORBELL2), doorbell2_int, FALLING); 376 | #endif 377 | #endif 378 | } 379 | 380 | void loop() { 381 | unsigned long t = millis(); 382 | if (setupDelay>0 && setupDelay>16), (unsigned int)(rfCode&&0xffff), bits); 409 | client.publish(outTopic, outPercent, false); 410 | #ifdef STATUS_LED 411 | led.flash(1); 412 | #endif 413 | 414 | Serial.print("Received RF Proto: "); 415 | Serial.print(proto); 416 | Serial.print(" - Code: "); 417 | Serial.print(rfCode); 418 | Serial.print(" - Bits: "); 419 | Serial.print(bits); 420 | Serial.print(" : "); 421 | for(unsigned b=bits; b>0; b--) { 422 | Serial.print(bitRead(rfCode,b-1)); 423 | } 424 | Serial.println(); 425 | 426 | #ifdef HAMPTONBAY 427 | if(!ignorerf) hamptonbayRF(rfCode,proto,bits); 428 | #endif 429 | #ifdef HAMPTONBAY2 430 | if(!ignorerf) hamptonbay2RF(rfCode,proto,bits); 431 | #endif 432 | #ifdef HAMPTONBAY3 433 | if(!ignorerf) hamptonbay3RF(rfCode,proto,bits); 434 | #endif 435 | #ifdef HAMPTONBAY4 436 | if(!ignorerf) hamptonbay4RF(rfCode,proto,bits); 437 | #endif 438 | #ifdef FANIMATION 439 | if(!ignorerf) fanimationRF(rfCode,proto,bits); 440 | #endif 441 | 442 | mySwitch.resetAvailable(); 443 | } 444 | // else { 445 | // Serial.println("RCSwitch not available!"); 446 | // } 447 | 448 | if (!client.connected()) { 449 | #ifdef MQTT_REBOOT_SECONDS 450 | if(reconnectReboot==0) 451 | reconnectReboot=t+(MQTT_REBOOT_SECONDS*1000); 452 | #endif 453 | if(reconnectDelay0) 456 | reconnectDelay=millis()+18000; 457 | else 458 | reconnectDelay=millis()+6000; 459 | #ifdef MQTT_REBOOT_SECONDS 460 | if(reconnectReboot0) 467 | reconnectReboot=0; 468 | //Serial.println("Starting MQTT loop..."); 469 | client.loop(); 470 | //Serial.println("Exiting MQTT loop..."); 471 | } 472 | 473 | ArduinoOTA.handle(); 474 | 475 | #ifdef HEAP_DELAY_SECONDS 476 | if(heapDelayDOORBELL_COOLDOWN) { 510 | doorbell1_milli=0; 511 | doorbell1=0; 512 | sprintf(outTopic, "%sdoorbell/1/state", STAT_TOPIC); 513 | client.publish(outTopic, "OFF", true); 514 | } 515 | } else { 516 | doorbell1_milli=t; 517 | #ifdef DOORBELL_INT 518 | doorbell&=(~0x01); 519 | #endif 520 | } 521 | } 522 | #endif 523 | 524 | #ifdef DOORBELL2 525 | if(doorbell2==0) { 526 | #ifdef DOORBELL_INT 527 | if((doorbell&0x02)==0x02) { 528 | doorbell&=(~0x02); 529 | #else 530 | if(digitalRead(DOORBELL2)==LOW) { 531 | #endif 532 | if(doorbell2_milli==0) { 533 | doorbell2_milli=t; 534 | sprintf(outTopic, "%sdoorbell/2/state", STAT_TOPIC); 535 | client.publish(outTopic, "ON", true); 536 | } 537 | doorbell2=1; 538 | } 539 | } else { 540 | #ifdef DOORBELL_INT 541 | if((doorbell&0x02)==0x00) { 542 | #else 543 | if(digitalRead(DOORBELL2)==HIGH) { 544 | #endif 545 | if((t-doorbell2_milli)>DOORBELL_COOLDOWN) { 546 | doorbell2_milli=0; 547 | doorbell2=0; 548 | sprintf(outTopic, "%sdoorbell/2/state", STAT_TOPIC); 549 | client.publish(outTopic, "OFF", true); 550 | } 551 | } else { 552 | doorbell2_milli=t; 553 | #ifdef DOORBELL_INT 554 | doorbell&=(~0x02); 555 | #endif 556 | } 557 | } 558 | #endif 559 | 560 | #ifdef DOORBELL3 561 | if(doorbell3==0) { 562 | #ifdef DOORBELL_INT 563 | if((doorbell&0x04)==0x04) { 564 | doorbell&=(~0x04); 565 | #else 566 | if(digitalRead(DOORBELL3)==LOW) { 567 | #endif 568 | if(doorbell3_milli==0) { 569 | doorbell3_milli=t; 570 | sprintf(outTopic, "%sdoorbell/3/state", STAT_TOPIC); 571 | client.publish(outTopic, "ON", true); 572 | } 573 | doorbell3=1; 574 | } 575 | } else { 576 | #ifdef DOORBELL_INT 577 | if((doorbell&0x04)==0x00) { 578 | #else 579 | if(digitalRead(DOORBELL3)==HIGH) { 580 | #endif 581 | if((t-doorbell3_milli)>DOORBELL_COOLDOWN) { 582 | doorbell3_milli=0; 583 | doorbell3=0; 584 | sprintf(outTopic, "%sdoorbell/3/state", STAT_TOPIC); 585 | client.publish(outTopic, "OFF", true); 586 | } 587 | } else { 588 | doorbell3_milli=t; 589 | #ifdef DOORBELL_INT 590 | doorbell&=(~0x04); 591 | #endif 592 | } 593 | } 594 | #endif 595 | 596 | #ifdef DHT_SENSOR 597 | //DHT_TEMP_SECONDS 598 | if (lastTempRead == 0 || (millis()-lastTempRead > DHT_TEMP_SECONDS*1000)) 599 | { 600 | lastTempRead = millis(); 601 | // Get temperature event and print its value. 602 | sensors_event_t event; 603 | dht.temperature().getEvent(&event); 604 | if (isnan(event.temperature)) { 605 | Serial.println(F("Error reading temperature!")); 606 | lastTempRead = 0; 607 | } 608 | else { 609 | Serial.print(F("Temperature: ")); 610 | Serial.print(event.temperature); 611 | Serial.println(F("°C")); 612 | sprintf(dhtTopic, "%s%s/temperature", STAT_TOPIC, MQTT_CLIENT_NAME); 613 | dtostrf(event.temperature,0,2,outDhtValue); 614 | client.publish(dhtTopic, outDhtValue, false); 615 | } 616 | // Get humidity event and print its value. 617 | dht.humidity().getEvent(&event); 618 | if (isnan(event.relative_humidity)) { 619 | Serial.println(F("Error reading humidity!")); 620 | lastTempRead = 0; 621 | } 622 | else { 623 | Serial.print(F("Humidity: ")); 624 | Serial.print(event.relative_humidity); 625 | Serial.println(F("%")); 626 | sprintf(dhtTopic, "%s%s/humidity", STAT_TOPIC, MQTT_CLIENT_NAME); 627 | dtostrf(event.relative_humidity,0,2,outDhtValue); 628 | client.publish(dhtTopic, outDhtValue, false); 629 | } 630 | } 631 | 632 | #endif 633 | 634 | unsigned long looptime = millis()-t; 635 | if(looptime.(at)gmail(dot)com 11 | - Andreas Steinel / A.(at)gmail(dot)com 12 | - Max Horn / max(at)quendi(dot)de 13 | - Robert ter Vehn / .(at)gmail(dot)com 14 | - Johann Richard / .(at)gmail(dot)com 15 | - Vlad Gheorghe / .(at)gmail(dot)com https://github.com/vgheo 16 | - Matias Cuenca-Acuna 17 | 18 | Project home: https://github.com/sui77/rc-switch/ 19 | 20 | This library is free software; you can redistribute it and/or 21 | modify it under the terms of the GNU Lesser General Public 22 | License as published by the Free Software Foundation; either 23 | version 2.1 of the License, or (at your option) any later version. 24 | 25 | This library is distributed in the hope that it will be useful, 26 | but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 28 | Lesser General Public License for more details. 29 | 30 | You should have received a copy of the GNU Lesser General Public 31 | License along with this library; if not, write to the Free Software 32 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 33 | */ 34 | 35 | #include "RCSwitch.h" 36 | 37 | #ifdef RaspberryPi 38 | // PROGMEM and _P functions are for AVR based microprocessors, 39 | // so we must normalize these for the ARM processor: 40 | #define PROGMEM 41 | #define memcpy_P(dest, src, num) memcpy((dest), (src), (num)) 42 | #endif 43 | 44 | #if defined(ESP8266) 45 | // interrupt handler and related code must be in RAM on ESP8266, 46 | // according to issue #46. 47 | #define RECEIVE_ATTR ICACHE_RAM_ATTR 48 | #define VAR_ISR_ATTR 49 | #elif defined(ESP32) 50 | #define RECEIVE_ATTR IRAM_ATTR 51 | #define VAR_ISR_ATTR DRAM_ATTR 52 | #else 53 | #define RECEIVE_ATTR 54 | #define VAR_ISR_ATTR 55 | #endif 56 | 57 | 58 | /* Format for protocol definitions: 59 | * {pulselength, Sync bit, "0" bit, "1" bit, invertedSignal} 60 | * 61 | * pulselength: pulse length in microseconds, e.g. 350 62 | * Sync bit: {1, 31} means 1 high pulse and 31 low pulses 63 | * (perceived as a 31*pulselength long pulse, total length of sync bit is 64 | * 32*pulselength microseconds), i.e: 65 | * _ 66 | * | |_______________________________ (don't count the vertical bars) 67 | * "0" bit: waveform for a data bit of value "0", {1, 3} means 1 high pulse 68 | * and 3 low pulses, total length (1+3)*pulselength, i.e: 69 | * _ 70 | * | |___ 71 | * "1" bit: waveform for a data bit of value "1", e.g. {3,1}: 72 | * ___ 73 | * | |_ 74 | * 75 | * These are combined to form Tri-State bits when sending or receiving codes. 76 | */ 77 | #if defined(ESP8266) || defined(ESP32) 78 | static const VAR_ISR_ATTR RCSwitch::Protocol proto[] = { 79 | #else 80 | static const RCSwitch::Protocol PROGMEM proto[] = { 81 | #endif 82 | { 350, { 1, 31 }, { 1, 3 }, { 3, 1 }, false }, // protocol 1 83 | { 650, { 1, 10 }, { 1, 2 }, { 2, 1 }, false }, // protocol 2 84 | { 100, { 30, 71 }, { 4, 11 }, { 9, 6 }, false }, // protocol 3 85 | { 380, { 1, 6 }, { 1, 3 }, { 3, 1 }, false }, // protocol 4 86 | { 500, { 6, 14 }, { 1, 2 }, { 2, 1 }, false }, // protocol 5 87 | { 450, { 23, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 6 (HT6P20B) 88 | { 150, { 2, 62 }, { 1, 6 }, { 6, 1 }, false }, // protocol 7 (HS2303-PT, i. e. used in AUKEY Remote) 89 | { 200, { 3, 130}, { 7, 16 }, { 3, 16}, false}, // protocol 8 Conrad RS-200 RX 90 | { 200, { 130, 7 }, { 16, 7 }, { 16, 3 }, true}, // protocol 9 Conrad RS-200 TX 91 | { 365, { 18, 1 }, { 3, 1 }, { 1, 3 }, true }, // protocol 10 (1ByOne Doorbell) 92 | { 270, { 36, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 11 (HT12E) 93 | { 320, { 36, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 12 (SM5212) 94 | { 346, { 35, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 13 (M1E-N 182khz, fanimation) 95 | { 400, { 34, 1 }, { 1, 2 }, { 2, 1 }, true }, // protocol 14 (ax25-tx028, hamptonbay/dawnsun) 96 | { 361, { 35, 2 }, { 1, 2 }, { 2, 1 }, true } // protocol 15 (fan-hd 201946) 97 | }; 98 | 99 | enum { 100 | numProto = sizeof(proto) / sizeof(proto[0]) 101 | }; 102 | 103 | #if not defined( RCSwitchDisableReceiving ) 104 | #ifdef RCSwitch64 105 | volatile unsigned long long RCSwitch::nReceivedValue = 0; 106 | #else 107 | volatile unsigned long RCSwitch::nReceivedValue = 0; 108 | #endif 109 | volatile unsigned int RCSwitch::nReceivedBitlength = 0; 110 | volatile unsigned int RCSwitch::nReceivedDelay = 0; 111 | volatile unsigned int RCSwitch::nReceivedProtocol = 0; 112 | int RCSwitch::nReceiveTolerance = 60; 113 | const unsigned int RCSwitch::nSeparationLimit = 4300; 114 | // separationLimit: minimum microseconds between received codes, closer codes are ignored. 115 | // according to discussion on issue #14 it might be more suitable to set the separation 116 | // limit to the same time as the 'low' part of the sync signal for the current protocol. 117 | unsigned int RCSwitch::timings[RCSWITCH_MAX_CHANGES]; 118 | #endif 119 | 120 | RCSwitch::RCSwitch() { 121 | this->nTransmitterPin = -1; 122 | this->setRepeatTransmit(10); 123 | this->setProtocol(1); 124 | #if not defined( RCSwitchDisableReceiving ) 125 | this->nReceiverInterrupt = -1; 126 | this->setReceiveTolerance(60); 127 | RCSwitch::nReceivedValue = 0; 128 | #endif 129 | } 130 | 131 | /** 132 | * Sets the protocol to send. 133 | */ 134 | void RCSwitch::setProtocol(Protocol protocol) { 135 | this->protocol = protocol; 136 | } 137 | 138 | /** 139 | * Sets the protocol to send, from a list of predefined protocols 140 | */ 141 | void RCSwitch::setProtocol(int nProtocol) { 142 | if (nProtocol < 1 || nProtocol > numProto) { 143 | nProtocol = 1; // TODO: trigger an error, e.g. "bad protocol" ??? 144 | } 145 | #if defined(ESP8266) || defined(ESP32) 146 | this->protocol = proto[nProtocol-1]; 147 | #else 148 | memcpy_P(&this->protocol, &proto[nProtocol-1], sizeof(Protocol)); 149 | #endif 150 | } 151 | 152 | /** 153 | * Sets the protocol to send with pulse length in microseconds. 154 | */ 155 | void RCSwitch::setProtocol(int nProtocol, int nPulseLength) { 156 | setProtocol(nProtocol); 157 | this->setPulseLength(nPulseLength); 158 | } 159 | 160 | 161 | /** 162 | * Sets pulse length in microseconds 163 | */ 164 | void RCSwitch::setPulseLength(int nPulseLength) { 165 | this->protocol.pulseLength = nPulseLength; 166 | } 167 | 168 | /** 169 | * Sets Repeat Transmits 170 | */ 171 | void RCSwitch::setRepeatTransmit(int nRepeatTransmit) { 172 | this->nRepeatTransmit = nRepeatTransmit; 173 | } 174 | 175 | /** 176 | * Set Receiving Tolerance 177 | */ 178 | #if not defined( RCSwitchDisableReceiving ) 179 | void RCSwitch::setReceiveTolerance(int nPercent) { 180 | RCSwitch::nReceiveTolerance = nPercent; 181 | } 182 | #endif 183 | 184 | 185 | /** 186 | * Enable transmissions 187 | * 188 | * @param nTransmitterPin Arduino Pin to which the sender is connected to 189 | */ 190 | void RCSwitch::enableTransmit(int nTransmitterPin) { 191 | this->nTransmitterPin = nTransmitterPin; 192 | pinMode(this->nTransmitterPin, OUTPUT); 193 | } 194 | 195 | /** 196 | * Disable transmissions 197 | */ 198 | void RCSwitch::disableTransmit() { 199 | this->nTransmitterPin = -1; 200 | } 201 | 202 | /** 203 | * Switch a remote switch on (Type D REV) 204 | * 205 | * @param sGroup Code of the switch group (A,B,C,D) 206 | * @param nDevice Number of the switch itself (1..3) 207 | */ 208 | void RCSwitch::switchOn(char sGroup, int nDevice) { 209 | this->sendTriState( this->getCodeWordD(sGroup, nDevice, true) ); 210 | } 211 | 212 | /** 213 | * Switch a remote switch off (Type D REV) 214 | * 215 | * @param sGroup Code of the switch group (A,B,C,D) 216 | * @param nDevice Number of the switch itself (1..3) 217 | */ 218 | void RCSwitch::switchOff(char sGroup, int nDevice) { 219 | this->sendTriState( this->getCodeWordD(sGroup, nDevice, false) ); 220 | } 221 | 222 | /** 223 | * Switch a remote switch on (Type C Intertechno) 224 | * 225 | * @param sFamily Familycode (a..f) 226 | * @param nGroup Number of group (1..4) 227 | * @param nDevice Number of device (1..4) 228 | */ 229 | void RCSwitch::switchOn(char sFamily, int nGroup, int nDevice) { 230 | this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, true) ); 231 | } 232 | 233 | /** 234 | * Switch a remote switch off (Type C Intertechno) 235 | * 236 | * @param sFamily Familycode (a..f) 237 | * @param nGroup Number of group (1..4) 238 | * @param nDevice Number of device (1..4) 239 | */ 240 | void RCSwitch::switchOff(char sFamily, int nGroup, int nDevice) { 241 | this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, false) ); 242 | } 243 | 244 | /** 245 | * Switch a remote switch on (Type B with two rotary/sliding switches) 246 | * 247 | * @param nAddressCode Number of the switch group (1..4) 248 | * @param nChannelCode Number of the switch itself (1..4) 249 | */ 250 | void RCSwitch::switchOn(int nAddressCode, int nChannelCode) { 251 | this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, true) ); 252 | } 253 | 254 | /** 255 | * Switch a remote switch off (Type B with two rotary/sliding switches) 256 | * 257 | * @param nAddressCode Number of the switch group (1..4) 258 | * @param nChannelCode Number of the switch itself (1..4) 259 | */ 260 | void RCSwitch::switchOff(int nAddressCode, int nChannelCode) { 261 | this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, false) ); 262 | } 263 | 264 | /** 265 | * Deprecated, use switchOn(const char* sGroup, const char* sDevice) instead! 266 | * Switch a remote switch on (Type A with 10 pole DIP switches) 267 | * 268 | * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") 269 | * @param nChannelCode Number of the switch itself (1..5) 270 | */ 271 | void RCSwitch::switchOn(const char* sGroup, int nChannel) { 272 | const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; 273 | this->switchOn(sGroup, code[nChannel]); 274 | } 275 | 276 | /** 277 | * Deprecated, use switchOff(const char* sGroup, const char* sDevice) instead! 278 | * Switch a remote switch off (Type A with 10 pole DIP switches) 279 | * 280 | * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") 281 | * @param nChannelCode Number of the switch itself (1..5) 282 | */ 283 | void RCSwitch::switchOff(const char* sGroup, int nChannel) { 284 | const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; 285 | this->switchOff(sGroup, code[nChannel]); 286 | } 287 | 288 | /** 289 | * Switch a remote switch on (Type A with 10 pole DIP switches) 290 | * 291 | * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") 292 | * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") 293 | */ 294 | void RCSwitch::switchOn(const char* sGroup, const char* sDevice) { 295 | this->sendTriState( this->getCodeWordA(sGroup, sDevice, true) ); 296 | } 297 | 298 | /** 299 | * Switch a remote switch off (Type A with 10 pole DIP switches) 300 | * 301 | * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") 302 | * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") 303 | */ 304 | void RCSwitch::switchOff(const char* sGroup, const char* sDevice) { 305 | this->sendTriState( this->getCodeWordA(sGroup, sDevice, false) ); 306 | } 307 | 308 | 309 | /** 310 | * Returns a char[13], representing the code word to be send. 311 | * 312 | */ 313 | char* RCSwitch::getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus) { 314 | static char sReturn[13]; 315 | int nReturnPos = 0; 316 | 317 | for (int i = 0; i < 5; i++) { 318 | sReturn[nReturnPos++] = (sGroup[i] == '0') ? 'F' : '0'; 319 | } 320 | 321 | for (int i = 0; i < 5; i++) { 322 | sReturn[nReturnPos++] = (sDevice[i] == '0') ? 'F' : '0'; 323 | } 324 | 325 | sReturn[nReturnPos++] = bStatus ? '0' : 'F'; 326 | sReturn[nReturnPos++] = bStatus ? 'F' : '0'; 327 | 328 | sReturn[nReturnPos] = '\0'; 329 | return sReturn; 330 | } 331 | 332 | /** 333 | * Encoding for type B switches with two rotary/sliding switches. 334 | * 335 | * The code word is a tristate word and with following bit pattern: 336 | * 337 | * +-----------------------------+-----------------------------+----------+------------+ 338 | * | 4 bits address | 4 bits address | 3 bits | 1 bit | 339 | * | switch group | switch number | not used | on / off | 340 | * | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | FFF | on=F off=0 | 341 | * +-----------------------------+-----------------------------+----------+------------+ 342 | * 343 | * @param nAddressCode Number of the switch group (1..4) 344 | * @param nChannelCode Number of the switch itself (1..4) 345 | * @param bStatus Whether to switch on (true) or off (false) 346 | * 347 | * @return char[13], representing a tristate code word of length 12 348 | */ 349 | char* RCSwitch::getCodeWordB(int nAddressCode, int nChannelCode, bool bStatus) { 350 | static char sReturn[13]; 351 | int nReturnPos = 0; 352 | 353 | if (nAddressCode < 1 || nAddressCode > 4 || nChannelCode < 1 || nChannelCode > 4) { 354 | return 0; 355 | } 356 | 357 | for (int i = 1; i <= 4; i++) { 358 | sReturn[nReturnPos++] = (nAddressCode == i) ? '0' : 'F'; 359 | } 360 | 361 | for (int i = 1; i <= 4; i++) { 362 | sReturn[nReturnPos++] = (nChannelCode == i) ? '0' : 'F'; 363 | } 364 | 365 | sReturn[nReturnPos++] = 'F'; 366 | sReturn[nReturnPos++] = 'F'; 367 | sReturn[nReturnPos++] = 'F'; 368 | 369 | sReturn[nReturnPos++] = bStatus ? 'F' : '0'; 370 | 371 | sReturn[nReturnPos] = '\0'; 372 | return sReturn; 373 | } 374 | 375 | /** 376 | * Like getCodeWord (Type C = Intertechno) 377 | */ 378 | char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus) { 379 | static char sReturn[13]; 380 | int nReturnPos = 0; 381 | 382 | int nFamily = (int)sFamily - 'a'; 383 | if ( nFamily < 0 || nFamily > 15 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) { 384 | return 0; 385 | } 386 | 387 | // encode the family into four bits 388 | sReturn[nReturnPos++] = (nFamily & 1) ? 'F' : '0'; 389 | sReturn[nReturnPos++] = (nFamily & 2) ? 'F' : '0'; 390 | sReturn[nReturnPos++] = (nFamily & 4) ? 'F' : '0'; 391 | sReturn[nReturnPos++] = (nFamily & 8) ? 'F' : '0'; 392 | 393 | // encode the device and group 394 | sReturn[nReturnPos++] = ((nDevice-1) & 1) ? 'F' : '0'; 395 | sReturn[nReturnPos++] = ((nDevice-1) & 2) ? 'F' : '0'; 396 | sReturn[nReturnPos++] = ((nGroup-1) & 1) ? 'F' : '0'; 397 | sReturn[nReturnPos++] = ((nGroup-1) & 2) ? 'F' : '0'; 398 | 399 | // encode the status code 400 | sReturn[nReturnPos++] = '0'; 401 | sReturn[nReturnPos++] = 'F'; 402 | sReturn[nReturnPos++] = 'F'; 403 | sReturn[nReturnPos++] = bStatus ? 'F' : '0'; 404 | 405 | sReturn[nReturnPos] = '\0'; 406 | return sReturn; 407 | } 408 | 409 | /** 410 | * Encoding for the REV Switch Type 411 | * 412 | * The code word is a tristate word and with following bit pattern: 413 | * 414 | * +-----------------------------+-------------------+----------+--------------+ 415 | * | 4 bits address | 3 bits address | 3 bits | 2 bits | 416 | * | switch group | device number | not used | on / off | 417 | * | A=1FFF B=F1FF C=FF1F D=FFF1 | 1=0FF 2=F0F 3=FF0 | 000 | on=10 off=01 | 418 | * +-----------------------------+-------------------+----------+--------------+ 419 | * 420 | * Source: http://www.the-intruder.net/funksteckdosen-von-rev-uber-arduino-ansteuern/ 421 | * 422 | * @param sGroup Name of the switch group (A..D, resp. a..d) 423 | * @param nDevice Number of the switch itself (1..3) 424 | * @param bStatus Whether to switch on (true) or off (false) 425 | * 426 | * @return char[13], representing a tristate code word of length 12 427 | */ 428 | char* RCSwitch::getCodeWordD(char sGroup, int nDevice, bool bStatus) { 429 | static char sReturn[13]; 430 | int nReturnPos = 0; 431 | 432 | // sGroup must be one of the letters in "abcdABCD" 433 | int nGroup = (sGroup >= 'a') ? (int)sGroup - 'a' : (int)sGroup - 'A'; 434 | if ( nGroup < 0 || nGroup > 3 || nDevice < 1 || nDevice > 3) { 435 | return 0; 436 | } 437 | 438 | for (int i = 0; i < 4; i++) { 439 | sReturn[nReturnPos++] = (nGroup == i) ? '1' : 'F'; 440 | } 441 | 442 | for (int i = 1; i <= 3; i++) { 443 | sReturn[nReturnPos++] = (nDevice == i) ? '1' : 'F'; 444 | } 445 | 446 | sReturn[nReturnPos++] = '0'; 447 | sReturn[nReturnPos++] = '0'; 448 | sReturn[nReturnPos++] = '0'; 449 | 450 | sReturn[nReturnPos++] = bStatus ? '1' : '0'; 451 | sReturn[nReturnPos++] = bStatus ? '0' : '1'; 452 | 453 | sReturn[nReturnPos] = '\0'; 454 | return sReturn; 455 | } 456 | 457 | /** 458 | * @param sCodeWord a tristate code word consisting of the letter 0, 1, F 459 | */ 460 | void RCSwitch::sendTriState(const char* sCodeWord) { 461 | // turn the tristate code word into the corresponding bit pattern, then send it 462 | #ifdef RCSwitch64 463 | unsigned long long code = 0; 464 | #else 465 | unsigned long code = 0; 466 | #endif 467 | unsigned int length = 0; 468 | for (const char* p = sCodeWord; *p; p++) { 469 | #ifdef RCSwitch64 470 | code <<= 2LL; 471 | #else 472 | code <<= 2L; 473 | #endif 474 | switch (*p) { 475 | case '0': 476 | // bit pattern 00 477 | break; 478 | case 'F': 479 | // bit pattern 01 480 | #ifdef RCSwitch64 481 | code |= 1LL; 482 | #else 483 | code |= 1L; 484 | #endif 485 | break; 486 | case '1': 487 | // bit pattern 11 488 | #ifdef RCSwitch64 489 | code |= 3LL; 490 | #else 491 | code |= 3L; 492 | #endif 493 | break; 494 | } 495 | length += 2; 496 | } 497 | this->send(code, length); 498 | } 499 | 500 | /** 501 | * @param sCodeWord a binary code word consisting of the letter 0, 1 502 | */ 503 | void RCSwitch::send(const char* sCodeWord) { 504 | // turn the tristate code word into the corresponding bit pattern, then send it 505 | #ifdef RCSwitch64 506 | unsigned long long code = 0; 507 | #else 508 | unsigned long code = 0; 509 | #endif 510 | unsigned int length = 0; 511 | for (const char* p = sCodeWord; *p; p++) { 512 | #ifdef RCSwitch64 513 | code <<= 1LL; 514 | #else 515 | code <<= 1L; 516 | #endif 517 | if (*p != '0') 518 | #ifdef RCSwitch64 519 | code |= 1LL; 520 | #else 521 | code |= 1L; 522 | #endif 523 | length++; 524 | } 525 | this->send(code, length); 526 | } 527 | 528 | /** 529 | * Transmit the first 'length' bits of the integer 'code'. The 530 | * bits are sent from MSB to LSB, i.e., first the bit at position length-1, 531 | * then the bit at position length-2, and so on, till finally the bit at position 0. 532 | */ 533 | #ifdef RCSwitch64 534 | void RCSwitch::send(unsigned long long code, unsigned int length) { 535 | #else 536 | void RCSwitch::send(unsigned long code, unsigned int length) { 537 | #endif 538 | if (this->nTransmitterPin == -1) 539 | return; 540 | 541 | #if not defined( RCSwitchDisableReceiving ) 542 | // make sure the receiver is disabled while we transmit 543 | int nReceiverInterrupt_backup = nReceiverInterrupt; 544 | if (nReceiverInterrupt_backup != -1) { 545 | this->disableReceive(); 546 | } 547 | #endif 548 | 549 | for (int nRepeat = 0; nRepeat < nRepeatTransmit; nRepeat++) { 550 | for (int i = length-1; i >= 0; i--) { 551 | #ifdef RCSwitch64 552 | if (code & (1LL << i)) 553 | #else 554 | if (code & (1L << i)) 555 | #endif 556 | this->transmit(protocol.one); 557 | else 558 | this->transmit(protocol.zero); 559 | } 560 | this->transmit(protocol.syncFactor); 561 | } 562 | 563 | // Disable transmit after sending (i.e., for inverted protocols) 564 | digitalWrite(this->nTransmitterPin, LOW); 565 | 566 | #if not defined( RCSwitchDisableReceiving ) 567 | // enable receiver again if we just disabled it 568 | if (nReceiverInterrupt_backup != -1) { 569 | this->enableReceive(nReceiverInterrupt_backup); 570 | } 571 | #endif 572 | } 573 | 574 | /** 575 | * Transmit a single high-low pulse. 576 | */ 577 | void RCSwitch::transmit(HighLow pulses) { 578 | uint8_t firstLogicLevel = (this->protocol.invertedSignal) ? LOW : HIGH; 579 | uint8_t secondLogicLevel = (this->protocol.invertedSignal) ? HIGH : LOW; 580 | 581 | digitalWrite(this->nTransmitterPin, firstLogicLevel); 582 | delayMicroseconds( this->protocol.pulseLength * pulses.high); 583 | digitalWrite(this->nTransmitterPin, secondLogicLevel); 584 | delayMicroseconds( this->protocol.pulseLength * pulses.low); 585 | } 586 | 587 | 588 | #if not defined( RCSwitchDisableReceiving ) 589 | /** 590 | * Enable receiving data 591 | */ 592 | void RCSwitch::enableReceive(int interrupt) { 593 | this->nReceiverInterrupt = interrupt; 594 | this->enableReceive(); 595 | } 596 | 597 | void RCSwitch::enableReceive() { 598 | if (this->nReceiverInterrupt != -1) { 599 | RCSwitch::nReceivedValue = 0; 600 | RCSwitch::nReceivedBitlength = 0; 601 | #if defined(RaspberryPi) // Raspberry Pi 602 | wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt); 603 | #else // Arduino 604 | attachInterrupt(this->nReceiverInterrupt, handleInterrupt, CHANGE); 605 | #endif 606 | } 607 | } 608 | 609 | /** 610 | * Disable receiving data 611 | */ 612 | void RCSwitch::disableReceive() { 613 | #if not defined(RaspberryPi) // Arduino 614 | detachInterrupt(this->nReceiverInterrupt); 615 | #endif // For Raspberry Pi (wiringPi) you can't unregister the ISR 616 | this->nReceiverInterrupt = -1; 617 | } 618 | 619 | bool RCSwitch::available() { 620 | return RCSwitch::nReceivedValue != 0; 621 | } 622 | 623 | void RCSwitch::resetAvailable() { 624 | RCSwitch::nReceivedValue = 0; 625 | } 626 | 627 | #ifdef RCSwitch64 628 | unsigned long long RCSwitch::getReceivedValue() { 629 | #else 630 | unsigned long RCSwitch::getReceivedValue() { 631 | #endif 632 | return RCSwitch::nReceivedValue; 633 | } 634 | 635 | unsigned int RCSwitch::getReceivedBitlength() { 636 | return RCSwitch::nReceivedBitlength; 637 | } 638 | 639 | unsigned int RCSwitch::getReceivedDelay() { 640 | return RCSwitch::nReceivedDelay; 641 | } 642 | 643 | unsigned int RCSwitch::getReceivedProtocol() { 644 | return RCSwitch::nReceivedProtocol; 645 | } 646 | 647 | unsigned int* RCSwitch::getReceivedRawdata() { 648 | return RCSwitch::timings; 649 | } 650 | 651 | /* helper function for the receiveProtocol method */ 652 | static inline unsigned int diff(int A, int B) { 653 | return abs(A - B); 654 | } 655 | 656 | /** 657 | * 658 | */ 659 | bool RECEIVE_ATTR RCSwitch::receiveProtocol(const int p, unsigned int changeCount) { 660 | #if defined(ESP8266) || defined(ESP32) 661 | const Protocol &pro = proto[p-1]; 662 | #else 663 | Protocol pro; 664 | memcpy_P(&pro, &proto[p-1], sizeof(Protocol)); 665 | #endif 666 | 667 | #ifdef RCSwitch64 668 | unsigned long long code = 0; 669 | #else 670 | unsigned long code = 0; 671 | #endif 672 | //Assuming the longer pulse length is the pulse captured in timings[0] 673 | const unsigned int syncLengthInPulses = ((pro.syncFactor.low) > (pro.syncFactor.high)) ? (pro.syncFactor.low) : (pro.syncFactor.high); 674 | const unsigned int delay = RCSwitch::timings[0] / syncLengthInPulses; 675 | const unsigned int delayTolerance = delay * RCSwitch::nReceiveTolerance / 100; 676 | 677 | /* For protocols that start low, the sync period looks like 678 | * _________ 679 | * _____________| |XXXXXXXXXXXX| 680 | * 681 | * |--1st dur--|-2nd dur-|-Start data-| 682 | * 683 | * The 3rd saved duration starts the data. 684 | * 685 | * For protocols that start high, the sync period looks like 686 | * 687 | * ______________ 688 | * | |____________|XXXXXXXXXXXXX| 689 | * 690 | * |-filtered out-|--1st dur--|--Start data--| 691 | * 692 | * The 2nd saved duration starts the data 693 | */ 694 | const unsigned int firstDataTiming = (pro.invertedSignal) ? (2) : (1); 695 | 696 | for (unsigned int i = firstDataTiming; i < changeCount - 1; i += 2) { 697 | #ifdef RCSwitch64 698 | code <<= 1LL; 699 | #else 700 | code <<= 1L; 701 | #endif 702 | if (diff(RCSwitch::timings[i], delay * pro.zero.high) < delayTolerance && 703 | diff(RCSwitch::timings[i + 1], delay * pro.zero.low) < delayTolerance) { 704 | // zero 705 | } else if (diff(RCSwitch::timings[i], delay * pro.one.high) < delayTolerance && 706 | diff(RCSwitch::timings[i + 1], delay * pro.one.low) < delayTolerance) { 707 | // one 708 | #ifdef RCSwitch64 709 | code |= 1LL; 710 | #else 711 | code |= 1L; 712 | #endif 713 | } else { 714 | // Failed 715 | return false; 716 | } 717 | } 718 | 719 | RCSwitch::nReceivedValue = code; 720 | RCSwitch::nReceivedBitlength = (changeCount - 1) / 2; 721 | RCSwitch::nReceivedDelay = delay; 722 | RCSwitch::nReceivedProtocol = p; 723 | return true; 724 | } 725 | 726 | void RECEIVE_ATTR RCSwitch::handleInterrupt() { 727 | 728 | static unsigned int changeCount = 0; 729 | static unsigned long lastTime = 0; 730 | static unsigned int repeatCount = 0; 731 | 732 | const long time = micros(); 733 | const unsigned int duration = time - lastTime; 734 | 735 | if (duration > RCSwitch::nSeparationLimit) { 736 | // A long stretch without signal level change occurred. This could 737 | // be the gap between two transmission. 738 | if ((repeatCount==0) || (diff(duration, RCSwitch::timings[0]) < 200)) { 739 | // This long signal is close in length to the long signal which 740 | // started the previously recorded timings; this suggests that 741 | // it may indeed by a a gap between two transmissions (we assume 742 | // here that a sender will send the signal multiple times, 743 | // with roughly the same gap between them). 744 | repeatCount++; 745 | if (repeatCount == 2 && changeCount>7 ) { 746 | for(unsigned int i = 1; i <= numProto; i++) { 747 | if (receiveProtocol(i, changeCount)) { 748 | // receive succeeded for protocol i 749 | break; 750 | } 751 | } 752 | repeatCount = 0; 753 | } 754 | } 755 | changeCount = 0; 756 | } 757 | 758 | // detect overflow 759 | if (changeCount >= RCSWITCH_MAX_CHANGES) { 760 | changeCount = 0; 761 | repeatCount = 0; 762 | } 763 | 764 | RCSwitch::timings[changeCount++] = duration; 765 | lastTime = time; 766 | } 767 | #endif 768 | --------------------------------------------------------------------------------