├── ShadeHub.stl ├── schematic.jpg ├── mounting_template.pdf ├── README.md ├── Shades_Portal_Config.ino.nodemcu.bin ├── shade.yaml ├── Node_Red_Alexa.js ├── ShadeMCU_CONFIGURE.ino ├── ShadeMCU_Double_CONFIGURE.ino └── Shades_Portal_Config.ino /ShadeHub.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thehookup/MQTT_Motorized_Shades/HEAD/ShadeHub.stl -------------------------------------------------------------------------------- /schematic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thehookup/MQTT_Motorized_Shades/HEAD/schematic.jpg -------------------------------------------------------------------------------- /mounting_template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thehookup/MQTT_Motorized_Shades/HEAD/mounting_template.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MQTT_Motorized_Shades 2 | MQTT Motorized Roller Shades 3 | 4 | 5 | https://www.youtube.com/watch?v=ocOO-8X_-Rk 6 | -------------------------------------------------------------------------------- /Shades_Portal_Config.ino.nodemcu.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thehookup/MQTT_Motorized_Shades/HEAD/Shades_Portal_Config.ino.nodemcu.bin -------------------------------------------------------------------------------- /shade.yaml: -------------------------------------------------------------------------------- 1 | input_number: 2 | mqtt_shade: 3 | name: MQTT Shade 4 | initial: 0 5 | min: 0 6 | max: 13 7 | step: 1 8 | 9 | automation old: 10 | - alias: Send MQTT Shade Value 11 | trigger: 12 | platform: state 13 | entity_id: input_number.mqtt_shade 14 | action: 15 | - service: mqtt.publish 16 | data_template: 17 | #change the topic to "shadePosition/[your client id] 18 | topic: "shadePosition/shade_stepper" 19 | #if you change the name from mqtt_shade you'll need to change this line to your new name 20 | payload_template: "{{ states('input_number.mqtt_shade') | int }}" 21 | qos: 2 22 | retain: true 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Node_Red_Alexa.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "fca96f45.977e9", 4 | "type": "alexa-local", 5 | "z": "771e30ce.ce646", 6 | "devicename": "Couch Shade", 7 | "inputtrigger": false, 8 | "x": 160, 9 | "y": 140, 10 | "wires": [ 11 | [ 12 | "ad6a1e51.4820c" 13 | ] 14 | ] 15 | }, 16 | { 17 | "id": "4b123492.2c80bc", 18 | "type": "function", 19 | "z": "771e30ce.ce646", 20 | "name": "Set Payload", 21 | "func": "msg.payload = { data: {'entity_id': 'input_number.couch_shade','value': \nmsg.bri } };\nreturn msg;", 22 | "outputs": 1, 23 | "noerr": 0, 24 | "x": 670, 25 | "y": 200, 26 | "wires": [ 27 | [ 28 | "2185acfc.6292e4" 29 | ] 30 | ] 31 | }, 32 | { 33 | "id": "13179573.43883b", 34 | "type": "range", 35 | "z": "771e30ce.ce646", 36 | "minin": "0", 37 | "maxin": "100", 38 | "minout": "0", 39 | "maxout": "13", 40 | "action": "scale", 41 | "round": true, 42 | "property": "bri", 43 | "name": "", 44 | "x": 470, 45 | "y": 200, 46 | "wires": [ 47 | [ 48 | "4b123492.2c80bc" 49 | ] 50 | ] 51 | }, 52 | { 53 | "id": "ad6a1e51.4820c", 54 | "type": "switch", 55 | "z": "771e30ce.ce646", 56 | "name": "on/off", 57 | "property": "on_off_command", 58 | "propertyType": "msg", 59 | "rules": [ 60 | { 61 | "t": "true" 62 | }, 63 | { 64 | "t": "false" 65 | } 66 | ], 67 | "checkall": "true", 68 | "repair": false, 69 | "outputs": 2, 70 | "x": 310, 71 | "y": 140, 72 | "wires": [ 73 | [ 74 | "57c015ba.cb27ac" 75 | ], 76 | [ 77 | "13179573.43883b" 78 | ] 79 | ] 80 | }, 81 | { 82 | "id": "2185acfc.6292e4", 83 | "type": "api-call-service", 84 | "z": "771e30ce.ce646", 85 | "name": "Set Couch Shade", 86 | "server": "1e776005.3ee9b", 87 | "service_domain": "input_number", 88 | "service": "set_value", 89 | "data": "{}", 90 | "x": 890, 91 | "y": 200, 92 | "wires": [] 93 | }, 94 | { 95 | "id": "57c015ba.cb27ac", 96 | "type": "function", 97 | "z": "771e30ce.ce646", 98 | "name": "Set Payload", 99 | "func": "if (msg.payload == \"on\")\n{\nmsg.payload = { data: {\"entity_id\": \"input_number.couch_shade\",\"value\": \n'13' } };\n}\nif (msg.payload == \"off\")\n{\nmsg.payload = { data: {\"entity_id\": \"input_number.couch_shade\",\"value\": \n\"0\" } };\n}\nreturn msg;", 100 | "outputs": 1, 101 | "noerr": 0, 102 | "x": 510, 103 | "y": 80, 104 | "wires": [ 105 | [ 106 | "51f55f3c.f75f4" 107 | ] 108 | ] 109 | }, 110 | { 111 | "id": "51f55f3c.f75f4", 112 | "type": "api-call-service", 113 | "z": "771e30ce.ce646", 114 | "name": "Set Couch Shade", 115 | "server": "1e776005.3ee9b", 116 | "service_domain": "input_number", 117 | "service": "set_value", 118 | "data": "{}", 119 | "x": 750, 120 | "y": 80, 121 | "wires": [] 122 | }, 123 | { 124 | "id": "1e776005.3ee9b", 125 | "type": "server", 126 | "z": "", 127 | "name": "Home Assistant", 128 | "url": "https://notmyhass.duckdns.org:8123", 129 | "pass": "notmypass" 130 | } 131 | ] -------------------------------------------------------------------------------- /ShadeMCU_CONFIGURE.ino: -------------------------------------------------------------------------------- 1 | #include //https://github.com/marcelloromani/Arduino-SimpleTimer/tree/master/SimpleTimer 2 | #include 3 | #include //https://github.com/knolleary/pubsubclient 4 | #include 5 | #include 6 | #include //https://github.com/esp8266/Arduino/tree/master/libraries/ArduinoOTA 7 | #include //http://www.alhin.de/arduino/downloads/AH_EasyDriver_20120512.zip 8 | 9 | 10 | //USER CONFIGURED SECTION START// 11 | const char* ssid = "YOUR_WIRELESS_SSID"; 12 | const char* password = "YOUR_WIRELESS_PASSWORD"; 13 | const char* mqtt_server = "YOUR_MQTT_SERVER_ADDRESS"; 14 | const int mqtt_port = 1883; 15 | const char *mqtt_user = "YOUR_MQTT_USERNAME"; 16 | const char *mqtt_pass = "YOUR_MQTT_PASSWORD"; 17 | const char *mqtt_client_id = "PICK_UNIQUE_NAME_NO_SPACES"; //This will be used for your MQTT topics 18 | const int unrolled = 13; //number of full rotations from fully rolled to fully unrolled 19 | //USER CONFIGURED SECTION END// 20 | 21 | 22 | WiFiClient espClient; 23 | PubSubClient client(espClient); 24 | SimpleTimer timer; 25 | 26 | //Global Variables 27 | bool boot = true; 28 | const int stepsPerRevolution = 200; 29 | int currentPosition = 0; 30 | int newPosition = 0; 31 | 32 | //AH_EasyDriver(int RES, int DIR, int STEP, int MS1, int MS2, int SLP); 33 | AH_EasyDriver shadeStepper(300,4,0,14,12,13); // init w/o "enable" and "reset" functions 34 | 35 | //Functions 36 | void setup_wifi() { 37 | // We start by connecting to a WiFi network 38 | Serial.println(); 39 | Serial.print("Connecting to "); 40 | Serial.println(ssid); 41 | 42 | WiFi.begin(ssid, password); 43 | 44 | while (WiFi.status() != WL_CONNECTED) { 45 | delay(500); 46 | Serial.print("."); 47 | } 48 | 49 | Serial.println(""); 50 | Serial.println("WiFi connected"); 51 | Serial.println("IP address: "); 52 | Serial.println(WiFi.localIP()); 53 | } 54 | 55 | void reconnect() 56 | { 57 | int retries = 0; 58 | while (!client.connected()) { 59 | if(retries < 150) 60 | { 61 | Serial.print("Attempting MQTT connection..."); 62 | if (client.connect(mqtt_client_id, mqtt_user, mqtt_pass)) 63 | { 64 | Serial.println("connected"); 65 | if(boot == false) 66 | { 67 | char topic[40]; 68 | strcpy(topic, "checkIn/"); 69 | strcat(topic, mqtt_client_id); 70 | client.publish(topic, "Reconnected"); 71 | } 72 | if(boot == true) 73 | { 74 | char topic[40]; 75 | strcpy(topic, "checkIn/"); 76 | strcat(topic, mqtt_client_id); 77 | client.publish(topic, "Rebooted"); 78 | } 79 | // ... and resubscribe 80 | char topic[40]; 81 | strcpy(topic, "shadePosition/"); 82 | strcat(topic, mqtt_client_id); 83 | client.subscribe(topic); 84 | } 85 | else 86 | { 87 | Serial.print("failed, rc="); 88 | Serial.print(client.state()); 89 | Serial.println(" try again in 5 seconds"); 90 | retries++; 91 | // Wait 5 seconds before retrying 92 | delay(5000); 93 | } 94 | } 95 | if(retries > 149) 96 | { 97 | ESP.restart(); 98 | } 99 | } 100 | } 101 | 102 | void callback(char* topic, byte* payload, unsigned int length) 103 | { 104 | Serial.print("Message arrived ["); 105 | String newTopic = topic; 106 | Serial.print(topic); 107 | Serial.print("] "); 108 | payload[length] = '\0'; 109 | String s = String((char*)payload); 110 | int newPayload = s.toInt(); 111 | Serial.println(newPayload); 112 | Serial.println(); 113 | if(boot == true) 114 | { 115 | newPosition = newPayload; 116 | currentPosition = newPayload; 117 | boot = false; 118 | } 119 | if(boot == false) 120 | { 121 | newPosition = newPayload; 122 | } 123 | } 124 | 125 | void processStepper() 126 | { 127 | if (newPosition > currentPosition) 128 | { 129 | shadeStepper.sleepON(); 130 | shadeStepper.move(4144, BACKWARD); 131 | currentPosition++; 132 | client.loop(); 133 | } 134 | if (newPosition < currentPosition) 135 | { 136 | shadeStepper.sleepON(); 137 | shadeStepper.move(4144, FORWARD); 138 | currentPosition--; 139 | client.loop(); 140 | } 141 | if (newPosition == currentPosition) 142 | { 143 | if (currentPosition == 0 || currentPosition == unrolled) 144 | { 145 | shadeStepper.sleepOFF(); 146 | client.loop(); 147 | } 148 | } 149 | Serial.println(currentPosition); 150 | Serial.println(newPosition); 151 | } 152 | 153 | void checkIn() 154 | { 155 | char topic[40]; 156 | strcpy(topic, "checkIn/"); 157 | strcat(topic, mqtt_client_id); 158 | client.publish(topic, "OK"); 159 | } 160 | 161 | 162 | //Run once setup 163 | void setup() { 164 | Serial.begin(115200); 165 | shadeStepper.setMicrostepping(2); // 0 -> Full Step 166 | shadeStepper.setSpeedRPM(200); // set speed in RPM, rotations per minute 167 | shadeStepper.sleepOFF(); // set Sleep mode OFF 168 | WiFi.mode(WIFI_STA); 169 | setup_wifi(); 170 | client.setServer(mqtt_server, mqtt_port); 171 | client.setCallback(callback); 172 | ArduinoOTA.setHostname("ShadeStepperMCU"); 173 | ArduinoOTA.begin(); 174 | delay(10); 175 | timer.setInterval(800, processStepper); 176 | timer.setInterval(90000, checkIn); 177 | } 178 | 179 | void loop() 180 | { 181 | if (!client.connected()) 182 | { 183 | reconnect(); 184 | } 185 | client.loop(); 186 | ArduinoOTA.handle(); 187 | timer.run(); 188 | } 189 | 190 | -------------------------------------------------------------------------------- /ShadeMCU_Double_CONFIGURE.ino: -------------------------------------------------------------------------------- 1 | #include //https://github.com/marcelloromani/Arduino-SimpleTimer/tree/master/SimpleTimer 2 | #include 3 | #include //https://github.com/knolleary/pubsubclient 4 | #include 5 | #include 6 | #include //https://github.com/esp8266/Arduino/tree/master/libraries/ArduinoOTA 7 | #include //http://www.alhin.de/arduino/downloads/AH_EasyDriver_20120512.zip 8 | 9 | 10 | //USER CONFIGURED SECTION START// 11 | const char* ssid = "YOUR_WIRELESS_SSID"; 12 | const char* password = "YOUR_WIRELESS_PASSWORD"; 13 | const char* mqtt_server = "YOUR_MQTT_SERVER_ADDRESS"; 14 | const int mqtt_port = 1883; 15 | const char *mqtt_user = "YOUR_MQTT_USERNAME"; 16 | const char *mqtt_pass = "YOUR_MQTT_PASSWORD"; 17 | const char *mqtt_client_id = "PICK_UNIQUE_NAME_NO_SPACES"; //This will be used for your MQTT topics 18 | const int unrolled = 13; //number of full rotations from fully rolled to fully unrolled 19 | //USER CONFIGURED SECTION END// 20 | 21 | 22 | WiFiClient espClient; 23 | PubSubClient client(espClient); 24 | SimpleTimer timer; 25 | 26 | //Global Variables 27 | bool boot = true; 28 | bool bootR = true; 29 | bool bootL = true; 30 | const int stepsPerRevolution = 200; 31 | int leftPosition = 0; 32 | int leftNew = 0; 33 | int rightPosition = 0; 34 | int rightNew = 0; 35 | char topic2[40]; 36 | char topic1[40]; 37 | 38 | //AH_EasyDriver(int RES, int DIR, int STEP, int MS1, int MS2, int SLP); 39 | AH_EasyDriver leftStepper(300,16,13,2,0,4); // init w/o "enable" and "reset" functions 40 | AH_EasyDriver rightStepper(300,14,12,2,0,15); // init w/o "enable" and "reset" functions 41 | 42 | //Functions 43 | void setup_wifi() { 44 | // We start by connecting to a WiFi network 45 | Serial.println(); 46 | Serial.print("Connecting to "); 47 | Serial.println(ssid); 48 | 49 | WiFi.begin(ssid, password); 50 | 51 | while (WiFi.status() != WL_CONNECTED) { 52 | delay(500); 53 | Serial.print("."); 54 | } 55 | 56 | Serial.println(""); 57 | Serial.println("WiFi connected"); 58 | Serial.println("IP address: "); 59 | Serial.println(WiFi.localIP()); 60 | } 61 | 62 | void reconnect() 63 | { 64 | int retries = 0; 65 | while (!client.connected()) { 66 | if(retries < 150) 67 | { 68 | Serial.print("Attempting MQTT connection..."); 69 | if (client.connect(mqtt_client_id, mqtt_user, mqtt_pass)) 70 | { 71 | Serial.println("connected"); 72 | if(boot == false) 73 | { 74 | char topic[40]; 75 | strcpy(topic, "checkIn/"); 76 | strcat(topic, mqtt_client_id); 77 | client.publish(topic, "Reconnected"); 78 | } 79 | if(boot == true) 80 | { 81 | char topic[40]; 82 | strcpy(topic, "checkIn/"); 83 | strcat(topic, mqtt_client_id); 84 | client.publish(topic, "Rebooted"); 85 | strcat(topic1, mqtt_client_id); 86 | strcpy(topic1, "/shadePositionL"); 87 | strcat(topic2, mqtt_client_id); 88 | strcpy(topic2, "/shadePositionR"); 89 | } 90 | // ... and resubscribe 91 | client.subscribe(topic1); 92 | client.subscribe(topic2); 93 | } 94 | else 95 | { 96 | Serial.print("failed, rc="); 97 | Serial.print(client.state()); 98 | Serial.println(" try again in 5 seconds"); 99 | retries++; 100 | // Wait 5 seconds before retrying 101 | delay(5000); 102 | } 103 | } 104 | if(retries > 149) 105 | { 106 | ESP.restart(); 107 | } 108 | } 109 | } 110 | 111 | void callback(char* topic, byte* payload, unsigned int length) 112 | { 113 | Serial.print("Message arrived ["); 114 | String newTopic = topic; 115 | Serial.print(topic); 116 | Serial.print("] "); 117 | payload[length] = '\0'; 118 | String s = String((char*)payload); 119 | int newPayload = s.toInt(); 120 | Serial.println(newPayload); 121 | Serial.println(); 122 | if(boot == true) 123 | { 124 | if(topic == topic1) 125 | { 126 | leftPosition = newPayload; 127 | leftNew = newPayload; 128 | bootL = false; 129 | } 130 | if(topic == topic2) 131 | { 132 | rightPosition = newPayload; 133 | rightNew = newPayload; 134 | bootR = false; 135 | } 136 | if(bootL == false && bootR == false) 137 | { 138 | boot = false; 139 | } 140 | 141 | } 142 | if(boot == false) 143 | { 144 | if(topic == topic1) 145 | { 146 | leftNew = newPayload; 147 | } 148 | if(topic == topic2) 149 | { 150 | rightNew = newPayload; 151 | } 152 | } 153 | } 154 | 155 | void processStepper() 156 | { 157 | if (boot == false) 158 | client.loop(); 159 | { 160 | if (leftNew > leftPosition && rightNew > rightPosition) 161 | { 162 | rightStepper.sleepON(); 163 | leftStepper.sleepON(); 164 | for (int x = 0; x < 4145; x++) 165 | { 166 | ESP.wdtFeed(); 167 | leftStepper.move(1, BACKWARD); 168 | rightStepper.move(1, BACKWARD); 169 | } 170 | leftPosition++; 171 | rightPosition++; 172 | } 173 | if (leftNew < leftPosition && rightNew < rightPosition) 174 | { 175 | rightStepper.sleepON(); 176 | leftStepper.sleepON(); 177 | for (int x = 0; x < 4145; x++) 178 | { 179 | ESP.wdtFeed(); 180 | leftStepper.move(1, FORWARD); 181 | rightStepper.move(1, FORWARD); 182 | } 183 | leftPosition--; 184 | rightPosition--; 185 | } 186 | if (leftNew < leftPosition && rightNew > rightPosition) 187 | { 188 | rightStepper.sleepON(); 189 | leftStepper.sleepON(); 190 | for (int x = 0; x < 4145; x++) 191 | { 192 | ESP.wdtFeed(); 193 | leftStepper.move(1, FORWARD); 194 | rightStepper.move(1, BACKWARD); 195 | } 196 | leftPosition--; 197 | rightPosition++; 198 | } 199 | if (leftNew > leftPosition && rightNew < rightPosition) 200 | { 201 | rightStepper.sleepON(); 202 | leftStepper.sleepON(); 203 | for (int x = 0; x < 4145; x++) 204 | { 205 | ESP.wdtFeed(); 206 | leftStepper.move(1, BACKWARD); 207 | rightStepper.move(1, FORWARD); 208 | } 209 | leftPosition++; 210 | rightPosition--; 211 | } 212 | if (leftNew == leftPosition && rightNew > rightPosition) 213 | { 214 | rightStepper.sleepON(); 215 | rightStepper.move(4144, BACKWARD); 216 | rightPosition++; 217 | } 218 | if (leftNew == leftPosition && rightNew < rightPosition) 219 | { 220 | rightStepper.sleepON(); 221 | rightStepper.move(4144, FORWARD); 222 | rightPosition--; 223 | } 224 | if (leftNew > leftPosition && rightNew == rightPosition) 225 | { 226 | leftStepper.sleepON(); 227 | leftStepper.move(4144, BACKWARD); 228 | leftPosition++; 229 | } 230 | if (leftNew < leftPosition && rightNew == rightPosition) 231 | { 232 | leftStepper.sleepON(); 233 | leftStepper.move(4144, FORWARD); 234 | leftPosition--; 235 | } 236 | if (rightNew == rightPosition) 237 | { 238 | if (rightPosition == 0 || rightPosition == unrolled) 239 | { 240 | rightStepper.sleepOFF(); 241 | } 242 | } 243 | if (leftNew == leftPosition) 244 | { 245 | if (leftPosition == 0 || leftPosition == unrolled) 246 | { 247 | leftStepper.sleepOFF(); 248 | } 249 | } 250 | } 251 | } 252 | 253 | void checkIn() 254 | { 255 | char topic[40]; 256 | strcpy(topic, "checkIn/"); 257 | strcat(topic, mqtt_client_id); 258 | client.publish(topic, "OK"); 259 | } 260 | 261 | 262 | //Run once setup 263 | void setup() { 264 | Serial.begin(115200); 265 | leftStepper.setMicrostepping(2); // 0 -> Full Step 266 | leftStepper.setSpeedRPM(200); // set speed in RPM, rotations per minute 267 | leftStepper.sleepOFF(); // set Sleep mode OFF 268 | rightStepper.setMicrostepping(2); // 0 -> Full Step 269 | rightStepper.setSpeedRPM(200); // set speed in RPM, rotations per minute 270 | rightStepper.sleepOFF(); // set Sleep mode OFF 271 | WiFi.mode(WIFI_STA); 272 | setup_wifi(); 273 | client.setServer(mqtt_server, mqtt_port); 274 | client.setCallback(callback); 275 | ArduinoOTA.setHostname("ShadeStepperMCU"); 276 | ArduinoOTA.begin(); 277 | delay(10); 278 | timer.setInterval(800, processStepper); 279 | timer.setInterval(90000, checkIn); 280 | } 281 | 282 | void loop() 283 | { 284 | if (!client.connected()) 285 | { 286 | reconnect(); 287 | } 288 | client.loop(); 289 | ArduinoOTA.handle(); 290 | timer.run(); 291 | } 292 | 293 | -------------------------------------------------------------------------------- /Shades_Portal_Config.ino: -------------------------------------------------------------------------------- 1 | #include //this needs to be first, or it all crashes and burns... 2 | #include //https://github.com/esp8266/Arduino 3 | //needed for library 4 | #include 5 | #include 6 | #include //https://github.com/tzapu/WiFiManager 7 | #include //https://github.com/bblanchon/ArduinoJson 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | //define your default values here, if there are different values in config.json, they are overwritten. 16 | char mqtt_server[40]; 17 | char mqtt_port[6] = "1883"; 18 | char mqtt_user[20] = "MQTT_USER"; 19 | char mqtt_pass[20] = "MQTT_PW"; 20 | char mqtt_client_id[20] = "UNIQUE_ID_NO_SPACES"; 21 | char rotations[6] = "13"; 22 | 23 | //Global Variables 24 | bool boot = true; 25 | const int stepsPerRevolution = 200; 26 | int unrolled = 13; 27 | int currentPosition = 0; 28 | int newPosition = 0; 29 | 30 | 31 | WiFiClient espClient; 32 | PubSubClient client(espClient); 33 | SimpleTimer timer; 34 | 35 | //AH_EasyDriver(int RES, int DIR, int STEP, int MS1, int MS2, int SLP); 36 | AH_EasyDriver shadeStepper(300,4,0,14,12,13); // init w/o "enable" and "reset" functions 37 | 38 | //flag for saving data 39 | bool shouldSaveConfig = false; 40 | 41 | //callback notifying us of the need to save config 42 | void saveConfigCallback () { 43 | Serial.println("Should save config"); 44 | shouldSaveConfig = true; 45 | } 46 | 47 | void callback(char* topic, byte* payload, unsigned int length) 48 | { 49 | Serial.print("Message arrived ["); 50 | String newTopic = topic; 51 | Serial.print(topic); 52 | Serial.print("] "); 53 | payload[length] = '\0'; 54 | String s = String((char*)payload); 55 | int newPayload = s.toInt(); 56 | Serial.println(newPayload); 57 | Serial.println(); 58 | if(boot == true) 59 | { 60 | newPosition = newPayload; 61 | currentPosition = newPayload; 62 | boot = false; 63 | } 64 | if(boot == false) 65 | { 66 | newPosition = newPayload; 67 | } 68 | } 69 | 70 | void checkIn() 71 | { 72 | char topic[40]; 73 | strcpy(topic, "checkIn/"); 74 | strcat(topic, mqtt_client_id); 75 | client.publish(topic, "OK"); 76 | } 77 | 78 | void processStepper() 79 | { 80 | if (newPosition > currentPosition) 81 | { 82 | shadeStepper.sleepON(); 83 | shadeStepper.move(4144, BACKWARD); 84 | currentPosition++; 85 | client.loop(); 86 | } 87 | if (newPosition < currentPosition) 88 | { 89 | shadeStepper.sleepON(); 90 | shadeStepper.move(4144, FORWARD); 91 | currentPosition--; 92 | client.loop(); 93 | } 94 | if (newPosition == currentPosition) 95 | { 96 | if (currentPosition == 0 || currentPosition == unrolled) 97 | { 98 | shadeStepper.sleepOFF(); 99 | client.loop(); 100 | } 101 | } 102 | Serial.println(currentPosition); 103 | Serial.println(newPosition); 104 | } 105 | 106 | void reconnect() 107 | { 108 | int retries = 0; 109 | while (!client.connected()) { 110 | if(retries < 150) 111 | { 112 | Serial.print("Attempting MQTT connection..."); 113 | if (client.connect(mqtt_client_id, mqtt_user, mqtt_pass)) 114 | { 115 | Serial.println("connected"); 116 | if(boot == false) 117 | { 118 | char topic[40]; 119 | strcpy(topic, "checkIn/"); 120 | strcat(topic, mqtt_client_id); 121 | client.publish(topic, "Reconnected"); 122 | } 123 | if(boot == true) 124 | { 125 | char topic[40]; 126 | strcpy(topic, "checkIn/"); 127 | strcat(topic, mqtt_client_id); 128 | client.publish(topic, "Rebooted"); 129 | } 130 | // ... and resubscribe 131 | char topic[40]; 132 | strcpy(topic, "shadePosition/"); 133 | strcat(topic, mqtt_client_id); 134 | client.subscribe(topic); 135 | } 136 | else 137 | { 138 | Serial.print("failed, rc="); 139 | Serial.print(client.state()); 140 | Serial.println(" try again in 5 seconds"); 141 | retries++; 142 | // Wait 5 seconds before retrying 143 | delay(5000); 144 | } 145 | } 146 | if(retries > 149) 147 | { 148 | ESP.restart(); 149 | } 150 | } 151 | } 152 | 153 | void setup() { 154 | // put your setup code here, to run once: 155 | Serial.begin(115200); 156 | Serial.println(); 157 | 158 | //clean FS, for testing 159 | //SPIFFS.format(); 160 | 161 | //read configuration from FS json 162 | Serial.println("mounting FS..."); 163 | 164 | if (SPIFFS.begin()) { 165 | Serial.println("mounted file system"); 166 | if (SPIFFS.exists("/config.json")) { 167 | //file exists, reading and loading 168 | Serial.println("reading config file"); 169 | File configFile = SPIFFS.open("/config.json", "r"); 170 | if (configFile) { 171 | Serial.println("opened config file"); 172 | size_t size = configFile.size(); 173 | // Allocate a buffer to store contents of the file. 174 | std::unique_ptr buf(new char[size]); 175 | 176 | configFile.readBytes(buf.get(), size); 177 | DynamicJsonBuffer jsonBuffer; 178 | JsonObject& json = jsonBuffer.parseObject(buf.get()); 179 | json.printTo(Serial); 180 | if (json.success()) { 181 | Serial.println("\nparsed json"); 182 | 183 | strcpy(mqtt_server, json["mqtt_server"]); 184 | strcpy(mqtt_port, json["mqtt_port"]); 185 | strcpy(mqtt_user, json["mqtt_user"]); 186 | strcpy(mqtt_pass, json["mqtt_pass"]); 187 | strcpy(mqtt_client_id, json["mqtt_client_id"]); 188 | strcpy(rotations, json["rotations"]); 189 | 190 | } else { 191 | Serial.println("failed to load json config"); 192 | } 193 | } 194 | } 195 | } else { 196 | Serial.println("failed to mount FS"); 197 | } 198 | //end read 199 | 200 | 201 | 202 | // The extra parameters to be configured (can be either global or just in the setup) 203 | // After connecting, parameter.getValue() will get you the configured value 204 | // id/name placeholder/prompt default length 205 | WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40); 206 | WiFiManagerParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 6); 207 | WiFiManagerParameter custom_mqtt_user("user", "mqtt user", mqtt_user, 20); 208 | WiFiManagerParameter custom_mqtt_pass("pass", "mqtt pass", mqtt_pass, 20); 209 | WiFiManagerParameter custom_mqtt_client_id("ID", "unique ID", mqtt_client_id, 20); 210 | WiFiManagerParameter custom_rotations("rotations", "13", rotations, 6); 211 | 212 | //WiFiManager 213 | //Local intialization. Once its business is done, there is no need to keep it around 214 | WiFiManager wifiManager; 215 | 216 | //set config save notify callback 217 | wifiManager.setSaveConfigCallback(saveConfigCallback); 218 | 219 | //add all your parameters here 220 | wifiManager.addParameter(&custom_mqtt_server); 221 | wifiManager.addParameter(&custom_mqtt_port); 222 | wifiManager.addParameter(&custom_mqtt_user); 223 | wifiManager.addParameter(&custom_mqtt_pass); 224 | wifiManager.addParameter(&custom_mqtt_client_id); 225 | wifiManager.addParameter(&custom_rotations); 226 | 227 | //reset settings - for testing 228 | //wifiManager.resetSettings(); 229 | 230 | //set minimu quality of signal so it ignores AP's under that quality 231 | //defaults to 8% 232 | //wifiManager.setMinimumSignalQuality(); 233 | 234 | //sets timeout until configuration portal gets turned off 235 | //useful to make it all retry or go to sleep 236 | //in seconds 237 | //wifiManager.setTimeout(120); 238 | 239 | //fetches ssid and pass and tries to connect 240 | //if it does not connect it starts an access point with the specified name 241 | //here "AutoConnectAP" 242 | //and goes into a blocking loop awaiting configuration 243 | if (!wifiManager.autoConnect("ShadeConfig")) { 244 | Serial.println("failed to connect and hit timeout"); 245 | delay(3000); 246 | //reset and try again, or maybe put it to deep sleep 247 | ESP.reset(); 248 | delay(5000); 249 | } 250 | 251 | //if you get here you have connected to the WiFi 252 | Serial.println("connected...yeey :)"); 253 | 254 | //read updated parameters 255 | strcpy(mqtt_server, custom_mqtt_server.getValue()); 256 | strcpy(mqtt_port, custom_mqtt_port.getValue()); 257 | strcpy(mqtt_user, custom_mqtt_user.getValue()); 258 | strcpy(mqtt_pass, custom_mqtt_pass.getValue()); 259 | strcpy(mqtt_client_id, custom_mqtt_client_id.getValue()); 260 | strcpy(rotations, custom_rotations.getValue()); 261 | 262 | //save the custom parameters to FS 263 | if (shouldSaveConfig) { 264 | Serial.println("saving config"); 265 | DynamicJsonBuffer jsonBuffer; 266 | JsonObject& json = jsonBuffer.createObject(); 267 | json["mqtt_server"] = mqtt_server; 268 | json["mqtt_port"] = mqtt_port; 269 | json["mqtt_user"] = mqtt_user; 270 | json["mqtt_pass"] = mqtt_pass; 271 | json["mqtt_client_id"] = mqtt_client_id; 272 | json["rotations"] = rotations; 273 | 274 | File configFile = SPIFFS.open("/config.json", "w"); 275 | if (!configFile) { 276 | Serial.println("failed to open config file for writing"); 277 | } 278 | 279 | json.printTo(Serial); 280 | json.printTo(configFile); 281 | configFile.close(); 282 | //end save 283 | } 284 | Serial.println("local ip"); 285 | Serial.println(WiFi.localIP()); 286 | shadeStepper.setMicrostepping(2); // 0 -> Full Step 287 | shadeStepper.setSpeedRPM(200); // set speed in RPM, rotations per minute 288 | shadeStepper.sleepOFF(); // set Sleep mode OFF 289 | unsigned long int_port = strtoul(mqtt_port, NULL, 10); 290 | unrolled = strtoul(rotations, NULL, 10); 291 | client.setServer(mqtt_server, int_port); 292 | client.setCallback(callback); 293 | ArduinoOTA.setHostname("ShadeStepperMCU"); 294 | ArduinoOTA.begin(); 295 | delay(10); 296 | timer.setInterval(800, processStepper); 297 | timer.setInterval(90000, checkIn); 298 | } 299 | 300 | void loop() 301 | { 302 | if (!client.connected()) 303 | { 304 | reconnect(); 305 | } 306 | client.loop(); 307 | ArduinoOTA.handle(); 308 | timer.run(); 309 | } 310 | --------------------------------------------------------------------------------