-
-
Save philharlow/6397fe8275e9bda25dcbeb37e290911f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <ESP8266WiFi.h> | |
#include <DNSServer.h> //Local DNS Server used for redirecting all requests to the configuration portal | |
#include <ESP8266WebServer.h> //Local WebServer used to serve the configuration portal | |
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager WiFi Configuration Magic | |
#include <WebSocketsServer.h> | |
#include <Encoder.h> | |
#include <EEPROM.h> | |
#include <Espalexa.h> | |
#include <ESP8266HTTPUpdateServer.h> | |
#define enablePin D8 | |
#define dir1Pin D4 | |
#define dir2Pin D5 | |
// Update me! | |
#define upButtonPin D7 | |
#define downButtonPin D6 | |
#define ledPin D3 | |
Encoder myEnc(D1, D2); | |
Espalexa espalexa; | |
EspalexaDevice* blindsDevice = NULL; | |
WebSocketsServer webSocket = WebSocketsServer(81); // create a websocket server on port 81 | |
ESP8266HTTPUpdateServer* httpUpdater; | |
bool motorEnabled = false; | |
int targetPosition = 0; | |
int disableMotorIn = 0; | |
int saveSettingsIn = 0; | |
int startingPositionOffset = 0; | |
int singleButtonHeldFor = 0; | |
int quickUpDownThreshold = 250; | |
int encoderPos = 0; | |
int lastEncoderPos = encoderPos; | |
int stallThreshold = 1000; | |
bool ledOn = true; | |
int changeLedIn = 20; | |
bool inProgrammingMode = false; | |
int programmingHeldFor = 0; | |
int unsetPosition = -100000; | |
int position1 = unsetPosition; | |
int position2 = unsetPosition; | |
int stalledTicks = 0; | |
// Change this before you flash | |
const char* ssid = "***"; | |
const char* password = "***"; | |
boolean wifiConnected = false; | |
boolean connectWifi(); | |
void brightnessChanged(uint8_t brightness); | |
#define INITED_VALUE 225 // changing will nuke all saved settings | |
struct SettingsStruct | |
{ | |
int inited; | |
int openPosition; | |
int currentPosition; | |
char name[30]; | |
}; | |
SettingsStruct defaultSettings = | |
{ | |
INITED_VALUE, | |
200, | |
0, | |
"Blinds" | |
}; | |
SettingsStruct settings = defaultSettings; | |
float getPercentageOpen() | |
{ | |
return clamp(settings.currentPosition, 0, settings.openPosition) / (float)settings.openPosition; | |
} | |
void saveSettings() | |
{ | |
if (inProgrammingMode) | |
return; | |
EEPROM.begin(sizeof(settings)); | |
EEPROM.put(0, settings); | |
EEPROM.end(); | |
Serial.println("Saved settings!"); | |
int value = getPercentageOpen() * 255.f; | |
if (blindsDevice) | |
blindsDevice->setValue(value); | |
sendToSocket("saved"); | |
} | |
void loadSettings() | |
{ | |
SettingsStruct newSettings; | |
EEPROM.begin(sizeof(newSettings)); | |
EEPROM.get(0, newSettings); | |
EEPROM.end(); | |
//Serial.print("Loaded struct inited: "); | |
//Serial.println(newSettings.inited); | |
if (newSettings.inited == INITED_VALUE) | |
{ | |
settings = newSettings; | |
Serial.print("Loaded settings openPosition: "); | |
Serial.print(settings.openPosition); | |
Serial.print(" currentPosition: "); | |
Serial.println(settings.currentPosition); | |
} | |
else | |
{ | |
Serial.println("Initial settings save"); | |
saveSettings(); | |
} | |
// Dont move on startup | |
startingPositionOffset = settings.currentPosition; | |
targetPosition = settings.currentPosition; | |
} | |
int tickCounter = 0; | |
void updateLED() | |
{ | |
//digitalWrite(ledPin, ledOn ? HIGH : LOW); | |
analogWrite(ledPin, ledOn ? 100 : 0); | |
if (changeLedIn > 0) | |
changeLedIn--; | |
if (changeLedIn == 0) | |
{ | |
if (wifiConnected == false) | |
{ | |
ledOn = !ledOn; | |
changeLedIn = 200; | |
} | |
else if (inProgrammingMode) | |
{ | |
ledOn = !ledOn; | |
changeLedIn = 100; | |
} | |
else | |
ledOn = motorEnabled; | |
} | |
} | |
void setup() | |
{ | |
Serial.begin(115200); | |
pinMode(enablePin, OUTPUT); | |
digitalWrite(enablePin, LOW); | |
pinMode(dir1Pin, OUTPUT); | |
pinMode(dir2Pin, OUTPUT); | |
pinMode(upButtonPin, INPUT_PULLUP); | |
pinMode(downButtonPin, INPUT_PULLUP); | |
pinMode(ledPin, OUTPUT); | |
updateLED(); | |
delay(100); | |
Serial.println(); | |
bool upButtonPressed = digitalRead(upButtonPin) == LOW; | |
bool downButtonPressed = digitalRead(downButtonPin) == LOW; | |
if (upButtonPressed && downButtonPressed) | |
saveSettings(); | |
else | |
loadSettings(); | |
WiFiManager wifiManager; | |
//wifiManager.resetSettings(); | |
String apName = "BlindsControllerSetup" + WiFi.macAddress().substring(11); | |
int str_len = apName.length() + 1; | |
char char_array[str_len]; | |
apName.toCharArray(char_array, str_len); | |
if (!wifiManager.autoConnect(char_array)) { | |
Serial.println("Failed to connect, we should reset as see if it connects"); | |
delay(3000); | |
ESP.reset(); | |
delay(5000); | |
return; | |
} | |
// Initialise wifi connection | |
wifiConnected = true;// connectWifi(); | |
if (wifiConnected) | |
{ | |
String blindsName = "Blinds" + WiFi.macAddress().substring(11); | |
int startingValue = getPercentageOpen() * 255; | |
Serial.println("Device name: " + blindsName); | |
Serial.print("Setting the device's value to: "); | |
Serial.println(startingValue); | |
blindsDevice = new EspalexaDevice(blindsName, brightnessChanged, startingValue); | |
espalexa.addDevice(blindsDevice); | |
// blindsDevice->setValue(startingValue); | |
espalexa.begin(); | |
httpUpdater = new ESP8266HTTPUpdateServer(); | |
httpUpdater->setup(espalexa.server); | |
espalexa.server->on("/", HTTP_GET, handleRoot); | |
espalexa.server->on("/on", HTTP_GET, handleOnRequest); | |
espalexa.server->on("/open", HTTP_GET, handleOnRequest); | |
espalexa.server->on("/off", HTTP_GET, handleOffRequest); | |
espalexa.server->on("/close", HTTP_GET, handleOffRequest); | |
webSocket.begin(); // start the websocket server | |
webSocket.onEvent(webSocketEvent); // if there's an incomming websocket message, go to function 'webSocketEvent' | |
Serial.println("WebSocket server started."); | |
} | |
} | |
String getSpan(String name, String value) | |
{ | |
String html = name + ": <span id='" + name + "'>" + value + "</span>"; | |
return html; | |
} | |
void sendPage(bool redirect = false) | |
{ | |
String html = "<meta name='viewport' content='user-scalable=no,width=device-width' />\r"; | |
html += "<script>var connection = new WebSocket('ws://' + location.hostname + ':81/', ['arduino']);\r"; | |
html += "connection.onopen = function() { console.log('connected socket!'); };\r"; | |
html += "connection.onmessage = function(e)\r" | |
"{\r" | |
" var changes = e.data.split('/');\r" | |
" for (var i=0; i< changes.length; i++)\r" | |
" {\r" | |
" var vals = changes[i].split('=');\r" | |
" var div = document.getElementById(vals[0]);\r" | |
" if (div) div.innerHTML = vals[1];\r" | |
" }" | |
" console.log('Server: ', e.data);\r" | |
" sendIt('Rcvd');\r" | |
"};\r"; | |
html += "connection.onerror = function(error) { console.log('WebSocket Error ', error); };\r"; | |
html += "connection.onclose = function() { console.log('WebSocket connection closed'); };\r"; | |
//html += "function intToHex(val) { var str = val.toString(16); if (str.length == 1) str = '0'+str; return str; } "; | |
//html += "function sendColor() { var str = intToHex(document.mainForm.red.value) + intToHex(document.mainForm.blue.value) + intToHex(document.mainForm.green.value) + intToHex(document.mainForm.white.value); connection.send('rgbw:' + str); } "; | |
html += "function sendIt(text) { connection.send(text); } \r</script> "; | |
String res = html; | |
res += "Hello from BlindsController v2!<br><br>\r\n\r\n"; | |
res += "<a onclick='sendIt(\"on\");'>Open</a><br>\r\n"; | |
res += "<a onclick='sendIt(\"off\");'>Close</a><br><br><br>\r\n"; | |
res += "blindsDevice->Value: " + String(blindsDevice->getValue()) + "<br>\r\n"; | |
res += getSpan("targetPosition", String(targetPosition)) + "<br>\r\n"; | |
res += getSpan("currentPosition", String(settings.currentPosition)) + "<br>\r\n"; | |
res += getSpan("openPosition", String(settings.openPosition)) + "<br>\r\n"; | |
res += getSpan("startingPositionOffset", String(startingPositionOffset)) + "<br>\r\n"; | |
res += getSpan("motorEnabled", String(motorEnabled)) + "<br>\r\n"; | |
res += "\r\nFree Heap: " + (String)ESP.getFreeHeap(); | |
res += "\r\nUptime: " + (String)millis(); | |
espalexa.server->sendHeader("Location", String("/"), true); | |
espalexa.server->send(redirect ? 302 : 200, "text/html", res); | |
} | |
void handleRoot() | |
{ | |
sendPage(false); | |
} | |
void handleOnRequest() | |
{ | |
setOpenPercentage(1); | |
sendPage(true); | |
} | |
void handleOffRequest() | |
{ | |
setOpenPercentage(0); | |
sendPage(true); | |
} | |
bool waiting = false; | |
String pending = ""; | |
/*void sendPosition() | |
{ | |
String msg = "currentPosition:"; | |
msg += settings.currentPosition; | |
sendToSocket(msg); | |
}*/ | |
void sendAll() | |
{ | |
String msg = "currentPosition="; | |
msg += settings.currentPosition; | |
msg += "/targetPosition="; | |
msg += targetPosition; | |
msg += "/openPosition="; | |
msg += settings.openPosition; | |
msg += "/startingPositionOffset="; | |
msg += startingPositionOffset; | |
msg += "/motorEnabled="; | |
msg += motorEnabled; | |
sendToSocket(msg); | |
} | |
void sendToSocket(String msg) | |
{ | |
if (waiting) | |
{ | |
pending = msg; | |
} | |
else | |
{ | |
waiting = true; | |
webSocket.sendTXT(0, msg); | |
} | |
} | |
bool send = false; | |
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) { // When a WebSocket message is received | |
switch (type) { | |
case WStype_DISCONNECTED: // if the websocket is disconnected | |
Serial.printf("[%u] Disconnected!\n", num); | |
break; | |
case WStype_CONNECTED: { // if a new websocket connection is established | |
IPAddress ip = webSocket.remoteIP(num); | |
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); | |
} | |
break; | |
case WStype_TEXT: // if new text data is received | |
Serial.printf("[%u] get Text: %s\n", num, payload); | |
String payload_str = String((char*)payload); | |
if (payload_str == "on") | |
{ | |
setOpenPercentage(1); | |
send = true; | |
} | |
if (payload_str == "off") | |
{ | |
setOpenPercentage(0); | |
send = true; | |
} | |
//int val = payload_str.toInt(); | |
//if (isnan(val) == false) | |
// setOpenPercentage(val / 255.f); | |
waiting = false; | |
} | |
} | |
int clamp(int input, int minV, int maxV) | |
{ | |
return max(minV, min(maxV, input)); | |
} | |
void setTargetPosition(int position) | |
{ | |
//Serial.print("Trying to goto: "); | |
//Serial.print(position); | |
//Serial.print(" current: "); | |
//Serial.println(settings.currentPosition); | |
if (inProgrammingMode == false) | |
position = clamp(position, 0, settings.openPosition); | |
if (settings.currentPosition == position) | |
return; | |
targetPosition = position; | |
motorEnabled = true; | |
if (inProgrammingMode == false) | |
ledOn = true; | |
} | |
void loop() | |
{ | |
webSocket.loop(); | |
updateLED(); | |
if (send && waiting == false) | |
{ | |
if (pending.length() > 0) | |
webSocket.sendTXT(0, pending); | |
else if (send) | |
sendAll(); | |
pending = ""; | |
send = false; | |
} | |
encoderPos = -myEnc.read(); | |
settings.currentPosition = encoderPos + startingPositionOffset; | |
if (abs(encoderPos - lastEncoderPos) > 2) | |
{ | |
Serial.print("encoder: "); | |
Serial.println(encoderPos); | |
lastEncoderPos = encoderPos; | |
stalledTicks = 0; | |
} | |
else if (motorEnabled) | |
{ | |
stalledTicks++; | |
if (stalledTicks > stallThreshold) | |
{ | |
Serial.println("Stall detected!!"); | |
sendToSocket("stall"); | |
motorEnabled = false; | |
ledOn = false; | |
} | |
} | |
bool upButtonPressed = digitalRead(upButtonPin) == LOW; | |
bool downButtonPressed = digitalRead(downButtonPin) == LOW; | |
int pressedCount = upButtonPressed + downButtonPressed; | |
if (pressedCount == 1) | |
singleButtonHeldFor++; | |
else | |
singleButtonHeldFor = 0; | |
if (upButtonPressed && downButtonPressed) | |
{ | |
setTargetPosition(settings.currentPosition); | |
int threshold = inProgrammingMode ? 10 : 3000; | |
if (programmingHeldFor > threshold) | |
{ | |
changeLedIn = 10; | |
programmingHeldFor = -1; | |
if (inProgrammingMode) | |
{ | |
// save first pos | |
if (position1 == unsetPosition) | |
{ | |
Serial.print("Setting position 1 to: "); | |
Serial.println(settings.currentPosition); | |
position1 = encoderPos;// settings.currentPosition; | |
ledOn = true; | |
changeLedIn = 2000; | |
} | |
else // second pos | |
{ | |
Serial.print("Setting position 2 to: "); | |
Serial.println(settings.currentPosition); | |
position2 = encoderPos;// settings.currentPosition; | |
// settings.currentPosition = encoderPos + startingPositionOffset; | |
// now closed, pos1 = top/open | |
if (position1 > position2) | |
{ | |
settings.openPosition = position1 - position2; | |
settings.currentPosition = 0; | |
} | |
// now open, pos2 = top/open | |
else | |
{ | |
settings.openPosition = position2 - position1; | |
settings.currentPosition = settings.openPosition; | |
} | |
startingPositionOffset = -encoderPos + settings.currentPosition; | |
targetPosition = settings.currentPosition; | |
Serial.print("Saving new settings! openPosition: "); | |
Serial.print(settings.openPosition); | |
inProgrammingMode = false; | |
saveSettings(); | |
ledOn = true; | |
changeLedIn = 10; | |
} | |
} | |
else | |
{ | |
Serial.println("Entering programming mode"); | |
position1 = unsetPosition; | |
position2 = unsetPosition; | |
inProgrammingMode = true; | |
changeLedIn = 1; | |
} | |
} | |
else if (programmingHeldFor > -1) | |
programmingHeldFor++; | |
} | |
else | |
{ | |
programmingHeldFor = 0; | |
if (stalledTicks > stallThreshold) | |
{ | |
if (upButtonPressed == false && downButtonPressed == false) | |
stalledTicks = 0; | |
} | |
else | |
{ | |
if (inProgrammingMode) | |
{ | |
if (upButtonPressed) | |
setTargetPosition(settings.currentPosition + 1); | |
else if (downButtonPressed) | |
setTargetPosition(settings.currentPosition - 1); | |
} | |
else | |
{ | |
// prevent judder, override wwhen in programming mode | |
if (upButtonPressed && settings.currentPosition < settings.openPosition) | |
setTargetPosition(singleButtonHeldFor > quickUpDownThreshold ? settings.currentPosition + 1 : settings.openPosition); | |
else if (downButtonPressed && settings.currentPosition > 0) | |
setTargetPosition(singleButtonHeldFor > quickUpDownThreshold ? settings.currentPosition - 1 : 0); | |
} | |
} | |
} | |
if (motorEnabled) | |
{ | |
bool goingUp = targetPosition > settings.currentPosition; | |
digitalWrite(enablePin, HIGH); | |
digitalWrite(dir1Pin, goingUp ? LOW : HIGH); | |
digitalWrite(dir2Pin, goingUp ? HIGH : LOW); | |
saveSettingsIn = 5000; | |
disableMotorIn = 2000; | |
if ((goingUp && settings.currentPosition >= targetPosition) || | |
(goingUp == false && settings.currentPosition <= targetPosition)) | |
{ | |
motorEnabled = false; | |
if (inProgrammingMode == false) | |
{ | |
int value = getPercentageOpen() * 255.f; | |
blindsDevice->setValue(value); | |
ledOn = false; | |
} | |
} | |
sendAll(); | |
} | |
else | |
{ | |
digitalWrite(dir1Pin, LOW); | |
digitalWrite(dir2Pin, LOW); | |
if (disableMotorIn > 0) | |
{ | |
disableMotorIn--; | |
if (disableMotorIn == 0) | |
digitalWrite(enablePin, LOW); | |
} | |
} | |
if (saveSettingsIn > 0) | |
{ | |
saveSettingsIn--; | |
if (saveSettingsIn == 0) | |
saveSettings(); | |
} | |
if (wifiConnected) | |
{ | |
espalexa.loop(); | |
delay(1); | |
} | |
} | |
// connect to wifi � returns true if successful or false if not | |
boolean connectWifi() { | |
boolean state = true; | |
int i = 0; | |
WiFi.mode(WIFI_STA); | |
WiFi.begin(ssid, password); | |
Serial.println(""); | |
Serial.println("Connecting to WiFi"); | |
// Wait for connection | |
Serial.print("Connecting ..."); | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(500); | |
Serial.print("."); | |
if (i > 10) { | |
state = false; | |
break; | |
} | |
i++; | |
} | |
if (state) { | |
Serial.println(""); | |
Serial.print("Connected to "); | |
Serial.println(ssid); | |
Serial.print("IP address: "); | |
Serial.println(WiFi.localIP()); | |
} | |
else { | |
Serial.println(""); | |
Serial.println("Connection failed."); | |
} | |
return state; | |
} | |
//our callback functions | |
void brightnessChanged(uint8_t brightness) | |
{ | |
Serial.print("brightnessChanged "); | |
Serial.println(brightness); | |
if (inProgrammingMode) | |
{ | |
Serial.println("Cannot set positin via alexa during programming!"); | |
return; | |
} | |
// this seems redundant | |
blindsDevice->setValue(brightness); | |
float percentage = brightness / 255.f; | |
setOpenPercentage(percentage); | |
} | |
void setOpenPercentage(float percentage) | |
{ | |
int newTarget = percentage * (float)settings.openPosition; | |
newTarget = clamp(newTarget, 0, settings.openPosition); | |
if (settings.currentPosition < 0 && newTarget == 0) | |
return; | |
if (settings.currentPosition > settings.openPosition && newTarget == settings.openPosition) | |
return; | |
setTargetPosition(newTarget); | |
Serial.print("newTarget: "); | |
Serial.println(newTarget); | |
Serial.print("Device 1 changed to "); | |
if (percentage > 0.f) { | |
Serial.print("ON, brightness: "); | |
Serial.println(percentage); | |
} | |
else { | |
Serial.println("OFF"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment