Files
BlindsController/blinds.ino
Cameron Cordes 4a7fe9deaf Save state to EEPROM
Set defaults in the Config on first launch, then update it as it
transitions to IDLE (only if its changed).

This should make the module more resistant to power loss and resets so
it doesn't lose position.
2021-09-22 20:13:47 -04:00

278 lines
6.0 KiB
C++

#include <ESP8266WiFi.h>
#include <MQTT.h>
#include <AccelStepper.h>
#include<EEPROM.h>
WiFiClient net;
MQTTClient client;
AccelStepper stepper(AccelStepper::HALF4WIRE, D0, D2, D1, D3);
// Config
const char ssid[] = "";
const char wifi_pw[] = "";
const char mqtt_user[] = "";
const char mqtt_pw[] = "";
const char mqtt_addr[] = "";
const char client_name[] = "";
const int INIT_POS = 511;
const bool CLEAR_EEPROM = false;
// End Config
unsigned long lastMillis = 0;
struct Config {
long openPosition;
long closedPosition;
long lastPosition;
bool blindsOpen;
};
// States
const int SETUP = 0;
const int SLACK = 1;
const int OPENING_BLINDS = 2;
const int CLOSING_BLINDS = 3;
const int IDLE = 4;
const int ADJUSTING = 5;
int state = SETUP;
int previousState = SETUP;
Config config;
void connect() {
Serial.println("checking wifi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnecting...");
while (!client.connect(client_name, mqtt_user, mqtt_pw)) {
Serial.print(".");
delay(1000);
}
Serial.println("\nconnected!");
client.subscribe("/bedroom/blinds/2");
client.subscribe("/bedroom/blinds/2/adjust");
}
void messageReceived(String &topic, String &payload) {
Serial.println("incoming: " + topic + " - " + payload);
if (topic.endsWith("adjust")) {
int adjustment;
sscanf(payload.c_str(), "%d", &adjustment);
adjustClosedPosition(adjustment);
} else {
if (payload.indexOf("open") != -1) {
openBlinds();
} else if (payload.indexOf("close") != -1) {
closeBlinds();
} else if (payload.indexOf("reset") != -1) {
Serial.println("Resetting current position and setting blinds state to closed");
stepper.setCurrentPosition(0);
config.blindsOpen = false;
} else {
Serial.println(payload);
}
}
}
void updateState(int newState) {
previousState = state;
state = newState;
if(newState == IDLE) {
config.lastPosition = stepper.currentPosition();
Serial.print("Transitioning to idle (");
Serial.print(config.lastPosition);
Serial.println(")");
EEPROM.put(0, config);
EEPROM.commit();
} else {
Serial.print("Moving ");
Serial.print(previousState);
Serial.print(" -> ");
Serial.println(newState);
}
}
void openBlinds() {
if (!config.blindsOpen && state == IDLE) {
Serial.println("Opening Blinds");
updateState(OPENING_BLINDS);
stepper.enableOutputs();
stepper.moveTo(config.openPosition);
}
}
void closeBlinds() {
if (config.blindsOpen && state == IDLE) {
Serial.println("Closing Blinds");
updateState(CLOSING_BLINDS);
stepper.enableOutputs();
stepper.moveTo(config.closedPosition);
}
}
void adjustClosedPosition(int offset) {
if(offset == 0 || state != IDLE) return;
Serial.print("Adjust by: ");
Serial.println(offset);
if(config.blindsOpen) {
config.openPosition += offset;
stepper.moveTo(config.openPosition);
Serial.print("New open position: ");
Serial.println(config.openPosition);
} else {
config.closedPosition += offset;
stepper.moveTo(config.closedPosition);
Serial.print("New closed position: ");
Serial.println(config.closedPosition);
}
updateState(ADJUSTING);
stepper.enableOutputs();
}
void setup() {
Serial.begin(115200);
while(!Serial);
WiFi.begin(ssid, wifi_pw);
client.begin(mqtt_addr, 1883, net);
client.onMessage(messageReceived);
connect();
EEPROM.begin(512);
// Only clears if flag is set
clearEeprom();
if(EEPROM.read(INIT_POS) != 'T') {
config.closedPosition = 0;
config.openPosition = -12000;
config.lastPosition = -3000;
Serial.println("Save default config state");
Serial.print("Open pos: ");
Serial.println(config.openPosition);
EEPROM.put(0, config);
EEPROM.write(INIT_POS, 'T');
if(!EEPROM.commit()) {
Serial.println("ERROR WRITING CONFIG");
}
} else {
Serial.println("Config already saved");
EEPROM.get(0, config);
}
stepper.setMaxSpeed(600);
stepper.setSpeed(100);
stepper.setAcceleration(250);
}
void clearEeprom() {
if(CLEAR_EEPROM) {
for(int i = 0; i < 512; i++) {
EEPROM.put(i, 0);
}
EEPROM.commit();
}
}
void loop() {
client.loop();
if (!client.connected()) {
connect();
}
switch (state) {
case SETUP:
setup_stepper();
break;
case IDLE:
delay(10); // <- fixes some issues with WiFi stability
break;
case SLACK:
if (previousState == OPENING_BLINDS) {
stepper.move(3000);
} else if (previousState == CLOSING_BLINDS) {
stepper.move(-3000);
} else if (stepper.distanceToGo() == 0) {
updateState(IDLE);
stepper.disableOutputs();
return;
}
stepper.run();
// Update previous to SLACK until we take up the slack to prevent the stepper from moving forever
previousState = state;
break;
case OPENING_BLINDS:
case CLOSING_BLINDS:
if (stepper.distanceToGo() != 0) {
if (millis() - lastMillis > 2000) {
Serial.print("Speed: ");
Serial.print(stepper.speed());
Serial.print(" Dist: ");
Serial.println(stepper.distanceToGo());
lastMillis = millis();
}
stepper.run();
} else {
stepper.disableOutputs();
bool open = state == OPENING_BLINDS;
config.blindsOpen = open;
sendBlindsUpdate(open);
updateState(SLACK);
}
break;
case ADJUSTING:
if (millis() - lastMillis > 1000) {
Serial.println("Adjusting...");
lastMillis = millis();
}
stepper.run();
if (stepper.distanceToGo() == 0) {
Serial.print("Finished adjusting to: ");
Serial.println(stepper.currentPosition());
updateState(IDLE);
stepper.disableOutputs();
}
break;
}
}
void setup_stepper() {
stepper.setCurrentPosition(config.lastPosition);
updateState(IDLE);
}
void sendBlindsUpdate(bool isOpen) {
client.publish("/bedroom/blinds/2/status", isOpen ? "true" : "false", true);
}