├── .gitignore ├── LICENSE ├── README.md ├── config.h ├── esp_fanControl.ino └── images ├── esp_fanControl_ha_device.png ├── esp_fanControl_ha_device_details.png ├── esp_fanControl_ha_entities.png ├── esp_fanControl_ha_fan.png ├── esp_fanControl_ha_light.png └── esp_fanControl_ha_sensor.png /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Logan McKenna 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP fanControl 2 | Control and Monitor a Fanimation Slinger V2 RF Fan via an ESP8266 and a TI CC1101 chip over MQTT. 3 | 4 | *This code base should work for other fans controlled via RF with small changes to the protocols/codes being sent.* 5 | 6 | # Table of Contents 7 | - [Features](#features) 8 | - [ESP8266 Wiring Setup (i.e. Connecting to the TI CC1101)](#esp8266-wiring-setup--ie-connecting-to-the-ti-cc1101-) 9 | - [Ardunio IDE/VSCode Board Specific Settings Setup](#ardunio-ide-vscode-board-specific-settings-setup) 10 | * [Arduino IDE](#arduino-ide) 11 | * [VSCode](#vscode) 12 | * [Install Required Libraries](#install-required-libraries) 13 | - [Clone, Initial Configuration, and Flash ESP8266](#clone--initial-configuration--and-flash-esp8266) 14 | * [Clone the Repo](#clone-the-repo) 15 | * [Perform Initial Configuration](#perform-initial-configuration) 16 | * [Flash the device](#flash-the-device) 17 | - [Learning Mode](#learning-mode) 18 | - [Home Assistant Information](#home-assistant-information) 19 | * [Device View](#device-view) 20 | * [Detailed View](#detailed-view) 21 | * [Entity Views](#entity-views) 22 | + [Sensor](#sensor) 23 | + [Light](#light) 24 | + [Fan](#fan) 25 | - [MQTT Information](#mqtt-information) 26 | * [MQTT Topics Published](#mqtt-topics-published) 27 | * [MQTT Retained Topics](#mqtt-retained-topics) 28 | --- 29 | # Features 30 | * Learning Mode 31 | * Useful for determining what codes you need to send to your fan 32 | * Light ON/OFF Control 33 | * Fan ON/OFF Control 34 | * Fan Speed Control 35 | * Fan Direction Control (i.e. winter/summer modes) 36 | * Fan State Monitoring 37 | * Monitor for changes to fan state being made with official Fanimation RF Remote 38 | * MQTT Autodiscovery 39 | * Supports MQTT Autodiscovery for automatic entity creation in Home Assistant and other Home Automation Systems 40 | --- 41 | # ESP8266 Wiring Setup (i.e. Connecting to the TI CC1101) 42 | To see how to connect the TI CC1101 chip to the ESP8266 please see the following wiring diagram. 43 | * [ESP8266 TICC1101 Wiring Diagram](https://github.com/LSatan/SmartRC-CC1101-Driver-Lib/blob/master/img/Esp8266_CC1101.png) 44 | --- 45 | # Ardunio IDE/VSCode Board Specific Settings Setup 46 | In order to flash this code you need to enable C++ exceptions for your board if not already enabled. 47 | 48 | ## Arduino IDE 49 | 1. Loading the Sketch 50 | 2. Select the correct board (Generic ESP8266 Module) 51 | 3. Tools -> C++ Exceptions -> Enabled 52 | 53 | ## VSCode 54 | 1. Load the Sketch 55 | 2. Open the Board Manager 56 | 3. Scroll Down to the Section C++ Exceptions and set to Enabled 57 | 58 | ## Install Required Libraries 59 | You can install these libraries via the Library Manager in Arduino IDE or VSCode 60 | * [SmartRC-CC1101-Driver-Lib by LSatan](https://github.com/LSatan/SmartRC-CC1101-Driver-Lib) 61 | * [rc-switch by sui77](https://github.com/sui77/rc-switch) 62 | * [PubSubClient by Nick O'Leary](https://pubsubclient.knolleary.net/) 63 | 64 | --- 65 | # Clone, Initial Configuration, and Flash ESP8266 66 | ## Clone the Repo 67 | 1. Open the termnial and cd to your Arduino project folder 68 | * `cd ~/Ardunio` 69 | 2. Clone this repo 70 | * `git clone https://github.com/1mckenna/esp_fanControl.git` 71 | 72 | ## Perform Initial Configuration 73 | Before flashing the code to your device you first need to make some updates to the `config.h` file. 74 | 75 | ## Flash the device 76 | After saving any changes you've made to the `config.h` file you can now flash the device in the Arduino IDE. 77 | 78 | **Required Modifications:** 79 | * MQTT Settings (`config.h` [lines 36-40] ) 80 | ``` 81 | #define MQTT_SERVER "127.0.0.1" 82 | #define MQTT_SERVERPORT "1883" 83 | #define MQTT_USERNAME "mqtt" 84 | #define MQTT_PASSWORD "password" 85 | #define MQTT_BASETOPIC "homeassistant" 86 | ``` 87 | * Wifi Settings (`config.h` [lines 43-44] ) 88 | ``` 89 | #define WIFI_SSID "your_wifi_ssid" 90 | #define WIFI_PASS "your_wifi_password" 91 | ``` 92 | 93 | **If you are using another fan or have configured your DIP switches differently please see the Learning Mode section to ensure you are sending the proper commands** 94 | 95 | --- 96 | # Learning Mode 97 | To put the device into learning mode you first need to edit line 13 in your `config.h` file to look like the following. 98 | ``` 99 | #define LEARNING_MODE true 100 | ``` 101 | 102 | Once you have enabled learning mode, connect the ESP8266 you will want to flash the modified code, and then connect over the serial port. 103 | 104 | Then use the RF remote that came with your fan and note down the information displayed for each button press you want to map. 105 | 106 | **Learning Mode Sample Output** 107 | ``` 108 | [Starting] Opening the serial port - /dev/ttyUSB0 109 | CC1101 SPI Connection: [OK] 110 | [LEARN] !!! BUTTON PRESS DETECTED !!! 111 | [LEARN] Code: 6784 112 | [LEARN] Bit: 24 113 | [LEARN] Protocol: 6 114 | [LEARN] Delay: 383 115 | [LEARN] !!! BUTTON PRESS DETECTED !!! 116 | [LEARN] Code: 6834 117 | [LEARN] Bit: 24 118 | [LEARN] Protocol: 6 119 | [LEARN] Delay: 383 120 | ``` 121 | 122 | Once you have all the required information make the necessary changes in the `config.h` file to ensure you are sending the proper codes for each of the commands listed (lines 17-33). 123 | ``` 124 | #define LIGHT_ON 6834 //RF CODE FOR LIGHT_ON 125 | #define LIGHT_OFF 6784 //RF CODE FOR LIGHT_OFF 126 | #define LIGHT_MIN 6789 //RF CODE FOR DIMMEST LIGHT LEVEL 127 | #define LIGHT_MAX 6830 //RF CODE FOR MAX LIGHT LEVEL 128 | //GENERIC FAN RELATED CODES 129 | #define FAN_OFF 6656 //RF CODE FOR TURNING FAN OFF (Both in Summer and Winter Mode) 130 | //FAN MODE RELATED CODES 131 | #define SUMMER_FAN_MODE 6754 132 | #define WINTER_FAN_MODE 6755 133 | //SUMMER SPECIFIC (i.e. fan pushing air down) 134 | #define SUMMER_FAN_ON 6687 135 | #define SUMMER_FAN_MAX 6687 136 | #define SUMMER_FAN_MIN 6657 137 | //WINTER SPECIFIC (i.e. fan pulling air up) 138 | #define WINTER_FAN_ON 6751 139 | #define WINTER_FAN_MAX 6751 140 | #define WINTER_FAN_MIN 6721 141 | ``` 142 | 143 | Additionally, if you are not using a Fanimation Slinger V2 fan or the Protocol is not 6 you will need to make modifications to lines 8-10 in the `config.h` file. 144 | ``` 145 | // RC-switch settings 146 | #define RF_PROTOCOL 6 147 | #define RF_REPEATS 8 148 | ``` 149 | --- 150 | # Home Assistant Information 151 | After you have configured the esp_fanControl Client and have it connected to the same MQTT Broker as your Home Assistant instance the device should automatically appear. 152 | 153 | ## Device View 154 | ![esp_fanControl_ha_device.png](https://github.com/1mckenna/esp_fanControl/blob/main/images/esp_fanControl_ha_device.png?raw=true) 155 | ## Detailed View 156 | ![esp_fanControl_ha_device_details.png](https://github.com/1mckenna/esp_fanControl/blob/main/images/esp_fanControl_ha_device_details.png?raw=true) 157 | 158 | ## Entity Views 159 | ![esp_fanControl_ha_entities.png](https://github.com/1mckenna/esp_fanControl/blob/main/images/esp_fanControl_ha_entities.png?raw=true) 160 | ### Sensor 161 | ![esp_fanControl_ha_sensor.png](https://github.com/1mckenna/esp_fanControl/blob/main/images/esp_fanControl_ha_sensor.png?raw=true) 162 | 163 | ### Light 164 | ![esp_fanControl_ha_light.png](https://github.com/1mckenna/esp_fanControl/blob/main/images/esp_fanControl_ha_light.png?raw=true) 165 | 166 | ### Fan 167 | ![esp_fanControl_ha_fan.png](https://github.com/1mckenna/esp_fanControl/blob/main/images/esp_fanControl_ha_fan.png?raw=true) 168 | 169 | --- 170 | # MQTT Information 171 | ## MQTT Topics Published 172 | | **MQTT Topic** | **Value(s)** | 173 | | :--------------------: | :--------------------: | 174 | |MQTT_BASETOPIC/sensor/fancontrol_espChipID/status | online: MQTT Connected
offline: MQTT Disconnected
**This is the availability topic**| 175 | |MQTT_BASETOPIC/sensor/fancontrol_espChipID/info/attributes | System Information about esp_fanControl Device
(Device Name, Uptime, Wifi Network, Wifi Signal Strength, IP Address)| 176 | |MQTT_BASETOPIC/sensor/fancontrol_espChipID/info/config | MQTT Autoconfiguration Settings for fanControl Device Sensor | 177 | |MQTT_BASETOPIC/light/fancontrol_espChipID/state | Current Light State (on,off) | 178 | |MQTT_BASETOPIC/light/fancontrol_espChipID/set | Light Command Topic (on,off) | 179 | |MQTT_BASETOPIC/light/fancontrol_espChipID/config | MQTT Autoconfiguration Settings for fanControl Light | 180 | |MQTT_BASETOPIC/fan/fancontrol_espChipID/state | Current Fan Power State (on,off) | 181 | |MQTT_BASETOPIC/fan/fancontrol_espChipID/set | Fan Command Topic (on,off) | 182 | |MQTT_BASETOPIC/fan/fancontrol_espChipID/config | MQTT Autoconfiguration Settings for fanControl Fan | 183 | |MQTT_BASETOPIC/fan/fancontrol_espChipID/speed/percentage_state | Current Fan Speed State (min: 1, max: 30) | 184 | |MQTT_BASETOPIC/fan/fancontrol_espChipID/speed/percentage | Fan Speed Command Topic (min: 1, max: 30)| 185 | |MQTT_BASETOPIC/fan/fancontrol_espChipID/preset/preset_mode_state | Current Fan Mode State ( Summer,Winter) | 186 | |MQTT_BASETOPIC/fan/fancontrol_espChipID/preset_mode | Fan Mode Command Topic ( Summer,Winter)| 187 | 188 | ## MQTT Retained Topics 189 | The below are the list of topics that have the retain flag set on them. 190 | * MQTT_BASETOPIC/sensor/fancontrol_espChipID/info/attributes 191 | * MQTT_BASETOPIC/sensor/fancontrol_espChipID/info/config 192 | * MQTT_BASETOPIC/light/fancontrol_espChipID/config 193 | * MQTT_BASETOPIC/light/fancontrol_espChipID/state 194 | * MQTT_BASETOPIC/fan/fancontrol_espChipID/config 195 | * MQTT_BASETOPIC/fan/fancontrol_espChipID/preset/preset_mode_state 196 | * MQTT_BASETOPIC/fan/fancontrol_espChipID/speed/percentage_state 197 | * MQTT_BASETOPIC/fan/fancontrol_espChipID/state 198 | 199 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | //Configuration Settings for the ESP FanControl Device 2 | //These codes are specific to the Fanimation Slinger V2 with DIP switches set to [OFF] [OFF] [ON] [OFF] [ON] 3 | 4 | //SERIAL LOGGING LEVEL FOR DEBUGGING (default: 1 max: 3) 5 | #define FAN_DEBUG_LVL 1 6 | // Frequency of Fanimation Slinger V2 Fan Remote 7 | #define FREQUENCY 304.25 8 | // RC-switch settings 9 | #define RF_PROTOCOL 6 10 | #define RF_REPEATS 8 11 | 12 | //Enable learning mode initally to capture the correct signals for each of the different codes in the below section 13 | #define LEARNING_MODE false 14 | //RF CODE SECTION (GET THESE VALUES USING LEARNING MODE AND THE REMOTE) 15 | 16 | //LIGHT RELATED CODES 17 | #define LIGHT_ON 6834 //RF CODE FOR LIGHT_ON 18 | #define LIGHT_OFF 6784 //RF CODE FOR LIGHT_OFF 19 | #define LIGHT_MIN 6784 //RF CODE FOR DIMMEST LIGHT LEVEL 20 | #define LIGHT_MAX 6834 //RF CODE FOR MAX LIGHT LEVEL 21 | //GENERIC FAN RELATED CODES 22 | #define FAN_OFF 6656 //RF CODE FOR TURNING FAN OFF (Both in Summer and Winter Mode) 23 | //FAN MODE RELATED CODES 24 | #define SUMMER_FAN_MODE 6754 25 | #define WINTER_FAN_MODE 6755 26 | //SUMMER SPECIFIC (i.e. fan pushing air down) 27 | #define SUMMER_FAN_ON 6687 28 | #define SUMMER_FAN_MAX 6687 29 | #define SUMMER_FAN_MIN 6657 30 | //WINTER SPECIFIC (i.e. fan pulling air up) 31 | #define WINTER_FAN_ON 6751 32 | #define WINTER_FAN_MAX 6751 33 | #define WINTER_FAN_MIN 6721 34 | 35 | //MQTT SETTINGS 36 | #define MQTT_SERVER "127.0.0.1" 37 | #define MQTT_SERVERPORT "1883" 38 | #define MQTT_USERNAME "mqtt" 39 | #define MQTT_PASSWORD "password" 40 | #define MQTT_BASETOPIC "homeassistant" //If you are using Home Assistant you should set this to your discovery_prefix (default: homeassistant) 41 | 42 | //WIFI SETTINGS 43 | #define WIFI_SSID "your_wifi_ssid" 44 | #define WIFI_PASS "your_wifi_password" 45 | #define WIFICHECK_INTERVAL 1000L 46 | #define LED_INTERVAL 2000L 47 | #define HEARTBEAT_INTERVAL 10000L 48 | #define FANCONTROLHEARTBEAT_INTERVAL 60000L 49 | -------------------------------------------------------------------------------- /esp_fanControl.ino: -------------------------------------------------------------------------------- 1 | /* esp_FanControl 2 | Code used to control a Fanimation Slinger V2 RF Fan via an ESP8266 and a TI CC1101 chip over MQTT 3 | 4 | ESP8266 CC1101 Wiring: https://github.com/LSatan/SmartRC-CC1101-Driver-Lib/blob/master/img/Esp8266_CC1101.png 5 | */ 6 | 7 | #include "config.h" 8 | #include //for CC1101 9 | #include //for CC1101 10 | #include //for ESP8266 11 | #include //for MQTT 12 | #include //Used for MQTT Autodiscovery Payload Creation 13 | 14 | // Set receive and transmit pin numbers (GDO0 and GDO2) 15 | #ifdef ESP32 // for esp32! Receiver on GPIO pin 4. Transmit on GPIO pin 2. 16 | #define RX_PIN 4 17 | #define TX_PIN 2 18 | #elif ESP8266 // for esp8266! Receiver on pin 4 = D2. Transmit on pin 5 = D1. 19 | #define RX_PIN 4 20 | #define TX_PIN 5 21 | #else // for Arduino! Receiver on interrupt 0 => that is pin #2. Transmit on pin 6. 22 | #define RX_PIN 0 23 | #define TX_PIN 6 24 | #endif 25 | 26 | #define DELETE(ptr) { if (ptr != nullptr) {delete ptr; ptr = nullptr;} } 27 | 28 | //MQTT TOPICS 29 | String INFO_TOPIC = (String)MQTT_BASETOPIC + "/sensor/fancontrol_" + String(ESP.getChipId()) + "/info/attributes"; 30 | String INFO_CONFIG_TOPIC = (String)MQTT_BASETOPIC + "/sensor/fancontrol_" + String(ESP.getChipId()) + "/info/config"; 31 | String AVAILABILITY_TOPIC = (String)MQTT_BASETOPIC + "/sensor/fancontrol_" + String(ESP.getChipId()) + "/status"; 32 | String LIGHT_STATE_TOPIC = (String)MQTT_BASETOPIC + "/light/fancontrol_" + String(ESP.getChipId()) + "/state"; 33 | String LIGHT_COMMAND_TOPIC = (String)MQTT_BASETOPIC + "/light/fancontrol_" + String(ESP.getChipId()) + "/set"; 34 | String LIGHT_CONFIG_TOPIC = (String)MQTT_BASETOPIC + "/light/fancontrol_" + String(ESP.getChipId())+"/config"; 35 | String LIGHT_BRIGHTNESS_STATE_TOPIC = (String)MQTT_BASETOPIC + "/light/fancontrol_" + String(ESP.getChipId()) +"/brightness/percentage_state"; 36 | String LIGHT_BRIGHTNESS_COMMAND_TOPIC = (String)MQTT_BASETOPIC + "/light/fancontrol_" + String(ESP.getChipId()) + "/brightness/percentage"; 37 | String FAN_STATE_TOPIC = (String)MQTT_BASETOPIC + "/fan/fancontrol_" + String(ESP.getChipId()) + "/state"; 38 | String FAN_COMMAND_TOPIC = (String)MQTT_BASETOPIC + "/fan/fancontrol_" + String(ESP.getChipId()) + "/set"; 39 | String FAN_PERCENT_STATE_TOPIC = (String)MQTT_BASETOPIC + "/fan/fancontrol_" + String(ESP.getChipId()) +"/speed/percentage_state"; 40 | String FAN_PERCENT_COMMAND_TOPIC = (String)MQTT_BASETOPIC + "/fan/fancontrol_" + String(ESP.getChipId()) + "/speed/percentage"; 41 | String FAN_MODE_STATE_TOPIC = (String)MQTT_BASETOPIC + "/fan/fancontrol_" + String(ESP.getChipId()) + "/preset/preset_mode_state"; 42 | String FAN_MODE_COMMAND_TOPIC = (String)MQTT_BASETOPIC + "/fan/fancontrol_" + String(ESP.getChipId()) + "/preset_mode"; 43 | String FAN_CONFIG_TOPIC = (String)MQTT_BASETOPIC + "/fan/fancontrol_" + String(ESP.getChipId())+"/config"; 44 | 45 | //Variable to track CC1101 State 46 | bool CC1101_RX_ON = true; 47 | 48 | //Variables used for tracking Fan State 49 | bool SUMMER_MODE = true; 50 | bool CURRENT_LIGHT_STATE = false; 51 | bool CURRENT_FAN_STATE = false; 52 | int CURRENT_FAN_SPEED = 0; 53 | int CURRENT_LIGHT_BRIGHTNESS = 0; 54 | 55 | bool CC1101_CONNECTED = false; 56 | RCSwitch fanControlClient = RCSwitch(); 57 | 58 | WiFiClient *client = nullptr; 59 | PubSubClient *mqtt_client = nullptr; 60 | static String deviceStr =""; 61 | 62 | #pragma region System_Or_Helper_Functions 63 | //Function for keeping track of system uptime. 64 | String getSystemUptime() 65 | { 66 | long millisecs = millis(); 67 | int systemUpTimeMn = int((millisecs / (1000 * 60)) % 60); 68 | int systemUpTimeHr = int((millisecs / (1000 * 60 * 60)) % 24); 69 | int systemUpTimeDy = int((millisecs / (1000 * 60 * 60 * 24)) % 365); 70 | return String(systemUpTimeDy)+"d:"+String(systemUpTimeHr)+"h:"+String(systemUpTimeMn)+"m"; 71 | } 72 | 73 | //Logging helper function 74 | void FANCONTORL_LOGGER(String logMsg, int requiredLVL, bool addNewLine) 75 | { 76 | if(requiredLVL <= FAN_DEBUG_LVL) 77 | { 78 | if(addNewLine) 79 | Serial.printf("%s\n", logMsg.c_str()); 80 | else 81 | Serial.printf("%s", logMsg.c_str()); 82 | } 83 | } 84 | 85 | //Toggle LED State 86 | void toggleLED() 87 | { 88 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); 89 | } 90 | 91 | //Check and validate MQTT is connected and 92 | void heartBeatPrint() 93 | { 94 | if(mqtt_client != nullptr) //We have to check and see if we have a mqtt client created 95 | { 96 | if(!mqtt_client->connected()) 97 | { 98 | FANCONTORL_LOGGER("MQTT Disconnected", 0, true); 99 | connectMQTT(); 100 | } 101 | } 102 | FANCONTORL_LOGGER(String("free heap memory: ") + String(ESP.getFreeHeap()), 4, true); 103 | } 104 | 105 | //Function to check status of Wifi and MQTT 106 | void check_status() 107 | { 108 | static ulong checkstatus_timeout = 0; 109 | static ulong LEDstatus_timeout = 0; 110 | static ulong checkwifi_timeout = 0; 111 | static ulong fancontrolheartbeat_timeout = 0; 112 | ulong current_millis = millis(); 113 | 114 | // Check WiFi every WIFICHECK_INTERVAL (1) seconds. 115 | if ((current_millis > checkwifi_timeout) || (checkwifi_timeout == 0)) 116 | { 117 | check_WiFi(); 118 | mqtt_client->loop(); 119 | checkwifi_timeout = current_millis + WIFICHECK_INTERVAL; 120 | } 121 | 122 | if ((current_millis > LEDstatus_timeout) || (LEDstatus_timeout == 0)) 123 | { 124 | // Toggle LED at LED_INTERVAL = 2s 125 | toggleLED(); 126 | LEDstatus_timeout = current_millis + LED_INTERVAL; 127 | } 128 | // Print hearbeat every HEARTBEAT_INTERVAL (10) seconds. 129 | if ((current_millis > checkstatus_timeout) || (checkstatus_timeout == 0)) 130 | { 131 | heartBeatPrint(); 132 | checkstatus_timeout = current_millis + HEARTBEAT_INTERVAL; 133 | } 134 | 135 | // Print FanControl System Info every FANCONTROLHEARTBEAT_INTERVAL (5) minutes. 136 | if ((current_millis > fancontrolheartbeat_timeout) || (fancontrolheartbeat_timeout == 0)) 137 | { 138 | publishSystemInfo(); 139 | fancontrolheartbeat_timeout = current_millis + FANCONTROLHEARTBEAT_INTERVAL; 140 | } 141 | } 142 | #pragma endregion 143 | #pragma region Wifi_Related_Functions 144 | void check_WiFi() 145 | { 146 | if ( (WiFi.status() != WL_CONNECTED) ) 147 | { 148 | FANCONTORL_LOGGER("WiFi Connection Lost!",0,true); 149 | disconnectMQTT(); 150 | connectWiFi(); 151 | } 152 | } 153 | 154 | void connectWiFi() 155 | { 156 | delay(10); 157 | FANCONTORL_LOGGER("Connecting to "+String(WIFI_SSID),0, false); 158 | WiFi.begin(WIFI_SSID, WIFI_PASS); 159 | while (WiFi.status() != WL_CONNECTED) 160 | { 161 | delay(500); 162 | FANCONTORL_LOGGER(".",0, false); 163 | } 164 | FANCONTORL_LOGGER("",0,true); 165 | randomSeed(micros()); 166 | FANCONTORL_LOGGER("WiFi connected",0,true); 167 | FANCONTORL_LOGGER("IP: "+ WiFi.localIP().toString(),0,true); 168 | 169 | } 170 | #pragma endregion 171 | #pragma region RF_Related_Functions 172 | //Function used to send the RF Command requested 173 | void sendRFCommand(int code) 174 | { 175 | fanControlClient.disableReceive(); //Turn Off Listening 176 | CC1101_RX_ON = false; 177 | FANCONTORL_LOGGER("[RX] RX LISTENING: OFF", 4, true); 178 | fanControlClient.enableTransmit(TX_PIN); //Enable RF TX 179 | ELECHOUSE_cc1101.SetTx(); 180 | FANCONTORL_LOGGER("[TX] Transmitting RF Code " + String(code), 2, true); 181 | fanControlClient.setRepeatTransmit(RF_REPEATS); 182 | fanControlClient.setProtocol(RF_PROTOCOL); 183 | fanControlClient.setPulseLength(382); 184 | fanControlClient.send(code, 24); 185 | FANCONTORL_LOGGER("[TX] Transmission Complete!", 2, true); 186 | ELECHOUSE_cc1101.SetRx(); 187 | fanControlClient.disableTransmit(); 188 | fanControlClient.enableReceive(RX_PIN); 189 | } 190 | 191 | //Call this function to get the codes for the buttons we want to use 192 | void startLearningMode() 193 | { 194 | if(!CC1101_RX_ON) 195 | { 196 | fanControlClient.enableReceive(RX_PIN); 197 | ELECHOUSE_cc1101.SetRx(); 198 | CC1101_RX_ON = true; 199 | FANCONTORL_LOGGER("[RX] RX LISTENING: ON", 4, true); 200 | fanControlClient.resetAvailable(); 201 | } 202 | 203 | if(fanControlClient.available()) 204 | { 205 | FANCONTORL_LOGGER("[LEARN] !!! BUTTON PRESS DETECTED !!!", 1, true); 206 | FANCONTORL_LOGGER("[LEARN] Code: "+String(fanControlClient.getReceivedValue()), 1, true); 207 | FANCONTORL_LOGGER("[LEARN] Bit: "+String(fanControlClient.getReceivedBitlength()), 1, true); 208 | FANCONTORL_LOGGER("[LEARN] Protocol: "+String(fanControlClient.getReceivedProtocol()), 1, true); 209 | FANCONTORL_LOGGER("[LEARN] Delay: "+String(fanControlClient.getReceivedDelay()), 1, true); 210 | fanControlClient.resetAvailable(); 211 | } 212 | } 213 | 214 | //This function is used inside the main loop to listen for codes from the remote while we are awaiting messages from MQTT 215 | void listenForCodes() 216 | { 217 | if(!CC1101_RX_ON) 218 | { 219 | fanControlClient.enableReceive(RX_PIN); 220 | ELECHOUSE_cc1101.SetRx(); 221 | CC1101_RX_ON = true; 222 | FANCONTORL_LOGGER("[RX] RX LISTENING: ON", 4, true); 223 | fanControlClient.resetAvailable(); 224 | } 225 | 226 | if(fanControlClient.available()) 227 | { 228 | String rxCode = String(fanControlClient.getReceivedValue()); 229 | FANCONTORL_LOGGER("[RX] Code: " + rxCode, 3, true); 230 | if(rxCode == String(LIGHT_ON)) 231 | mqtt_client->publish(LIGHT_STATE_TOPIC.c_str(),"on",true); 232 | else if(rxCode == String(LIGHT_OFF)) 233 | mqtt_client->publish(LIGHT_STATE_TOPIC.c_str(),"off",true); 234 | else if(rxCode == String(FAN_OFF)) 235 | mqtt_client->publish(FAN_STATE_TOPIC.c_str(),"off",true); 236 | else if(rxCode == String(SUMMER_FAN_ON) ) 237 | { 238 | mqtt_client->publish(FAN_STATE_TOPIC.c_str(),"on",true); 239 | mqtt_client->publish(FAN_MODE_STATE_TOPIC.c_str(),"Summer",true); 240 | } 241 | else if(rxCode == String(WINTER_FAN_ON)) 242 | { 243 | mqtt_client->publish(FAN_STATE_TOPIC.c_str(),"on",true); 244 | mqtt_client->publish(FAN_MODE_STATE_TOPIC.c_str(),"Winter",true); 245 | } 246 | else if( rxCode.toInt() >= LIGHT_MIN && rxCode.toInt() <= LIGHT_MAX ) 247 | { 248 | mqtt_client->publish(LIGHT_STATE_TOPIC.c_str(),"on",true); 249 | int brightness = rxCode.toInt() - LIGHT_MIN; 250 | mqtt_client->publish(LIGHT_BRIGHTNESS_STATE_TOPIC.c_str(),String(brightness).c_str(),true); 251 | } 252 | else if( rxCode.toInt() >= SUMMER_FAN_MIN && rxCode.toInt() <= SUMMER_FAN_MAX ) 253 | { 254 | mqtt_client->publish(FAN_STATE_TOPIC.c_str(),"on",true); 255 | int speed = rxCode.toInt() - SUMMER_FAN_MIN; 256 | mqtt_client->publish(FAN_PERCENT_STATE_TOPIC.c_str(),String(speed).c_str(),true); 257 | } 258 | else if( rxCode.toInt() >= WINTER_FAN_MIN && rxCode.toInt() <= WINTER_FAN_MAX ) 259 | { 260 | mqtt_client->publish(FAN_STATE_TOPIC.c_str(),"on",true); 261 | int speed = rxCode.toInt() - WINTER_FAN_MIN; 262 | mqtt_client->publish(FAN_PERCENT_STATE_TOPIC.c_str(),String(speed).c_str(),true); 263 | } 264 | else if(rxCode == String(SUMMER_FAN_MODE)) 265 | { 266 | mqtt_client->publish(FAN_MODE_STATE_TOPIC.c_str(),"Summer",true); 267 | } 268 | else if(rxCode == String(WINTER_FAN_MODE)) 269 | { 270 | mqtt_client->publish(FAN_MODE_STATE_TOPIC.c_str(),"Winter",true); 271 | } 272 | fanControlClient.resetAvailable(); 273 | } 274 | } 275 | #pragma endregion 276 | #pragma region MQTT_Related_Functions 277 | void callback(char* topic, byte* payload, unsigned int length) 278 | { 279 | String payloadStr = ""; 280 | for (int i=0;ipublish(LIGHT_STATE_TOPIC.c_str(),"on",true); 325 | } 326 | if(payloadStr == "off") 327 | { 328 | sendRFCommand(LIGHT_OFF); 329 | mqtt_client->publish(LIGHT_STATE_TOPIC.c_str(),"off",true); 330 | } 331 | } 332 | else if( String(topic) == LIGHT_BRIGHTNESS_COMMAND_TOPIC ) 333 | { 334 | int brightness = payloadStr.toInt(); 335 | int txCode = LIGHT_MIN + brightness; 336 | sendRFCommand(txCode); 337 | mqtt_client->publish(LIGHT_BRIGHTNESS_STATE_TOPIC.c_str(),payloadStr.c_str(),true); 338 | } 339 | else if(String(topic) == FAN_COMMAND_TOPIC ) 340 | { 341 | if(payloadStr == "on") 342 | { 343 | if(SUMMER_MODE) 344 | sendRFCommand(SUMMER_FAN_ON); 345 | else 346 | sendRFCommand(WINTER_FAN_ON); 347 | mqtt_client->publish(FAN_STATE_TOPIC.c_str(),"on",true); 348 | } 349 | if(payloadStr == "off") 350 | { 351 | sendRFCommand(FAN_OFF); 352 | mqtt_client->publish(FAN_STATE_TOPIC.c_str(),"off",true); 353 | } 354 | } 355 | else if( String(topic) == FAN_PERCENT_COMMAND_TOPIC ) 356 | { 357 | int fanSpeed = payloadStr.toInt(); 358 | mqtt_client->publish(FAN_STATE_TOPIC.c_str(),"on",true); 359 | mqtt_client->publish(FAN_PERCENT_STATE_TOPIC.c_str(),payloadStr.c_str(),true); 360 | if(SUMMER_MODE) 361 | { 362 | int txCode = SUMMER_FAN_MIN + fanSpeed; 363 | sendRFCommand(txCode); 364 | } 365 | else 366 | { 367 | int txCode = WINTER_FAN_MIN + fanSpeed; 368 | sendRFCommand(txCode); 369 | } 370 | } 371 | else if( String(topic) == FAN_MODE_COMMAND_TOPIC ) 372 | { 373 | if(payloadStr == "Summer") 374 | { 375 | if(CURRENT_FAN_STATE) 376 | FANCONTORL_LOGGER("!! FAN MUST BE OFF TO CHANGE MODES !!",0,true); 377 | else 378 | { 379 | sendRFCommand(SUMMER_FAN_MODE); 380 | SUMMER_MODE = true; 381 | mqtt_client->publish(FAN_MODE_STATE_TOPIC.c_str(),payloadStr.c_str(),true); 382 | 383 | } 384 | } 385 | else if(payloadStr == "Winter") 386 | { 387 | if(CURRENT_FAN_STATE) 388 | FANCONTORL_LOGGER("!! FAN MUST BE OFF TO CHANGE MODES !!",0,true); 389 | else 390 | { 391 | sendRFCommand(WINTER_FAN_MODE); 392 | SUMMER_MODE = false; 393 | mqtt_client->publish(FAN_MODE_STATE_TOPIC.c_str(),payloadStr.c_str(),true); 394 | } 395 | } 396 | } 397 | else 398 | FANCONTORL_LOGGER("UNKNOWN PAYLOAD: " + payloadStr,0,true); 399 | } 400 | 401 | void disconnectMQTT() 402 | { 403 | try 404 | { 405 | if (mqtt_client != nullptr) 406 | { 407 | if(mqtt_client->connected()) 408 | { 409 | mqtt_client->disconnect(); 410 | } 411 | DELETE(mqtt_client); 412 | } 413 | } 414 | catch(...) 415 | { 416 | FANCONTORL_LOGGER("Error disconnecting MQTT",0,true); 417 | } 418 | } 419 | 420 | //Connect to MQTT Server 421 | void connectMQTT() 422 | { 423 | FANCONTORL_LOGGER("Connecting to MQTT...", 0,true); 424 | if (client == nullptr) 425 | client = new WiFiClient(); 426 | if(mqtt_client == nullptr) 427 | { 428 | mqtt_client = new PubSubClient(*client); 429 | mqtt_client->setBufferSize(2048); //Needed as some JSON messages are too large for the default size 430 | mqtt_client->setKeepAlive(60); //Added to Stabilize MQTT Connection 431 | mqtt_client->setSocketTimeout(60); //Added to Stabilize MQTT Connection 432 | mqtt_client->setServer(MQTT_SERVER, atoi(MQTT_SERVERPORT)); 433 | mqtt_client->setCallback(&callback); 434 | } 435 | if (!mqtt_client->connect(String(ESP.getChipId()).c_str(), MQTT_USERNAME, MQTT_PASSWORD, AVAILABILITY_TOPIC.c_str(), 1, true, "offline")) 436 | { 437 | FANCONTORL_LOGGER("MQTT connection failed: " + String(mqtt_client->state()), 0,true); 438 | DELETE(mqtt_client); 439 | delay(1*5000); //Delay for 5 seconds after a connection failure 440 | } 441 | else 442 | { 443 | FANCONTORL_LOGGER("MQTT connected", 1,true); 444 | mqtt_client->publish(AVAILABILITY_TOPIC.c_str(),"online"); 445 | delay(500); 446 | mqtt_client->subscribe(LIGHT_STATE_TOPIC.c_str()); 447 | mqtt_client->loop(); 448 | mqtt_client->loop(); 449 | mqtt_client->subscribe(LIGHT_COMMAND_TOPIC.c_str()); 450 | mqtt_client->loop(); 451 | mqtt_client->loop(); 452 | mqtt_client->subscribe(LIGHT_BRIGHTNESS_STATE_TOPIC.c_str()); 453 | mqtt_client->loop(); 454 | mqtt_client->loop(); 455 | mqtt_client->subscribe(LIGHT_BRIGHTNESS_COMMAND_TOPIC.c_str()); 456 | mqtt_client->loop(); 457 | mqtt_client->loop(); 458 | mqtt_client->subscribe(FAN_STATE_TOPIC.c_str()); 459 | mqtt_client->loop(); 460 | mqtt_client->loop(); 461 | mqtt_client->subscribe(FAN_COMMAND_TOPIC.c_str()); 462 | mqtt_client->loop(); 463 | mqtt_client->loop(); 464 | mqtt_client->subscribe(FAN_PERCENT_STATE_TOPIC.c_str()); 465 | mqtt_client->loop(); 466 | mqtt_client->loop(); 467 | mqtt_client->subscribe(FAN_PERCENT_COMMAND_TOPIC.c_str()); 468 | mqtt_client->loop(); 469 | mqtt_client->loop(); 470 | mqtt_client->subscribe(FAN_MODE_STATE_TOPIC.c_str()); 471 | mqtt_client->loop(); 472 | mqtt_client->loop(); 473 | mqtt_client->subscribe(FAN_MODE_COMMAND_TOPIC.c_str()); 474 | mqtt_client->loop(); 475 | mqtt_client->loop(); 476 | } 477 | } 478 | 479 | //Publish fanControl Client info to MQTT 480 | void publishSystemInfo() 481 | { 482 | if(mqtt_client) 483 | { 484 | if(mqtt_client->connected()) 485 | { 486 | FANCONTORL_LOGGER("==== espFanControl Internal State ==== ",3,true); 487 | if(SUMMER_MODE) 488 | FANCONTORL_LOGGER("[MODE]: Summer",3,true); 489 | else 490 | FANCONTORL_LOGGER("[MODE]: Winter",3,true); 491 | if(CURRENT_LIGHT_STATE) 492 | FANCONTORL_LOGGER("[LIGHT]: ON",3,true); 493 | else 494 | FANCONTORL_LOGGER("[LIGHT]: OFF",3,true); 495 | if(CURRENT_FAN_STATE) 496 | FANCONTORL_LOGGER("[FAN]: ON",3,true); 497 | else 498 | FANCONTORL_LOGGER("[FAN]: OFF",3,true); 499 | FANCONTORL_LOGGER("[BRIGHTNESS]: "+String(CURRENT_LIGHT_BRIGHTNESS),2,true); 500 | FANCONTORL_LOGGER("[SPEED]: "+String(CURRENT_FAN_SPEED),2,true); 501 | mqttAnnounce(); 502 | } 503 | else 504 | connectMQTT(); 505 | } 506 | else 507 | connectMQTT(); 508 | } 509 | 510 | //Publish MQTT Configuration Topics used by MQTT Auto Discovery 511 | void mqttAnnounce() 512 | { 513 | String syspayload=""; 514 | String lightPayload =""; 515 | String fanPayload = ""; 516 | String infoSensorPayload =""; 517 | 518 | DynamicJsonDocument deviceJSON(1024); 519 | JsonObject deviceObj = deviceJSON.createNestedObject("device"); 520 | deviceObj["identifiers"] = String(ESP.getChipId()); 521 | deviceObj["manufacturer"] = String(ESP.getFlashChipVendorId()); 522 | deviceObj["model"] = "ESP8266"; 523 | deviceObj["name"] = "fancontrol_" + String(ESP.getChipId()); 524 | deviceObj["sw_version"] = "1.0"; 525 | serializeJson(deviceObj, deviceStr); 526 | 527 | DynamicJsonDocument sysinfoJSON(1024); 528 | sysinfoJSON["device"] = deviceObj; 529 | sysinfoJSON["name"] = "System Info"; 530 | sysinfoJSON["Uptime"] = getSystemUptime(); 531 | sysinfoJSON["Network"] = WiFi.SSID(); 532 | sysinfoJSON["Signal Strength"] = String(WiFi.RSSI()); 533 | sysinfoJSON["IP Address"] = WiFi.localIP().toString(); 534 | serializeJson(sysinfoJSON,syspayload); 535 | 536 | DynamicJsonDocument infoSensorJSON(1024); 537 | infoSensorJSON["device"] = deviceObj; 538 | infoSensorJSON["name"] = "Connectivity Sensor"; 539 | infoSensorJSON["icon"] = "mdi:chip"; 540 | infoSensorJSON["unique_id"] = "fancontrol_" + String(ESP.getChipId())+"_info"; 541 | infoSensorJSON["state_topic"] = AVAILABILITY_TOPIC; 542 | infoSensorJSON["json_attributes_topic"] = INFO_TOPIC; 543 | serializeJson(infoSensorJSON,infoSensorPayload); 544 | 545 | DynamicJsonDocument lightJSON(1024); 546 | lightJSON["device"] = deviceObj; 547 | lightJSON["name"] = "Light"; 548 | lightJSON["unique_id"] = "fancontrol_" + String(ESP.getChipId())+"_light"; 549 | lightJSON["state_topic"] = LIGHT_STATE_TOPIC; 550 | lightJSON["command_topic"] = LIGHT_COMMAND_TOPIC; 551 | lightJSON["payload_on"] = "on"; 552 | lightJSON["payload_off"] = "off"; 553 | lightJSON["brightness"] = true; 554 | lightJSON["brightness_state_topic"] = LIGHT_BRIGHTNESS_STATE_TOPIC; 555 | lightJSON["brightness_command_topic"] = LIGHT_BRIGHTNESS_COMMAND_TOPIC; 556 | lightJSON["brightness_scale"] = LIGHT_MAX - LIGHT_MIN; 557 | lightJSON["availability_topic"] = AVAILABILITY_TOPIC; 558 | lightJSON["payload_available"] = "online"; 559 | lightJSON["payload_not_available"] = "offline"; 560 | serializeJson(lightJSON,lightPayload); 561 | 562 | DynamicJsonDocument fanJSON(1024); 563 | fanJSON["device"] = deviceObj; 564 | fanJSON["name"] = "Fan"; 565 | fanJSON["unique_id"] = "fancontrol_" + String(ESP.getChipId())+"_fan"; 566 | fanJSON["state_topic"] = FAN_STATE_TOPIC; 567 | fanJSON["command_topic"] = FAN_COMMAND_TOPIC; 568 | fanJSON["payload_on"] = "on"; 569 | fanJSON["payload_off"] = "off"; 570 | fanJSON["percentage_state_topic"] = FAN_PERCENT_STATE_TOPIC; 571 | fanJSON["percentage_command_topic"] = FAN_PERCENT_COMMAND_TOPIC; 572 | fanJSON["speed_range_min"] = 1; 573 | fanJSON["speed_range_max"] = 30; 574 | fanJSON["preset_mode_state_topic"] = FAN_MODE_STATE_TOPIC; 575 | fanJSON["preset_mode_command_topic"]= FAN_MODE_COMMAND_TOPIC; 576 | JsonArray FAN_PRESET_MODES = fanJSON.createNestedArray("preset_modes"); 577 | FAN_PRESET_MODES.add("Summer"); 578 | FAN_PRESET_MODES.add("Winter"); 579 | fanJSON["availability_topic"] = AVAILABILITY_TOPIC; 580 | fanJSON["payload_available"] = "online"; 581 | fanJSON["payload_not_available"] = "offline"; 582 | serializeJson(fanJSON,fanPayload); 583 | 584 | if(mqtt_client) 585 | { 586 | if(mqtt_client->connected()) 587 | { 588 | mqtt_client->publish(AVAILABILITY_TOPIC.c_str(),"online"); 589 | delay(100); 590 | mqtt_client->publish(LIGHT_CONFIG_TOPIC.c_str(),lightPayload.c_str(),true); 591 | delay(100); 592 | mqtt_client->publish(FAN_CONFIG_TOPIC.c_str(),fanPayload.c_str(),true); 593 | delay(100); 594 | mqtt_client->publish(INFO_CONFIG_TOPIC.c_str(),infoSensorPayload.c_str(),true); 595 | delay(100); 596 | mqtt_client->publish(INFO_TOPIC.c_str(),syspayload.c_str(),true); 597 | delay(100); 598 | } 599 | else 600 | { 601 | connectMQTT(); 602 | } 603 | } 604 | } 605 | 606 | #pragma endregion 607 | 608 | //Setup Function 609 | void setup() 610 | { 611 | //Start Serial Connection 612 | Serial.begin(115200); 613 | while (!Serial); 614 | delay(200); 615 | FANCONTORL_LOGGER("Starting fanControl Client on " + String(ARDUINO_BOARD), 0, true); 616 | // Initialize the LED digital pin as an output. 617 | pinMode(LED_BUILTIN, OUTPUT); 618 | // Check the CC1101 Spi connection is working. 619 | if (ELECHOUSE_cc1101.getCC1101()) 620 | { 621 | FANCONTORL_LOGGER("CC1101 SPI Connection: [OK]", 0, true); 622 | CC1101_CONNECTED = true; 623 | } 624 | else 625 | { 626 | FANCONTORL_LOGGER("CC1101 SPI Connection: [ERR]", 0, true); 627 | CC1101_CONNECTED = false; 628 | } 629 | //Initialize the CC1101 and Set the Frequency 630 | ELECHOUSE_cc1101.Init(); 631 | ELECHOUSE_cc1101.setMHZ(FREQUENCY); 632 | //Put Device in Listening Mode 633 | fanControlClient.enableReceive(RX_PIN); 634 | ELECHOUSE_cc1101.SetRx(); 635 | connectWiFi(); 636 | connectMQTT(); 637 | } 638 | 639 | //Loop Function 640 | void loop() 641 | { 642 | //Make sure the CC1101 Chip is connected otherwise do nothing 643 | if(CC1101_CONNECTED) 644 | { 645 | int counter = 0; 646 | if(LEARNING_MODE) 647 | startLearningMode(); 648 | else 649 | { 650 | //Check to see if someone is using the remote (We only TX when told via MQTT) 651 | listenForCodes(); 652 | } 653 | } 654 | check_status(); 655 | } 656 | -------------------------------------------------------------------------------- /images/esp_fanControl_ha_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1mckenna/esp_fanControl/43dfc7d68d8feb641b95810ceb0d6e5150445ea6/images/esp_fanControl_ha_device.png -------------------------------------------------------------------------------- /images/esp_fanControl_ha_device_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1mckenna/esp_fanControl/43dfc7d68d8feb641b95810ceb0d6e5150445ea6/images/esp_fanControl_ha_device_details.png -------------------------------------------------------------------------------- /images/esp_fanControl_ha_entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1mckenna/esp_fanControl/43dfc7d68d8feb641b95810ceb0d6e5150445ea6/images/esp_fanControl_ha_entities.png -------------------------------------------------------------------------------- /images/esp_fanControl_ha_fan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1mckenna/esp_fanControl/43dfc7d68d8feb641b95810ceb0d6e5150445ea6/images/esp_fanControl_ha_fan.png -------------------------------------------------------------------------------- /images/esp_fanControl_ha_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1mckenna/esp_fanControl/43dfc7d68d8feb641b95810ceb0d6e5150445ea6/images/esp_fanControl_ha_light.png -------------------------------------------------------------------------------- /images/esp_fanControl_ha_sensor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1mckenna/esp_fanControl/43dfc7d68d8feb641b95810ceb0d6e5150445ea6/images/esp_fanControl_ha_sensor.png --------------------------------------------------------------------------------