├── .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 | 
155 | ## Detailed View
156 | 
157 |
158 | ## Entity Views
159 | 
160 | ### Sensor
161 | 
162 |
163 | ### Light
164 | 
165 |
166 | ### Fan
167 | 
168 |
169 | ---
170 | # MQTT Information
171 | ## MQTT Topics Published
172 | | **MQTT Topic** | **Value(s)** |
173 | | :--------------------: | :--------------------: |
174 | |MQTT_BASETOPIC/sensor/fancontrol_espChipID/status | online: MQTT Connectedoffline: 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
--------------------------------------------------------------------------------